链表详解来啦

目录

单链表:

双向链表:  

循环链表:

参考:


单链表:

通过单向链表可以很方便的获取每一个接点的直接后继,但要获取直接前驱比较麻烦。其中包含数据域和链域。通常,链表的第一个结点不含任何数据信息,称为头结点,其链域为空时,链表为空链表。

#include<iostream>
using namespace std;
typedef int datatype;
//1.链表的表示
//链表结点的类型定义:设置clNode和*chainList两种形式,是为了后面避免二级指针的出现
//二级指针的使用,一定要注意一级指针指向地址,切勿为空
typedef struct clNode {
	datatype data; //数据域
	clNode* next; //链域
	//一定要对每个结点进行初始化操作,不然创建的结点链域无指向,为野指针
	//不然就在初始化的时候,new clNode{x,null} 进行初始化
	clNode() :next(NULL) {
		data = 0;
	}
}*chainList;

//2.链表的基本操作
//2.1遍历链表:从头结点出发,通过每个结点的链域可以访问下一个节点,直到最后一个结点为止
void cl_traverse(chainList h)
{
	if (h == NULL) return;
	h = h->next; //指向链表的第一个结点(与头结点区分)
	while (h != NULL)
	{
		cout << h->data << endl;
		h = h->next;
	}
	cout << endl;
}

//2.2查找操作:查询值为x的结点,需要进行遍历,同时对结点的data进行判断
chainList cl_search(chainList h, datatype x)  //此处的指针需要变换,不能加const
{
	if (h == NULL) return NULL;
	h = h->next;
	while (h != NULL) {
		if (h->data == x)
			return h;
		h = h->next;
	}
	return NULL;
}

//2.3插入节点:在链表的结点p后面插入一个数据域的值为x的结点q
//当链表中的一个结点p后面插入一个结点时,只需要重新定义p的链域和插入结点q的链域
//第二个思路,通过函数cl_search获取某个值的位置p,然后通过cl_insert函数在p的后面插入指定值的结点
void cl_insert(chainList p, datatype x)
{
	chainList q = new clNode;//定义新的结点,用指针指向新定义的指针
	//判断是否有足够的空闲存储空间存放新结点
	if (q == NULL)
	{
		cout << "插入结点失败!" << endl;
		return;
	}
	q->data = x;
	q->next = p->next;
	p->next = q;
}

//若实现pushback尾插,则包含cl_traverse函数得到最后一个结点p,用cl_insert函数在p后插入结点

//2.4删除p结点的直接后继操作
//删除头结点后的结点
void cl_delete(chainList p)
{
	if (p == NULL)return;
	chainList q = p->next;
	if (q == NULL)return;
	p->next = q->next;
	delete q; //释放了q所占用的空间,但q指针仍有指向
	q = NULL; //设置为空指针
}
//删除指定位置后面的结点
void cl_delete(chainList h, datatype x)
{
	chainList p = new clNode;
	p = cl_search(h, x);
	if (p == NULL)return;
	chainList q = p->next;
	if (q == NULL)return;
	p->next = q->next;
	delete q; //释放了q所占用的空间,但q指针仍有指向
	q = NULL; //设置为空指针
}

//2.5创建链表
void cl_create(chainList& h, datatype a[], int n)
{
	if (h == NULL) h = new clNode;//若表头为空,为表头动态分配存储空间
	for (int i = n - 1; i >= 0; i--) //将数组a的元素从后向前插入链表中,尾插
		cl_insert(h, a[i]);
}

//2.6删除链表
void cl_destroy(chainList& h)
{
	//int cnt = 0;
	//每行都会执行过去
	while (h->next != NULL)
	{
		cl_delete(h); //删除后,第一个结点的值不断进行改变,所以不需要用h=h->next
		//cout << cnt++ << endl;
	}
	//确保删除完毕
	delete h;
	h = NULL;
}

