数据结构-带头双向循环链表

一、关于逻辑概念

链表包含有带头和不带头,循环和不循环这些结构。前面我们讲到单链表的实现,主要是一个节点一个节点存放数据,每个节点依次指向下一个节点的地址。带头双向循环链表主要是多了一个指向数据的哨兵节点,它是不放数据的;每个节点多了一个指向前一个节点的指针,就形成了双向;最后我们让尾节点指向头节点,头节点的前一个节点指向尾节点,就形成了封闭的循环,这就是带头双向循环链表的大致思路。

二、链表的实现

我们已经了解了逻辑概念,接下来就来实践一下,实践是检验真理的唯一标准。

1、创建返回链表的头结点.

注意我们这个节点是不放数据的,因为这个时候还没有插入数据,所以我们让这个节点的两个指针都指向自己就可以了。

2、链表销毁

3、链表打印

这里我的思路出现一点问题,注释部分可以不用理会,阿里嘎多。

4、链表尾插

当我们插入数据时,我们创建一个函数去申请一个节点用来放入数据,最后尾插头插都引用这个数据,整体代码就会比较优雅,后面头插就不过多说明了,阿里嘎多。

5、链表尾删

6、链表头插

7、链表头删

8、链表查找

9、在pos位置的前面插入数据

这里pos都引用了查找函数,后面源码会提到

10、删除pos位置的节点数据

三、源码分享

从上到下依次是头文件,函数文件和测试文件。测试文件调用的是头文件,头文件调用函数文件。

#define _CRT_SECURE_NO_WARNINGS 


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


// 带头+双向+循环链表增删查改实现
typedef int LTDataType;
typedef struct ListNode
{
	LTDataType data;
	struct ListNode* next;
	struct ListNode* prev;
}ListNode;

// 创建返回链表的头结点.
ListNode* ListCreate();
// 双向链表销毁
void ListDestory(ListNode* pHead);
// 双向链表打印
void ListPrint(ListNode* pHead);
// 双向链表尾插
void ListPushBack(ListNode* pHead, LTDataType x);
// 双向链表尾删
void ListPopBack(ListNode* pHead);
// 双向链表头插
void ListPushFront(ListNode* pHead, LTDataType x);
// 双向链表头删
void ListPopFront(ListNode* pHead);
// 双向链表查找
ListNode* ListFind(ListNode* pHead, LTDataType x);
// 双向链表在pos的前面进行插入
void ListInsert(ListNode* pos, LTDataType x);
// 双向链表删除pos位置的节点
void ListErase(ListNode* pos);

#define _CRT_SECURE_NO_WARNINGS 

#include"Dlist.h"

ListNode* ListCreate() //头结点(哨兵位)
{
	//创建返回链表的头结点
	    ListNode* pHead = (ListNode*)malloc(sizeof(ListNode));
		
		pHead->prev = pHead;
		pHead->next = pHead;
		return pHead;
}
ListNode* ListBuyNewNode(LTDataType x)
{
	ListNode* pp = (ListNode*)malloc(sizeof(ListNode));
	if (!pp)
	{
		perror("malloc fail");//如果扩容失败,显示扩容失败提示
	}
	pp->next = pp;
	pp->prev = pp;
	pp->data = x;
	return pp;
}

// 双向链表销毁
void ListDestory(ListNode* pHead)
{
	
	ListNode* tail = pHead;
	while (pHead  != pHead->next)
	{
		tail = pHead->prev;

		pHead->prev = tail->prev;
		pHead->prev->next = pHead;
		free(tail);
	}
	free(pHead);
}
// 双向链表打印
void ListPrint(ListNode* pHead)
{
	ListNode* tail = pHead;
	while (tail->next != pHead)
	{//pHead->next != pHead此时* pHead只是对地址进行引用,不会改变原来的地址,
		//所以两个地址一直不会相等,二级指针则会修改原地址的值。
		printf("%d\n", tail->next->data);
		tail = tail->next;
	}
	printf("NULL\n");
}

// 双向链表尾插
void ListPushBack(ListNode* pHead, LTDataType x)
{
	ListNode* newnode = ListBuyNewNode(x);
	ListNode* tail = pHead->prev;
	tail->next = newnode;
	newnode->prev = tail;
	pHead->prev = newnode;
	newnode->next = pHead;

}
// 双向链表尾删
void ListPopBack(ListNode* pHead)
{
	assert(pHead->next != pHead);//避免链表中没有数据仍在删除造成越界

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

// 双向链表头插
void ListPushFront(ListNode* pHead, LTDataType x)
{
	ListNode* newnode = ListBuyNewNode(x);
	ListNode* tail = pHead;

	newnode->next = pHead->next;
	pHead->next->prev = newnode;
	//倒反天罡会死循环,先把后面的连接,再前。否则丢失数据。
	tail->next = newnode;
	newnode->prev = tail;
}
// 双向链表头删
void ListPopFront(ListNode* pHead)
{
	assert(pHead->next != pHead);//避免链表中没有数据仍在删除造成越界
	pHead->next = pHead->next->next;
	pHead->next->prev = pHead;
}

// 双向链表查找
ListNode* ListFind(ListNode* pHead, LTDataType x)
{
	ListNode* tail = pHead->next;
	while (tail != pHead)
	{
		if (x == tail->data)

		{
			/*printf("找到了,在%p",tail);*/
			return tail;
		}
			tail = tail->next;
	}
	printf("找不到了");
	return -1;
}

// 双向链表在pos的前面进行插入
void ListInsert(ListNode* pos, LTDataType x)
{
	ListNode* newnode = ListBuyNewNode(x);
	ListNode* prev = pos->prev;
	prev->next = newnode;
	newnode->prev = prev;

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

// 双向链表删除pos位置的节点
void ListErase(ListNode* pos)
{
	    assert(pos->next != pos);//其实不用的,查找的时候找不到是没有pos的
		pos->prev->next = pos->next;
		pos->next->prev = pos->prev;
	
}
#define _CRT_SECURE_NO_WARNINGS
#include"Dlist.h"


void test1()
{
	ListNode* pHead = ListCreate();

	ListPushBack(pHead, 1);
	ListPushBack(pHead, 2);
	ListPushBack(pHead, 3);
	ListPushBack(pHead, 4);
	ListPushBack(pHead, 5);
	ListPopBack(pHead);
	ListPopBack(pHead);
	ListPopBack(pHead);
	ListPushFront(pHead, 7);
	ListPushFront(pHead, 8);
	ListPushFront(pHead, 9);
	
	ListPopFront(pHead);
	ListPopFront(pHead);
	/*ListPrint(pHead);*/
	

	ListNode* pos = ListFind(pHead, 1);
	ListInsert(pos, 3);
	ListErase(pos);
	 pos = ListFind(pHead, 7);
	ListErase(pos);
	 pos = ListFind(pHead, 3);
	ListErase(pos);
	 pos = ListFind(pHead, 2);
	ListErase(pos);
	ListPrint(pHead);
	ListDestory(pHead);
}
int main()
{
	test1();


	return 0;
}

四、总结

带头双向循环链表相比单链表,结构上更加完善,插入删除也更加方便。在理解了单链表的结构后,再来理解带头双向循环链表,就会变得简单很多,学习之路亦是如此,一步一脚印!

写的不好的地方欢迎多多指正批评,道阻且长,加油吧!

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值