【数据结构 双链表】超详细理解&&例题&&错题

你好,这里是新人 Sunfor

这篇是我最近对于数据结构 双链表的学习心得和错题整理
有任何错误欢迎指正,欢迎交流!
会持续更新,希望对你有所帮助,我们一起学习,一起进步

前言

在这里插入图片描述

一、双链表的概念

带头双向循环链表简称为:双链表
那我们之前提到的单链表是怎样的链表的简称呢?
答案是:不带头单向不循环链表

在这里插入图片描述

二、双链表的基本操作

创建双链表、插入、删除、遍历、查找、获取长度

三、双链表用C语言实现

类比于我们之前实现顺序表和单链表,同样是多文件操作

  • List.h:主要存放代码实现过程中需要的头文件,同时充当目录
  • List.c:函数的具体功能的实现
  • text.c:测试函数的功能,包含主函数

List.h

#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>

//定义双向链表的结构
typedef int LTDataType;
typedef struct ListNode
{
	LTDataType x;
	struct ListNode* next;//后继
	struct ListNode* prev;//前驱
}LTNode;

//初始化
LTNode* LTInit();

//插入
```c
//判断传一级指针还是二级指针,就看phead指向的结点会不会发生改变
//如果发生改变,那么phead的改变要影响实参,传二级指针
//如果不发生改变,那么phead的改变不会影响实参,传一级指针
//在双向链表中 哨兵位不变(不能被修改 不能被释放)
void LTPushBack(LTNode* phead,LTDataType x);//尾插

void LTPushFront(LTNode* phead,LTDataType x);//头插

//打印
void LTPrint(LTNode* phead);

//判空
bool LTEmpty(LTNode* phead);

//删除
void LTPopBack(LTNode* phead);//尾删

void LTPopFront(LTNode* phead);//头删
//查找
LTNode* LTFind(LTNode* phead,LTDataType x);

//在指定结点之后插入数据
void LTInsert(LTNode* pos,LTDataType x);

//删除指定结点
void LTErase(LTNode* pos);

//销毁
void LTDesTroy(LTNode* phead);//保持接口的一致性,传一级指针


List.c

#include"List.h"

//申请一个结点
LTNode* LTBuyNode(LTNDataType x)
{
	LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));
	if(newnode == NULL)
	{
		perror("malloc fail!");
		exit(1);
	}
	newnode->data = x;
	newnode->prev = newnode->next = NULL;
}

//初始化
//为了统一接口,传一级指针,就直接返回哨兵位
LTNode* LTInit()
{
	LTNode* phead = LTBuyNode(-1);
	return phead;
}

//尾插
void LTPushBack(LTNode* phead,LTDataType x)
{
	assert(phead);
	LTNode* newnode = LTBuyNode(x);

	//phead phead->prev(tail) newnode
	//先修改newnode
	newnode->next = phead;
	newnode->prev = phead->prev;

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

在这里插入图片描述

void LTPushFront(LTNode* phead,LTDataType x)//头插
//注意头插是在哨兵位之后插入,如果插入在哨兵位之前其实等同于尾插
{
	assert(phead);
	LTNode* newnode = LTBuyNode(x);
	//phead newnode phead->next
	newnode->prev = phead;
	newnode->next = phead->next;

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

在这里插入图片描述

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

//判空
bool LTEmpty(LTNode* phead)
{
	assert(phead);
	return phead->next == phead;
}
void LTPopBack(LTNode* phead)//尾删
{
	assert(phead);
	//phead prev(del->prev) del(phead->prev)
	LTNode* del = phead->prev;
	LTNode* prev = del->prev;

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

	free(del);
	del = NULL;
	
}

在这里插入图片描述

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

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

	free(del);
	del = NULL;
}

在这里插入图片描述

//查找
LTNode* LTFind(LTNode* phead,LTDataType x)
{
	assert(phead);
	LTNode* pcur = phead->next;
	while(pcur!= phead)
	{
		if(pcur->data == x)
		{
			return pcur;
		}
		pcur = pcur->next;
	}
	return NULL;
}
void LTInsert(LTNode* pos,LTDataType x)
{
	assert(pos);
	LTNode* newnode = LTBuyNode(x);
	//pos newnode pos->next
	newnode->prev = pos;
	newnode->next = pos->next;

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

在这里插入图片描述

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

	free(pos);
	pos = NULL;
}

在这里插入图片描述

//销毁
void LTDesTroy(LTNode* phead)
{
	assert(phead);
	LTNode* pcur = phead->next;
	while(pcur != phead)
	{
		LTNode* Next = pcur->next;
		free(pcur);
		pcur = Next;
	}
	free(phead);//需要手动将哨兵位置空
	phead = pcur = NULL;
}

text.c

#include"List.h"

void ListTest01()
{
	//创建双向链表
	/*LTNode* plist = NULL;
	LTInit(&plist);*/

	LTNode* plist = LTInit();

	LTPushBack(plist, 1);
	LTPushBack(plist, 2);
	LTPushBack(plist, 3);
	LTPushBack(plist, 4);
	LTPrint(plist);

	/*LTPopBack(plist);
	LTPrint(plist);
	LTPopBack(plist);
	LTPrint(plist);
	LTPopBack(plist);
	LTPrint(plist);
	LTPopBack(plist);
	LTPrint(plist);*/


	LTPopFront(plist);
	LTPrint(plist);
	LTPopFront(plist);
	LTPrint(plist);
	LTPrint(plist);
	LTPrint(plist);
	LTPrint(plist);


	/*LTPushFront(plist, 1);
	LTPrint(plist);
	LTPushFront(plist, 2);
	LTPrint(plist);
	LTPushFront(plist, 3);
	LTPrint(plist);
	LTPushFront(plist, 4);
	LTPrint(plist);*/

	LTNode* pos = LTFind(plist, 1);
	/*if (pos == NULL)
	{
		printf("没有找到!\n");
	}
	else
	{
		printf("找到了!\n");
	}*/


	/*LTInsert(pos, 11);
	LTPrint(plist);*/

	/*LTErase(pos);
	LTPrint(plist);*/

	LTDesTroy(&plist);
	LTDesTroy(plist);
	plist = NULL;
}

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

四、双链表的优缺点

优点

  • 双向遍历:每个结点都有两个指针,一个指向前一个结点,另一个指向下一个结点,这使得从任一结点都可以方便地向前或向后遍历链表
  • 插入和删除操作更灵活:在双链表中,删除一个结点时,只需要更改前后结点地指针,不需要从头遍历链表来找前一个结点,这样提高了效率
  • 支持更复杂的数据结构:双链表适合实现一些更复杂的数据结构,例如双向队列,因为它们需要频繁地在两端插入和删除结点
  • 实现回退功能:在某些应用中,双链表可以更容易地实现向前和向后导航功能

缺点

  • 空间开销大:每个结点需要额外的存储空间来存储指向前一个结点的指针,这导致比单链表占用更多的空间
  • 实现复杂性:由于需要管理两个指针,双链表的实现和维护相对较复杂,容易出现指针错误或内存泄漏的问题
  • 缓存局部性差:双链表的结点在内存中可能不连续分布,这会导致较差的缓存性能,相比于数组和单链表,访问效率可能降低
  • 操作开销:尽管插入和删除操作在已知结点时是O(1),但是在某些情况下(查找结点),整体操作可能会变得不如单链表高效,因为需要两次遍历,来找特定结点

五、双链表 VS 单链表

在这里插入图片描述

后记

双链表的知识就先在此告一段落啦,之后还会有相关知识随机掉落~
接下来就是对栈,队,堆 等知识的展开和了解啦
大家可以持续关注 会稳定更新~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值