【数据结构】双向链表 超详细 (含:何时用一级指针或二级指针;指针域的指针是否要释放)

这篇具有很好参考价值的文章主要介绍了【数据结构】双向链表 超详细 (含:何时用一级指针或二级指针;指针域的指针是否要释放)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

目录

一、简介

二. 双链表的实现

1.准备工作及其注意事项

1.1 先创建三个文件

1.2 注意事项:帮助高效记忆

1.3   关于什么时候 用 一级指针接收,什么时候用 二级指针接收?

1.4 释放节点时,要将节点地址 置为NULL,难道 节点内部的 指针域的指针 就不用置为 NULL吗? 

2.双链表的基本功能接口

2.1 初始化哨兵位

 2.2 链表的创建新节点接口

2.3 打印

3. 插入 接口

3.1 尾插法

3.2 头插法

3.3 在 pos 位置之后插入数据

4. 查找

5.删除  接口

5.1 尾删法

5.2 头删法

5.3  删除 pos 位置的数据

6. 销毁链表 接口

6.1 二级指针版 

6.2 一级指针版

7. 总代码概览

List.h

List.c

test.c

三. 顺序表和双向链表的优缺点分析


二级指针和双向链表,c语言,visualstudio,数据结构,链表


一、简介

        单向链表在解决那些需要大量查找前趋节点的问题时,效率较为低下,因为单向链表适合“从前往后”查找,并不适合“从后往前”查找。

(若想要看 单链表,可以点击跳转:【数据结构】单向链表实现 超详细)

如果要提高链表的查找效率,那  双向链表(双链表)无疑是首选。

双向链表:即为 字面上的意思是“双向”的链表,如下图所示。

二级指针和双向链表,c语言,visualstudio,数据结构,链表
       

  • 双向 指各个节点之间的逻辑关系是 双向的,该链表通常 有一个头节点 -- 称为 哨兵位
  • 概念: 当链表中只有哨兵位节点的时候,我们称该链表为空链表,即哨兵位是不能删除的
  • 从上图还可以看出,双向链表中每个节点包括一下3个部分,分别是:
  • 指针域(前驱节点 prev )、
  • 数据域(用于存储数据元素 data)
  • 指针域(后继节点 next)。

二. 双链表的实现

1.准备工作及其注意事项

1.1 先创建三个文件

二级指针和双向链表,c语言,visualstudio,数据结构,链表

 解释这三个文件的作用
 1、头文件List.h  是来声明接口函数,定义链表,将几个公共用到的库函数集合起来
 2、源文件List.c  是用来具体实现接口
 3、源文件test.c  用于接口的测试工作 ,即具体的使用场景

1.2 注意事项:帮助高效记忆

1. 传递指针,都要断言 不能为 NULL ,指针不能为空:assert(phead);
2. 存在 关系到  前趋节点prev 或 后继节点next  的 情况,

    可以 直接定义变量 Prev = .... Next = ....   便于 理清思路,不易乱
3. 所有的 删除接口 都需要 断言链表不能只有 哨兵位: assert(phead->next != phead); 
4. 尾删法: 记得双链表的找尾 不像 单链表需要循环去找尾!!ptail = phead->prev;

5. 初始化创建头节点:推荐使用 调用 创建节点接口  的 方法
5. 销毁接口:推荐使用 传一级指针的方法

