带头双向循环链表的细致讲解

八种链表

首先我们应该了解一下八种链表

链表一共有八种组合:单双向、带头或者不带头、循环或者非循环。

单双链表与循环非循环比较好理解,即指针是单个还是两个,头尾指针是否相互指向对方形成循环
双链表就是在单链表的基础上新增了一个从尾部连接到头部的指针。

带头与不带头说的是是否有哨兵位的头节点,即链表是连接到哨兵位头节点后面的。
带哨兵位的头节点不用进行开始头部是否为空的判断,可以直接将节点连接到哨兵位头节点后面,十分方便

两种常用链表及它们的优势

虽然说我们有很多种链表结构,但是我们常用的还是无头单向非循环链表和带头双向循环链表
那么我们肯定有很多疑问?为啥你说的就常用。我们的就不常用呢?

那是因为:
1.无头单向非循环链表:结构简单,一般不会单独用来存数据。实际中更多是作为其他数据结
构的子结构,如哈希桶、图的邻接表等等。另外这种结构在笔试面试中出现很多。 https://blog.csdn.net/weixin_67466860/article/details/130252461
2. 带头双向循环链表:结构最复杂,一般用在单独存储数据。实际中使用的链表数据结构,都
是带头双向循环链表。另外这个结构虽然结构复杂,但是使用代码实现以后会发现结构会带来很多优势,实现反而简单了,后面我们代码实现了就知道了。

带头双向循环构建

构建节点的前置要求

双向链表需要两个指针,一个数据,所以我们的结构体框架为

typedef int DataType;
typedef struct DListLink
{
	struct DListLink* next;
	struct DListLink* prev;
	DataType data;
}DList;

节点生成

前置结构体完成后,我们就需要构建出节点了

DList* BuyNewNode(DataType x)
{
	DList* newnode = (DList*)malloc(sizeof(DList));/malloc一个节点,并将地址赋给newnode
	if (newnode == NULL)//开辟是否成功
	{
		perror("malloc fail!");
		exit(-1);
	}
	newnode->next = NULL;//将指针置为空
	newnode->prev = NULL;
	newnode->data = x;//需要的数值给data
	return newnode;//将新节点的地址返回
}

单个节点的初始化

双向循环链表有一个需要注意的地方是我们需要将单个节点的指针指向自身

DList* InitNode()
{
	DList* phead = BuyNewNode(-1);
	phead->next = phead;
	phead->prev = phead;

	return phead;
}

打印我们的节点进行查看

void Print(DList* phead)
{
	assert(phead);//注意判断是否为空
	DList* cur = phead->next;
	while (cur != phead)//由于我们是循环链表,所以不能让cur跟phead重合,否则便会死循环
	{
		printf("%d ", cur->data);
		cur = cur->next;
	}
	printf("\n");
}

尾插

带头双向循环链表的尾插十分简单,因为他比较复杂的结构会让我们对链表进行修改比较方便
只需要让节点的前后链表指针的指向进行修改

void DLTPushBack(DList* phead, DataType x)
{
	assert(phead);

	DList* newnode = BuyNewNode(x);
	DList* tail = phead->prev;

	tail->next = newnode;
	newnode->prev = tail;

	newnode->next = phead;
	phead->prev = newnode;
}

尾删

只需要断掉尾部节点的指针链接,并将需要删除的节点进行释放就好

void DLTPopBack(DList* phead)
{
	assert(phead);
	assert(phead->next!=phead);//防止将自身删除

	DList* tail = phead->prev;
	DList* tailPrev = tail->prev;

	phead->prev = tailPrev;
	tailPrev->next = phead;

	free(tail);
}

头插

只需要在哨兵位头节点后面进行插入
注意不要在哨兵位头节点前进行插入

void DLTPushFront(DList* phead, DataType x)
{
	assert(phead);

	DList* newnode = BuyNewNode(x);
	DList* pheadnext = phead->next;

	newnode->next = pheadnext;
	pheadnext->prev = newnode;

	phead->next = newnode;
	newnode->prev = phead;
}

