初识双向循环链表

         大家好呀,我们今天来认识一下双向循环链表。

双向循环链表

目录

双向循环链表

 对双向循环链表的认识

创建双向循环链表

初始化

遍历(检查打印)函数 

尾插,尾删 

 头插,头删

 查找

 在任意位置前插入和任意位置删除

 完整代码

list.h 

 list.c

 test.c

 小结


 对双向循环链表的认识

         学习了单链表后,对双向循环链表会变得较为简单。那么,什么是双向循环链表呢?

        所谓双向循环链表,就是链表其中的节点都含有两个指针,一个指向后一个节点,一个指向前一个节点。如图:

        也就是说,对于双向循环链表来说,头插和尾插会及其方便。那么接下来我们就来编写带哨兵尾的双向循环链表。

创建双向循环链表

         创建链表,首先便是创建结构体,创建两个结构体指针和节点数据。代码如下:

typedef struct ListNode
{
	struct ListNode* prev;
	struct ListNode* next;
	LTDataType data;
}LTNode;

        双向循环链表的功能与链表一致,头插尾插,头删尾删,查找。在编写功能之前,我们先对初始化和销毁函数进行编写。

初始化

         我们先了解下什么是哨兵位。所谓哨兵位,就是带头结点的单链表的第一个节点,
属于附加的链表节点,无有效数值,只储存第一个有效节点的地址,负责找到第一个节点。

        那么我们在初始化时,就需要使用创建节点的函数赋一个无效值,并将两个指针置为NULL

        因此优先编写创建节点的函数。创建一个新节点,使用malloc进行开辟即可。进行检查,并将指针置空。代码如下:

LTNode* CreateLTNode(LTDataType x)
{
	LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));
	if (newnode == NULL)
	{
		perror("malloc fail");
		exit(-1);
	}
	newnode->data = x;
	newnode->next = NULL;
	newnode->prev = NULL;

	return newnode;
}

        紧接着编写初始化函数。代码如下:

LTNode* LTInit()
{
	LTNode* phead = CreateLTNode(-1);
	phead->next = phead;
	phead->prev = phead;

	return phead;
}

遍历(检查打印)函数 

         在编写功能之前,我们先编写打印函数。打印需要遍历,那么如何进行遍历,遍历的结束标志又是什么呢?

        进行遍历,只需要不断地next即可,结束标志需要对哨兵位不需要打印进行考虑,所以让遍历指针cur!=phead即可。代码如下:

void LTPrint(LTNode* phead)
{
	LTNode* cur = phead->next;
	printf("哨兵位  ");
	while (cur != phead)
	{
		printf("%d<=>", cur->data);
		cur = cur->next;
	}
}

尾插,尾删 

        我们还是先来编写尾插。如何进行尾插呢?

        只需要将原来的尾节点的next指向新节点,新节点的next指向头节点。头节点的prev指向新节点,新节点的prev指向原来的尾节点即可。如图:

        代码如下:

