最完美的链表结构——带头双向循环链表

一、结点的存储结构

  既然是双向循环带头链表,其结点一定有指向后面一个结点的next指针和指向前面一个结点的prev指针,虽然在存储结构上好像比单链表复杂了一些,但是很快我们就能看到这么设计的优势。

typedef int LTDataType;

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

二、带头双向循环链表的初始化

  初始化的目的主要是让我们的链表有个哨兵位头结点,并且让它的next和prev都指向它自己。

LTNode* ListInit()
{
	LTNode* phead = (LTNode*)malloc(sizeof(LTNode));
	if (phead == NULL)
	{
		printf("malloc fault\n");
		exit(-1);
	}
	phead->next = phead;
	phead->prev = phead;
	return phead;
}

三、带头双向循环链表的头插尾插和头删尾删

1.尾插 O(1)

  根据我们在单链表中学到的知识,尾插的关键在于找尾,在带头双向循环链表中,这点很简单,尾不就是哨兵位头结点的prev吗?
  我们用一个临时变量tail=phead->prev,然后创建一个新节点指向它的指针是newnode,然后修改
t a i l − > n e x t = n e w n o d e ; n e w n o d e − > p r e v = t a i l ; p h e a d − > p r e v = n e w n o d e ; n e w n o d e − > n e x t = p h e a d ; tail->next=newnode;\\ newnode->prev=tail;\\ phead->prev=newnode;\\ newnode->next=phead; tail>next=newnode;newnode>prev=tail;phead>prev=newnode;newnode>next=phead;
  我们可以发现这个逻辑相对于单链表的尾插是要简单很多的,就当前尾巴的next等于新节点,新节点的prev等于当前尾巴,头结点的prev等于新节点,新节点的next等于头结点就完成了,并且由于我们带了哨兵位头结点,这个逻辑也是可以处理链表为空的情况:
在这里插入图片描述

LTNode* Buynewnode(LTDataType x)
{
	LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));
	if (newnode == NULL)
	{
		printf("malloc fault\n");
		exit(-1);
	}
	newnode->data = x;
	return newnode;
}

void PushbackList(LTNode* phead, LTDataType x)
{
	assert(phead);
	LTNode* newnode = Buynewnode(x);
	LTNode* tail = phead->prev;
	tail->next = newnode;
	phead->prev = newnode;
	newnode->next = phead;
	newnode->prev = tail;
}

2. 头插 O(1)

  头插的逻辑也是类似的,创建一个新结点和指向新节点的指针newnode和指向当前第一个元素的指针next,然后phead的next赋值成newnode,newnode的prev赋值正phead,next的prev赋值成newnode,newnde的next赋值成next。

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

  同样的,头插也能完成链表为空的时候的插入的逻辑。

3.尾删 O(1)

  我们首先要保证这个链表已经初始化了且不为空,由于是循环链表,为空的情况就是
p h e a d − > n e x t = = p h e a d ; phead->next==phead; phead>next==phead;
未初始化的情况就是:
p h e a d = = N U L L ; phead==NULL; phead==NULL;
  尾删的逻辑也很简单,创建一个临时变量tail=phead->prev找到尾,然后创建一个临时变量prev=tail->prev存储tail之前的元素,然后free(tail),然后新的尾prev的next=phead,哨兵位头结点phead的prev等于新的尾prev,由于我们创建了临时变量,free(tail)这一步可以在我们创建完两个临时变量后的任何一步进行,从这里可以看出带头双向循环链表优秀的结构让它的尾删找尾很简单,删尾的动作也很简单,而且自由度很大,不容易写错。

void PopbackList(LTNode* phead)
{
	assert(phead && phead->next != phead);
	LTNode* tail = phead->prev;
	LTNode* prev = tail->prev;
	prev->next = phead;
	phead->prev = prev;
	free(tail);
}

4.头删 O(1)

  逻辑类似,这里就不赘述了。

void PopfrontList(LTNode* phead)
{
	assert(phead && phead->next != phead);
	LTNode* front = phead->next;
	LTNode* next = front->next;
	phead->next = next;
	next->prev = phead;
	free(front);
}

5.打印 O(N)

  为了查看一下我们的链表中都有什么元素,我们可以创建一个打印函数,由于是循环链表,所以打印可以从哨兵位头结点的下一个节点开始打印,到回到哨兵位头结点为止。

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

四、带头双向循环链表的查找

1.返回指向目标位置指针的查找 O(N)

  查找与打印的逻辑是相似的,只不过把printf换成了判断元素是否相等,如果相等就return这个指针,这么做的目的是为了方便的利用返回的指针再来做指定位置的插入、删除、修改。

