带哨兵位双向循环链表

目录

一、双向循环链表的介绍

二、双链表的实现

0x01. 双链表的定义

0x10.接口函数

三、接口函数的实现

1) 初始化双链表

 2) 双向链表的打印

3) 创建新节点

4) 尾插

 5) 头插

 6) 判断链表是否为空

7) 尾删

8) 头删

9) 链表大小

10 )查找 

11) pos前位置插入

12)删除pos位置

13)销毁链表

 三、完整代码

List.h

List.c

test.c


一、双向循环链表的介绍

带头双向循环链表:结构最复杂,但是实现反而简单。一般用来单独存储数据,实际中使用的链表数据结构都是带头双向链表。另外,这个结构虽然结构复杂,但是使用代码实现后会发现结构会带来很多优势。双向链表严格来说只需要快速的实现两个接口,insert 和 earse 头尾的插入和删除就可以搞定了,这就是结构的优势!

二、双链表的实现

0x01. 双链表的定义

typedef int LTDataType;

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

0x10.接口函数

void ListDestory(LTNode* phead);

void ListPrint(LTNode* phead);
LTNode* ListInit();
LTNode* BuyListNode(LTDataType x);
void ListPushBack(LTNode* phead, LTDataType x);
void ListPushFront(LTNode* phead, LTDataType x);
void ListPopBack(LTNode* phead);
void ListPopFront(LTNode* phead);
bool ListEmpty(LTNode* phead);
size_t ListSize(LTNode* phead);
LTNode* ListFind(LTNode* phead, LTDataType x);

//pos前插入
void ListInsert(LTNode* pos, LTDataType x);
//删除pos位置;
void ListErase(LTNode* pos);

三、接口函数的实现

1) 初始化双链表

LTNode* ListInit() 
{
	LTNode* guard = (LTNode*)malloc(sizeof(LTNode));
	if (guard == NULL)
	{
		perror("malloc fail");
		exit(-1);
	}
	guard->next = guard;
	guard->prev = guard;
	return guard;
}

 这里我们使用 malloc 函数开辟一块空间作为 "哨兵位" guard ,最后将其进行一个初始化。最后再将 guard 作为结果返回回去,外面就可以接收到了。这就是返回值的方法,当然这里也可以采用二级指针的方法来完成。

 2) 双向链表的打印

phead表示哨兵位节点

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

创建遍历指针 cur,因为 pHead 是哨兵位所以存的不是有效数据,我们想要遍历链表就需要从 pHead->next 开始(即第一个有效数据节点),当 cur 等于 pHead 就相当于全部走了一遍了,这时就结束。 

3) 创建新节点

这里基本和单链表相同

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

	return node;
}

4) 尾插

void ListPushBack(LTNode* phead, LTDataType x)
{
	assert(phead);
	LTNode* newnode = BuyListNode(x);
	LTNode* tail = phead->prev;
	
	tail->next = newnode;
	newnode->next = phead;
	newnode->prev = tail;
	phead->prev = newnode;
    
    //
	//ListInsert(phead, x);
}

1)这里的phead没有改变,所以不用传二级指针

2)双链表的尾插节点可以直接从phead->prev那里获得,不用像单链表那样遍历,时间复杂度只需要O(1),但这里创建出来的新节点要将头和尾都要连上

3)如果以及写出ListInsert(任意位置前插入),可以直接调用该函数实现尾插。

 5) 头插

void ListPushFront(LTNode* phead, LTDataType x)
{
	assert(phead);
	/*LTNode* newnode = BuyListNode(x);
	newnode->next = phead->next;
	phead->next->prev = newnode;

	newnode->prev = phead;
	phead->next = newnode;*/

	//不关心顺序
	LTNode* newnode = BuyListNode(x);
	LTNode* first = phead->next;
	newnode->next = first;
	first->prev = newnode;
	phead->next = newnode;
	newnode->prev = phead;
	//ListInsert(phead->next, x);
}

 无论是头插还是尾插,都要注意改变next和prev修改时的顺序,这里将第一个节点位置保存下来,修改时即不用关心顺序,可以让代码的错误率更低。

同理,可以直接调用ListInsert函数完成头插

 6) 判断链表是否为空

bool ListEmpty(LTNode* phead)
{
	assert(phead);
	return phead->next == phead;
}

 虽然代码比较简洁,但后面删除节点要调用,所以先写出来。

7) 尾删

void ListPopBack(LTNode* phead)
{
	assert(phead);
	assert(!ListEmpty(phead));
	LTNode* tail = phead->prev;
	LTNode* prev = tail->prev;

	prev->next = phead;
	phead->prev = prev;
	free(tail);
	tail = NULL;
	//ListErase(phead->prev);
}

 避免把哨兵节点删除,需要先断言一下,链表是否为空。

删尾部节点的时候,要将它的前一个节点和phead链接上,画出草图即可以轻松写出代码。

8) 头删

void ListPopFront(LTNode* phead)
{
	assert(phead);
	assert(!ListEmpty(phead));
	LTNode* first = phead->next;
	LTNode* second = first->next;

	phead->next = second;
	second->prev = phead;
	free(first);
	first = NULL;
	//ListErase(phead->next);
}

 头删有哨兵节点时十分简单,直接保存头部节点,将phead链接到它的next即可,在把second的prev连到phead上即可。

9) 链表大小

直接遍历

size_t ListSize(LTNode* phead)
{
	assert(phead);
	size_t n = 0;
	LTNode* cur = phead->next;
	while (cur!=phead)
	{
		n++;
		cur = cur->next;
	}
	return n;
}

10 )查找 

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

11) pos前位置插入

