室友一把LOL的时间学会最优链表之——(双向带头循环链表)

目录

双向带头循环链表的优点

双向带头循环链表的定义

定义

初始化

 双向带头循环链表的插入

头插

尾插

任意位置前的插入

 双向带头循环链表的删除

头删

 尾删

任意位置的删除

双向链表的查找

双向链表的销毁

双向链表的打印

测试用例

完整代码


双向带头循环链表的优点

最值得学习的两个链表:1、单链表 2、双向带头循环链表

1、单链表

为什么单链表也值得我们学习呢,虽然他非常麻烦,有时候理解也比较困难,但是它的优点是适合做题,锻炼我们对指针的运用,并且大部分链表题目中都需要你学会单链表的相关知识。

2、双向带头循环链表

双向带头循环链表结构上能称得上是链表中最优的结构,若我们需要找头结点或者是尾结点,我们甚至根本不用遍历,直接利用一个头结点就能找到,而且不用判空,因为是循环结构,并不存在空的情况。

双向带头循环链表的定义

与单链表的定义差不多,只不过加了一个prev指针变量来存储前面结点的地址。

定义

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

初始化

由于我们的链表是带头的,所以初始化无非是开辟一个头结点。

头结点的特征是:不存储有效数据,所以我们初始化头结点时不需要初始化头结点中的数据。

由于是循环的,所以我们将头结点的prev和next都指向自己,从而实现循环的特征。

// 开辟并返回链表的头结点.
ListNode* ListCreate()
{
	ListNode* head = (ListNode*)malloc(sizeof(ListNode));
	if (head == NULL)
		return NULL;
	head->prev = head;
	head->next = head;
	return head;
}

 双向带头循环链表的插入

由于我们这里有头结点,所以在插入过程中头结点的指向永远不会变,所以我们不用传二级指针就可以。

头插

先保留头结点指向的那个结点为cur,创建一个新的结点,让头结点的next指向新结点,再让新结点的prev指向头结点。再让新结点的next指向cur,cur的prev指向新结点,就链接完成了。如图所示:

// 双向链表头插
void ListPushFront(ListNode* pHead, LTDataType x)
{
	assert(pHead);
	ListNode* cur = pHead->next;
	ListNode* newNode = BuyListNode(x);
	pHead->next = newNode;
	newNode->prev = pHead;
	newNode->next = cur;
	cur->prev = newNode;
}

尾插

同头插一样,创建一个新的结点,完成链接。由图所示:

// 双向链表尾插
void ListPushBack(ListNode* pHead, LTDataType x)
{
	ListNode* tail = pHead->prev;
	ListNode* newNode = BuyListNode(x);
	//由于是双向带头循环链表
	//所以链接关系直接依次建立就可以了
	tail->next = newNode;
	newNode->prev = tail;
	newNode->next = pHead;
	pHead->prev = newNode;
}

任意位置前的插入

只要运用好pos的prev和next,这个迎刃而解。

如图所示:

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

 双向带头循环链表的删除

删除那个结点,只要将他们前后的结点链接起来就可以了

头删

直接将头结点与他后面结点的下一个结点链接起来就可以了。

如图所示:

// 双向链表头删
void ListPopFront(ListNode* pHead)
{
	ListNode* cur = pHead->next;
	assert(pHead);
	if (cur == NULL)
		return NULL;
	else if(cur->next==NULL)
	{
		pHead->next = pHead;
		pHead->prev = pHead;
	}
	else
	{
		pHead->next = cur->next;
		cur->next->prev = pHead;
	}
}

 尾删

同头删一样,想要找到要删除的结点,直接用头结点的prev就是我们要删除的尾结点。

如图所示:

