数据结构之双向链表

目录

双向链表的基本概念和结构

初始化

尾插

头插

尾删

头删

查找

在指定位置之后插入

删除指定位置节点

判空

销毁

完整代码

测试代码


双向链表的基本概念和结构

**双向链表(Doubly Linked List)**‌是一种链式存储结构,每个节点除了存储数据外,还包含两个指针,分别指向前一个节点(prev)和后一个节点(next)。这种结构使得双向链表可以从头到尾遍历,也可以从尾到头遍历,非常灵活‌。

双向链表的每个节点包含以下部分:

  • 数据元素‌:可以是任何类型的数据,如整数、浮点数、字符串或对象。
  • prev指针‌:指向前一个节点的指针。
  • next指针‌:指向下一个节点的指针。

双向链表通常包含一个头节点(哨兵位节点),这个节点不存储有效数据,只有指向第一个有效节点的next指针和指向尾节点的prev指针。只要链表存在,哨兵位节点就存在‌。

优点‌:

  • 双向遍历‌:可以从两个方向遍历链表,既可以从头到尾,也可以从尾到头。
  • 插入和删除操作高效‌:在已知前后节点的情况下,插入或删除操作可以直接进行,不需要遍历整个链表。
  • 动态内存分配‌:链表可以在运行时动态地分配和释放内存,适合需要频繁增删操作的场景。

初始化

cpp 复制代码
LTNode* ByNode(LTDatatype x)
{
	LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));
	if (newnode == NULL)
	{
		perror("申请失败!\n");
		exit(1);
	}
	//自成循环
	newnode->val = x;
	newnode->next = newnode;
	newnode->prev = newnode;

	return newnode;
}

void LTInit(LTNode **phead)
{
	//创建一个哨兵卫节点
	//*phead指向哨兵卫
	*phead = ByNode(-1);
}

初始化的目的就是创建一个哨兵卫节点,传参为二级指针是因为初始化时要对哨兵卫进行改变,改变一个指针就要传这个指针的地址,传指针的地址就要用二级指针来接收。

让每一个新增节点都先自成循环,这样方便与链表连接起来。

尾插