1.3   关于什么时候 用 一级指针接收,什么时候用 二级指针接收?(不看水话,可以直接 看下面 总结 部分

(有点水话,实在不明白思路的可以看一下详细解说的 ”水话“)

         在 单向链表 中,有一个指向 第一个节点的指针 plist,由于 头插法等操作,可能会改变 第一个节点,则 plist 要对应进行 更新,而 要想直接改变一个变量(plist是指针变量)的值,需要传地址,plist 的 &plist 是 一级指针的地址,就要用 二级指针 来接收

         在 双向链表 中,存在 头节点 head ,即 哨兵位,哨兵是 不用进行 改变本身这个节点的 地址的!!

那就有铁铁要问了,不是还要改变 头节点 head( 哨兵位 ) 的指向,要指向 第一个 节点 或 尾节点 吗? 

回答:因为 我们 要改变的 是 双链表节点 结构 中 的 结构体成员变量prev 和 next ,改变 结构体的成员变量 只需要利用 结构体指针 p->prev = .... 或 p->next   = .... 就达到 修改 双链表节点指向 问题了,而你本身并不需要改变 一个节点的地址 p

总结 

总结:修改 双链表节点 的指向,是通过  修改节点结构体内部的 两个成员变量 来实现的只需要用到 结构体指针(即该节点的地址 p),找到  两个成员变量,即可完成修改,因而传递 一级指针就好,不用像 单链表那样还要 传递 一级指针的 地址

1.4 释放节点时,要将节点地址 置为NULL,难道 节点内部的 指针域的指针 就不用置为 NULL吗? 

回答:不用 因为节点的空间已经还给操作系统了 那个指针域的指针所占据的空间也还回去了 操作系统后续分配内存就会把那块空间覆盖掉 就不会又啥影响

2.双链表的基本功能接口

2.1 初始化哨兵位

初始化哨兵位 第一种方法:传入 指针,进行"加工" 成 哨兵位

// 初始化一个哨兵位:
void LTInit(LTNode** pphead)
{
	*pphead = (LTNode*)malloc(sizeof(LTNode));
	// 开辟空间是否成功的判断:一般malloc不会失败,失败证明内存不够了,写下面的证明算是好习惯,不写一般没问题
	if (*pphead == NULL) {
		perror("malloc fail!");
		exit(1);
	}
	(*pphead)->data = -1;
	(*pphead)->next = (*pphead)->prev = *pphead; // 哨兵位初识化,两个指针都指向自己
}

初始化哨兵位第二种方法:直接一个函数生成一个哨兵位,返回哨兵位就好,不用传指针

LTNode* LTInit()
{
	LTNode* phead = (LTNode*)malloc(sizeof(LTNode));
	// 开辟空间是否成功的判断
	if (phead == NULL) {
		perror("malloc fail!");
		exit(1);
	}
	phead->data = -1; // data 随便定,反正哨兵位data无效
	phead->next = phead->prev = phead;
	return phead;
}

初始化哨兵位 第二种方法的2.0 版本:因为哨兵位的初始化 和 2.2 创建新新节点的方法一样,可以合并调用


LTNode* LTInit_2()
{
	LTNode* phead = LTCreatNode(-1);
	return phead;
}

 2.2 链表的创建新节点接口

// 创建新节点
LTNode* LTCreatNode(LTDataType x)
{
	LTNode* newNode = (LTNode*)malloc(sizeof (LTNode));
	if (newNode == NULL)
	{
		perror("malloc fail!");
		exit(1);
	}
	newNode->data = x;
	newNode->prev = newNode->next = newNode; // 都是 指向自己
	return newNode;
}

2.3 打印

双链表的打印
和 单链表打印一样,都需要循环,但是结束条件不一样
单链表以 pcur = NULL 为结束条件,双链表是 一种循环链表,头尾相连,不会有  pcur = NULL 的情况
正解:既然 哨兵位无有效数据,同时 循环一轮 还是 回到头(哨兵位),干脆:while (pcur != phead)


void LTPrint(LTNode* phead)
{
	assert(phead);// 哨兵位不能为空
	LTNode* pcur = phead->next;
	while (pcur != phead)
	{
		printf("%d -> ", pcur->data);
		pcur = pcur->next;
	}
	printf("\n");
}

3. 插入 接口

前言:

// 不需要改变哨兵位,则不需要传二级指针
// 如果需要修改哨兵位的话,则传二级指针

3.1 尾插法

二级指针和双向链表,c语言,visualstudio,数据结构,链表 二级指针和双向链表,c语言,visualstudio,数据结构,链表 

双链表的 尾插法
双向链表尾插需不需要找尾的操作 ?

不需要 :ptail = head->prev;  注意这个 等价关系,便于理解下面的代码
尾插法 也称作 在哨兵位之前插入节点/最后一个有效节点之后插入数据

void LTPushBack(LTNode* phead, LTDataType x)
{
	assert(phead);// 哨兵位不能为空
	LTNode* newNode = LTCreatNode(x); 
	LTNode* ptail = phead->prev;
	// 三者关系:phead->prev(即ptail)    newNode      phead
	// 处理顺序:先 newNode,  再 phead->prev(即ptail) ,最后  phead:否则会乱套
	// 先 newNode
	newNode->next = phead;
	newNode->prev = ptail; // 就是  phead->prev
	// 再尾节点 ptail = head->prev  ;   ptail  -> next = head-> prev -> next;
	ptail->next = newNode;
	// 最后头节点 
	phead->prev = newNode; 
}

3.2 头插法

双链表的 头插法
注意:头插 是在第一个有效位置进行插入,不是在哨兵位 前面,由于 双链表的循环成环状的特性,若在哨兵位前面,就是尾插了

二级指针和双向链表,c语言,visualstudio,数据结构,链表二级指针和双向链表,c语言,visualstudio,数据结构,链表 

void LTPushFront(LTNode* phead, LTDataType x)
{
	assert(phead);
	LTNode* newNode = LTCreatNode(x);
	// 三者关系:phead   newNode   phead->next (即 第一个 有效节点,命名为Next) ;
	// 处理顺序:先 newNode,  再 phead->next(即 第一个 有效节点,命名为Next) ,最后  phead:否则会乱套
	LTNode* Next = phead->next; // 这里就是定义了个变量,便于梳理
	// 先 newNode
	newNode->next = Next;
	newNode->prev = phead;
	// 再 phead->next(即 第一个 有效节点) 
	Next->prev = newNode;
	// 最后头节点 
	phead->next = newNode;
}

3.3 在 pos 位置之后插入数据

在 pos 位置之后插入数据:要配合   4.查找   接口实现

和 头插法思路一样:只是将 哨兵的带头作用 换成了 pos

pos 是通过  4.查找  的接口找的

void LTInsert(LTNode* pos, LTDataType x)
{
	assert(pos);
	// 先创建新节点
	LTNode* newNode = LTCreatNode(x);
	// 关系三个节点:pos     newNode    pos->next
	// 先处理newNode,后 pos->next , 最后 pos 
	// 注意三者的执行顺序 不能换!! 
	LTNode* Next = pos->next; // 这里将 pos 的下一个节点(pos->next) 命名成 Next (避免和 next 混淆)
	newNode->next = Next;
	newNode->prev = pos;
	Next->prev = newNode;
	pos->next = newNode;
}

4. 查找

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;
}

