数据结构之“双向链表”

2024-09-06 18:04
文章标签 链表 数据结构 双向

本文主要是介绍数据结构之“双向链表”,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

文章目录

  • 1. 链表的分类
  • 2.双向链表
    • 2.2 双向链表的打印
  • 3.双向链表的插入/删除
    • 3.1双向链表的尾插
    • 3.2双线链表的头插
    • 3.3双向链表的尾删
    • 3.4双线链表的头删
  • 4. 在pos位置之后插入结点
  • 5. 删除pos指定位置的结点
  • 6. 双向链表的销毁
  • 7.比较

1. 链表的分类

链表的种类有(2x2x2)种,带头/不带头;单向/双向;循环/不循环。

带头/不带头
在之前学习单链表(不带头单向不循环链表)时,它是不带头的,它的第一个结点是可以变的,比如: 我们还可以将第一个结点删除;头插一个数据,等等
但是带头的链表,它的第一个结点(头结点)存储的不是有效数据,其余结点存储的是有效数据。头结点俗称为“哨兵位”,它只是用来占位子的。

在这里插入图片描述

单向/双向
单向:只能通过前一个的next找到下一个结点,不能通过下一个结点找到前一个结点的地址。
双向:通过前一个可找到后一个,通过后一个可找到前一个

在这里插入图片描述

循环/不循环
在这里插入图片描述

2.双向链表

1.全称是:带头双向循环链表
2.双向链表的结点结构:数据+下一个结点的地址next+上一个结点的地址prev

typedef int DataType;//双像链表的定义
typedef struct ListNode
{DataType val;struct ListNode* next;struct ListNode* prev;
};

3.双向链表在使用之前就要有头结点(哨兵位)。(初始化创建一个头结点)
记得:双向链表是:带头双向循环链表,要循环!!!那必定next和prev不可能指向空,而是指向它自己

//LTNode.c里的内容LTNode* LTBuynode(DataType x)
{LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));if (newnode == NULL){perror("malloc fail!");exit(1);}newnode->data = x;newnode->next = newnode->prev = newnode; //上/下结点地址都是自己,达到循环的目的return newnode;
}void LTInit(LTNode** pphead)
{//创建一个头结点*pphead = LTBuynode(-1);
}

在这里插入图片描述

4.双向链表在插入新结点的时候,不需要判断链表有没有头结点。双向链表一定有头结点(哨兵位)。
5.双向链表为空:只有哨兵位。
6.记得写#include<malloc.h> #include<stdlib.h>

2.2 双向链表的打印

记得注意循环打印链表时,什么时候停止打印

void LTPrint(LTNode* phead)
{LTNode* pcur = phead->next; //pcur等于谁呢?不要写成哨兵位了,要写成第一个有效结点while (pcur!=phead)   //双向链表打印到什么时候停止,当pcur==phead时{printf("%d(%p) ->", pcur->val, pcur);pcur = pcur->next;}printf("NULL\n");
}

3.双向链表的插入/删除

双向链表插入或者删除,传过去的都是一级指针。因为phead是头结点,也就是哨兵位。哨兵位是不能被改变的,所以传一级指针。

3.1双向链表的尾插

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

3.2双线链表的头插

重点:头插是指将新数据成为第一个有效结点,而不是换掉“哨兵位”。

在这里插入图片描述
在这里插入图片描述

void LTPushFront(LTNode* phead, DataType x)
{assert(phead);LTNode* newnode = LTBuynode(x);newnode->prev = phead;newnode->next = phead->next;phead->next->prev = newnode;phead->next = newnode;
}

3.3双向链表的尾删

如果双向链表里,只有一个哨兵位,也是不可以删除的。所以要对链表进行判断,是否为空(空就是:phead->next=phead; phead->prev=phead;)。

判断用bool,记得包含头文件#include<stdbool.h>

bool LTempty(LTNode* phead)
{//传进来的哨兵位不能为空assert(phead);return phead->next == phead;//如果相等,则是return true
}
void LTPopBack(LTNode* phead)
{assert(phead);assert(!LTempty(phead));  //若链表不为空,则LTempty(phead)返回的是false,!LTempty(phead)则是trueLTNode* del = phead->prev;del->prev->next = phead;phead->prev = del->prev;free(del);del->val = 0;del->next = del->prev = NULL;
}

3.4双线链表的头删

bool LTempty(LTNode* phead)
{//传进来的哨兵位不能为空assert(phead);return phead->next == phead;//如果相等,则是return true
}
void LTPopFront(LTNode* phead)
{assert(phead);assert(!LTempty(phead));  //若链表不为空,则LTempty(phead)返回的是false,!LTempty(phead)则是trueLTNode* del = phead->next;LTNode* Next = del->next;phead->next = Next;Next->prev = phead;free(del);del->val = 0;del->next = del->prev = NULL;
}

4. 在pos位置之后插入结点

不需要头结点,只需要pos

LTNode* LTFind(LTNode* phead, DataType x)
{assert(phead);LTNode* pcur = phead->next; //要让pcur是第一个有效结点while (pcur != phead){if (pcur->val == x)return pcur;pcur = pcur->next;}
}
void LTInsert(LTNode* pos, DataType x)
{assert(pos);LTNode* newnode = LTBuynode(x); //插入的数据LTNode* Next = pos->next;newnode->prev = pos;newnode->next = Next;pos->next = newnode;Next->prev = newnode;
}

在这里插入图片描述