// 双向链表尾删
void ListPopBack(ListNode* pHead)
{
	assert(pHead);
	ListNode* tail = pHead->prev;
	ListNode* tailPrev = tail->prev;

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

任意位置的删除

直接让前后结点链接起来就行了

代码如下:

// 双向链表删除pos位置的节点
void ListErase(ListNode* pos)
{
	assert(pos);
	ListNode* prevPos = pos->prev;
	ListNode* tailPos = pos->next;
	prevPos->next = tailPos;
	tailPos->prev = prevPos;
}

此外,当我们会写任意位置的删除后,其实头删和尾删只要引用这个函数就可以了,因为所要删除的结点我们可以直接访问。

优化后的头删:

// 双向链表头删
void ListPopFront(ListNode* pHead)
{
    assert(pHead);
	ListErase(pHead->next);
}

优化后的尾删:

// 双向链表尾删
void ListPopBack(ListNode* pHead)
{
	assert(pHead);
	ListErase(pHead->prev);
}

双向链表的查找

同单链表的查找没有太大的区别,只不过判断条件发生了改变,由于循环链表没有空,所以判断条件改为是否为头结点。

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

双向链表的销毁

这里需要注意的是,最终头结点也需要释放。

// 双向链表销毁
void ListDestory(ListNode* pHead)
{
	ListNode* cur = pHead;
	while (cur!=pHead)//当cur遍历到头结点时 说明遍历完全啦
	{
		ListNode* next = cur->next;
		free(cur);
		cur = next;
	}
	free(pHead);//释放头结点
}

双向链表的打印

这个直接遍历打印就行。

// 双向链表打印
void ListPrint(ListNode* pHead)
{
	assert(pHead);
	ListNode* cur = pHead->next;
	while (cur!=pHead)
	{
		printf("%d->", cur->data);
		cur = cur->next;
	}
	printf("\n");
}

测试用例

#include"SList.h"
void Test1()
{
	//ListNode* head = InitListNode();
	ListNode* head = ListCreate();

	ListPushBack(head, 1);
	ListPushBack(head, 2);
	ListPushBack(head, 3);
	ListPushBack(head, 4);
	ListPushFront(head, 0);
	ListPopBack(head);
	ListPopFront(head);
	ListPrint(head);
	printf("\n");

	ListInsert(head->next, 9);
	//ListErase(head->next);
	//ListNode* cur = ListFind(head, 1);
	//printf("%d ", cur->data);
	ListPrint(head);
	printf("\n");

	

	ListDestory(head);

}
int main()
{
	Test1();
	return 0;
}

完整代码

分别创建一个 slist.c 文件  一个 slist.h 文件 一个  test.c 文件

slist.c文件  用来实现各个函数

#include"SList.h"

双向链表的初始化
//ListNode* InitListNode()
//{
//	ListNode * head = malloc(sizeof(ListNode));
//	if (!head)
//	{
//		printf("malloc fail\n");
//		exit(-1);
//	}
//	head->next = head;
//	head->prev = head;
//	return head;
//
//}

//创建一个新结点
ListNode* BuyListNode(LTDataType x)
{
	ListNode* node = (ListNode*)malloc(sizeof(ListNode));
	if (node == NULL)
		return NULL;
	node->data = x;
	node->prev = NULL;
	node->next = NULL;
	return node;
	
}


// 创建返回链表的头结点.
ListNode* ListCreate()
{
	ListNode* head = (ListNode*)malloc(sizeof(ListNode));
	if (head == NULL)
		return NULL;
	head->prev = head;
	head->next = head;
	return head;
}

// 双向链表销毁
void ListDestory(ListNode* pHead)
{
	ListNode* cur = pHead;
	while (cur!=pHead)//当cur遍历到头结点时 说明遍历完全啦
	{
		ListNode* next = cur->next;
		free(cur);
		cur = next;
	}
	free(pHead);//释放头结点
}


// 双向链表打印
void ListPrint(ListNode* pHead)
{
	assert(pHead);
	ListNode* cur = pHead->next;
	while (cur!=pHead)
	{
		printf("%d->", cur->data);
		cur = cur->next;
	}
	printf("\n");
}

// 双向链表尾插
void ListPushBack(ListNode* pHead, LTDataType x)
{
	ListNode* tail = pHead->prev;
	ListNode* newNode = BuyListNode(x);
	//由于是双向带头循环链表
	//所以链接关系直接依次建立就可以了
	tail->next = newNode;
	newNode->prev = tail;
	newNode->next = pHead;
	pHead->prev = newNode;
}

// 双向链表尾删
void ListPopBack(ListNode* pHead)
{
	assert(pHead);
	ListNode* tail = pHead->prev;
	ListNode* tailPrev = tail->prev;

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

// 双向链表头插
void ListPushFront(ListNode* pHead, LTDataType x)
{
	assert(pHead);
	ListNode* cur = pHead->next;
	ListNode* newNode = BuyListNode(x);
	pHead->next = newNode;
	newNode->prev = pHead;
	newNode->next = cur;
	cur->prev = newNode;
}

// 双向链表头删
void ListPopFront(ListNode* pHead)
{
	ListNode* cur = pHead->next;
	assert(pHead);
	if (cur == NULL)
		return NULL;
	else if(cur->next==NULL)
	{
		pHead->next = pHead;
		pHead->prev = pHead;
	}
	else
	{
		pHead->next = cur->next;
		cur->next->prev = pHead;
	}
}

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

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

// 双向链表删除pos位置的节点
void ListErase(ListNode* pos)
{
	assert(pos);
	ListNode* prevPos = pos->prev;
	ListNode* tailPos = pos->next;
	prevPos->next = tailPos;
	tailPos->prev = prevPos;
}

slist.h 文件 存放各个函数的定义

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

// 带头+双向+循环链表增删查改实现

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

//创建一个新结点
ListNode* BuyListNode(LTDataType x);

// 创建链表的头结点/初始化链表
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);

test.c 文件 测试代码

#include"SList.h"
void Test1()
{
	//ListNode* head = InitListNode();
	ListNode* head = ListCreate();

	ListPushBack(head, 1);
	ListPushBack(head, 2);
	ListPushBack(head, 3);
	ListPushBack(head, 4);
	ListPushFront(head, 0);
	ListPopBack(head);
	ListPopFront(head);
	ListPrint(head);
	printf("\n");

	ListInsert(head->next, 9);
	//ListErase(head->next);
	//ListNode* cur = ListFind(head, 1);
	//printf("%d ", cur->data);
	ListPrint(head);
	printf("\n");

	

	ListDestory(head);

}
int main()
{
	Test1();
	return 0;
}

猜你喜欢

转载自blog.csdn.net/m0_57249790/article/details/124161737