数据结构——双向链表

双向链表

1.双向链表的定义

我们之前学过单链表,也就是无头单向非循环链表。那么我们今天学的是带头双向循环链表。虽然它的结构相较于单链表复杂一些,但在实际应用中具有很好的应用意义。

带头的意思就是带有一个哨兵位的头结点,此结点用来存放头结点,不存放有效数据。之前单向链表只可以指向下一个链表,双向就可以指向上一个。循环则是指可以从最后一个链表循环到第一个。

定义代码如下:

typedef int LTDataType;
typedef struct ListNode
{
	struct ListNode* next;
	struct ListNode* prev;
	LTDataType data;
}LTNode;
2.双向链表的基本操作
(1)双向链表建立结点
LTNode* BuyLTNode(LTDataType x)
{
	LTNode* node = (LTNode*)malloc(sizeof(LTNode));
	if (node == NULL)
	{
		perror("malloc fail");
		exit(-1);
	}

	node->data = x;
	node->next = NULL;
	node->prev = NULL;

	return node;
}
(2)双向链表的初始化
LTNode* LTInit()
{
	LTNode* phead = BuyLTNode(0);
	phead->next = phead;
	phead->prev = phead;

	return phead;
}
(3)打印双向链表

这里有个很有意思的地方,注意看循环判断条件是:cur!=phead,也就是说当cur==phead的时候跳出循环。这是因为这是一个循环链表,当cur==phead的时候说明已经又到了头结点,说明已经遍历结束了。大家可以再体会一下这个思想。

void LTPrint(LTNode* phead)
{
	assert(phead);

	printf("phead<=>");
	LTNode* cur = phead->next;
	while (cur != phead)
	{
		printf("%d<=>", cur->data);
		cur = cur->next;
	}
	printf("\n");
}
(4)双向链表的尾插

要实现尾插,我们只需要找到尾,按照双向循环链表的特点,head的prev指向尾结点,所以我们定义一个tail指针,从而实现尾插。

void LTPushBack(LTNode* phead, LTDataType x)
{
	assert(phead);

	LTNode* tail = phead->prev;
	LTNode* newnode = BuyLTNode(x);

	newnode->prev = tail;
	tail->next = newnode;

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


	/*LTInsert(phead, x);*/
}
(5)双向链表的头插
void LTPushFront(LTNode* phead, LTDataType x)
{
	assert(phead);

	/*LTNode* newnode = BuyLTNode(x);
	newnode->next = phead->next;
	phead->next->prev = newnode;

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

	LTNode* newnode = BuyLTNode(x);
	LTNode* first = phead->next;

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

	/*LTInsert(phead->next, x);*/
}
(6)双向链表的尾删

尾删只需要找到尾结点,再将尾结点的前驱节点记录下来,然后释放尾结点,再将前驱和头结点之间连接起来就可以实现。

void LTPopBack(LTNode* phead)
{
	assert(phead);
	assert(phead->next != phead);

	LTNode* tail = phead->prev;
	LTNode* tailPrev = tail->prev;
	free(tail);

	tailPrev->next = phead;
	phead->prev = tailPrev;

	/*LTErase(phead->prev);*/
}
(7)双向链表的头删

大家思考一下,怎么进行头删?是不是和尾删是一样的思路,只不过这次找到的是第一个结点和第二个结点,释放第一个结点,然后改变头结点和第二个结点的指向关系。

void LTPopFront(LTNode* phead)
{
	assert(phead);
	assert(phead->next != phead);

	//LTNode* first = phead->next;
	//LTNode* second = first->next;

	//free(first);

	//phead->next = second;
	//second->prev = phead;

	LTErase(phead->next);
}
(8)获取双向链表的数据个数
int LTSize(LTNode* phead)
{
	assert(phead);

	int size = 0;
	LTNode* cur = phead->next;
	while (cur != phead)
	{
		++size;
		cur = cur->next;
	}

	return size;
}
(9)pos位置之前进行插入
// pos之前插入
void LTInsert(LTNode* pos, LTDataType x)
{
	assert(pos);

	LTNode* posPrev = pos->prev;
	LTNode* newnode = BuyLTNode(x);

	posPrev->next = newnode;
	newnode->prev = posPrev;
	newnode->next = pos;
	pos->prev = newnode;
}
(10)在pos位置进行删除
// 删除pos位置
void LTErase(LTNode* pos)
{
	assert(pos);
	LTNode* posPrev = pos->prev;
	LTNode* posNext = pos->next;

	free(pos);

	posPrev->next = posNext;
	posNext->prev = posPrev;
}
(11)双向链表的销毁
void LTDestroy(LTNode* phead)
{
	assert(phead);

	LTNode* cur = phead->next;
	while (cur != phead)
	{
		LTNode* next = cur->next;
		free(cur);
		cur = next;
	}
	free(phead);
	phead = NULL;
}
3.双向链表妙用------pos位置前插入

我们先再来看一下pos位置前进行插入:

那如果我现在要头插,根据LTInsert函数,我的参数应该如何定义?头插就是在一个位置处,所以是我们要插入的位置就是phead->next。

LTInsert(phead->next, x)

那么尾插呢?大家仔细思考一下,是在tail后面吗?实际上呢,尾插的位置就是phead。在pos位置之前插入,那么在phead之前插入,不就是在tail之后插入。

LTInsert(phead, x)
相关推荐
AC使者1 小时前
5820 丰富的周日生活
数据结构·算法
无 证明2 小时前
new 分配空间;引用
数据结构·c++
别NULL6 小时前
机试题——疯长的草
数据结构·c++·算法
ZSYP-S8 小时前
Day 15:Spring 框架基础
java·开发语言·数据结构·后端·spring
唐叔在学习8 小时前
【唐叔学算法】第21天:超越比较-计数排序、桶排序与基数排序的Java实践及性能剖析
数据结构·算法·排序算法
ALISHENGYA8 小时前
全国青少年信息学奥林匹克竞赛(信奥赛)备考实战之分支结构(switch语句)
数据结构·算法
武昌库里写JAVA11 小时前
浅谈怎样系统的准备前端面试
数据结构·vue.js·spring boot·算法·课程设计
S-X-S11 小时前
代码随想录刷题-数组
数据结构·算法
l1384942745111 小时前
每日一题(4)
java·数据结构·算法
kyrie_sakura11 小时前
c++数据结构算法复习基础--13--基数算法
数据结构·c++·算法