5.删除  接口

5.1 尾删法

// 思路:让 倒数第二个 节点 next 指向 head ,head 指向 倒数第二个节点,最后 free 掉 ptail
// 尾节点前一个节点:ptail->prev = phead->prev->prev
// 尾节点:ptail = phead->prev
// "尾节点前一个节点" 指向 head哨兵位:ptail->prev->next = phead;
// head哨兵位 指向 "尾节点前一个节点" :phead->prev = ptail->prev;
// 最后 free 掉 ptail

二级指针和双向链表,c语言,visualstudio,数据结构,链表 二级指针和双向链表,c语言,visualstudio,数据结构,链表

// 尾删
void LTPopBack(LTNode* phead)
{
	assert(phead);
	//链表为空:只有一个哨兵位节点
	assert(phead->next != phead);
	
	LTNode* ptail = phead->prev;
	ptail->prev->next = phead;
	phead->prev = ptail->prev; 
	free(ptail);
	ptail = NULL;
}

5.2 头删法

    //  被删除的 第一个有效节点:pFirst = phead->next; 

    //  下一个节点 pSecond = pFirst->next;
    //  先 下一个节点 指向 head : pSecond->prev = phead;
    //  后 head 指向 下一个节点 :phead->next = pSecond; 
    //  注意:因为对于循环链表来说, 第一个节点的下个节点 也可以是 哨兵位,所以 只存在一个有效节点,也是可以直接删除第一个节点的、

二级指针和双向链表,c语言,visualstudio,数据结构,链表

二级指针和双向链表,c语言,visualstudio,数据结构,链表

// 头删
// 删除的是 第一个有效节点
void LTPopFront(LTNode* phead)
{
	assert(phead); // 地址不能为NULL
	assert(phead->next != phead); // 链表不能只有 哨兵位

	LTNode* pFirst = phead->next;  // 要被删除的 第一个节点
	LTNode* pSecond = pFirst->next;
	pSecond->prev = phead;
	phead->next = pSecond;
	free(pFirst);
	pFirst = NULL;
}

5.3  删除 pos 位置的数据

删除 pos 位置的数据:要配合   4.查找   接口实现

void LTErase(LTNode* pos)
{
    assert(pos);
    // 关系三个节点: pos->prev      pos     pos->next     
    LTNode* Prev = pos->prev; //  pos 的前一个 
    LTNode* Next = pos->next; //  pos 的下一个
    Next->prev = Prev;
    Prev->next = Next;
    free(pos);
    pos = NULL;
}