void LTPushBack(LTNode* phead, LTDataType x)
{
	assert(phead);
	LTNode* tail = phead->prev;
	LTNode* newnode = CreateLTNode(x);

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

        之后就是尾删。

        尾删就较为简单了,将原来尾节点的前一个节点的next指向头节点,再让头节点的prev指向其即可。如图:

        但是尾删需要注意,当链表只剩哨兵位的时候,就不能再删了,需要判断。代码如下:

void LTPopBack(LTNode* phead)
{
	assert(phead);
	assert(phead->next = phead);
	LTNode* tail = phead->prev;
	LTNode* tailPrev = tail->prev;

	free(tail);
	tailPrev->next = phead;
	phead->prev = tailPrev;
}

 头插,头删

        那么如何进行头插呢?

        由于拥有哨兵位,那么我们首先找到哨兵位的头节点的next节点,我们让创建出的新节点newnodenext指向它,再让该节点的prev指向newnode,再让哨兵位和新节点建立联系就可以了。如图:

 

        代码如下:

void LTPushFront(LTNode* phead, LTDataType x)
{
	assert(phead);
	LTNode* newnode = CreateLTNode(x);
	newnode->next = phead->next;
	phead->next->prev = newnode;
	phead->next = newnode;
	newnode->prev = phead;
}

        紧接着就是头删,头删就比较简单了,直接让phead与其nextnext相互链接,再将要删除的节点free掉就可以了。当然需要判断如果只有哨兵位的情况。为了方便编写,我们创建两个指针来保存后两个节点。如图:

        代码如下:

void LTPopFront(LTNode* phead)
{
	assert(phead);
	assert(phead->next != phead);
	LTNode* first = phead->next;
	LTNode* second = phead->next->next;
	phead->next = second;
	second->prev = phead;
	free(first);
	first = NULL;
}

 查找

        查找函数就很好编写了,直接从哨兵位往后遍历就可以了,当回到哨兵位停止遍历即可。代码如下:

LTNode* LTFind(LTNode* phead, LTDataType x)
{
	assert(phead);
	LTNode* cur = phead->next;
	while (cur != phead)
	{
		if (cur->data == x)
		{
			return cur;
		}
		cur = cur->next;
	}
	return NULL;
}

 在任意位置前插入和任意位置删除

        我们先来编写在任意位置前插入的函数。

        有了前指针,在任意位置插入也就简单了很多。只需要记录一下需要插入位置前的一个节点,直接链接即可。如图:

        代码如下:

void LTInsert(LTNode* pos, LTDataType x)
{
	assert(pos);
	LTNode* posPrev = pos->prev;
	LTNode* newnode = CreateLTNode(x);
	posPrev->next = newnode;
	newnode->prev = posPrev;
	newnode->next = pos;
	pos->prev = newnode;
}

        接下来便是在任意位置删除。

        任意位置删除就更为简单了,我们直接记录要删除节点的前一个与后一个节点,再将两个节点链接就可以了,最后进行free。当然还需要检查链表是否为空,为空则不能进行删除,编写完成。代码如下:

void LTErase(LTNode* pos)
{
	assert(pos);
	LTNode* posPrev = pos->prev;
	LTNode* posNext = pos->next;

	posPrev->next = posNext;
	posNext->prev = posPrev;
	free(pos);
	pos = NULL;
}

 完整代码

list.h 

#pragma once

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

typedef int LTDataType;

typedef struct ListNode
{
	struct ListNode* prev;
	struct ListNode* next;
	LTDataType data;
}LTNode;

//初始化
LTNode* LTInit();

//销毁
void LTDestory(LTNode* phead);

//打印
void LTPrint(LTNode* phead);

//创建节点
LTNode* CreateLTNode(LTDataType x);

//尾插
void LTPushBack(LTNode* phead, LTDataType x);

//尾删
void LTPopBack(LTNode* phead);

//头插
void LTPushFront(LTNode* phead, LTDataType x);

//头删
void LTPopFront(LTNode* phead);

//查找
LTNode* LTFind(LTNode* phead, LTDataType x);

//任意位置插入
void LTInsert(LTNode* pos, LTDataType x);

//任意位置删除
void LTErase(LTNode* pos);

 list.c

#include"list.h"

//初始化
LTNode* LTInit()
{
	LTNode* phead = CreateLTNode(-1);
	phead->next = phead;
	phead->prev = phead;

	return phead;
}

//打印
void LTPrint(LTNode* phead)
{
	LTNode* cur = phead->next;
	printf("哨兵位  ");
	while (cur != phead)
	{
		printf("%d<=>", cur->data);
		cur = cur->next;
	}
}

//创建节点
LTNode* CreateLTNode(LTDataType x)
{
	LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));
	if (newnode == NULL)
	{
		perror("malloc fail");
		exit(-1);
	}
	newnode->data = x;
	newnode->next = NULL;
	newnode->prev = NULL;

	return newnode;
}

//尾插
void LTPushBack(LTNode* phead, LTDataType x)
{
	assert(phead);
	LTNode* tail = phead->prev;
	LTNode* newnode = CreateLTNode(x);

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

//尾删
void LTPopBack(LTNode* phead)
{
	assert(phead);
	assert(phead->next = phead);
	LTNode* tail = phead->prev;
	LTNode* tailPrev = tail->prev;

	free(tail);
	tailPrev->next = phead;
	phead->prev = tailPrev;
}

//头插
void LTPushFront(LTNode* phead, LTDataType x)
{
	assert(phead);
	LTNode* newnode = CreateLTNode(x);
	newnode->next = phead->next;
	phead->next->prev = newnode;
	phead->next = newnode;
	newnode->prev = phead;
}

//头删
void LTPopFront(LTNode* phead)
{
	assert(phead);
	assert(phead->next != phead);
	LTNode* first = phead->next;
	LTNode* second = phead->next->next;
	phead->next = second;
	second->prev = phead;
	free(first);
	first = NULL;
}

//查找
LTNode* LTFind(LTNode* phead, LTDataType x)
{
	assert(phead);
	LTNode* cur = phead->next;
	while (cur != phead)
	{
		if (cur->data == x)
		{
			return cur;
		}
		cur = cur->next;
	}
	return NULL;
}

//任意位置前插入
void LTInsert(LTNode* pos, LTDataType x)
{
	assert(pos);
	LTNode* posPrev = pos->prev;
	LTNode* newnode = CreateLTNode(x);
	posPrev->next = newnode;
	newnode->prev = posPrev;
	newnode->next = pos;
	pos->prev = newnode;
}

//任意位置删除
void LTErase(LTNode* pos)
{
	assert(pos);
	LTNode* posPrev = pos->prev;
	LTNode* posNext = pos->next;

	posPrev->next = posNext;
	posNext->prev = posPrev;
	free(pos);
	pos = NULL;
}

 test.c

#include"list.h"

int main()
{
	LTNode* plist = LTInit();
	LTPushBack(plist, 1);
	LTPushBack(plist, 2);
	LTPushBack(plist, 3);
	LTPushBack(plist, 4);
	LTPushBack(plist, 5);

	LTPrint(plist);
	return 0;
}

 小结

        那么对双向循环链表的认识到这里就结束啦,其实小编偷了个懒,没有写销毁函数,这个相信大家都比较熟悉了,就不进行赘述了,我们下次再见! 

  • 8
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值