cpp 复制代码
void LTPushBack(LTNode* phead, LTDatatype x)
{
	//不对哨兵卫进行改变,所以传一级指针
	assert(phead); //哨兵卫不能为空
	LTNode* newnode = ByNode(x);

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

与尾插相关的只有哨兵卫的prev指针指向的数据和哨兵卫。哨兵卫的prev指针指向的是链表中的最后一个数据,不管该数据是否是哨兵卫都没有关系。先让newnode的prev指针指向最后一个数据,next指针指向哨兵卫,再让最后一个数据的next指针指向newnode。

头插

cpp 复制代码
void LTPushFront(LTNode* phead, LTDatatype x)
{
	assert(phead);
	LTNode* newnode = ByNode(x);

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

头插是往第一个有效数据前面插入,而不是在哨兵卫前面插入。先让newnode的next指针指向最后一个数据,newnode的prev指针指向哨兵卫,再让第一个有效数据的prev指针指向newnode,哨兵卫的next指针指向newnode就完成了头插。

尾删

cpp 复制代码
void LTPopBack(LTNode* phead)
{
	assert(phead && phead->next != phead);
	LTNode* del = phead->prev;
	del->prev->next = phead;
	phead->prev = del->prev;
	free(del);
	del = NULL;
}

尾删的前提是你要有有效的数据,那么哨兵卫节点的next指针就不能指向哨兵卫自己。首先要记录最后的一个数据存储在del变量里,先让del的前一个数据的next指针指向哨兵卫,哨兵卫的prev指针指向del的前一个数据。然后释放del空间,将del置为空。

头删

cpp 复制代码
void LTPopFront(LTNode* phead)
{
	assert(phead && phead->next != phead);

	LTNode* del = phead->next;
	del->next->prev = phead;
	phead->next = del->next;

	free(del);
	del = NULL;
}

首先要记录第一个有效数据存储在del变量里,先让del的下一个数据的prev指针指向哨兵卫,哨兵卫的next指针指向del的下一个数据。然后释放del空间,将del置为空。

查找

cpp 复制代码
LTNode* LTFind(LTNode* phead, LTDatatype x)
{
	assert(phead);
	LTNode* pcur = phead->next;
	while (pcur != phead)
	{
		if (pcur->val == x)
			return pcur;
		pcur = pcur->next;
	}
	return NULL;
}

直接从链表的第一个有效数据开始遍历,结束条件是遍历到哨兵卫就结束,如果节点的值是你要查询的值,就返回这个节点。如果链表中没有你要查找的数据,就返回空。

在指定位置之后插入

cpp 复制代码
void LTInsertBack(LTNode* pos, LTDatatype x)
{
	assert(pos);
	LTNode* newnode = ByNode(x);

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

这个接口的原理是根据查找函数返回的节点进行插入。如果你指定在d2的后面插入数据,你就要先查找d2在哪里,返回这个节点。newnode的next指针指向pos的next指针指向的节点,newnode的prev指针指向pos节点,pos的next指针指向的节点的prev指针指向newnode,pos的next指针指向newnode。

删除指定位置节点

cpp 复制代码
void LTErase(LTNode* pos)
{
	assert(pos);
	pos->next->prev = pos->prev;
	pos->prev->next = pos->next;
	free(pos);
	pos = NULL;
}

这个接口的原理是根据查找函数返回的节点进行删除。让pos节点的next指向的节点和prev指向的节点连接起来,然后释放pos节点,将pos节点置为空。

判空

cpp 复制代码
bool LTEmpty(LTNode* phead)
{
	assert(phead);
	return phead->next == phead;
}

判断该链表是否有有效数据,直接查看哨兵卫指向第一个有效节点的next指针是否指向哨兵卫,如果是,则说明该链表中只有一个哨兵卫,没有有效数据,就说该链表为空链表。

销毁

cpp 复制代码
void LTDestory(LTNode* phead)
{
	assert(phead);
	LTNode* pcur = phead->next;
	while (pcur->next != phead)
	{
		LTNode* next = pcur->next;
		free(pcur);
		pcur = next;
	}
	free(phead);
	phead = NULL;
}

链表的销毁要先从第一个有效数据开始删,最后释放掉哨兵卫节点。先定义一个指针pcur指向哨兵卫节点的next指针指向的节点,然后开始遍历链表进行删除。

完整代码

cpp 复制代码
#define _CRT_SECURE_NO_WARNINGS

#include "list.h"

//申请节点
LTNode* ByNode(LTDatatype x)
{
	LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));
	if (newnode == NULL)
	{
		perror("申请失败!\n");
		exit(1);
	}
	//自成循环
	newnode->val = x;
	newnode->next = newnode;
	newnode->prev = newnode;

	return newnode;
}

void LTInit(LTNode **phead)
{
	//创建一个哨兵卫节点
	//*phead指向哨兵卫
	*phead = ByNode(-1);
}

void LTDestory(LTNode* phead)
{
	assert(phead);
	LTNode* pcur = phead->next;
	while (pcur->next != phead)
	{
		LTNode* next = pcur->next;
		free(pcur);
		pcur = next;
	}
	free(phead);
	phead = NULL;
}

//打印
void LTPrint(LTNode* phead)
{
	assert(phead);
	LTNode* pcur = phead->next;
	while (pcur != phead)
	{
		printf("%d->", pcur->val);
		pcur = pcur->next;
	}
	printf("\n");
}

//尾插
void LTPushBack(LTNode* phead, LTDatatype x)
{
	//不对哨兵卫进行改变,所以传一级指针
	assert(phead); //哨兵卫不能为空
	LTNode* newnode = ByNode(x);

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

//头插
void LTPushFront(LTNode* phead, LTDatatype x)
{
	assert(phead);
	LTNode* newnode = ByNode(x);

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

//尾删
void LTPopBack(LTNode* phead)
{
	assert(phead && phead->next != phead);
	LTNode* del = phead->prev;
	del->prev->next = phead;
	phead->prev = del->prev;
	free(del);
	del = NULL;
}

//头删
void LTPopFront(LTNode* phead)
{
	assert(phead && phead->next != phead);

	LTNode* del = phead->next;
	del->next->prev = phead;
	phead->next = del->next;

	free(del);
	del = NULL;
}

//查找
LTNode* LTFind(LTNode* phead, LTDatatype x)
{
	assert(phead);
	LTNode* pcur = phead->next;
	while (pcur != phead)
	{
		if (pcur->val == x)
			return pcur;
		pcur = pcur->next;
	}
	return NULL;
}

//在指定位置之后插入
void LTInsertBack(LTNode* pos, LTDatatype x)
{
	assert(pos);
	LTNode* newnode = ByNode(x);

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

//在指定位置之前插入
void LTInitFront(LTNode* pos, LTDatatype x)
{
	assert(pos);
	LTNode* newnode = ByNode(x);

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

//删除pos节点
void LTErase(LTNode* pos)
{
	assert(pos);
	pos->next->prev = pos->prev;
	pos->prev->next = pos->next;
	free(pos);
	pos = NULL;
}

//判空
bool LTEmpty(LTNode* phead)
{
	assert(phead);
	return phead->next == phead;
}

测试代码

cpp 复制代码
#define _CRT_SECURE_NO_WARNINGS

#include "list.h"

void test()
{
	LTNode *plist = NULL;
	LTInit(&plist);  //创建了一个哨兵卫

	头插
	//LTPushBack(plist, 1);
	//LTPrint(plist);
	//LTPushBack(plist, 2);
	//LTPrint(plist);
	//LTPushBack(plist, 3);
	//LTPrint(plist);

	//尾插
	LTPushFront(plist, 1);
	LTPrint(plist);
	LTPushFront(plist, 2);
	LTPrint(plist);
	LTPushFront(plist, 3);
	LTPrint(plist);

	尾删
	//LTPopBack(plist);
	//LTPrint(plist);
	//LTPopBack(plist);
	//LTPrint(plist);

	头删
	//LTPopFront(plist);
	//LTPrint(plist);
	//LTPopFront(plist);
	//LTPrint(plist);

	//查找
	LTNode* find = LTFind(plist, 2);
	//if (find)
	//	printf("找到了\n");
	//else
	//	printf("没有找到\n");

	//在指定位置之后插入
	//LTInsertBack(find, 66);
	//LTPrint(plist);
	//LTInsertBack(find, 88);
	//LTPrint(plist);

	在指定位置之前插入
	//LTInitFront(find, 66);
	//LTPrint(plist);
	//LTInitFront(find, 88);
	//LTPrint(plist);

	删除pos节点
	//LTErase(find);
	//LTPrint(plist);

	//判空
	//if (LTEmpty(plist))
	//	printf("为空\n");
	//else
	//	printf("不为空\n");

	LTDestory(plist);
}

int main()
{
	test();
	return 0;
}
相关推荐
Ckyeka4 小时前
Leetcode刷题笔记—栈与队列
数据结构·python·算法·leetcode
幸运小圣4 小时前
LeetCode热题100-合并两个有序链表【JavaScript讲解】
javascript·leetcode·链表
夏末秋也凉5 小时前
力扣-数组-169 多数元素
数据结构·算法·leetcode
skywalk81635 小时前
C语言基本知识复习浓缩版:数组
c语言·数据结构·c++·算法
Ning_.6 小时前
LeetCode 283题:移动零
数据结构·算法·leetcode
请叫我大虾6 小时前
数据结构与算--堆实现线段树
java·数据结构·算法
工一木子7 小时前
【数据结构】第1天之Java中的数据结构
java·数据结构
labuladuo5208 小时前
洛谷 P3435 [POI2006] OKR-Periods of Words(扩展KMP+线段树做法)
数据结构·c++·算法
我叫王棱夹9 小时前
acwing-3194 最大的矩形
数据结构·c++·算法