6. 销毁链表 接口

更推荐 一级指针版:手动置为NULL

为了保持 接口的一致性:不然前面接口都是 一级指针,到这里突然 二级指针,当程序交给用户时,会增加记忆的成本

6.1 二级指针版 

// 思路:先将 有效节点删除,后删除哨兵位
// 删除有效节点, 要将下个节点保存起来,不然找不到
// 注意:这里 最后需要   改变  哨兵位 为NULL ,因而要传递地址,用二级指针接收
// 否则,传值 只会影响 形参,哨兵位 需要手动 置为NULL


void LTDestory(LTNode** pphead) 
{
	assert(pphead);// 指针本身不能为 空
	assert(*pphead); // 哨兵位 不能为 空
	LTNode* pcur = (*pphead)->next; // *pphead = phead 即哨兵位 ;还有记得 加括号
	while (pcur != *pphead)
	{
		LTNode* next = pcur->next;
		free(pcur);
		pcur = next;
	}
	// 最后删除 哨兵位
	free(*pphead);
	*pphead = NULL;
}

6.2 一级指针版

// 双链表的 销毁:一级指针版:哨兵位 需要 手动额外 置为空

void LTDestory(LTNode* phead)
{
    assert(phead);// 哨兵位 不能为 空
    LTNode* pcur = (phead)->next; // *pphead = phead 即哨兵位 ;还有记得 加括号
    while (pcur != phead)
    {
        LTNode* next = pcur->next;
        free(pcur);
        pcur = next;
    }
    // 最后释放掉 形参的指针:这里不是释放 哨兵位
    free(phead);
    phead = NULL;
}

7. 总代码概览

List.h

#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>

// 创建新节点
typedef int  LTDataType;
typedef struct LTistNode
{
	LTDataType data;
	struct LTistNode* prev;
	struct LTistNode* next;
}LTNode;

// 创建新节点
LTNode* LTCreatNode(LTDataType x);

// 初始化:生成哨兵位
LTNode* LTInit();

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

// 尾插法
void LTPushBack(LTNode* phead, LTDataType x);

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

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

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

// 头删法
void LTPopFront(LTNode* phead);

// 删除 pos 位置节点
void LTErase(LTNode* pos);

// 查找
LTNode* LTFind(LTNode* phead, LTDataType x);

// 销毁
void LTDestory(LTNode* phead);

List.c

#include"List.h"

// 创建新节点
LTNode* LTCreatNode(LTDataType x)
{
	LTNode* newNode = (LTNode*)malloc(sizeof(LTNode));
	if (newNode == NULL)
	{
		perror("malloc fail!");
		exit(1);
	}
	newNode->data = x;
	newNode->prev = newNode->next = newNode;
	return newNode;
}

// 初始化:生成哨兵位
LTNode* LTInit()
{
	LTNode* head = LTCreatNode(-1);
	return head;
}

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

// 尾插法
void LTPushBack(LTNode* phead, LTDataType x)
{
	assert(phead);
	// 创建新节点
	LTNode* newNode = LTCreatNode(x);
	LTNode* ptail = phead->prev;
	// 三者关系:ptail   newNode   phead
	newNode->next = phead;
	newNode->prev = ptail;
	ptail->next = newNode;
	phead->prev = newNode;
}

// 头插法
void LTPushFront(LTNode* phead, LTDataType x)
{
	assert(phead);
	// 创建新节点
	LTNode* newNode = LTCreatNode(x);
	// 三者关系:phead   newNode   phead->next (定为Next);
	LTNode* Next = phead->next; // 这里就是定义了个变量,便于梳理
	newNode->next = Next;
	newNode->prev = phead;
	Next->prev = newNode;
	phead->next = newNode;
}

// 查找
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;
}

// 在 pos 之后插入数据:头插法实现逻辑一样
void LTInsert(LTNode* pos, LTDataType x)
{
	assert(pos);
	// 创建新节点
	LTNode* newNode = LTCreatNode(x);
	// 三者关系:pos   newNode   pso->next (定为Next);
	LTNode* Next = pos->next;
	newNode->next = Next;
	newNode->prev = pos;
	Next->prev = newNode;
	pos->next = newNode;
}