LTNode* Listfind(LTNode* phead, LTDataType x)
{
	assert(phead && phead->next != phead);
	LTNode* cur = phead->next;
	while (cur != phead)
	{
		if (cur->data == x)
			return cur;
		cur = cur->next;
	}
	return NULL;//表示查找失败
}

2.返回目标结点是在链表中位置的查找 O(N)

  核心逻辑是利用一个整型变量做计数器count,初始值为0(表示当前是链表的第一个元素,对应成数组下标为0),每走一步计数器加一,返回计数器值,如果查找失败返回-1。

int Listfind2(LTNode* phead, LTDataType x)
{
	assert(phead && phead->next != phead);
	int count = 0;
	LTNode* cur = phead->next;
	while (cur != phead)
	{
		if (cur->data == x)
		{
			return count;
		}
		count++;
		cur = cur->next;
	}
	return -1;//表示查找失败
}

五、带头双向循环链表的指定位置修改

1.参数是指向给定位置的指针 O(1)

void Listchange(LTNode* pos, LTDataType x)
{
	assert(pos);
	pos->data = x;
}

2.参数是链表中的第pos-1个元素 O(N)

void Listchange2(LTNode* phead, int pos, LTDataType x)
{
	assert(phead && phead->next != phead);
	LTNode* cur = phead->next;
	while (pos--)
	{
		cur = cur->next;
	}
	cur->data = x;
}

  显然利用find找到指向目标结点的指针后,修改目标结点的时间复杂度要小的多。

六、带头双向循环链表在任意位置的插入和删除

1. 参数为指向给定位置指针pos的插入 O(1)

  根据西瓜,我们要实现在pos位置之前插入,插入的逻辑都是类似的,创建一个临时变量posprev指向pos的前面一个结点,然后创建一个新结点newnode,posprev的next指向newnode,newnode的prev指向posprev,pos的prev指向newnode,newnode的next指向pos。

void ListInsert(LTNode* pos, LTDataType x)
{
	assert(pos);
	LTNode* posprev = pos->prev;
	LTNode* newnode = Buynewnode(x);
	posprev->next = newnode;
	newnode->prev = posprev;
	pos->prev = newnode;
	newnode->next = pos;
}

2.参数为链表的第pos个位置的插入 O(N)

  先让指针走pos步走到给定位置,然后再做插入操作,由于要走,所以它的时间复杂度比上一个插入函数要高。

void ListInsert2(LTNode* phead, int pos, LTDataType x)
{
	assert(phead);
	LTNode* cur = phead->next;
	while (pos--)
	{
		cur = cur->next;
	}
	LTNode* curprev = cur->prev;
	LTNode* newnode = Buynewnode(x);
	curprev->next = newnode;
	newnode->prev = curprev;
	cur->prev = newnode;
	newnode->next = cur;
}

3.参数为指向给定位置指针pos的删除 O(1)

  我们要删除pos指针指向的结点。
  删除的逻辑也是类似的,记录pos位置的前继posprev,记录pos位置的后继posnext,然后free(pos),然后posprev->next=posnext,posnext->prev=posprev把他们连起来,同样的free的顺序和后面链接的顺序由于我们创建了临时变量可以互换。

void ListErase(LTNode* pos)
{
	assert(pos);
	LTNode* posprev = pos->prev;
	LTNode* posnext = pos->next;
	free(pos);
	posprev->next = posnext;
	posnext->prev = posprev;
}

4.参数为链表的第pos个位置的删除 O(N)

  我们要删除链表中第pos+1个结点。
  和插入一样,先要走pos步到那个给定位置,然后删除这个元素,由于要走,所以它的时间复杂度比上一个删除函数要高。

void ListErase2(LTNode* phead, int pos)
{
	assert(phead && phead->next != phead);
	LTNode* cur = phead->next;
	while (pos--)
	{
		cur = cur->next;
	}
	LTNode* curprev = cur->prev;
	LTNode* curnext = cur->next;
	free(cur);
	curprev->next = curnext;
	curnext->prev = curprev;
}

5.根据指定位置的插入和删除函数修改头插尾插头删尾删函数

  头插其实就是在phead->next之前的位置插入。
  头删其实就是删除phead->next位置的元素。
  尾插其实就是在phead之前的位置插入(这个位置相当于尾)。
  尾删其实就是删除phead->prev位置的元素。

void PushbackList(LTNode* phead, LTDataType x)
{
	assert(phead);
	ListInsert(phead, x);
}

