四类链表的创建、增删改查及其衍生关系

关系分析

按照有无头结点,单双向,是否循环可分为八类链表,这里我们约定都有头结点

为啥选择带头结点呢?因为简单呀~
假设此时无头结点,现在要向链表插入一个结点,那么得考虑三种情况:
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;
}
  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值