关系分析
按照有无头结点,单双向,是否循环可分为八类链表,这里我们约定都有头结点
为啥选择带头结点呢?因为简单呀~
假设此时无头结点,现在要向链表插入一个结点,那么得考虑三种情况:
1:插入头之前,2:插入尾之后,3:插入头尾之间
若是此时带有头结点,那么第一种情况就退化成第三种情况,于是我们只需考虑两种情况即可,能省则省咯
那么还剩下四类链表:单向链表,单向循环链表,双向链表,双向循环链表
这四类链表的核心在于单向链表,单向链表能够顺利创建,其余均可由它衍生而来
- 1,单向链表首尾相连即是单向循环链表
- 2,单向链表创建时给每个节点赋与前驱,即是双向链表
- 3,双向链表首尾相连即是双向循环链表(与1本质相同)
了解关系及思想后第一要务就是如何实现,实现的第一要务就是设计合适的数据结构,数据结构决定了功能实现的难易,所以合理设计数据结构是极其重要的
数据结构
由他们之间关系可知,单向链表与单向循环链表结构一致;双向链表与双向循环链表一致且比单向类型多一个前驱指针。所以可设计根据方向分为单向,双向两类结构体
单向类型
//单向链表
typedef struct LNode{
int data;//数据域
struct LNode* next;//指针域:后继节点
}LNode,*LinkList;//分别表示节点,链表;命名最好有意义
双向类型
//双向链表
typedef struct DLNode{
int data;
struct DLNode *prior,*next;//比单向链表多一个指向前驱的指针
}DLNode,*DLinkList;
头/尾插法创建四类链表
想对链表进行任何操作,他得先存在不是,所以第一要紧就是想办法建立链表,建立链表常用的有两种方式
- 头插法(新结点均插入头结点之后,数据逆序)
- 尾插法(新结点均插入尾结点之后,数据正序)
根据前面的四类链表关系分析,可知我们只需先掌握单向链表的两种创建方式,加以扩展,即可得到其余三类的实现方法
- 在单向/双向链表(非循环)的实现中,两种方法难度基本一致;
- 但在扩展为另外两类循环类链表时,头插法比尾插法多了寻找尾节点的步骤
程序的测试数据均从文件读取
文件链表操作.txt内容如下
1 2 3 4 5 6 7 8 9 10
单向链表
头插法与尾插法整体思路类似
- 不过头插法需要一个指向头结点后的节点的指针
- 而尾插法需要指向尾部的指针
头插法
//头插法创建带头结点的单向链表 :数据逆序
void CreateLinkList_Head(LinkList &L)
{
//头结点申请,数据区不用
L = (LinkList)malloc(sizeof(LNode));
L->next = NULL;
//从文件读取数据
fstream inFile("链表操作.txt",ios::in);
if(!inFile)cout<<"fail to open file!"<<endl;
int tmp;//存储文件读出的数据
LNode *p,*pnext; //p:每次申请新空间的中间变量;pnext:头结点的后一个节点
while(true){
inFile>>tmp;
if(!inFile)break;//结束标记;此方式可避免使用eof而导致文件最后一个字符被读取两次的情况
pnext = L->next;//防止断链,记录头结点的后一个节点;其实只利用L就可完成头插法,不过为了可读性,还是多用一个指针
p = (LNode*)malloc(sizeof(LNode));//申请新节点
p->data = tmp;//存入要插入的数据
//新节点已准备好,以下将其插入;这里插入顺序无所谓,不会断链
L->next = p;
p->next = pnext;
}
inFile.close();//好习惯
Traverse_LinkList(L);//打印调试,该函数稍后介绍~
}
尾插法
//尾插法:数据正序
void CreateLinkList_Rear(LinkList &L)
{
//头结点申请,数据区不用
L = (LinkList)malloc(sizeof(LNode));
L->next = NULL;
//从文件读取数据
fstream inFile("链表操作.txt",ios::in);
if(!inFile)cout<<"fail to open file!"<<endl;
int tmp;//存储文件读出的数据
LNode *p,*prear=L; //p:每次申请新空间的中间变量;prear:链表最后一个节点
while(true){
inFile>>tmp;
if(!inFile)break;//结束标记;此方式可避免使用eof而导致文件最后一个字符被读取两次的情况
p = (LNode*)malloc(sizeof(LNode));//申请新节点
p->data = tmp;//存入要插入的数据
p->next = NULL;//插入作为尾节点,所以后继为空
//插入到上一个尾的后面
prear->next = p;
prear = p; //更新尾部
}
inFile.close();//好习惯
Traverse_LinkList(L);//打印调试
}
单向循环链表
将已创建的带头结点的单向链表首尾连接(这里的首不是头结点,二是头结点的后一个节点),即可得到单向循环链表,此时头结点已被排除在链表外(真可怜~,用完就被抛弃)
- 由于尾插法本来就有尾部指针,直接相连头尾即可
- 而头插法则需要从头遍历一次链表,找到尾指针,再连接头尾
头插法
//基于头插法创建带头结点的单向链表连接首尾,得到单向循环链表,不过此时头结点已被忽略
void CreateCircleLinkList_Head(LinkList &L)
{
//头结点申请,数据区不用
L = (LinkList)malloc(sizeof(LNode));
L->next = NULL;
//从文件读取数据
fstream inFile("链表操作.txt",ios::in);
if(!inFile)cout<<"fail to open file!"<<endl;
int tmp;//存储文件读出的数据
LNode *p,*pnext; //p:每次申请新空间的中间变量;pnext:头结点的后一个节点
while(true){
inFile>>tmp;
if(!inFile)break;//结束标记;此方式可避免使用eof而导致文件最后一个字符被读取两次的情况
pnext = L->next;//防止断链,记录头结点的后一个节点;其实只利用L就可完成头插法,不过为了可读性,还是多用一个指针
p = (LNode*)malloc(sizeof(LNode));//申请新节点
p->data = tmp;//存入要插入的数据
//新节点已准备好,以下将其插入;这里插入顺序无所谓,不会断链
L->next = p;
p->next = pnext;
}
//找到尾节点,与头节点后的一个节点相连
LNode *prear;
prear = L;
while(prear->next != NULL){//查找尾节点
prear = prear->next;
}
prear->next = L->next;//连接
L = L->next;//略过头结点
inFile.close();//好习惯
Traverse_CircleLinkList(L);//打印调试
}
尾插法
//尾插法建立单向循环链表:在建立带头结点单向链表的基础上将头尾相连即可,此时头结点被略过
void CreateCircleLinkList_Rear(LinkList &L)
{
//头结点申请,数据区不用
L = (LinkList)malloc(sizeof(LNode));
L->next = NULL;
//从文件读取数据
fstream inFile("链表操作.txt",ios::in);
if(!inFile)cout<<"fail to open file!"<<endl;
int tmp;//存储文件读出的数据
LNode *p,*prear=L; //p:每次申请新空间的中间变量;prear:链表最后一个节点
while(true){
inFile>>tmp;
if(!inFile)break;//结束标记;此方式可避免使用eof而导致文件最后一个字符被读取两次的情况
p = (LNode*)malloc(sizeof(LNode));//申请新节点
p->data = tmp;//存入要插入的数据
p->next = NULL;//插入作为尾节点,所以后继为空
//插入到上一个尾的后面
prear->next = p;
prear = p; //更新尾部
}
prear->next = L->next;//首尾相连,略过了头结点
L = L->next;//记录起点位置,用于判断是否遍历完全
inFile.close();//好习惯
Traverse_CircleLinkList(L);//打印调试
}
双向链表
在创建单向链表时处理完后继结点,顺便处理一下前驱结点即可得到双向链表
- 头插法处理前驱时先判断pnext是否为空,再处理,否则空指针乱指可不是开玩笑滴
头插法
void CreateDLinkList_Head(DLinkList &L)
{ //头结点申请,数据区不用
L = (DLinkList)malloc(sizeof(DLNode));
L->next = L->prior = NULL;
//从文件读取数据
fstream inFile("链表操作.txt",ios::in);
if(!inFile)cout<<"fail to open file!"<<endl;
int tmp;//存储文件读出的数据
DLNode *p,*pnext; //p:每次申请新空间的中间变量;pnext:头结点的后一个节点
while(true){
inFile>>tmp;
if(!inFile)break;//结束标记;此方式可避免使用eof而导致文件最后一个字符被读取两次的情况
pnext = L->next;//防止断链,记录头结点的后一个节点;其实只利用L就可完成头插法,不过为了可读性,还是多用一个指针
p = (DLNode*)malloc(sizeof(DLNode));//申请新节点
p->data = tmp;//存入要插入的数据
//处理后继
L->next = p;
p->next = pnext;
//处理先驱
if(pnext != NULL)pnext->prior = p;//记得加上判断,否则插入第一个节点就报错
p->prior = L;
}
inFile.close();//好习惯
Traverse_DLinkList(L);//打印调试
}
尾插法
//带头结点双向链表,尾插法建立:数据正序
void CreateDLinkList_Rear(DLinkList &L)
{
//头结点申请,数据区不用
L = (DLinkList)malloc(sizeof(DLNode));
L->next = L->prior = NULL;
//从文件读取数据
fstream inFile("链表操作.txt",ios::in);
if(!inFile)cout<<"fail to open file!"<<endl;
int tmp;//存储文件读出的数据
DLNode *p,*prear=L; //p:每次申请新空间的中间变量;prear:链表最后一个节点
while(true){
inFile>>tmp;
if(!inFile)break;//结束标记;此方式可避免使用eof而导致文件最后一个字符被读取两次的情况
p = (DLNode*)malloc(sizeof(DLNode));//申请新节点
p->data = tmp;//存入要插入的数据
p->next = NULL;//插入作为尾节点,所以后继为空
//插入到上一个尾的后面
prear->next = p;
p->prior = prear;//前驱处理
prear = p; //更新尾部
}
inFile.close();//好习惯
Traverse_DLinkList(L);//打印调试
}
双向循环链表
与单向链表过渡到单向循环链表的思想一致,在建立好的双向链表上,连接首尾(注意首不是头结点),即为双向循环链表
- 尾插法比头插法多一步查找尾节点的操作,其余基本一致
头插法
void CreateCircleDLinkList_Head(DLinkList &L)
{ //头结点申请,数据区不用
L = (DLinkList)malloc(sizeof(DLNode));
L->next = L->prior = NULL;
//从文件读取数据
fstream inFile("链表操作.txt",ios::in);
if(!inFile)cout<<"fail to open file!"<<endl;
int tmp;//存储文件读出的数据
DLNode *p,*pnext; //p:每次申请新空间的中间变量;pnext:头结点的后一个节点
while(true){
inFile>>tmp;
if(!inFile)break;//结束标记;此方式可避免使用eof而导致文件最后一个字符被读取两次的情况
pnext = L->next;//防止断链,记录头结点的后一个节点;其实只利用L就可完成头插法,不过为了可读性,还是多用一个指针
p = (DLNode*)malloc(sizeof(DLNode));//申请新节点
p->data = tmp;//存入要插入的数据
//处理后继
L->next = p;
p->next = pnext;
//处理先驱
if(pnext != NULL)pnext->prior = p;//记得加上判断,否则插入第一个节点就报错
p->prior = L;
}
DLNode* prear=L;
while(prear->next != NULL)prear = prear->next;
prear->next = L->next;
L->next->prior = prear;
L = L->next;
inFile.close();//好习惯
Traverse_CircleDLinkList(L);//打印调试
}
尾插法
//基于尾插法建立带头结点的双向循环链,创建双向循环链表
void CreateCircleDLinkList_Rear(DLinkList L)
{
//头结点申请,数据区不用
L = (DLinkList)malloc(sizeof(DLNode));
L->next = L->prior = NULL;
//从文件读取数据
fstream inFile("链表操作.txt",ios::in);
if(!inFile)cout<<"fail to open file!"<<endl;
int tmp;//存储文件读出的数据
DLNode *p,*prear=L; //p:每次申请新空间的中间变量;prear:链表最后一个节点
while(true){
inFile>>tmp;
if(!inFile)break;//结束标记;此方式可避免使用eof而导致文件最后一个字符被读取两次的情况
p = (DLNode*)malloc(sizeof(DLNode));//申请新节点
p->data = tmp;//存入要插入的数据
p->next = NULL;//插入作为尾节点,所以后继为空
//插入到上一个尾的后面
prear->next = p;
p->prior = prear;//前驱处理
prear = p; //更新尾部
}
//连接首尾,去除头结点
prear->next = L->next;
L->next->prior = prear;
L = L->next;//设置起点
inFile.close();//好习惯
Traverse_CircleDLinkList(L);//打印调试
}
测试效果
遍历
一个链表无论他是用头插法还是尾插法建立的,殊途同归,最终结果是一样的,所以针对该链表只需要一种遍历方法
非循环类链表遍历终止判断利用尾节点后的空;而循环类链表利用是否再次回到起点判断
同理,还是先掌握单向链表的遍历方式,再借此推广到其它三类
以下为遍历方式的兼容关系(如第一组,意思是单向链表的遍历方法适用于单向循环链表)
- 单向链表<单向循环链表
- 双向链表<双向循环链表
- 单向链表<双向链表
单向链表
读取完当前记得后移一位,不然就死循环咯
void Traverse_LinkList(LinkList L)
{
if(L == NULL)return;//链表不存在
if(L->next != NULL){
LNode* pcur = L->next;
while(pcur != NULL){
cout<<pcur->data<<" ";
pcur = pcur->next;//读完记得后移呀
}
cout<<endl;
}
else cout<<"链表为空!"<<endl;
}
单向循环链表
这里完全可以用单向链表的遍历方式,不过要改改参数类型,但是这里为了测试循环建立是否成功,加入了读取17个元素的循环
//判断条件改变:起点判断,无头结点
void Traverse_CircleLinkList(LinkList L)
{
if(L != NULL){
LNode* pcur = L;//L不是头结点,而是起点
//用do..while先输出,在判断,提高简洁些,若是用while,则在循环体外先读出起点,再进入循环内部读取其他节点
do{
cout<<pcur->data<<" ";
pcur = pcur->next;//读完记得后移呀
}while(pcur != L);//判断条件为是否再次读取到起点
cout<<endl;
cout<<"======循环测试:连续输出17个节点======="<<endl;
pcur = L;
for(int i = 0; i < 17; i++){
cout<<pcur->data<<" ";
pcur = pcur->next;
}cout<<endl;
}
else cout<<"链表为空!"<<endl;
}
双向链表
void Traverse_DLinkList(DLinkList L)
{
if(L == NULL)return;//不存在
if(L->next != NULL){
cout<<endl<<"===========带头结点双向链表正序遍历=============="<<endl;
DLNode* pcur = L->next;
cout<<pcur->data<<" ";
pcur = pcur->next;//读完记得后移呀
while(pcur->next != NULL){
cout<<pcur->data<<" ";
pcur = pcur->next;//读完记得后移呀
}
if(pcur != NULL)cout<<pcur->data<<" ";
cout<<endl<<"===========带头结点双向链表逆序遍历=============="<<endl;
while(pcur != L){//以头结点为结束判断
cout<<pcur->data<<" ";
pcur = pcur->prior;
}
}
else cout<<"链表为空!"<<endl;
}
双向循环链表
//判断条件改变:起点判断,无头结点
void Traverse_CircleDLinkList(DLinkList L)
{
if(L != NULL){
DLNode* pcur = L;//L不是头结点,而是起点
cout<<endl<<"======后继循环测试:连续输出17个节点======="<<endl;
pcur = L;
for(int i = 0; i < 17; i++){
cout<<pcur->data<<" ";
pcur = pcur->next;
}
cout<<endl<<"======前驱循环测试:连续输出17个节点======="<<endl;
for(int i = 0; i < 17; i++){
cout<<pcur->data<<" ";
pcur = pcur->prior;
}
}
else cout<<"链表为空!"<<endl;
}
增删改查核心在于查,改删增都得先查找相应位置,其中改最简单,找到后修改即可,插入和删除需要记录找到相应位置的前驱,不过根建立比起来,简直大巫见小巫咯,由于四类链表的增删改查核心在于单向链表,所以以下只介绍单链表的四种功能,其余的留给你自由发挥叭
单向链表查改增删
查找
会遍历,就会查找
LNode* Search(LinkList L,int e)
{
if(L == NULL || L->next == NULL)return NULL;//链表不存在或为空
else{
LNode* pcur = L->next;
while(pcur != NULL){
if(pcur->data == e)return pcur;
else pcur = pcur->next;
}
return NULL;//没找到该数
}
}
修改
//修改:调用查找函数,获取修改位置,直接修改即可
void Set(LinkList &L)
{
int e;
cout<<"请输入要修改的元素:";
cin>>e;
if(L == NULL || L->next == NULL)return;//链表不存在或为空
LNode* pcur = Search(L,e);
if(pcur != NULL){
cout<<"请输入修改后的值:";
cin>>e;
pcur->data = e;
}
}
增加(插入)
void Insert(LinkList &L)
{
int e;
cout<<"请输入要插入的位置:";
cin>>e;
if(L == NULL || L->next == NULL)return;//链表不存在或为空
LNode* pcur = Search(L,e);
if(pcur != NULL){
cout<<"请输入插入的值:";
cin>>e;
LNode* p = (LNode*)malloc(sizeof(LNode));
p->data = e;
p->next = pcur->next;
pcur->next = p;
}
}
删除
//与找到元素,记录其前驱,才可删除
void Delete(LinkList &L)
{
int e;
cout<<"请输入要删除的元素:";
cin>>e;
if(L == NULL || L->next == NULL)return;//链表不存在或为空
else{
LNode* pcur = L->next,*ppre=L;//ppre记录pcur的前驱
while(pcur != NULL){
if(pcur->data == e)break;
ppre = pcur;
pcur = pcur->next;
}
if(pcur == NULL)return;
ppre->next = pcur->next;//删除
free(pcur);//释放
}
}
测试效果
小收获
- 过两天及数据结构机考,果真温故而知新,通过实践的方式获取的理解更深,coding速度快了许多,明显感觉到自身编码水平提高,正好对知识做个梳理,往后复习也方便
- 写博客还是很有效果的,在这过程中会逼着自己去总结,比较,分析,对知识有系统掌握,以后回顾时也会很有成就感的,特别是当你的博客帮助到他人,坚持下去,嘿嘿嘿~
完整Code
#include<iostream>
using namespace std;
#include<stdlib.h>
#include<fstream>
//单向链表
typedef struct LNode{
int data;//数据域
struct LNode* next;//指针域:后继节点
}LNode,*LinkList;
//双向链表
typedef struct DLNode{
int data;
struct DLNode *prior,*next;//比单向链表多一个指向前驱的指针
}DLNode,*DLinkList;
void Traverse_LinkList(LinkList L)
{
if(L == NULL)return;//不存在
if(L->next != NULL){
LNode* pcur = L->next;
while(pcur != NULL){
cout<<pcur->data<<" ";
pcur = pcur->next;//读完记得后移呀
}
cout<<endl;
}
else cout<<"链表为空!"<<endl;
}
//头插法创建带头结点的单向链表 :数据逆序
void CreateLinkList_Head(LinkList &L)
{
//头结点申请,数据区不用
L = (LinkList)malloc(sizeof(LNode));
L->next = NULL;
//从文件读取数据
fstream inFile("链表操作.txt",ios::in);
if(!inFile)cout<<"fail to open file!"<<endl;
int tmp;//存储文件读出的数据
LNode *p,*pnext; //p:每次申请新空间的中间变量;pnext:头结点的后一个节点
while(true){
inFile>>tmp;
if(!inFile)break;//结束标记;此方式可避免使用eof而导致文件最后一个字符被读取两次的情况
pnext = L->next;//防止断链,记录头结点的后一个节点;其实只利用L就可完成头插法,不过为了可读性,还是多用一个指针
p = (LNode*)malloc(sizeof(LNode));//申请新节点
p->data = tmp;//存入要插入的数据
//新节点已准备好,以下将其插入;这里插入顺序无所谓,不会断链
L->next = p;
p->next = pnext;
}
inFile.close();//好习惯
Traverse_LinkList(L);//打印调试
}
//尾插法:数据正序
void CreateLinkList_Rear(LinkList &L)
{
//头结点申请,数据区不用
L = (LinkList)malloc(sizeof(LNode));
L->next = NULL;
//从文件读取数据
fstream inFile("链表操作.txt",ios::in);
if(!inFile)cout<<"fail to open file!"<<endl;
int tmp;//存储文件读出的数据
LNode *p,*prear=L; //p:每次申请新空间的中间变量;prear:链表最后一个节点
while(true){
inFile>>tmp;
if(!inFile)break;//结束标记;此方式可避免使用eof而导致文件最后一个字符被读取两次的情况
// pnext = L->next;//防止断链,记录头结点的后一个节点;其实只利用L就可完成头插法,不过为了可读性,还是多用一个指针
p = (LNode*)malloc(sizeof(LNode));//申请新节点
p->data = tmp;//存入要插入的数据
p->next = NULL;//插入作为尾节点,所以后继为空
//插入到上一个尾的后面
prear->next = p;
prear = p; //更新尾部
// p->next = pnext;
}
inFile.close();//好习惯
Traverse_LinkList(L);//打印调试
}
//判断条件改变:起点判断,无头结点
void Traverse_CircleLinkList(LinkList L)
{
if(L != NULL){
LNode* pcur = L;//L不是头结点,而是起点
//用do..while先输出,在判断,提高简洁些,若是用while,则在循环体外先读出起点,再进入循环内部读取其他节点
do{
cout<<pcur->data<<" ";
pcur = pcur->next;//读完记得后移呀
}while(pcur != L);//判断条件为是否再次读取到起点
cout<<endl;
cout<<"======循环测试:连续输出17个节点======="<<endl;
pcur = L;
for(int i = 0; i < 17; i++){
cout<<pcur->data<<" ";
pcur = pcur->next;
}cout<<endl;
}
else cout<<"链表为空!"<<endl;
}
//基于头插法创建带头结点的单向链表连接首尾,得到单向循环链表,不过此时头结点已被忽略
void CreateCircleLinkList_Head(LinkList &L)
{
//头结点申请,数据区不用
L = (LinkList)malloc(sizeof(LNode));
L->next = NULL;
//从文件读取数据
fstream inFile("链表操作.txt",ios::in);
if(!inFile)cout<<"fail to open file!"<<endl;
int tmp;//存储文件读出的数据
LNode *p,*pnext; //p:每次申请新空间的中间变量;pnext:头结点的后一个节点
while(true){
inFile>>tmp;
if(!inFile)break;//结束标记;此方式可避免使用eof而导致文件最后一个字符被读取两次的情况
pnext = L->next;//防止断链,记录头结点的后一个节点;其实只利用L就可完成头插法,不过为了可读性,还是多用一个指针
p = (LNode*)malloc(sizeof(LNode));//申请新节点
p->data = tmp;//存入要插入的数据
//新节点已准备好,以下将其插入;这里插入顺序无所谓,不会断链
L->next = p;
p->next = pnext;
}
//找到尾节点,与头节点后的一个节点相连
LNode *prear;
prear = L;
while(prear->next != NULL){//查找尾节点
prear = prear->next;
}
prear->next = L->next;//连接
L = L->next;//略过头结点
inFile.close();//好习惯
Traverse_CircleLinkList(L);//打印调试
}
//尾插法建立单向循环链表:在建立带头结点单向链表的基础上将头尾相连即可,此时头结点被略过
void CreateCircleLinkList_Rear(LinkList &L)
{
//头结点申请,数据区不用
L = (LinkList)malloc(sizeof(LNode));
L->next = NULL;
//从文件读取数据
fstream inFile("链表操作.txt",ios::in);
if(!inFile)cout<<"fail to open file!"<<endl;
int tmp;//存储文件读出的数据
LNode *p,*prear=L; //p:每次申请新空间的中间变量;prear:链表最后一个节点
while(true){
inFile>>tmp;
if(!inFile)break;//结束标记;此方式可避免使用eof而导致文件最后一个字符被读取两次的情况
p = (LNode*)malloc(sizeof(LNode));//申请新节点
p->data = tmp;//存入要插入的数据
p->next = NULL;//插入作为尾节点,所以后继为空
//插入到上一个尾的后面
prear->next = p;
prear = p; //更新尾部
}
prear->next = L->next;//首尾相连,略过了头结点
L = L->next;//记录起点位置,用于判断是否遍历完全
inFile.close();//好习惯
Traverse_CircleLinkList(L);//打印调试
}
void Traverse_DLinkList(DLinkList L)
{
if(L == NULL)return;//不存在
if(L->next != NULL){
cout<<endl<<"===========带头结点双向链表正序遍历=============="<<endl;
DLNode* pcur = L->next;
cout<<pcur->data<<" ";
pcur = pcur->next;//读完记得后移呀
while(pcur->next != NULL){
cout<<pcur->data<<" ";
pcur = pcur->next;//读完记得后移呀
}
if(pcur != NULL)cout<<pcur->data<<" ";
cout<<endl<<"===========带头结点双向链表逆序遍历=============="<<endl;
while(pcur != L){//以头结点为结束判断
cout<<pcur->data<<" ";
pcur = pcur->prior;
}
}
else cout<<"链表为空!"<<endl;
}
void CreateDLinkList_Head(DLinkList &L)
{ //头结点申请,数据区不用
L = (DLinkList)malloc(sizeof(DLNode));
L->next = L->prior = NULL;
//从文件读取数据
fstream inFile("链表操作.txt",ios::in);
if(!inFile)cout<<"fail to open file!"<<endl;
int tmp;//存储文件读出的数据
DLNode *p,*pnext; //p:每次申请新空间的中间变量;pnext:头结点的后一个节点
while(true){
inFile>>tmp;
if(!inFile)break;//结束标记;此方式可避免使用eof而导致文件最后一个字符被读取两次的情况
pnext = L->next;//防止断链,记录头结点的后一个节点;其实只利用L就可完成头插法,不过为了可读性,还是多用一个指针
p = (DLNode*)malloc(sizeof(DLNode));//申请新节点
p->data = tmp;//存入要插入的数据
//处理后继
L->next = p;
p->next = pnext;
//处理先驱
if(pnext != NULL)pnext->prior = p;//记得加上判断,否则插入第一个节点就报错
p->prior = L;
}
inFile.close();//好习惯
Traverse_DLinkList(L);//打印调试
}
//带头结点双向链表,尾插法建立:数据正序
void CreateDLinkList_Rear(DLinkList &L)
{
//头结点申请,数据区不用
L = (DLinkList)malloc(sizeof(DLNode));
L->next = L->prior = NULL;
//从文件读取数据
fstream inFile("链表操作.txt",ios::in);
if(!inFile)cout<<"fail to open file!"<<endl;
int tmp;//存储文件读出的数据
DLNode *p,*prear=L; //p:每次申请新空间的中间变量;prear:链表最后一个节点
while(true){
inFile>>tmp;
if(!inFile)break;//结束标记;此方式可避免使用eof而导致文件最后一个字符被读取两次的情况
p = (DLNode*)malloc(sizeof(DLNode));//申请新节点
p->data = tmp;//存入要插入的数据
p->next = NULL;//插入作为尾节点,所以后继为空
//插入到上一个尾的后面
prear->next = p;
p->prior = prear;//前驱处理
prear = p; //更新尾部
}
inFile.close();//好习惯
Traverse_DLinkList(L);//打印调试
}
//判断条件改变:起点判断,无头结点
void Traverse_CircleDLinkList(DLinkList L)
{
if(L != NULL){
DLNode* pcur = L;//L不是头结点,而是起点
cout<<endl<<"======后继循环测试:连续输出17个节点======="<<endl;
pcur = L;
for(int i = 0; i < 17; i++){
cout<<pcur->data<<" ";
pcur = pcur->next;
}
cout<<endl<<"======前驱循环测试:连续输出17个节点======="<<endl;
for(int i = 0; i < 17; i++){
cout<<pcur->data<<" ";
pcur = pcur->prior;
}
}
else cout<<"链表为空!"<<endl;
}
void CreateCircleDLinkList_Head(DLinkList &L)
{ //头结点申请,数据区不用
L = (DLinkList)malloc(sizeof(DLNode));
L->next = L->prior = NULL;
//从文件读取数据
fstream inFile("链表操作.txt",ios::in);
if(!inFile)cout<<"fail to open file!"<<endl;
int tmp;//存储文件读出的数据
DLNode *p,*pnext; //p:每次申请新空间的中间变量;pnext:头结点的后一个节点
while(true){
inFile>>tmp;
if(!inFile)break;//结束标记;此方式可避免使用eof而导致文件最后一个字符被读取两次的情况
pnext = L->next;//防止断链,记录头结点的后一个节点;其实只利用L就可完成头插法,不过为了可读性,还是多用一个指针
p = (DLNode*)malloc(sizeof(DLNode));//申请新节点
p->data = tmp;//存入要插入的数据
//处理后继
L->next = p;
p->next = pnext;
//处理先驱
if(pnext != NULL)pnext->prior = p;//记得加上判断,否则插入第一个节点就报错
p->prior = L;
}
DLNode* prear=L;
while(prear->next != NULL)prear = prear->next;
prear->next = L->next;
L->next->prior = prear;
L = L->next;
inFile.close();//好习惯
Traverse_CircleDLinkList(L);//打印调试
}
//基于尾插法建立带头结点的双向循环链,创建双向循环链表
void CreateCircleDLinkList_Rear(DLinkList L)
{
//头结点申请,数据区不用
L = (DLinkList)malloc(sizeof(DLNode));
L->next = L->prior = NULL;
//从文件读取数据
fstream inFile("链表操作.txt",ios::in);
if(!inFile)cout<<"fail to open file!"<<endl;
int tmp;//存储文件读出的数据
DLNode *p,*prear=L; //p:每次申请新空间的中间变量;prear:链表最后一个节点
while(true){
inFile>>tmp;
if(!inFile)break;//结束标记;此方式可避免使用eof而导致文件最后一个字符被读取两次的情况
p = (DLNode*)malloc(sizeof(DLNode));//申请新节点
p->data = tmp;//存入要插入的数据
p->next = NULL;//插入作为尾节点,所以后继为空
//插入到上一个尾的后面
prear->next = p;
p->prior = prear;//前驱处理
prear = p; //更新尾部
}
//连接首尾,去除头结点
prear->next = L->next;
L->next->prior = prear;
L = L->next;//设置起点
inFile.close();//好习惯
Traverse_CircleDLinkList(L);//打印调试
}
LNode* Search(LinkList L,int e)
{
if(L == NULL || L->next == NULL)return NULL;//链表不存在或为空
else{
LNode* pcur = L->next;
while(pcur != NULL){
if(pcur->data == e)return pcur;
else pcur = pcur->next;
}
return NULL;//没找到该数
}
}
//与找到元素,记录其前驱,才可删除
void Delete(LinkList &L)
{
int e;
cout<<"请输入要删除的元素:";
cin>>e;
if(L == NULL || L->next == NULL)return;//链表不存在或为空
else{
LNode* pcur = L->next,*ppre=L;//ppre记录pcur的前驱
while(pcur != NULL){
if(pcur->data == e)break;
ppre = pcur;
pcur = pcur->next;
}
if(pcur == NULL)return;
ppre->next = pcur->next;//删除
free(pcur);//释放
}
}
//修改:调用查找函数,获取修改位置,直接修改即可
void Set(LinkList &L)
{
int e;
cout<<"请输入要修改的元素:";
cin>>e;
if(L == NULL || L->next == NULL)return;//链表不存在或为空
LNode* pcur = Search(L,e);
if(pcur != NULL){
cout<<"请输入修改后的值:";
cin>>e;
pcur->data = e;
}
}
void Insert(LinkList &L)
{
int e;
cout<<"请输入要插入的位置:";
cin>>e;
if(L == NULL || L->next == NULL)return;//链表不存在或为空
LNode* pcur = Search(L,e);
if(pcur != NULL){
cout<<"请输入插入的值:";
cin>>e;
LNode* p = (LNode*)malloc(sizeof(LNode));
p->data = e;
p->next = pcur->next;
pcur->next = p;
}
}
int main()
{
LinkList L1_1=NULL;
LinkList L1_2=NULL;
LinkList L2_1=NULL;
LinkList L2_2=NULL;
cout<<"带头结点单向链表--头插法创建: ";CreateLinkList_Head(L1_1);
cout<<endl<<"带头结点单向链表--尾插法创建: ";CreateLinkList_Rear(L1_2);
cout<<endl<<"单向循环链表--头插法创建: ";CreateCircleLinkList_Head(L2_1);
cout<<endl<<"单向循环链表--尾插法创建: ";CreateCircleLinkList_Rear(L2_2);
DLinkList L3_1=NULL;
DLinkList L3_2=NULL;
DLinkList L4_1=NULL;
DLinkList L4_2=NULL;
cout<<endl<<"带头结点双向链表--头插法创建: ";CreateDLinkList_Head(L3_1);
cout<<endl<<"带头结点双向链表--尾插法创建: ";CreateDLinkList_Rear(L3_2);
cout<<endl<<"双向循环链表--头插法创建: ";CreateCircleDLinkList_Head(L4_1);
cout<<endl<<"双向循环链表--尾插法创建: ";CreateCircleDLinkList_Rear(L4_2);
LNode* p = Search(L1_1,11);
if(p != NULL)cout<<p->data<<endl;
else cout<<"查无此数!"<<endl;
Delete(L1_1);
Traverse_LinkList(L1_1);
Set(L1_1);
Traverse_LinkList(L1_1);
Insert(L1_1);
Traverse_LinkList(L1_1);
return 0;
}