int main()
{
	int a[] = { 1,4,7,2,5,8,3,6,9 };
	chainList header = NULL;
	//创建链表
	cl_create(header, a, sizeof(a) / sizeof(int));
	//遍历链表
	cl_traverse(header);
	//查找值为5的结点
	chainList search = new clNode;
	search = cl_search(header, 5);
	cl_traverse(search);
	//删除5后面的直接后继
	cl_delete(header, 5);
	cl_traverse(header);
	//头结点后插入8
	cl_insert(header, 8);
	cl_traverse(header);
	//删除整个列表
	cl_destroy(header);
	cl_traverse(header);
}

在单向链表中,可以高效的确定每一个结点的直接后继,但确定结点的直接前驱比较麻烦,从而查找效率降低。为了增加链表操作的灵活性,可以对单向链表做一些改变,得到另外两种类型的链表:双向链表和循环链表。

双向链表:  

含一个数据域和两个链域(前驱链域和后继链域)。同样,和单链表一样都用第一个结点header表示整个链表,但所不同的是,双向链表的header结点的数据域存放有效数据,不为空。

优点:很方便的实现双向查找,插入结点可前可后

劣势:由于增加了一个前驱链域,双向链表比单向链表多了一些存储空间的开销。此外,由于在操作中需要处理前驱链域,双向链表操作的效率比单向链表低。(尽量使用单链表)

#include<iostream>
using namespace std;
typedef int datatype;

//双向链表的结点的类型定义
typedef struct dclNode {
	datatype data;
	dclNode* pre;
	dclNode* next;
	dclNode() :pre(NULL), next(NULL) {
		data = 0;
	}
}*dchainList;

//在结点p的后面插入一个值为x的结点
void dcl_insert_post(dchainList& p, datatype x)
{
	dchainList q = new dclNode, nxt = p->next;//nxt的出现只为了简介的表达
	q->data = x;
	//四个连接
	if (nxt != NULL) nxt->pre = q;
	q->next = nxt;
	p->next = q;
	q->pre = p;
}

//在结点p的前面插入一个值为x的结点
void dcl_insert_pre(dchainList& p, datatype x)  //改变链表需要取址符
{
	dchainList q = new dclNode, pre = p->pre;

	q->data = x;
	p->pre = q;
	q->next = p;

	if (pre != NULL) {
		pre->next = q;
		q->pre = pre;
	}
	else //在表头的前面插入,需更新表头,p与q都代表的是指针,而不是确切的存储空间
		p = q;
}

//创建双向链表,包含n个结点,节点数据存放在数组a中
void dcl_create(dchainList& h, datatype a[], int n)
{
	h = new dclNode; //首先需要构建第一个结点,并且第一个结点应有数据域
	h->data = a[0];
	for (int i = n - 1; i > 0; i--) {
		dcl_insert_post(h, a[i]);
	}
}

//删除双向链表h中第一个值为x的结点
void dcl_delete(dchainList& h, datatype x)
{
	dchainList dcl = h;
	//寻找位置的指向
	while (dcl != NULL) {
		if (dcl->data == x) break; //dcl的指向已经到达了所要的position
		dcl = dcl->next;
	}
	if (dcl == NULL) return;
	//pre和nxt分别为删除节点的直接前驱和直接后继
	dchainList pre = dcl->pre, nxt = dcl->next;
	//进行变动,只需改动前后的直接后继和直接前驱,
	//有头结点,尾结点和一般结点三种情况
	//一般情况
	if (pre != NULL) pre->next = nxt; //尾结点仅此一步操作即可
	if (nxt != NULL) nxt->pre = pre;
	//头结点情况+上面的nxt!=NULL,赋予直接前驱NULL的初始化,防止野指针
	if (dcl == h) h = nxt;

	delete dcl;dcl = NULL;
}

//同单链表一样查找
void dcl_nxt_print(dchainList h)
{
	while (h != NULL) {
		cout << h->data << endl;;
		h = h->next;
	}
	cout << endl;
}

//前驱节点查找
void dcl_pre_print(dchainList h)
{
	/*
	这样的做法有误,因为当h = NULL时,没有前驱结点,无法调回最后一个结点
	while (h != NULL) {
		cout << h->data << endl;;
		h = h->next;
	}
	//调回至最后一个结点
	h = h->pre;
	*/

	while (h->next != NULL)
		h = h->next;

	while (h != NULL)
	{
		cout << h->data << endl;
		h = h->pre;
	}
	cout << endl;
}

