数据结构实例精讲链表.docx
- 文档编号:7266567
- 上传时间:2023-01-22
- 格式:DOCX
- 页数:77
- 大小:83.91KB
数据结构实例精讲链表.docx
《数据结构实例精讲链表.docx》由会员分享,可在线阅读,更多相关《数据结构实例精讲链表.docx(77页珍藏版)》请在冰豆网上搜索。
数据结构实例精讲链表
数据结构实例精讲:
链表
2.3链表的定义和创建
线性表的链式存储结构简称链表。
链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。
链表由一系列结点(链表中每一个元素称为结点)组成,结点可以在运行时动态生成。
每个结点包括两个部分:
一为存储数据元素的数据域,二为存储下一个结点地址的指针域。
例如,可以将结点结构定义为:
structLNode{
ElemTypedata;
structLnode*next;
}
其中,ElemType可以是基本数据类型(如整型、实型或字符型),也可以是我们自定义的数据类型。
由此可见,链表这种数据结构必须利用指针变量才能实现,指针作为维系结点的纽带,可以通过它实现链式存储。
上面定义的链表的每个结点中只有一个指向后继结点的指针,该链表称为单链表。
其实结点中可以有不只一个用于链接其他结点的指针。
如果每个结点中有两个用于链接其他结点的指针,一个指向前驱结点(称前驱指针),另一个指向后继结点(称后继指针),则构成双向链表。
可以将双向链表中的结点结构定义为:
structDuLNode{
ElemTypedata;
structDuLNode*prior,*next;
}
【实例2-12】单链表的创建和输出。
编写程序,定义并创建一个单链表,并将其输出。
(1)问题分析。
建立链表的思想是:
—个—个地动态生成结点并输入各结点数据,同时建立起各结点前后相链的关系。
这种关系可以是正挂、倒挂或插入。
采用正挂的方法建立单链表,具体步骤为:
①定义链表的数据结构;
②创建一个空表;
③利用new运算符向系统申请分配一个结点;
④输入结点的数据域。
如果输入的数据为指定的结束标志,转到⑦;
⑤若是空表,将新结点链接到表头;若是非空表,将新结点链接到表尾;
⑥转到③;
⑦返回头指针。
采用倒挂法的步骤与正挂法差不多,只是插入新结点时,总是将其插入到表头。
(2)源程序。
#include
usingnamespacestd;
typedefintElemType;
structLNode
{ElemTypedata;
LNode*next;
};
typedefLNode*LinkList;
LinkListcreate();
LinkListcreate1();
voidprint(LinkListL);
voidDestroyList(LinkList&L);
intmain()
{
LinkListL;
cout<<"输入链表中的数据,以-1作为结束:
"< L=create(); cout<<"采用正挂法建立的单链表为: "< print(L); DestroyList(L); cout<<"输入链表中的数据,以-1作为结束: "< L=create1(); cout<<"采用倒挂法建立的单链表为: "< print(L); DestroyList(L); return0; } LinkListcreate()//采用正挂的方法建立不带头结点的单链表 { LinkListp1,p2,head; intnum; head=NULL;//创建一个空表 cin>>num; while(num! =-1)//如果输入的数据为指定的结束标志,结束 { p1=newLNode;//利用new运算符向系统申请分配一个结点 p1->data=num;p1->next=NULL; if(head==NULL) head=p1;//若是空表,将新结点链接到表头 else p2->next=p1;//若是非空表,将新结点链接到表尾 p2=p1; cin>>num; } returnhead;//返回头指针 } LinkListcreate1()//采用倒挂的方法建立不带头结点的单链表 { LinkListp,head; intnum; head=NULL; cin>>num; while(num! =-1) { p=newLNode; p->data=num; p->next=head;head=p;//插入到表头 cin>>num; } returnhead; } voidprint(LinkListL) { LinkListp; p=L; if(p==NULL) cout<<"链表是空表! "< else { while(p->next! =NULL) { cout< p=p->next; } cout< } } voidDestroyList(LinkList&L) { LinkListp=L; while(p! =NULL) { L=L->next; deletep; p=L; } } (3)应用于后面实例的头文件LinkList.h和LinkList.cpp文件。 本例实现了链表的创建和输出等基本运算,为了在后面的实例中直接应用这些基本运算,将链表的定义及函数声明保存到LinkList.h头文件中,各函数的实现保存到LinkList.cpp中。 头文件LinkList.h保存内容如下: typedefintElemType; structLNode {ElemTypedata; LNode*next; }; typedefLNode*LinkList; LinkListcreate(); voidprint(LinkListL); voidDestroyList(LinkList&L); 源程序文件LinkList.cpp保存内容如下: #include #include"Linklist.h" usingnamespacestd; LinkListcreate()//采用正挂的方法建立不带头结点的单链表 { LinkListp1,p2,head; head=NULL;//创建一个空表 p1=newLNode;//利用new运算符向系统申请分配一个结点 p2=p1; cin>>p1->data;//输入结点的数据域 while(p1->data! =-1)//如果输入的数据为指定的结束标志,结束 { if(head==NULL) head=p1;//若是空表,将新结点链接到表头 else p2->next=p1;//若是非空表,将新结点链接到表尾 p2=p1; p1=newLNode;//利用new运算符向系统申请分配一个结点 cin>>p1->data;//输入结点的数据域 } p2->next=NULL; deletep1; returnhead;//返回头指针 } voidprint(LinkListL) { LinkListp; p=L; if(p==NULL) cout<<"链表是空表! "< else { while(p->next! =NULL) { cout< p=p->next; } cout< } } voidDestroyList(LinkList&L) { LinkListp=L; while(p! =NULL) { L=L->next; deletep; p=L; } } 这样,在下面的实例中如果需要用到这些基本运算,只需要#include"LinkList.h"包含头文件即可。 2.4链表的操作实例 1.插入和删除 链表的一个重要特点是插入、删除操作灵活方便,不需移动结点,只需改变结点中指针域的值即可。 【实例2-13】链表有序插入。 编写一个函数voidInsert(LinkList&L,ElemTypenum)在链表L中插入一个值为num的结点,插入后链表仍保持有序。 设单链表L中的结点按数据域data的值从小到大的顺序排列。 并调用所编写的有序插入函数建立一个有序链表。 (1)问题分析。 在插入时,有三种情况: ①插入到一个空表中; ②插入到链表的首部; ③插入到链表中间或链表的最后。 其中,前两种情况会改变头指针。 而后一种情况,需要先在单链表中寻找到正确的插入位置(设寻找到的插入位置在第i-1个结点之后)并由指针p指示,然后通过修改指针,重新建立ai-1与num、num与ai两两数据元素之间的链,从而实现三个元素之间链接关系的变化。 num插入时指针变化如图2-2所示,图中虚线所示为插入前的指针。 图2-2单链表的插入 (2)函数程序代码。 voidInsert(LinkList&L,ElemTypenum) { LinkListp,s; p=L; s=newLNode; s->data=num; if(p==NULL)//插入到空表 { L=s; s->next=NULL; } elseif(p->data>num)//插入到链表首 { s->next=L; L=s; } else//插入到中间或最后 { while(p->next&&p->next->data p=p->next; s->next=p->next; p->next=s; } } (3)函数应用的示例源程序。 #include #include"Linklist.h" usingnamespacestd; voidInsert(LinkList&L,ElemTypenum); intmain() { LinkListL; cout<<"输入链表中的数据,以-1作为结束: "< ElemTypenum; L=NULL; cin>>num; while(num! =-1) { Insert(L,num); cin>>num; } cout<<"采用插入法建立的有序单链表为: "< print(L); DestroyList(L); return0; } voidInsert(LinkList&L,ElemTypenum) { LinkListp,s; p=L; s=newLNode; s->data=num; if(p==NULL)//插入到空表 { L=s; s->next=NULL; } elseif(p->data>num)//插入到链表首 { s->next=L; L=s; } else//插入到中间或最后 { while(p->next&&p->next->data p=p->next; s->next=p->next; p->next=s; } } 【实例2-14】删除最小值。 在单链表中删除最小值结点。 (1)问题分析。 若要在L为头指针的单链表中,删除数据域值最小的结点。 首先要搜索单链表以找到指定删除结点(以指针q指示)的前驱结点(以指针pre指示),然后仅需修改结点pre中的指针域,最后释放待删除结点q所占的存储空间。 图2-3显示了删除时指针变化情况,虚线所示为删除前的指针。 图2-3单链表的结点删除 一般来说,在链表中指定数据域的结点时,有四种情况要考虑: ①链表是一个空表; ②要删除的结点在链表的首部(表头指针被修改); ③要删除的结点在链表中间或最后; ④要删除的结点在链表中不存在。 单链表中删除最小值结点,为使结点删除后不出现“断链”,应知道被删结点的前驱。 而“最小值结点”是在遍历整个链表后才能知道。 所以算法应首先遍历链表,求得最小值结点及其前驱。 遍历结束后再执行删除操作。 将“在单链表中删除最小值结点”这一操作写成一个函数DeleteMin。 函数体中,用指针p来遍历单链表,用指针q指向最小值结点,指针pre指向最小值结点q的前驱结点。 (2)函数程序代码。 voidDeleteMin(LinkList&L) //L是不带头结点的单链表,算法删除其最小值结点。 { LinkListp,pre,q; if(L==NULL) { return;//链表为空,无结点可删,直接返回 } p=L;//p为工作指针,指向待处理的结点。 此时链表非空。 pre=NULL;//pre用于指向最小值结点的前驱 q=p;//q指向最小值结点,初始假定第1个结点是最小值结点 while(p->next! =NULL) { if(p->next->data { pre=p;q=p->next; } p=p->next;//p指针后移。 } if(pre==NULL) L=L->next;//头结点是最小值结点 else pre->next=q->next;//最小值结点不是头结点 deleteq;//释放最小值结点空间 } (3)函数应用的示例源程序。 #include #include"Linklist.h" usingnamespacestd; voidDeleteMin(LinkList&L); intmain() { LinkListL; cout<<"输入链表中的数据,以-1作为结束: "< L=create(); cout<<"链表初始情况为: "< print(L); DeleteMin(L); cout<<"删除最小值后,链表为: "< print(L); DestroyList(L); return0; }voidDeleteMin(LinkList&L) //L是不带头结点的单链表,算法删除其最小值结点。 { LinkListp,pre,q; if(L==NULL) { return;//链表为空,无结点可删,直接返回 } p=L;//p为工作指针,指向待处理的结点。 此时链表非空。 pre=NULL;//pre用于指向最小值结点的前驱 q=p;//q指向最小值结点,初始假定第1个结点是最小值结点 while(p->next! =NULL) { if(p->next->data { pre=p;q=p->next; } p=p->next;//p指针后移。 } if(pre==NULL) L=L->next;//头结点是最小值结点 else pre->next=q->next;//最小值结点不是头结点 deleteq;//释放最小值结点空间 } 通常可以在线性链表的第一个结点之前附设一个结点,称之为头结点。 头结点的数据域可以不存储任何信息,也可存储如线性表的长度等附加信息,头结点的指针域存储指向第一个结点的指针,而链表的头指针指向头结点。 这样,在第一个结点前面插入新结点和删除第一个结点就不影响表头指针L的值(如上面算法就引起了头指针L的变化),而只改变头结点的指针域的值,因此就可以和其他位置的插入、删除同样处理了。 例如,若L为带头结点的单链表,则插入函数Insert可简单地写成: voidInsert(LinkList&L,ElemTypenum) {//L是带头结点的递增有序单链表,插入num后仍保持有序 LinkListp,s; p=L; s=newLNode; s->data=num; while(p->next&&p->next->data p=p->next; s->next=p->next; p->next=s; } 在双向链表中,若p是指向表中某个结点的指针,则有 p==p->next->prior==p->prior->next 双向链表的插入和删除算法与单链表有很大的不同,需同时修改两个方向上的指针。 例如,在双向链表中指针p所指的结点之前插入元素x的操作步骤为: s=newDuLNode; s->data=x; s->prior=p->prior; p->prior->next=s; s->next=p; p->prior=s; 删除双向链表中指针p所指的结点的操作步骤为: p->prior->next=p->next; p->next->prior=p->prior; deletep; 【实例2-15】删除重复结点。 编写一个函数,删除不带头结点的单链表L中的重复结点。 (1)问题分析。 用指针p来遍历单链表,p的初始值为单链表的头指针。 p指向一个数据结点时,从它的后继结点开始到表的结束,找与其值相同的结点并删除之;然后p指向下一个结点;依此类推,直到p指向最后结点时,遍历结束。 (2)函数程序代码。 voidDeleteSame(LinkList&L) //L是不带头结点的单链表,算法删除其重复结点。 { LinkListp,q,r; p=L;//p指向第一个结点 if(p==NULL) return; while(p->next) { q=p; while(q->next)//从p的后继开始找重复结点 { if(q->next->data==p->data) { r=q->next;//找到重复结点,用r指向该结点,删除r q->next=r->next; deleter; } else q=q->next; } if(p->next! =NULL) p=p->next; } } (3)函数应用的示例源程序。 #include #include"Linklist.h" usingnamespacestd; voidDeleteSame(LinkList&L); intmain() { LinkListL; cout<<"输入链表中的数据,以-1作为结束: "< L=create(); cout<<"链表初始情况为: "< print(L); DeleteSame(L); cout<<"删除重复结点后,链表为: "< print(L); DestroyList(L); return0; } voidDeleteSame(LinkList&L) //L是不带头结点的单链表,算法删除其重复结点。 { LinkListp,q,r; p=L;//p指向第一个结点 if(p==NULL) return; while(p->next) { q=p; while(q->next)//从p的后继开始找重复结点 { if(q->next->data==p->data) { r=q->next;//找到重复结点,用r指向该结点,删除r q->next=r->next; deleter; } else q=q->next; } if(p->next! =NULL) p=p->next; } } 【实例2-16】集合的差集。 已知两个带头结点的递增有序的单链表La、Lb分别存储集合A和B,编写一个函数求两个集合A和B的差集A=A-B(即仅由在A中出现而不在B中出现的元素所构成的集合)。 (1)问题分析。 求两个集合A和B的差集A=A-B,即在A中删除A和B中共有的元素。 由于集合用单链表存储,问题变成删除链表中的结点问题。 删除结点时,要记住被删除结点的前驱,以便顺利删除被删结点。 由于两个链表递增有序,因此遍历时,两链表均从第1个结点开始,直到其中一个链表到尾为止。 (2)函数程序代码。 voidDifference(LinkList&La,LinkListLb) //La和Lb是带头结点的递增有序的单链表,算法求两集合的差集A=A-B { LinkListpa,pb,pre; pa=La->next;//pa和pb分别是链表La和Lb的工作指针 pb=Lb->next; pre=La;//pre为La中pa所指结点的前驱结点的指针 while(pa! =NULL&&pb! =NULL) if(pa->data { pre=pa;pa=pa->next;//La表中当前结点指针后移 } elseif(pa->data>pb->data) pb=pb->next;//Lb表中当前结点指针后移 else { pre->next=pa->next;//处理A、B中元素值相同的结点,应删除 deletepa;pa=pre->next; } } (3)函数应用的示例源程序。 #include #include"Linklist.h" usingnamespacestd; voidDifference(LinkList&La,LinkListLb); voidInsert(LinkList&L,ElemTypenum); intmain() { LinkListLa,Lb; ElemTypenum; cout<<"输入集合A中的数据,以-1作为结束: "< La=newLNode; La->next=NULL; cin>>num; while(num! =-1) { Insert(La,num); cin>>num; } cout<<"集合A初始情况为: "< print(La->next);
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 数据结构 实例 精讲链表