void ListInsert(LTNode* pos, LTDataType x)
{
	assert(pos);
	LTNode* prev = pos->prev;
	LTNode* newnode = BuyListNode(x);

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

 防止pos位置为空,断言一下。

写出该函数后,前面的头插和尾插就可以直接调用该函数完成。

12)删除pos位置

void ListErase(LTNode* pos)
{
	assert(pos);
	LTNode* next = pos->next;
	LTNode* prev = pos->prev;

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

 较为简单,pos前和pos后建立链接即可

13)销毁链表

void ListDestory(LTNode* phead)
{
	assert(phead);
	LTNode* cur = phead->next;
	while (cur != phead)
	{
		LTNode* next = cur->next;
		free(cur);
		cur = next;
	}
	free(phead);
}

 在程序的最后,一定要销毁链表,链表需遍历销毁,避免内存泄露

 三、完整代码

List.h

#define  _CRT_SECURE_NO_WARNINGS
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <stdbool.h>

typedef int LTDataType;

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

void ListDestory(LTNode* phead);

void ListPrint(LTNode* phead);
LTNode* ListInit();
LTNode* BuyListNode(LTDataType x);
void ListPushBack(LTNode* phead, LTDataType x);
void ListPushFront(LTNode* phead, LTDataType x);
void ListPopBack(LTNode* phead);
void ListPopFront(LTNode* phead);
bool ListEmpty(LTNode* phead);
size_t ListSize(LTNode* phead);
LTNode* ListFind(LTNode* phead, LTDataType x);

//pos前插入
void ListInsert(LTNode* pos, LTDataType x);
//删除pos位置;
void ListErase(LTNode* pos);

List.c

#define  _CRT_SECURE_NO_WARNINGS
#include "List.h"

LTNode* ListInit() 
{
	LTNode* guard = (LTNode*)malloc(sizeof(LTNode));
	if (guard == NULL)
	{
		perror("malloc fail");
		exit(-1);
	}
	guard->next = guard;
	guard->prev = guard;
	return guard;
}

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



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

	return node;
}

void ListPushBack(LTNode* phead, LTDataType x)
{
	assert(phead);
	/*LTNode* newnode = BuyListNode(x);
	LTNode* tail = phead->prev;
	
	tail->next = newnode;
	newnode->next = phead;
	newnode->prev = tail;
	phead->prev = newnode;*/
	ListInsert(phead, x);
}

void ListPushFront(LTNode* phead, LTDataType x)
{
	assert(phead);
	/*LTNode* newnode = BuyListNode(x);
	newnode->next = phead->next;
	phead->next->prev = newnode;

	newnode->prev = phead;
	phead->next = newnode;*/

	//不关心顺序
	/*LTNode* newnode = BuyListNode(x);
	LTNode* first = phead->next;
	newnode->next = first;
	first->prev = newnode;
	phead->next = newnode;
	newnode->prev = phead;*/
	ListInsert(phead->next, x);
}

bool ListEmpty(LTNode* phead)
{
	assert(phead);
	return phead->next == phead;
}

void ListPopBack(LTNode* phead)
{
	assert(phead);
	assert(!ListEmpty(phead));
	/*LTNode* tail = phead->prev;
	LTNode* prev = tail->prev;

	prev->next = phead;
	phead->prev = prev;
	free(tail);
	tail = NULL;*/
	ListErase(phead->prev);
}

void ListPopFront(LTNode* phead)
{
	assert(phead);
	assert(!ListEmpty(phead));
	/*LTNode* first = phead->next;
	LTNode* second = first->next;

	phead->next = second;
	second->prev = phead;
	free(first);
	first = NULL;*/
	ListErase(phead->next);
}

size_t ListSize(LTNode* phead)
{
	assert(phead);
	size_t n = 0;
	LTNode* cur = phead->next;
	while (cur!=phead)
	{
		n++;
		cur = cur->next;
	}
	return n;
}

LTNode* ListFind(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 ListInsert(LTNode* pos, LTDataType x)
{
	assert(pos);
	LTNode* prev = pos->prev;
	LTNode* newnode = BuyListNode(x);

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

void ListErase(LTNode* pos)
{
	assert(pos);
	LTNode* next = pos->next;
	LTNode* prev = pos->prev;

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

void ListDestory(LTNode* phead)
{
	assert(phead);
	LTNode* cur = phead->next;
	while (cur != phead)
	{
		LTNode* next = cur->next;
		free(cur);
		cur = next;
	}
	free(phead);
}

 test.c

#define  _CRT_SECURE_NO_WARNINGS
#include "List.h"

void TestList1()
{
	LTNode* plist = ListInit();
	ListPushBack(plist, 1);
	ListPushBack(plist, 2);
	ListPushBack(plist, 3);
	ListPushBack(plist, 4);
	ListPrint(plist);
	ListPushFront(plist, 0);
	ListPushFront(plist, -1);
	ListPrint(plist);
	ListPushFront(plist, 10);
	ListPushFront(plist, 20);
	ListPushFront(plist, 30);
	ListPrint(plist);
	ListInsert(ListFind(plist, 3), 0);
	ListPrint(plist);
	ListErase(ListFind(plist, 2));
	ListPrint(plist);
	ListPopFront(plist);
	ListPopFront(plist);
	ListPopFront(plist);
	ListPrint(plist);
	ListPopBack(plist);
	ListPopBack(plist);
	ListPopBack(plist);
	ListPrint(plist);
	ListDestory(plist);
	plist = NULL;






}

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

 test.c是用来测试代码的,建议每写出一个函数,就测试一下,如果出现错误,这时通过调试很容易发现。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值