单链表有两种形式,带头结点和不带头结点,本文分析带头结点的单链表操作。
无论有无头结点,都是有头指针的(相当于对链表得有个抓手)。


由上ASCII码表知:NULL的化为转义字符为:'\0',故以下写法也可以。
与顺序结构相比,链式结构在插入和删除数据上时,不需要移动大量数据,适用于经常需要插入和删除数据的线性表。
struct Node{
int data;
Node *next;
};
typedef Node *LinkList;
1、初始化链表//就是对头指针进行动态赋值,使其指向头结点
void InitList(LinkList &slink)//将头指针的引用传进来,即传头指针本身
{
slink = (LinkList)malloc(sizeof(Node));
if(!slink){exit(1);}
slink->next = NULL;
}
初始化后,如左图所示结果。
2、销毁线性表//将malloc的内存空间释放掉,让头指针指向空,变成InitList前的状态。

方法一:将当前结点赋值给一个新的变量
void DestroyList(LinkList &slink)//将头指针的引用传进来,即传头指针本身
{
//因为链表我们只能通过头指针来操作,故前面指针释放前需要先进行保存他的next.
// LinkList p=slink->next;
//free(slink);
//LinkList q=p->next;
//free(p);
//链表不断往后移动,直至表尾,故需要构建一个循环
/* 所以我们需要
1、变量保存当前结点的next指针
2、循环判断条件为:当前结点为NULL
3、变量指向当前结点,因为每次都要free (所以该变量需要得在循环外就定义好,因为2的原因)
*/
LinkList q = slink;//指向当前结点
LinkList p = NULL;//保存当前结点的next
while(q)//对应步骤2
{
p = q->next; //对应步骤1
free(q); //具体操作
q = p; //对应步骤3//使循环条件趋于假
}
slink = NULL;
}
小结:1)对于链表的操作,一般是需要两个指针的,一个指向当前结点,一个指向当前结点的next.
2)需要定义多少个变量,取决于每次有多少个量在变化,如上,当前结点在变化,当前结点的next也就对应变化,所以需要两个变量。而变化的量需要写在while中进行重新赋值的。
方法二:直接用slink,指向当前结点(slink为删除的结点,“自己冲在前面”)
void DestroyList(LinkList &slink)//将头指针的引用传进来,即传头指针本身
{
LinkList p = NULL;//保存当前结点的next
while(slink)
{
p = slink->next;
free(slink);
slink = p;
}
}
方法三:构建一个用来删除的变量(新的变量为删除的结点,slink“躲在后面”)
void DestroyList(LinkList &slink)//将头指针的引用传进来,即传头指针本身
{
LinkList p = NULL;//保存当前结点的next
while(slink)
{
p = slink;
slink = slink->next;
free(p);
}
}
3、将链表重置为空表//将除头结点外的结点全都free,头结点next置空

