双向带头循环链表

双向带头循环链表_第1张图片
今天我们在来学一个链表的内容,就是我们的双向带头循环链表,听名字就是一个很牛的链表,那我们今天就来把它的每个接口一个一个来实现。首先我们来看一下它的物理结构是一个什么样子的。

双向带头循环链表_第2张图片

我们通过这个图可以很清楚的看到我们的前一个节点都是指向我们后一个节点的,但是同时我们也可以看到我们的后一个节点是指向前一个节点的,这样就很高效的帮助我们去实现一些随机插入,我们如果在仔细看这个链表的时候,我们可以发现我们的head指针,我们可以叫他们是哨兵位的节点,他是指向我们的尾节点,那我们这里就可以很快找到我们尾,并对它进行一些操作,这样也就能满足我们之前链表每次找尾节点的时候都需要遍历一次链表这样的时间复杂度就是O(N),现在直接提升成O(1)。

那我们对它结构体的定义就是下面这些代码。

typedef int LTDataType;
typedef struct ListNode
{
	LTDataType _data;
	struct ListNode* _next;
	struct ListNode* _prev;
}LT;


那我们第一个需要对它实现的接口函数就是初始化,这样初始化的作用就是我们需要创建出第一个节点,也就是我们平常口中说的哨兵位。

LT* LTInit()
{
	LT* plist = (LT*)malloc(sizeof(LT));
	if (plist == NULL)
	{
		perror("malloc fail\n");
		exit(-1);
	}
	plist->next = plist;
	plist->prev = plist;
	return plist;
}

这个就是我们的初始化,我们的初始化在这里是创造出一个哨兵位的头节点,然后让它指向自己,这是为了后面的插入和删除更加方便,我们这里有一个和之前不太一样的是我们这里用了返回值,如果没有返回值会产生两个问题,一个最重要的是我们记录malloc这个节点的地址是个局部变量,malloc如果不释放是会存在内存泄漏的,所以我们这里用了返回值·,还有就是避免了二级指针,如果我们一开始定义一个指针,我们进行初始化对我们产生的影响就是我们无法改变这个指针的指向,我们这里就得传一个二级指针,所以这里用了返回值是最好的办法。

下面就是我们马上来写一个尾插的函数,尾插的函数我们之前单链表是需要找尾的,但是我们这里就可以直接把O(N)的时间复杂度变成O(1)因为我们双向链表的定义就是我们的头指针得指向我们的尾,这样就能构成循环。

那我们的push是要创造新的节点出来的,这里我们需要再写一个创造节点的函数出来,这样才能保证我们push的实现。


LT* CreateNode(LTDataType x)
{
	LT* newnode = (LT*)malloc(sizeof(LT));
	if (newnode == NULL)
	{
		perror("malloc fail\n");
		exit(-1);
	}
	newnode->data = x;
	return newnode;
}

void LTPushBack(LT* plist, LTDataType x)
{
	assert(plist);
	LT* newnode = CreateNode(x);
	/*newnode->prev = plist->prev;
	plist->next = newnode;
	newnode->next = plist;
	plist->prev = newnode;*///错误的,有指针进行改变了,导致链接不上了。
	LT* tail = plist->prev;
	tail->next = newnode;
	newnode->prev = tail;
	newnode->next = plist;
	plist->prev = newnode;


}

这里也给出了一个错误的方法,因为小编一开始就是这样写的,结果发现最后双向循环链表事链接不上的,尾插的实现也就很简单,只要找到为节点然后进行保存再进行链接,保证还是一个双向循环链表就可以了。

那我们再把后面的接口函数也补上。

LT* CreateNode(LTDataType x);

void LTPrint(LT* plist);


void LTPushFront(LT* plist, LTDataType x);

void LTPopFront(LT* plist);


void LTPopBack(LT* plist);

LT* LTFind(LT* plist, LTDataType x);


void LTInsert(LT* plist, LT* pos, LTDataType x);


void LTErase(LT* plist, LT* pos);


void LTDstory(LT* plist);

其实这些接口的思路实现就和我们的单链表是差不多的,这里也就不过多的讲解,主要是一些注意的事项进行讲解。

下面再来实现打印函数,这里打印我们不能以cur为空来进行判断,因为这样就会进行死循环的打印,所以我们这里要可将cur写成head的next,判断条件就是如果cur的next是head,打印条件判断就结束了,那我们再来实现一下。

void LTPrint(LT* plist)
{
	assert(plist);
	LT* cur = plist->next;
	printf("哨兵位<=>");
	while (cur != plist)
	{
		printf("%d<=>", cur->data);
		cur = cur->next;
	}
	printf("\n");
}

下一个就是头插,双向循环链表带头的好处也就出来了,我们不用怕是不是空,因为不管怎么样都是有一个哨兵位的,所以只要大胆的插入就可以了。

void LTPushFront(LT* plist, LTDataType x)
{
	assert(plist);
	LT* newnode = CreateNode(x);
	newnode->next = plist->next;
	plist->next->prev = newnode;
	plist->next = newnode;
	newnode->prev = plist;
}

下面就是pop环节,这个时候就是需要注意一些了,没有节点就不能pop,但是下面的代码是针对所以情况,哪怕你只有一个节点也是可以实现的,我们来看看吧

void LTPopFront(LT* plist)
{
	assert(plist);
	LT* first = plist->next;
	LT* second = plist->next->next;
	free(first);
	first = NULL;
	plist->next = second;
	second->prev = plist;
}