void PopbackList(LTNode* phead)
{
	assert(phead && phead->next != phead);
	ListErase(phead->prev);
}

void PushfrontList(LTNode* phead, LTDataType x)
{
	assert(phead);
	ListInsert(phead->next, x);
}

void PopfrontList(LTNode* phead)
{
	assert(phead && phead->next != phead);
	ListErase(phead->next);
}

七、销毁带头双向循环链表

  我们创建一个临时变量cur=(*pphead)->next来遍历链表,curnext来存储cur->next以便free(cur)后让cur继续遍历链表,遍历结束的条件是cur回到*pphead,这里传二级指针的目的是为了下一步free(*pphead)并且把*pphead置空,这样就把链表恢复成了初始化之前的状态,并且把在堆区上申请的内存都还给了操作系统。

void Listdestroy(LTNode** pphead)
{
	assert(*pphead);
	LTNode* cur = (*pphead)->next;
	while (cur != *pphead)
	{
		LTNode* curnext = cur->next;
		free(cur);
		cur = curnext;
	}
	free(*pphead);
	*pphead = NULL;
}

八、与单链表的各种操作时间复杂度对比

链表长度N单链表带头双向循环链表
打印O(N)O(N)
尾插O(N)O(1)
头插O(1)O(1)
头删O(1)O(1)
尾删O(N)O(1)
查找O(N)O(N)
任意给定位置插入O(N)O(1)
任意给定位置删除O(N)O(1)
任意给定位置修改O(N)O(1)
销毁O(N)O(N)

九、汇总

//List.h
#ifndef _LIST_H_
#define _LIST_H_
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>

typedef int LTDataType;

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

//初始化带头双向循环链表
LTNode* ListInit();
//打印 O(N)
void printList(LTNode* phead);
//尾插 O(1)
void PushbackList(LTNode* phead, LTDataType x);
//尾删 O(1)
void PopbackList(LTNode* phead);
//头插 O(1)
void PushfrontList(LTNode* phead, LTDataType x);
//头删 O(1)
void PopfrontList(LTNode* phead);
//查找 O(N)
LTNode* Listfind(LTNode* phead, LTDataType x);
int Listfind2(LTNode* phead, LTDataType x);
//修改pos位置的元素 
void Listchange(LTNode* pos, LTDataType x);//O(1)
void Listchange2(LTNode* phead, int pos, LTDataType x);//O(N)
//在pos位置之前插入
void ListInsert(LTNode* pos, LTDataType x);//O(1)
void ListInsert2(LTNode* phead, int pos, LTDataType x);//O(N)
//删除pos位置
void ListErase(LTNode* pos);//O(1)
void ListErase2(LTNode* phead, int pos);//O(N)
//销毁链表 O(N)
void Listdestroy(LTNode** pphead);
#endif
//List.c
#include "List.h"
LTNode* ListInit()
{
	LTNode* phead = (LTNode*)malloc(sizeof(LTNode));
	if (phead == NULL)
	{
		printf("malloc fault\n");
		exit(-1);
	}
	phead->next = phead;
	phead->prev = phead;
	return phead;
}
LTNode* Buynewnode(LTDataType x)
{
	LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));
	if (newnode == NULL)
	{
		printf("malloc fault\n");
		exit(-1);
	}
	newnode->data = x;
	return newnode;
}

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

void PushbackList(LTNode* phead, LTDataType x)
{
	assert(phead);
	//LTNode* newnode = Buynewnode(x);
	//LTNode* tail = phead->prev;
	//tail->next = newnode;
	//phead->prev = newnode;
	//newnode->next = phead;
	//newnode->prev = tail;
	ListInsert(phead, x);
}
void PopbackList(LTNode* phead)
{
	assert(phead && phead->next != phead);
	//LTNode* tail = phead->prev;
	//LTNode* prev = tail->prev;
	//prev->next = phead;
	//phead->prev = prev;
	//free(tail);
	ListErase(phead->prev);
}
void PushfrontList(LTNode* phead, LTDataType x)
{
	assert(phead);
	//LTNode* newnode = Buynewnode(x);
	//LTNode* next = phead->next;
	//phead->next = newnode;
	//newnode->prev = phead;
	//next->prev = newnode;
	//newnode->next = next;
	ListInsert(phead->next, x);
}
void PopfrontList(LTNode* phead)
{
	assert(phead && phead->next != phead);
	//LTNode* front = phead->next;
	//LTNode* next = front->next;
	//phead->next = next;
	//next->prev = phead;
	//free(front);
	ListErase(phead->next);
}
LTNode* Listfind(LTNode* phead, LTDataType x)
{
	assert(phead && phead->next != phead);
	LTNode* cur = phead->next;
	while (cur != phead)
	{
		if (cur->data == x)
			return cur;
		cur = cur->next;
	}
	return NULL;//表示查找失败
}