头删

跟尾删几乎一摸一样

void DLTPopFront(DList* phead)
{
	assert(phead);
	assert(phead->next != phead);//判断是否为空

	DList* del = phead->next;

	phead->next = del->next;
	del->next->prev = phead;

	free(del);
}

双向链表中查找

找到了返回地址,找不到返回NULL

DList* DLTFind(DList* phead, DataType x)
{
	assert(phead);

	DList* cur = phead->next;
	while (cur != phead)
	{
		if (cur->data == x)
		{
			return cur;
		}
		cur = cur->next;
	}
	return NULL;
}

随机插入

1.找位置并申请空间
2.链接节点到链表

void DLTInsert(DList* pos, DataType x)
{
	assert(pos);

	DList* PPrev = pos->prev;
	DList* newnode = BuyNewNode(x);

	newnode->next = pos;
	pos->prev = newnode;

	PPrev->next = newnode;
	newnode->prev = PPrev;
}

任意位置删除数据

1.断掉节点处链接,将断开的链接重新连接起来
2.释放节点数据

void DLTErase(DList* pos)
{
	assert(pos);

	DList* prev = pos->prev;
	DList* next = pos->next;
	free(pos);

	prev->next = next;
	next->prev = prev;
}

到这里我们需要明白,头插、尾插、头删、尾删可以用任意位置插入删除来替代了

//头插
void DLTPushFront(DList* phead, DataType x)
{
	assert(phead);
	DLTInsert(phead->next, x);
}
//尾插
void DLTPushBack(DList* phead, DataType x)
{
	assert(phead);
	DLTInsert(phead, x);
}
//头删
void DLTPopFront(DList* phead)
{
	assert(phead);
	assert(phead->next != phead);
	DLTErase(phead->next);
}
//尾删
void DLTPopBack(DList* phead)
{
	assert(phead);
	assert(phead->next!=phead);
	DLTErase(phead->prev);
}

销毁

既然是使用malloc开辟出来的,那么我们就需要销毁掉这个处于堆上的空间,否则可能会导致占用空间过大,使程序崩掉,作为一个严谨的程序员,当然是不能允许这种情况出现的