ClearList(Linklist slink)//不对头指针进行修改,所以不需要传引用
{
/*
分析:同销毁线性表类似,区别是此时不需要free头结点
1、需要保存当前结点,因为每次都要free掉当前结点
2、需要保存当前结点的next
3、循环条件为当前结点是否为NULL
4、当前结点从头结点的next开始
LinkList p = slink-next;//当前指针,从头结点后的第一个开始
LinkList q;
while(p)
{
q = p->next;
free(p);
p = q;
}
slink-next = NULL;
*/
}
4、求链表中数据元素个数
int LengthList(LinkList *slink)
{
/* 从头结点的下一个结点开始遍历,直到尾结点
1、结点个数在变,所以所以一个计数变量
2、指向的当前结点在变,所以需要一个指针指向当前变量
3、循环判断条件,当前结点是否为空
*/
LinkList p = slink->next;//指向头结点的next结点(因为步骤3,所以需要在while外就初始化)
int i = 0;
while(p)//步骤3
{
i++;//步骤1
p = p->next;//步骤2
}
return i;
}
5、在链表的第i个位置前插入数据
以下三种方法都是寻找i-1,这样算法可以包含住在链尾插入结点的情况。
方法一:
bool insertList(int e,int i,LinkList slink)
{
/*
i的合理取值是在头结点之后的所有位置,包括最后一个结点的后面
需要遍历链表,并判断当前位置是否等于i-1,若等于则在i位置插入,若不等继续遍历。
1、变量j,来计当前位置
2、需要变量保存当前结点指针,一直在变,所以需要放在while中,
3、因为要插入元素,故需要malloc内存空间,故需要一个临时指针
4、循环判断条件:(是否到表尾,且j<i-1)
*/
//我们采用反向思路,遍历过程中,j<i-1,则继续循环,找到j=i-1,退出循环。
//步骤1,因为j表示当前位置,i=1,表示第一个结点,所以,j=0,表示当前位置为头结
//点,j的取值是与i的取值对应的
//或者,这么解释,第一个结点位置是1,第二个结点位置是2,所以头结点位置是
int j = 0;
if(i<1)return false;
LinkList p = slink;//步骤2,从头结点开始,因为头结点后也可以插入数据
while(p!=NULL&&j<i-1)//带入j=0,则i>1,即循环中不包含i=1的情况;也不包含p=NULL的情况
{
p = p->next;//步骤2//p指向i-1的位置
j++;
}
if(p!=NULL&&j==i-1)//与while配合,表示j=i-1//
{
LinkList q = (LinkList)malloc(sizeof(Node));
q->data = e;
q->next = p->next;//因为接在后面,next就丢掉了,所以需要先把next赋值出去
p->next = q;//然后为p->next重新赋值
return true;
}
if(p==NULL&&j<i-1)return false;//与while配合
if(p==NULL&&j==i-1)//与while配合
{
LinkList q = (LinkList)malloc(sizeof(Node));
q->data = e;
q->next = p->next;//因为接在后面,next就丢掉了,所以需要先把next赋值出去
p->next = q;//然后为p->next重新赋值
return true;
}
if(i==1)//因为while循环中不包含i==1的情况。
{
LinkList q = (LinkList)malloc(sizeof(Node));
q->data = e;
q->next = p->next;//因为接在后面,next就丢掉了,所以需要先把next赋值出去
p->next = q;//然后为p->next重新赋值
return true;
}
}
注意:链表中插入数据,一定是要找到该位置的前一个元素(链表的特性,找到前面的,才能操作后面的)
//以上内容有重复的地方,可以合并。如下所示,看看是否能够合并此三类情况。
方法二:
bool insertList(int e,int i,LinkList slink)
{
int j = 0;
if(i<1)return false;
LinkList p = slink;
while(p!=NULL&&j<i-1)
{
p = p->next;
j++;
}
if(p==NULL&&j<i-1)return false;//把这种情况去掉,剩下的都可以操作了。
LinkList q = (LinkList)malloc(sizeof(Node));
q->data = e;
q->next = p->next;//因为接在后面,next就丢掉了,所以需要先把next赋值出去
p->next = q;//然后为p->next重新赋值
return true;
}
以上思路是,首先排除i<1的情况,把i的值缩小;
然后再排除使while循环变为假的情况中不合理的那个,剩下的无论是进入while循环还是未进入while循环的情况都适用。
另外,还有种思路,可以不用先缩小i的取值范围
方法三:
bool insertList(int e,int i,LinkList slink)
{
int j = 0;
LinkList p = slink;
while(p!=NULL&&j<i-1)
{
p = p->next;
j++;
}
if(p==NULL||j>i-1)return false;
//这里面包含了
LinkList q = (LinkList)malloc(sizeof(Node));
q->data = e;
q->next = p->next;
p->next = q;
return true;
}
|
分类 |
| ||
|
p |
j<i-1 |
while循环 | |
|
p |
j>=i-1 |
排除了j>i,就只剩下p&&j==i的情况了 |
while循环外3中情况 |
|
!p |
j<i-1 |
排除 | |
|
!p |
j>=i-1 |
排除 | |
方法三的逻辑分析,最后验证刚好能够包含i=1的情况,故最后可以将if(i==1) return false; 略去。
6、链表中删除第i个元素
bool destroyList(LinkList slink,int i)
{
/*
缩小i的取值范围
找到第i-1个元素;
free第i个元素;
1、变量计数,记录当前的位置;
2、指针变量指向第i-1个元素
*/
if(i<1)return false;
int j =0;
LinkList p = slink;//指向头结点
while(p->next&&j<i-1)//j=0代入,i>1;//保证p->next不为空
{
p=p->next;
j++;
}
if(p->next)//包含了while循环外的情况和i=1的情况。
{
LinkList q = p->next;
p->next = q->next;
free(q);
return true;
}
return false;
}
还可以写成另一种形式:
bool destroyList(LinkList slink,int i)
{
/*
缩小i的取值范围
找到第i-1个元素;
free第i个元素;
1、变量计数,记录当前的位置;
2、指针变量指向第i-1个元素
*/
int j =0;
LinkList p = slink;//指向头结点
while(p->next&&j<i-1)//j=0代入,i>1;//保证p->next不为空
{
p=p->next;
j++;
}
if(!p->next||j>i-1)//删除不合理的位置
return false;
LinkList q = p->next;
p->next = q->next;
free(q);
return true;
}
注意:无论是删除结点,还是增加结点,都要保存好该结点的直接前继的next指针!!!!,因为这个值都会被改变。
7、获取链表中的第i个元素
方法一:
bool GetList(LinkList slink,int i,int &e)
{
/*
缩小i的范围;
找到第i个结点,故需要计数变量j
指针指向当前结点,故需要一个指针
循环条件:链表内,位置为i的结点
*/
if(i<1) return false;
int j=1;
LinkList p = slink->next;
while(p&&j<i)
{
p = p->next;
j++;
}
if(p)
{
e = p->data;
return true;
}
return false;
}
方法二:
bool GetList(LinkList slink,int i,int &e)
{
int j=1;
LinkList p = slink->next;
while(p&&j<i)
{
p = p->next;
j++;
}
if(!p||j>i)
return false;
e = p->data;
return true;
}
我们看到,用这种方法,可以包含主 if(i<1) return false;的情况。
while(p&&j<i) 退出循环的条件是p==NULL|| j>=i;
if(!p||j>i) if中,把p==NULL ||j>i的情况都去掉了,剩下的就是j==i的情况了。
7、返回链表中某元素的直接前驱
bool PriorList(LinkList slink,int e,int &ret)
{
/*
该元素得在链表中有,且不能是第一个元素
逻辑:从第一个结点开始判断当前元素的下一个元素==e?如果是,返回当前元素
循环判断条件:当前元素->next存在且!=e
1、需要一个变量指向当前元素
*/
LinkList p = slink->next;
while(p->next&&p->next->data!=e)
{
p = p->next;
}
if(p->next)
{
ret = p->data;
return true;
}
return false;
}
8、返回链表中某元素的直接后继
bool NextList(LinkList slink,int e,int &ret)
{
/*
该元素得在链表中有,且不能是最后一个元素
逻辑:从第一个结点开始判断当前元素的==e?如果是,返回当前元素
循环判断条件:当前元素->next存在且!=e
1、需要一个变量指向当前元素
*/
LinkList p = slink->next;
while(p->next&&p->data!=e)
{
p = p->next;
}
if(p->next)
{
ret = p->next->data;
return true;
}
return false;
}
本文深入解析单链表的基本操作,包括初始化、销毁、重置、长度查询、元素插入与删除、元素获取及前后驱查找,提供多种实现方法并附详细代码说明。
&spm=1001.2101.3001.5002&articleId=104898286&d=1&t=3&u=ea9b3b3433d545eba47cd318fb416468)
332

被折叠的 条评论
为什么被折叠?



