知识总览
2-3.1单链表
单链表与顺序表的对比
单链表的创建
链表结构体声明
typedef struct Node
{
int data; //数据域 每个节点存放一个数据
struct Node *pNext; //指针域 指针指向下一个节点
} NODE, *PNODE;
//NODE * 声明指向单链表的第一个节点的指针
//PNODE 声明指向单链表的第一个节点的指针--可读性更强
不带头结点与带头结点的空链表初始化
//不带头结点的空链表初始化
LinkList NHInitList(){
return NULL; //空表,没有数据
}
//带头结点的空链表初始化
LinkList HInitList(){
LinkList L = NULL;
L = (LinkList)malloc(sizeof(LNode));
if(L=NULL){
printf("创建失败!\n");
exit(-1);
}
L->pNext = NULL;
return L;
}
头插法创建单链表
代码实现
//带头结点的头插法创建单链表
LinkList ListCreatHeadInsert()
{
int len; //所需创建的链表长度
LNode *s; //暂存新建节点指针
LinkList pHead; //返回的创建的链表头节点指针
//创建头节点
pHead = (LNode *)malloc(sizeof(LNode));
if(pHead==NULL)
{
printf("分配内存失败!\n");
exit(-1);
}
printf("<=========头插法=========>\n");
printf("请输入需要生产的节点数:\n");
scanf("%d", &len);
for (int val,i = 0; i < len;i++)
{
printf("请输入第%d个节点的值:", i + 1);
scanf("%d", &val);
s = (LinkList)malloc(sizeof(LNode)); //分配空间给新节点
if(s==NULL)
{
printf("分配内存失败!\n");
exit(-1);
}
s->data = val; //赋值给新创建的节点
s->pNext = pHead->pNext; //新节点接管头节点的后继节点
pHead->pNext = s; //新节点成为头节点的后继节点
}
return pHead; //返回头节点
}
重要应用:链表逆序
//利用头插法逆序带头结点的链表
LinkList Reverse(LinkList pHead){
if(HEmpty(pHead))
return pHead;
LinkList p = HInitList(); //初始化空的带头结点链表
while(!HEmpty(pHead)){
pHead = pHead->pNext; //链表指向下一个节点,注意,一开始进来需要跳过头节点,
//因为头节点不带数据
LNode *s = (LNode *)malloc(sizeof(LNode));//为新节点分配空间
s->data = pHead->data;//赋值
s->pNext = p->pNext;
p->pNext = s;
}
return p; //返回逆序后的头节点
}
尾插法创建单链表
代码实现
// 带头节点的尾插法创建单链表
LinkList ListCreatTailInsert(){
int len; //所需创建的链表长度
LNode *s, *r; // s:暂存新建节点指针,r保存当前的尾节点的指针
LinkList pHead; //返回的创建的链表头节点指针
//创建头节点
pHead = (LNode *)malloc(sizeof(LNode));
r = pHead; //尾指针指向pHead节点,因为是新建立的,故为最后一个节点
if(pHead==NULL)
{
printf("分配内存失败!\n");
exit(-1);
}
printf("<=========尾插法=========>\n");
printf("请输入需要生产的节点数:\n");
scanf("%d", &len);
for (int val,i = 0; i < len;i++)
{
printf("请输入第%d个节点的值:", i + 1);
scanf("%d", &val);
s = (LinkList)malloc(sizeof(LNode)); //分配空间给新节点
if(s==NULL)
{
printf("分配内存失败!\n");
exit(-1);
}
s->data = val; //赋值给新创建的节点
s->pNext = NULL; //新节点的后继节点置空
r->pNext = s; //尾节点的后继节点指向新节点
r = s; //尾节点指向新节点
}
/*
也可以将上面的语句 s->pNext = NULL 从循环中删除
改成在循坏外最后执行:r->pNext = NULL;
*/
return pHead; //返回头节点
}
小节总结
单链表的插入与删除
知识总览
按位序插入(带头结点)
代码实现
/** 带头结点的单链表按位序插入
* @param L 操作的链
* @param i 需要插入的位序
* @param e 需要插入的元素
* 头节点作为第0号节点,而插入第 i 位节点可看成插入第 i -1 节点之后
* 故我们需要定位到 第 i - 1 节点(因为链表插入到某个节点后,需要要知道该节点的前继节点)
* 下面定位节点的 for 循环功能每次执行完一次,指针都会指向到下一个下一个节点
* 因此如果我们想要当最后一次执行完for循环之后能正确的指向我们所需的节点位置( 第i - 1节点)
* 我们就得需要却确保最后一次进入for循环时遍历到的是第 i -2 个节点
* 故下面for循环的终止条件为 j< i-1 <=> j<= i - 2
* */
bool HListInsert(LinkList *L, int i, int e){
if(i<i) //判断位序的合法性
return false;
LNode *p; //指针p记录当前扫描到的节点
int j = 0; //当前p指向的是第几个节点
p = L; //L指向头节点,头节点是第0个节点(不存数据)
//循环找到第 i-1 个节点
while(p!=NULL&&j<i-1){
p = p->pNext;
j++;
}
if(p==NULL)//i值不合法
return false;
//分配新节点空间
LNode *s = (LNode *)malloc(sizeof(LNode));
s->data = e; //赋值给新节点
s->pNext = p->pNext; //将p节点看成头节点
p->pNext = s; //使用 头插法
return true;
}
几种情况:
- 插入表头: i = 0
-
插入表中:0<i<length
-
插入表尾:i=length
其逻辑与插入表中类似
-
插入表外:i>length
按位序插入(不带头节点)
代码实现
/** 不带头结点的按序插入
* @param L 需要操作的链表 //L指向的是第一个节点元素指针的指针
* @param i 需要插入的位序
* @param e 需要插入的元素
* 注意,由于不带头结点,插入第一位时需要修改头指针指向,所以传参数时
* 不能值传递,只能指针传递,即传递指针的指针
* */
bool NHListInsert(LinkList *L, int i, int e) {
if(i<1)
return false; //判断位序的合法性
//因为不带头结点,故需指要单独判断和操作。具体的,让新节点插入表头
//并让表头指针指向该节点
LNode *s = (LNode *)malloc(sizeof(LNode)); //给新结点分配空间
if(i==1){
s->data = e; //给新结点赋值
s->pNext = (*L); //将头节点插入表头
(*L) = s; //将头指针的指向新节点
return true;
}
/* 以下代码段同头插法的寻找第 i-1 个节点 */
LNode *p = *(L); //指针p指向当前扫描到的节点
int j = 1; //当前p指向的第几个几点,注意,由于是不带头节点的,故从1开始
while(p!=NULL&&j<i-1){
p = p->pNext;
j++;
}
if(p==NULL) //i值不合法,即i大小超过当前链表长度
return false;
s->data = e;
s->pNext = p->pNext;
p->pNext = s;
return true;
}
需要特别注意的是:i=1插入表首的情况