// 尾删法:记得双链表的找尾 不像 单链表需要循环去找尾
void LTPopBack(LTNode* phead)
{
	assert(phead);
	assert(phead->next != phead);// 链表不能只有 哨兵位 (删除的接口 都要断言这条)
	// 三者关系:ptail->prev   ptail   head
	LTNode* ptail = phead->prev;
	ptail->prev->next = phead;
	phead->prev = ptail->prev;
	free(ptail);
	ptail = NULL;

}

// 头删法
void LTPopFront(LTNode* phead)
{
	assert(phead);
	assert(phead->next != phead); // 链表不能只有 哨兵位 (删除的接口 都要断言这条)
	// 三者关系:phead    phead->next    phead->next->next(命名为pSecond)
	LTNode* pFirst = phead->next;  // 要被删除的 第一个节点  一定要先保存下来!!
	LTNode* pSecond = pFirst->next;
	pSecond->prev = phead;
	phead->next = pSecond;
	free(pFirst);
	pFirst = NULL;
}

// 删除 pos 位置节点
void LTErase(LTNode* pos)
{
	assert(pos);
	// 关系三个节点:pos->prev    pos     pos->next
	LTNode* Prev = pos->prev; //  pos 的前一个 
	LTNode* Next = pos->next; //  pos 的下一个
	Next->prev = Prev;
	Prev->next = Next;
	free(pos);
	pos = NULL;
}


// 销毁
void LTDestory(LTNode* phead)
{
	assert(phead);
	// 先全部删除有效节点,后删除头节点
	LTNode* pcur = phead->next;
	while (pcur != phead)
	{
		LTNode* Next = pcur->next;
		free(pcur);
		pcur = Next;
	}
	// 最后释放掉 形参的指针:这里不是释放 哨兵位
	free(phead);
	phead = NULL;
}

test.c

#include"List.h"

void LTTest()
{
	// 创建哨兵位
	LTNode* head = LTInit();  // 这里直接用 head 代表哨兵位:更直观一点,和 前面讲解用的 plist 是一样的

	// 尾插法
	LTPushBack(head, 1);
	LTPushBack(head, 2);
	LTPushBack(head, 3);
	LTPushBack(head, 4); // 1 -> 2 -> 3 -> 4 ->
	printf("测试尾插:");
	LTPrint(head);

	// 头插法
	LTPushFront(head, 5);
	LTPushFront(head, 6);// 6 -> 5 -> 1 -> 2 -> 3 -> 4 ->
	printf("测试头插:");
	LTPrint(head);

	// 查找
	//LTFind(head, 1);

	// 在 pos 之后插入数据:头插法实现逻辑一样
	LTNode* FindRet1 = LTFind(head, 1);
	LTInsert(FindRet1, 100);
	LTNode* FindRet2 = LTFind(head, 2);
	LTInsert(FindRet2, 200); // 6 -> 5 -> 1 -> 100 -> 2 -> 200 -> 3 -> 4 ->
	printf("测试pos 之后插入:");
	LTPrint(head);

	// 头删法
	LTPopFront(head);
	LTPopFront(head); // 1 -> 100 -> 2 -> 200 -> 3 -> 4 ->
	printf("测试头删:");
	LTPrint(head);

	// 尾删法:记得双链表的找尾 不像 单链表需要循环去找尾
	LTPopBack(head);
	LTPopBack(head); // 1 -> 100 -> 2 -> 200 ->
	printf("测试尾删:");
	LTPrint(head);

	// 删除 pos 位置节点
	LTNode* FindRet3 = LTFind(head, 1);
	LTErase(FindRet3); // 100 -> 2 -> 200 ->
	printf("测试删除 pos 位置节点:");
	LTPrint(head);

	// 双链表的 销毁:这里就不演示销毁了
	//LTDesTroy(head);
	//head= NULL;  //哨兵位 需要手动 置为NULL
}
int main()
{
	LTTest();
	return 0;
}

三. 顺序表和双向链表的优缺点分析

不同点

顺序表

链表(单链表)

存储空间

物理上⼀定连续

逻辑连续,但物理上不⼀定连续

机访问

支持O(1)

不支持:O(N)

任意位置插或者删除

可能要搬移素,率低O(N)

指针指向

插入

动态顺序表,空间够时要扩容

没有容的概念

应用场景

素高存储+频繁访问

任意位置插和删除频繁

完。

若上述文章有什么错误,欢迎各位大佬及时指出,我们共同进步!文章来源地址https://www.toymoban.com/news/detail-829173.html