void DLTDestroy(DList* phead)
{
	assert(phead);

	DList* cur = phead->next;
	while (cur != phead)
	{
		DList* next = cur->next;
		free(cur);

		cur = next;
	}

	free(phead);
	//phead = NULL;//没有必要,这是个形参

带头双向循环链表整体代码如下

#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>

typedef int DataType;
typedef struct DListLink
{
	struct DListLink* next;
	struct DListLink* prev;
	DataType data;
}DList;

DList* BuyNewNode(DataType x)
{
	DList* newnode = (DList*)malloc(sizeof(DList));
	if (newnode == NULL)
	{
		perror("malloc fail!");
		exit(-1);
	}
	newnode->next = NULL;
	newnode->prev = NULL;
	newnode->data = x;
	return newnode;
}
//初始化,带哨兵位头节点是需要初始化的,而且指针是指向自己的
DList* InitNode()
{
	DList* phead = BuyNewNode(-1);
	phead->next = phead;
	phead->prev = phead;

	return phead;
}

void Print(DList* phead)
{
	assert(phead);
	DList* cur = phead->next;
	while (cur != phead)
	{
		printf("%d ", cur->data);
		cur = cur->next;
	}
	printf("\n");
}

//尾插
void DLTPushBack(DList* phead, DataType x)
{
	assert(phead);

	/*DList* newnode = BuyNewNode(x);
	DList* tail = phead->prev;

	tail->next = newnode;
	newnode->prev = tail;

	newnode->next = phead;
	phead->prev = newnode;*/
	DLTInsert(phead, x);
}
//尾删
void DLTPopBack(DList* phead)
{
	assert(phead);
	assert(phead->next!=phead);

	/*DList* tail = phead->prev;
	DList* tailPrev = tail->prev;

	phead->prev = tailPrev;
	tailPrev->next = phead;

	free(tail);*/
	DLTErase(phead->prev);
}
//头插
void DLTPushFront(DList* phead, DataType x)
{
	assert(phead);

	/*DList* newnode = BuyNewNode(x);
	DList* pheadnext = phead->next;

	newnode->next = pheadnext;
	pheadnext->prev = newnode;

	phead->next = newnode;
	newnode->prev = phead;*/
	DLTInsert(phead->next, x);
}
//头删
void DLTPopFront(DList* phead)
{
	assert(phead);
	assert(phead->next != phead);//判断是否为空

	//DList* del = phead->next;

	//phead->next = del->next;
	//del->next->prev = phead;

	//free(del);
	DLTErase(phead->next);
}
//查找
DList* DLTFind(DList* phead, DataType x)
{
	assert(phead);

	DList* cur = phead->next;
	while (cur != phead)
	{
		if (cur->data == x)
		{
			return cur;
		}
		cur = cur->next;
	}
	return NULL;
}
//随机插入
void DLTInsert(DList* pos, DataType x)
{
	assert(pos);

	DList* PPrev = pos->prev;
	DList* newnode = BuyNewNode(x);

	newnode->next = pos;
	pos->prev = newnode;

	PPrev->next = newnode;
	newnode->prev = PPrev;
}
//删除pos位
void DLTErase(DList* pos)
{
	assert(pos);

	DList* prev = pos->prev;
	DList* next = pos->next;
	free(pos);

	prev->next = next;
	next->prev = prev;
}
//置为自身判空
bool DLTEmpty(DList* phead)
{
	assert(phead);

	/*if (phead->next == phead)
	{
		return true;
	}
	else
	{
		return false;
	}*/

	return phead->next == phead;
}
//数量
size_t DLTSize(DList* phead)
{
	assert(phead);

	size_t size = 0;
	DList* cur = phead->next;
	while (cur != phead)
	{
		++size;
		cur = cur->next;
	}

	return size;
}
//销毁
void DLTDestroy(DList* phead)
{
	assert(phead);

	DList* cur = phead->next;
	while (cur != phead)
	{
		DList* next = cur->next;
		free(cur);

		cur = next;
	}

	free(phead);
	//phead = NULL;
}
//测试
void Test1()//尾插删
{
	DList* phead = InitNode();

	DLTPushBack(phead, 1);
	DLTPushBack(phead, 2);
	DLTPushBack(phead, 3);
	DLTPushBack(phead, 4);
	DLTPushBack(phead, 5);
	Print(phead);

	DLTPopBack(phead);
	DLTPopBack(phead);
	DLTPopBack(phead);
	DLTPopBack(phead);
	Print(phead);
	DLTDestroy(phead);

}
void Test2()//头插删
{
	DList* phead = InitNode();

	DLTPushFront(phead, 1);
	DLTPushFront(phead, 2);
	DLTPushFront(phead, 3);
	DLTPushFront(phead, 4);
	DLTPushFront(phead, 5);
	Print(phead);

	DLTPopFront(phead);
	DLTPopFront(phead);
	DLTPopFront(phead);
	DLTPopFront(phead);


	Print(phead);
	DLTDestroy(phead);

}
void Test3()//查找
{
	DList* phead = InitNode();
	DLTPushFront(phead, 1);
	DLTPushFront(phead, 2);
	DLTPushFront(phead, 3);
	DLTPushFront(phead, 4);
	DLTPushFront(phead, 5);
	Print(phead);

	DList* pos = DLTFind(phead, 3);
	if (pos)
	{
		pos->data = 10;
	}
	Print(phead);

	//DLTDestroy(phead);
	//phead = NULL;
}

int main()
{
	//Test1();
	Test2();
	//Test3();
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

SCENCER243

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

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

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

打赏作者

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

抵扣说明:

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

余额充值