通过一个含有自身结构的指针,我们可以实现随机分布的结构变量的遍历
对于以下结构
struct list
{
char name[20];
list * pn;
};
name成员含有结构中的某些信息,pn成员是指向另一个list的指针,这种结点(结构的实例)通过每个list的pn成员链接起来,能用于构造任意长的结构链,这样的结构链即被称为链表。链表中的每一个list结构变量称为结点
链表中的第一个list结点通常由一个指向list的指针引导,这个结构指针(称为链首指针)不是自身结构成员,但它指向第一个结点,而该结点的pn成员又指向下一个结点,以此类推,直至最后一个结点,最后一个结点的pn成员值为空(NULL)
下面介绍链表的创建与遍历的代码实现
struct student
{
int num;//结点包含的信息
float score;//结点包含的信息
student* next;//指向下一结点的指针
};
student* head;//链首指针
student* getnode()//创建结点
{
int num;
float score;
cin>>num>>score;//输入结点信息
if(!num)//终止创建 条件
{
return NULL;
}
student *p=new student;//创建
p->num=num;//导入数据
p->score=score;//导入数据
p->next=0;
return p;//返回该结点
}
void creat()
{
head=getnode();//先传入头结点
if((head)==0)
{
return ;
}
for(student *pe=head,*ps;ps=getnode();pe=ps)//同时创建pe、ps结构指针,pe用来指向上一个结点,ps用来指向下一个结点
{
pe->next=ps;//链接
}
}
void showlist()//遍历该链表
{
cout<<"here is the list\n";//注意这里不能直接用head去遍历,不然会丢失头结点
for(student*p=head;p;p=p->next)//循环终止条件是p,即p是NULL的时候
{
cout<<p->num<<' '<<p->score<<endl;
}
}
int main()
{
cout<<fixed<<setprecision(1);
creat();
showlist();
return 0;
}
该图清晰地展现了链表创建的过程,可帮助理解链表
下面介绍链表的基本操作
1.删除链表结点
我们的操作应该想上面这个图一样,这样就不会因删除而使链表链接中断
删除往往还包含查找子过程,因为首先要找到想要删除的结点(注意还包含找不到的处理)
例如:下面的代码实现了删除学号num的结点:
void deletenode(int target)
{
if(!head)//链表为空
{
cout<<"List null"<<endl;
return ;
}
student* pguard=head;
if(pguard->num==target)//删除链首结点的情况单独讨论
{
head=head->next;
delete pguard;
cout<<target<<"The head of node has been deleted"<<endl;
return ;
}
//从头结点的下一结点开始遍历
for(student* p=pguard->next;p;pguard=p,p=p->next)//注意这里在循环结束之后先更新pguard再更新p
{
if(p->num==target)
{
pguard->next=p->next;//将待删除结点的上一结点与下一结点直接相连
delete p;//记得delete
cout<<target<<"The head of node has been deleted"<<endl;
return ;
}
}
cout<<"NO find it!"<<endl;//遍历之后没找到
}
注意上面删除链首结点的步骤为:
- p指向链首结点;
- head指向链首结点的下一结点;
- 删除p指向的结点
这里的顺序是很重要的,如果p不指向链首结点,则当head指向链首结点的下一结点后,原链首结点脱链,导致结点地址丢失,以致无法释放堆空间 如果先删除链首结点,head指针无法指向由链首结点导出的下一个结点地址
其次,当delete()函数找到要删除的结点时,删除的步骤为:
- p指向待删除的结点;
- pguard所指向结点的next成员指向待删结点的下一个结点;
- 删除p指向的结点
在找到待删结点时,pguard指向待删结点的下一个结点是很重要的。否则,待删结点的前一结点地址丢失,其next成员无法与待删结点的后一结点链接
2.插入链表结点
请看图
插入操作也包含一个插入位置查找的子过程,同样面临链表是空表或插入到链首的特殊情况(需要单独讨论)
假设创建(调用create())链表的时候,结点是按照num的值由小到大顺序排列,则插入函数设计如下:
void insertnode(student& data)
{
student *ps=new student;//链结点统一由堆空间结点构成
*ps=data;
if(!head||ps->num<head->num)//链表为空或者插在链首(二者操作一样)
{
ps->next=head;
head=ps;
delete ps;
}
student* pguard=head;
for(student *p=pguard->next;p;pguard=p,p=p->next)//欲插在pguard与p中间
{
if(p->num>ps->num)
{
ps->next=p;
pguard->next=ps;//结合上图理解
return ;
}
}
pguard->next=ps;//直到链尾还未插入,则插在最后
ps->next=NULL;
}
值得注意的是insertnode()函数的参数来自主函数实参的引用,数据属于栈空间,不能作为链表的结点,所以函数一上来就申请堆空间,复制函数参数到ps