到了这里,关于【数据结构】双向链表 超详细 (含:何时用一级指针或二级指针;指针域的指针是否要释放)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【数据结构与算法】 - 双向链表 - 详细实现思路及代码

    前几篇文章介绍了怎样去实现单链表、单循环链表, 这篇文章主要介绍 双向链表 以及实现双向链表的步骤,最后提供我自己根据理解实现双向链表的C语言代码 。跟着后面实现思路看下去,应该可以看懂代码,看懂代码后,就对双向链表有了比较抽象的理解了,最后自己再

    2024年02月01日
    浏览(28)
  • 数据结构-链表结构-双向链表

    双向链表也叫双链表,与单向链表不同的是,每一个节点有三个区域组成:两个指针域,一个数据域 前一个指针域:存储前驱节点的内存地址 后一个指针域:存储后继节点的内存地址 数据域:存储节点数据 以下就是双向链表的最基本单位 节点的前指针域指向前驱,后指针

    2024年02月04日
    浏览(36)
  • 【数据结构】双向奔赴的爱恋 --- 双向链表

    关注小庄 顿顿解馋๑ᵒᯅᵒ๑ 引言:上回我们讲解了单链表(单向不循环不带头链表),我们可以发现他是存在一定缺陷的,比如尾删的时候需要遍历一遍链表,这会大大降低我们的性能,再比如对于链表中的一个结点我们是无法直接访问它的上一个结点,那有什么解决方法呢

    2024年04月08日
    浏览(84)
  • 数据结构—双向链表

    目录 1.  链表的种类 2.  最实用的两种链表类型 3.  实现双向带头循环链表                   3.1 创建头节点         3.2 实现双向循环功能—返回头指针         3.3  尾插           3.4 头插         3.5 尾删         3.6 头删 4.  实现两个重要接口函数  

    2023年04月23日
    浏览(53)
  • 数据结构——双向链表

    🍇系列专栏:🌙数据结构 🍉  欢迎关注:👍点赞🍃收藏🔥留言 🍎 博客主页:🌙_麦麦_的博客_CSDN博客-领域博主 🌙如果我们都不能够拥有黑夜,又该怎样去仰望星空?   目录 一、前言 二、正文——双向链表的实现 2.1模块化 2.2 数据类型与结构体定义  2.3链表的初始化

    2024年02月02日
    浏览(35)
  • 数据结构-双向链表

    在单链表那一篇博客中介绍了单链表和双向链表的优缺点,所以此篇博客直接分享怎样实现一个带头双向循环链表。 单链表博客: 首先我们需要写一个结构体,双向带头链表的话需要一个前驱指针prev和一个后驱指针next,前驱指针的作用是方便找尾节点,因为头节点的prev指

    2024年02月05日
    浏览(34)
  • 数据结构双向链表

    Hello,好久不见,今天我们讲链表的双向链表,这是一个很厉害的链表,带头双向且循环,学了这个链表,你会发现顺序表的头插头删不再是一个麻烦问题,单链表的尾插尾删也变得简单起来了,那废话不多说,让我们开始我们的学习吧! 首先我们要了解它的物理和逻辑结构

    2024年02月11日
    浏览(32)
  • 数据结构 - 双向链表

    文章目录 目录 文章目录 前言 一、什么是双向链表? 双向链表有什么优势? 二、双向链表的设计和实现 1.设计思想 尾增 : 在链表的末尾添加新的元素  头插 : 在链表头部插入节点  删除 : 根据val的值删除节点  查找 : 根据索引的值查找并返回节点 总结 大家好,今天给大家讲解

    2024年02月09日
    浏览(26)
  • 数据结构---双向链表

    单向链表:一块内存指向下一个内存。 单链表存在一些缺陷: 1.查找速度慢。 2.不能从后往前找。 3.找不到前驱。 链表的结构分为8种: 1.单向和双向 2.带头和不带头 带头的链表有一个带哨兵位的头结点,这个节点不存储有效数据。 好处 :尾插更方便,不需要二级指针了,

    2024年02月02日
    浏览(38)
  • 数据结构——实现双向链表

    怎么说呢?光乍一听名字好像很难的样子是吧,那如果你这样认为的话,可就要让你大跌眼镜了哦,其实双向带头循环链表从操作和理解上来说都是要易于单项不带头不循环链表(俗称单链表)的。 咱们就来见识见识吧!希望真的能让你们“大跌眼镜”哈! 双向带头循环链

    2024年02月07日
    浏览(43)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包