5. 删除pos指定位置的结点

LTNode* LTFind(LTNode* phead, DataType x)
{assert(phead);LTNode* pcur = phead->next; //要让pcur是第一个有效结点while (pcur != phead){if (pcur->val == x)return pcur;pcur = pcur->next;}
}
void LTErase(LTNode* pos)
{assert(pos);pos->prev->next = pos->next;pos->next->prev = pos->prev;free(pos);pos->val = 0;pos->prev = pos->next = NULL;
}

6. 双向链表的销毁

循环,将每一个结点都销毁(先保存pcur->next,防止pcur销毁之后,找不到下一个结点)。最后将哨兵位也销毁。

传二级指针

void LTDestroy(LTNode** pphead)
{assert(pphead && *pphead);LTNode* pcur = (*pphead)->next;while (pcur != (*pphead)){LTNode* Next = pcur->next;free(pcur);pcur = NULL;pcur = Next;}free(*pphead);*pphead = NULL;
}

7.比较

在这里插入图片描述

这篇关于数据结构之“双向链表”的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1142734

相关文章

【数据结构】——原来排序算法搞懂这些就行,轻松拿捏

前言:快速排序的实现最重要的是找基准值,下面让我们来了解如何实现找基准值 基准值的注释:在快排的过程中,每一次我们要取一个元素作为枢纽值,以这个数字来将序列划分为两部分。 在此我们采用三数取中法,也就是取左端、中间、右端三个数,然后进行排序,将中间数作为枢纽值。 快速排序实现主框架: //快速排序 void QuickSort(int* arr, int left, int rig

csu1329(双向链表)

题意:给n个盒子,编号为1到n,四个操作:1、将x盒子移到y的左边;2、将x盒子移到y的右边;3、交换x和y盒子的位置;4、将所有的盒子反过来放。 思路分析:用双向链表解决。每个操作的时间复杂度为O(1),用数组来模拟链表,下面的代码是参考刘老师的标程写的。 代码如下: #include<iostream>#include<algorithm>#include<stdio.h>#

6.1.数据结构-c/c++堆详解下篇(堆排序,TopK问题)

上篇:6.1.数据结构-c/c++模拟实现堆上篇(向下,上调整算法,建堆,增删数据)-CSDN博客 本章重点 1.使用堆来完成堆排序 2.使用堆解决TopK问题 目录 一.堆排序 1.1 思路 1.2 代码 1.3 简单测试 二.TopK问题 2.1 思路(求最小): 2.2 C语言代码(手写堆) 2.3 C++代码(使用优先级队列 priority_queue)

深入手撕链表

链表 分类概念单链表增尾插头插插入 删尾删头删删除 查完整实现带头不带头 双向链表初始化增尾插头插插入 删查完整代码 数组 分类 #mermaid-svg-qKD178fTiiaYeKjl {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-

建立升序链表

题目1181:遍历链表 时间限制:1 秒 内存限制:32 兆 特殊判题:否 提交:2744 解决:1186 题目描述: 建立一个升序链表并遍历输出。 输入: 输入的每个案例中第一行包括1个整数:n(1<=n<=1000),接下来的一行包括n个整数。 输出: 可能有多组测试数据,对于每组数据, 将n个整数建立升序链表,之后遍历链表并输出。 样例输

《数据结构(C语言版)第二版》第八章-排序(8.3-交换排序、8.4-选择排序)

8.3 交换排序 8.3.1 冒泡排序 【算法特点】 (1) 稳定排序。 (2) 可用于链式存储结构。 (3) 移动记录次数较多,算法平均时间性能比直接插入排序差。当初始记录无序,n较大时, 此算法不宜采用。 #include <stdio.h>#include <stdlib.h>#define MAXSIZE 26typedef int KeyType;typedef char In

【每日一题】LeetCode 2181.合并零之间的节点(链表、模拟)

【每日一题】LeetCode 2181.合并零之间的节点(链表、模拟) 题目描述 给定一个链表,链表中的每个节点代表一个整数。链表中的整数由 0 分隔开,表示不同的区间。链表的开始和结束节点的值都为 0。任务是将每两个相邻的 0 之间的所有节点合并成一个节点,新节点的值为原区间内所有节点值的和。合并后,需要移除所有的 0,并返回修改后的链表头节点。 思路分析 初始化:创建一个虚拟头节点

学习记录:js算法(二十八):删除排序链表中的重复元素、删除排序链表中的重复元素II

文章目录 删除排序链表中的重复元素我的思路解法一:循环解法二:递归 网上思路 删除排序链表中的重复元素 II我的思路网上思路 总结 删除排序链表中的重复元素 给定一个已排序的链表的头 head , 删除所有重复的元素,使每个元素只出现一次 。返回 已排序的链表 。 图一 图二 示例 1:(图一)输入:head = [1,1,2]输出:[1,2]示例 2:(图

【408数据结构】散列 (哈希)知识点集合复习考点题目

苏泽  “弃工从研”的路上很孤独,于是我记下了些许笔记相伴,希望能够帮助到大家    知识点 1. 散列查找 散列查找是一种高效的查找方法,它通过散列函数将关键字映射到数组的一个位置,从而实现快速查找。这种方法的时间复杂度平均为(

浙大数据结构:树的定义与操作

四种遍历 #include<iostream>#include<queue>using namespace std;typedef struct treenode *BinTree;typedef BinTree position;typedef int ElementType;struct treenode{ElementType data;BinTree left;BinTre