int Listfind2(LTNode* phead, LTDataType x)
{
	assert(phead && phead->next != phead);
	int count = 0;
	LTNode* cur = phead->next;
	while (cur != phead)
	{
		if (cur->data == x)
		{
			return count;
		}
		count++;
		cur = cur->next;
	}
	return -1;//表示查找失败
}
void Listchange(LTNode* pos, LTDataType x)
{
	assert(pos);
	pos->data = x;
}
void Listchange2(LTNode* phead, int pos, LTDataType x)
{
	assert(phead && phead->next != phead);
	LTNode* cur = phead->next;
	while (pos--)
	{
		cur = cur->next;
	}
	cur->data = x;
}
void ListInsert(LTNode* pos, LTDataType x)
{
	assert(pos);
	LTNode* posprev = pos->prev;
	LTNode* newnode = Buynewnode(x);
	posprev->next = newnode;
	newnode->prev = posprev;
	pos->prev = newnode;
	newnode->next = pos;
}
void ListInsert2(LTNode* phead, int pos, LTDataType x)
{
	assert(phead);
	LTNode* cur = phead->next;
	while (pos--)
	{
		cur = cur->next;
	}
	LTNode* curprev = cur->prev;
	LTNode* newnode = Buynewnode(x);
	curprev->next = newnode;
	newnode->prev = curprev;
	cur->prev = newnode;
	newnode->next = cur;
}
void ListErase(LTNode* pos)
{
	assert(pos);
	LTNode* posprev = pos->prev;
	LTNode* posnext = pos->next;
	free(pos);
	posprev->next = posnext;
	posnext->prev = posprev;
}
void ListErase2(LTNode* phead, int pos)
{
	assert(phead && phead->next != phead);
	LTNode* cur = phead->next;
	while (pos--)
	{
		cur = cur->next;
	}
	LTNode* curprev = cur->prev;
	LTNode* curnext = cur->next;
	free(cur);
	curprev->next = curnext;
	curnext->prev = curprev;
}
void Listdestroy(LTNode** pphead)
{
	assert(*pphead);
	LTNode* cur = (*pphead)->next;
	while (cur != *pphead)
	{
		LTNode* curnext = cur->next;
		free(cur);
		cur = curnext;
	}
	free(*pphead);
	*pphead = NULL;
}
//test.c
#include "List.h"
void test1()
{
	LTNode* pList = NULL;
	pList = ListInit();
	PushbackList(pList, 1);
	PushbackList(pList, 2);
	PushbackList(pList, 3);
	PushbackList(pList, 4);
	PushbackList(pList, 5);
	printList(pList);
	PopbackList(pList);
	PopbackList(pList);
	PopbackList(pList);
	PopbackList(pList);
	printList(pList);
	PopbackList(pList);
	//PopbackList(pList);
}

void test2()
{
	LTNode* pList = NULL;
	pList = ListInit();
	PushfrontList(pList, 1);
	PushfrontList(pList, 2);
	PushfrontList(pList, 3);
	PushfrontList(pList, 4);
	PushfrontList(pList, 5);
	printList(pList);
	PopfrontList(pList);
	PopfrontList(pList);
	PopfrontList(pList);
	printList(pList);
	PopfrontList(pList);
	PopfrontList(pList);
	//PopfrontList(pList);
}

void test3()
{
	LTNode* pList = NULL;
	pList = ListInit();
	PushbackList(pList, 1);
	PushbackList(pList, 2);
	PushbackList(pList, 3);
	PushbackList(pList, 4);
	PushbackList(pList, 5);
	LTNode* pos = Listfind(pList, 3);
	Listchange(pos, 6);
	Listchange2(pList, 1, 15);
	printList(pList);
}

void test4()
{
	LTNode* pList = NULL;
	pList = ListInit();
	PushbackList(pList, 1);
	PushbackList(pList, 2);
	PushbackList(pList, 3);
	PushbackList(pList, 4);
	PushbackList(pList, 5);
	printList(pList);
	ListInsert2(pList, 3, 12);
	printList(pList);
	ListErase2(pList, 2);
	printList(pList);
	Listdestroy(&pList);
}

int main()
{
	//test1();
	//test2();
	//test3();
	test4();
	return 0;
}
  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值