其他情况与带头结点一致:

指定节点的后插操作

代码实现
/** 插入到指定节点 p 后面
* @param P 指定的节点
* @param e 需要插入的元素
* */
bool HInsertNextNode(LNode *p, int e){
if(p==NULL)
return false;
LNode *s = (LNode *)malloc(sizeof(LNode));
if(s==NULL)
return false;
s->data = e;
s->pNext = p->pNext; //后插
p->pNext = s;
return true;
}
指定节点的前插操作
解法一:
/** 将元素 e 前插到指定节点 p 之前
* @param L 需要菜操作的链表头指针
* @param P 指定的节点
* @param e 需要插入的元素
**/
bool HInsertPriorNode(LinkList L, LNode *p, int e){
//如果需要插入到头节点之后,即需要充当第一个节点
if(L==p){
LNode *s = (LNode *)malloc(sizeof(LNode));
s->data = e;
s->pNext = L->pNext; //1-1使用头插法将s插到头节点L之后
L->pNext = s; //1-2使用头插法将s插到头节点L之后
return true;
}
LNode *r = L->pNext; //创建尾指针 初始化为指向头节点
/* r指针寻找目标节点:p ;L指针充当r的前继结点的指针 */
while(r!=NULL){
//如果 r 指针找到目标节点 p
if(r==p){
LNode *s = (LNode *)malloc(sizeof(LNode));
s->data = e;
s->pNext = L->pNext; // 1-1将s节点接到L节点后面
L->pNext = s; // 1-2将s节点接到L节点后面
return true;
}
//如果没有找到,继续进行指针的传递
else{
L = r->pNext;
r = r->pNext;
}
}
//没有找到p,结束
return false;
}

解法二:
/** 将元素 e 前插到指定节点 p 之前
* @param p 指定的节点
* @param e 需要插入的元素
* */
bool HInsertPriorNodeC(LNode *p, int e){
if(p==NULL)
return false;
LNode *s = (LNode *)malloc(sizeof(LNode));
if(s==NULL)
return false;
s->pNext = p->pNext; //1.1将新节点插入到指定节点p之后
p->pNext = s; //1.2将新节点插入到指定节点p之后
s->data = p->data; //2.1交换s与p两节点的数据
p->data = e; //2.2交换s与p两节点的数据
return true;
}
按位序删除(带头结点/不带头结点)
代码实现
/** 带头结点删除位序为 i 的节点 并将删除的元素赋给 e
* @param L 需要操作的链表
* @param i 需要删除的节点位序
* @param e 将删除的值赋给e
* */
bool HListDelete(LinkList L, int i, int *e){
if(i<1) //判断i的范围合法性
return false;
LNode *p; //指针p指向的是当前扫描的节点
p = L; //开始指向头节点
int j = 0; //L指向头节点,头结点是第0个节点,不存数据
//循环找到第 i-1 个节点
while(p!=NULL&&j<i-1){
p = p->pNext;
j++;
}
if(p==NULL) //如果 i 值不合法
return false;
if(p->pNext==NULL) //第 i-1 个节点之后已无其他节点
return false;
LNode *q = p->pNext; //令q指向被删除的节点
*e = q->data;
p->pNext = q->pNext; //将*q节点从从链中“断开”
free(q); //释放节点的存储空间
return true; //删除成功
}