void LTPopBack(LT* plist)
{
	assert(plist);
	assert(plist->next != plist);
	LT* tail = plist->prev;
	LT* prev = tail->prev;
	free(tail);
	tail = NULL;
	prev->next = plist;
	plist->prev = prev;

}

尾删和头删都可以直接进行,而且都适用,如果画图仔细地话,当只要一个节点地时候,second就是我们创建地哨兵位。

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

FInd函数也是特别简单地实现,我们遍历一遍就行,而且cur应该是要指向我们哨兵位后面的一个节点。

那我们再来实现在pos位置前插入和pos位置进行删除的代码,双向循环带头链表就是可以这样实现的。这里我的find要和这些一起用,才能大达到我们pos的效果

void LTInsert(LT* plist, LT* pos, LTDataType x)
{
	assert(plist);
	assert(pos);
	LT* newnode = CreateNode(x);
	LT* prev = pos->prev;
	prev->next = newnode;
	newnode->prev = prev;
	newnode->next = pos;
	pos->prev = newnode;
}


void LTErase(LT* plist, LT* pos)
{
	assert(plist);
	assert(pos);
	assert(plist->next != plist);
	LT* prev = pos->prev;
	LT* next = pos->next;
	free(pos);
	prev->next = next;
	next->prev = prev;
}

下面再来实现一下destory代码就算是全部欧克

void LTDstory(LT* plist)
{
	assert(plist);
	LT* cur = plist->next;
	while (cur->next != plist)
	{
		LT* next = cur->next;
		free(cur);
		cur = next;
	}
}

那我们双向链表就这样简单的实现,完整代码也发下面了,这个没有过的讲解时因为很多和单链表相似,如果实现不会看看代码就是能看懂的,带头双向循环链表的优势就是看起来好像很复杂,但是用起来就是爽的特点
List.h

#pragma once
#include
#include
#include

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

LT* LTInit();

void LTPushBack(LT* plist, LTDataType x);

LT* CreateNode(LTDataType x);

void LTPrint(LT* plist);


void LTPushFront(LT* plist, LTDataType x);

void LTPopFront(LT* plist);


void LTPopBack(LT* plist);

LT* LTFind(LT* plist, LTDataType x);


void LTInsert(LT* plist, LT* pos, LTDataType x);


void LTErase(LT* plist, LT* pos);


void LTDstory(LT* plist);

list.c

#define _CRT_SECURE_NO_WARNINGS 1

#include"SList.h"

LT* LTInit()
{
	LT* plist = (LT*)malloc(sizeof(LT));
	if (plist == NULL)
	{
		perror("malloc fail\n");
		exit(-1);
	}
	plist->next = plist;
	plist->prev = plist;
	return plist;
}


LT* CreateNode(LTDataType x)
{
	LT* newnode = (LT*)malloc(sizeof(LT));
	if (newnode == NULL)
	{
		perror("malloc fail\n");
		exit(-1);
	}
	newnode->data = x;
	return newnode;
}

void LTPushBack(LT* plist, LTDataType x)
{
	assert(plist);
	LT* newnode = CreateNode(x);
	/*newnode->prev = plist->prev;
	plist->next = newnode;
	newnode->next = plist;
	plist->prev = newnode;*///错误的,有指针进行改变了,导致链接不上了。
	LT* tail = plist->prev;
	tail->next = newnode;
	newnode->prev = tail;
	newnode->next = plist;
	plist->prev = newnode;


}



void LTPrint(LT* plist)
{
	assert(plist);
	LT* cur = plist->next;
	printf("哨兵位<=>");
	while (cur != plist)
	{
		printf("%d<=>", cur->data);
		cur = cur->next;
	}
	printf("\n");
}

void LTPushFront(LT* plist, LTDataType x)
{
	assert(plist);
	LT* newnode = CreateNode(x);
	newnode->next = plist->next;
	plist->next->prev = newnode;
	plist->next = newnode;
	newnode->prev = plist;
}


void LTPopFront(LT* plist)
{
	assert(plist);
	LT* first = plist->next;
	LT* second = plist->next->next;
	free(first);
	first = NULL;
	plist->next = second;
	second->prev = plist;
}


void LTPopBack(LT* plist)
{
	assert(plist);
	assert(plist->next != plist);
	LT* tail = plist->prev;
	LT* prev = tail->prev;
	free(tail);
	tail = NULL;
	prev->next = plist;
	plist->prev = prev;

}


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


void LTInsert(LT* plist, LT* pos, LTDataType x)
{
	assert(plist);
	assert(pos);
	LT* newnode = CreateNode(x);
	LT* prev = pos->prev;
	prev->next = newnode;
	newnode->prev = prev;
	newnode->next = pos;
	pos->prev = newnode;
}


void LTErase(LT* plist, LT* pos)
{
	assert(plist);
	assert(pos);
	assert(plist->next != plist);
	LT* prev = pos->prev;
	LT* next = pos->next;
	free(pos);
	prev->next = next;
	next->prev = prev;
}


void LTDstory(LT* plist)
{
	assert(plist);
	LT* cur = plist->next;
	while (cur->next != plist)
	{
		LT* next = cur->next;
		free(cur);
		cur = next;
	}
}

那我们今天的分享也就到这里结束了,我们下次再见,今天连写两篇,感觉都是很水的文章,主要是最近要期末考试了,我还啥也不会。

双向带头循环链表_第3张图片

你可能感兴趣的:(链表,数据结构)