int main()
{
	int a[] = { 1,4,7,2,5,8,3,6,9 };
	dchainList h;
	dcl_create(h, a, sizeof(a) / sizeof(int));
	dcl_nxt_print(h);
	dcl_pre_print(h);
}

循环链表:

在不带头结点的单向链表中,最后一个结点的链域为空指针,如果将该链域指向第一个结点,则该类链表成为循环链表。

与单向链表相比,循环链表的优势是可以从任意结点出发访问链表的所有结点;与双向链表相比,循环链表不需要额外的存储开销。

为了与单向链表的操作一致,通常用最后一个结点来表示循环链表。

#include<iostream>
using namespace std;
typedef int datatype;
//1.链表的表示
//链表结点的类型定义:设置clNode和*chainList两种形式,是为了后面避免二级指针的出现
//二级指针的使用,一定要注意一级指针指向地址,切勿为空
typedef struct rclNode {
	datatype data; //数据域
	rclNode* next; //链域
	//一定要对每个结点进行初始化操作,不然创建的结点链域无指向,为野指针
	//不然就在初始化的时候,new clNode{x,null} 进行初始化
	rclNode():next(NULL) {
		data = 0;
	}
}*chainList;

//查询循环链表中是否存在值为x的结点
chainList rcl_search(chainList h, datatype x)
{
	if (h == NULL) return NULL;
	chainList h1 = h;
	do {
		h1 = h1->next;
		if (h1->data == x)return h1;
	} while (h1 != h);
	return NULL;
}

//在结点p的后面插入一个值为x的结点
void rcl_insert(chainList& p, datatype x)
{
	chainList q = new rclNode;
	if (q == NULL)
	{
		cout << "插入节点失败!" << endl;
		return;
	}
	q->data = x;
	if (p == NULL)
		q->next = q, p = q;
	else  //同单链表
		q->next = p->next, p->next = q;
}

//创建循环链表h,共有n个结点,每一个节点的数据域存放在数组a中
void rcl_creater(chainList& h, datatype a[], int n)
{
	rcl_insert(h, a[n - 1]); //先构建最后一个结点
	for (int i = n - 2; i >= 0; i--) //将数组a的元素从后往前一次插入链表中
		rcl_insert(h, a[i]);
}

//删除循环链表中p的直接后继
void rcl_delete(chainList& p)
{
	if (p == NULL)return;
	chainList q = p->next;
	if (p == q) {
		delete p;
		p = NULL;
		return;
	}
	p->next = q->next; //重新定义p的链域
	delete q;	//释放q所占用的存储空间
	q = NULL;
}

//删除循环链表h中的所有结点
void rcl_destroy(chainList& h)
{
	while (h != NULL)
		rcl_delete(h);
}

void rcl_print(chainList h)
{
	if (h == NULL) return;
	chainList h1 = h;
	do {
		h1 = h1->next;
		cout << h1->data << endl;
	} while (h1 != h);
	cout << endl;
}

int main()
{
	int a[] = { 1,4,7,2,5,8,3,6,9 };
	chainList header = NULL;
	//创建链表
	rcl_creater(header, a, sizeof(a) / sizeof(int));
	rcl_print(header);

	//从5后面的结点的位置开始遍历
	chainList result = new rclNode;
	result = rcl_search(header, 5);
	rcl_print(result);

	//添加位置在第一个元素
	rcl_insert(header, 666);
	rcl_print(header);

	//删除第一个元素
	rcl_delete(header);
	rcl_print(header);

	//删除整个链表
	rcl_destroy(header);
	rcl_print(header);
}

以上为基于单向链表所构建的循环链表,也可以基于双向链表构建循环链表,称为双向循环链表。其中一般用第一个结点表示双向循环链表。

一些有待商榷的问题:

 

 

 

 

 

参考:

1.有关结构体typedef struct Node{ }Node, *LinkedList;的解惑_coder_by的博客-CSDN博客

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

努力的小羽儿

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值