不带头结点
/** 不带头结点删除位序为 i 的节点 并将删除的元素赋给 e
* @param L 需要操作的链表
* @param i 需要删除的节点位序
* @param e 将删除的值赋给e
* */
bool NHListDelete(LinkList *L, int i, int *e){
if(i<1||L==NULL) //判断 i 与 指针 的合法性
return false;
if(i==1){
LNode *q = (*L);
*e = q->data;
*L = q->pNext;
free(q);
return true;
}
//<<=====余下操作同带头结点的====>>//
LNode *p; //指针p指向当前扫描的节点
p = *L; //开始指向首节点
int j = 1; //L指向首节点,首节点是第1个节点
//循环找到第 i-1 个节点
while(p!=NULL&&j<i-1){
p = p->pNext;
j++;
}
if(p==NULL) //如果 i 值不合法
return false;
if(p->pNext==NULL) //第 i-1 个节点之后已无其他节点
return false;
LNode *q = p->pNext; //令q指向被删除的节点
*e = q->data;
p->pNext = q->pNext; //将*q节点从从链中“断开”
free(q); //释放节点的存储空间
return true; //删除成功
}
指定节点的删除

方法一:传入头指针,查找所需删除节点p的前继结点
代码实现:
/** 删除指定节点 方法一:查找前继结点
* @param L 需要操作的链表
* @param p 需要删除的节点
* */
bool HDeleteNode(LinkList L, LNode *p){
LNode *r = L->pNext; //设置扫描尾指针,指向当前扫描到的值,初始化为第一个节点
//查找需要删除的节点的前继结点 用L指向;此时r匹配到需要删除的节点
while(r!=NULL&&r!=p){
L = r; //1-1进行指针传递
r = r->pNext; //1-2进行指针传递
}
if(r==NULL)
return false;
L->pNext = r->pNext; //将指针r从链表中“断开”
free(r);
return true;
}
方法二:与后继节点交换数据,并将后继节点删除
代码实现:
/** 删除指定节点
* 交换p节点与其后继节点之间的数据,然后将后继节点删除
* @param p 需要删除的节点
* */
bool HDeleteNode1(LNode *p){
if(p==NULL)
return false;
LNode *q = p->pNext; //令q指向*p的后继节点
p->data = p->pNext->data; //和后继节点交换数据 注意,如果p节点为最后一个节点的话,该语句会出错
p->pNext = q->pNext; // 将*q节点从链中"断开"
free(q); //释放后继节点的存储空间
return true;
}

注意,该方法不能删除最一个节点

小节总结
单链表的查询
注意,以下代码是建立在包含头节点的链表中实现的

按位查询
代码实现;

/** 删除指定节点
* 交换p节点与其后继节点之间的数据,然后将后继节点删除
* @param p 需要删除的节点
* */
bool HDeleteNode1(LNode *p){
if(p==NULL)
return false;
LNode *q = p->pNext; //令q指向*p的后继节点
p->data = p->pNext->data; //和后继节点交换数据 注意,如果p节点为最后一个节点的话,该语句会出错
p->pNext = q->pNext; // 将*q节点从链中"断开"
free(q); //释放后继节点的存储空间
return true;
}
/** 按位查找,返回第 i 个节点元素(包含头节点)
* @param L 需要操作的链表
* @param i 需要返回的节点位序
* */
LNode *HGetElem(LinkList L, int i){
if(i<1) //判断位序的合法性
return NULL;
LNode *p = L; //p指针指向当前扫描到的节点元素
int j = 0; //p初始化为指向头节点,头节点属与第0号节点
//循环找到第 i 个节点
while(p!=NULL&&j<i){
p = p->pNext;
j++;
}
return p;
}
按值查询

/** 按位查找,返回第 i 个节点元素(包含头节点)
* @param L 需要操作的链表
* @param i 需要返回的节点位序
* */
LNode *HGetElem(LinkList L, int i){
if(i<1) //判断位序的合法性
return NULL;
LNode *p = L; //p指针指向当前扫描到的节点元素
int j = 0; //p初始化为指向头节点,头节点属与第0号节点
//循环找到第 i 个节点
while(p!=NULL&&j<i){
p = p->pNext;
j++;
}
return p;
}
/** 按值查找,返回值=e的节点(包含头节点)
* @param L 需要操作的链表
* @param e 需要查找的元素
* */
LNode *HLocateElem(LinkList L, int e){
LNode *p = L->pNext;
//从第1个节点开始查找数据域为e的节点
while(p!=NULL&&p->data!=e){
p = p->pNext;
}
return p; // 若找到则返回对应的节点,否则返回NULL
}