双向,带头节点,带环链表的基本操作

2023-10-29 13:58

本文主要是介绍双向,带头节点,带环链表的基本操作,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

        前面已经介绍过了不带头节点,不带环的双向链表,以下将介绍带头节点,带环的双向链表的基本操作。

        不带头结点时,用一个头指针代表整个链表。带头节点,则用头结点来表示整个链表。此时,头结点的数据域是没有意义的,对其任意赋值即可。如下图:


        当链表是单向时,只有一个next指针指向下一个节点。而双向时,除有next指向下一个节点外,还有一个prev指针指向前一个结点。

        当链表不带环时,链表的最后一个元素的next域为空,而带环时,链表的最后一个结点的next域要指向头结点,而头结点的prev域要指向链表的最后一个节点。这样,整个链表就呈一个环状。

       通过上述分析,便可知道,当双向链表为空时,头结点的next域和prev域均指向头结点。

1. 链表的结点结构

        上述分析已经知道,双向链表的一个节点包括三部分:数据域,next域,prev域,所以,结点结构定义如下:

typedef char DlinklistType;//将节点的数据域类型统一表示                                                                               //定义双向链表的节点结构
typedef struct DlinkNode
{DlinklistType data;//数据域struct DlinkNode* next;//指向下一个节点struct DlinkNode* prev;//指向上一个节点
}DlinkNode;

2. 双向链表的初始化

        因为该链表是带头节点的,所以初始化时,要创建一个头结点来表示整个链表,此时,还没有真正的节点,所以链表为空,所以将头结点的next指针和prev指针均指向自己,数据任意赋值。

        因为初始化时,要使头指针指向头结点,即要改变头指针的指向,所以,在函数传参时,传的是头指针的地址,即二级指针(在测试函数中定义的是头指针,即DlinkNode*类型的变量)。

代码如下:

//创建结点
DlinkNode* CreateNode(DlinklistType value)
{DlinkNode* new_node = (DlinkNode*)malloc(sizeof(DlinkNode));new_node->data = value;new_node->next = new_node;//初始时,使节点的两个指针域均指向自身                                                                  new_node->prev = new_node;return new_node;
}//初始化双向链表
void InitDlinklist(DlinkNode** phead)
{if(phead == NULL){   //非法输入return;}   //申请头结点,使头指针指向新节点*phead = CreateNode(0);return;
}

         因为再对头指针初始化后,头指针始终指向头结点,对链表进行以下操作(不包括销毁链表)时,改变的是头结点的相关指针域,而未改变头指针的指向,所以,在函数传参时传的均是头指针,即一级指针。

3. 对双向链表进行尾插

        在尾插时,首先要找到尾节点,因为链表是带环的双向链表,所以,可以通过头结点的prev指针找到尾节点。然后在尾节点后面创建一个新节点进行插入。

         在插入时,需要改变四个指针的指向,两两一组,分别为:

(1)原尾节点的next域要指向新节点,新节点的prev要指向原尾节点;

(2)新节点的next域要指向头结点,头结点的prev要指向新节点。

代码如下:

//对双向链表进行尾插
//head为双向链表的头指针,value为要插入的值
void DlinklistPushBack(DlinkNode* head,DlinklistType value)
{if(head == NULL){//非法输入return;}DlinkNode* new_node = CreateNode(value);//创建新节点DlinkNode* tail = head->prev;//找到尾节点//一共要修改四个指针,两两一组进行修改                                                                                            //tail和new_nodetail->next = new_node;new_node->prev = tail;//new_node和headhead->prev = new_node;new_node->next = head;return;
}   

4. 对双向链表进行尾删

        要删除尾节点

(1)首先,要判断链表是否为空。为空的删除失败。链表为空的条件是头结点head满足:

head->next = head  或  head->prev = head

(2)若链表不为空。

        1)首先要根据头结点的prev指针找到尾节点。

        2)再根据尾节点找到它的前一个节点,将找到的前一个节点作为新的尾节点。

        4)此时,只需修改两个指针域:将新的尾节点的next指针指向头结点,头结点的prev指针指向新的尾节点

        5)最后释放原尾节点即可。

代码如下:

//销毁节点
void DestoryNode(DlinkNode* node)
{free(node);return;
}//对双向链表进行尾删
//head为双向链表的头指针,即头结点的地址
void DlinklistPopBack(DlinkNode* head)
{if(head == NULL){//非法输入return;}if(head->next == head){//空链表return;}//根据头节点找到尾节点DlinkNode* to_delete = head->prev;//根据尾节点找到它的前一个节点DlinkNode* prev_node = to_delete->prev;//修改两个指针的指向,使上一个节点作为新的尾节点prev_node->next = head;                                                                                                           head->prev = prev_node;//释放原来的尾节点DestoryNode(to_delete);return;
}

5. 对链表进行头插

        首先要创建一个新节点,然后在头结点之后,真正的第一个节点之前插入该新节点,根据头结点可以找到第一个结点,然后修改这三个结点的相关指针域即可:

        共需修改四个指针,两两为一组:

(1)头结点的next指向新节点,新节点的prev指向头结点

(2)新节点的next指向第一个结点,第一个结点的prev指向新节点。

代码如下:

//头插
//head为双向链表的头指针,value为要插入的值
void DlinklistPushFront(DlinkNode* head,DlinklistType value)
{if(head == NULL)                                                                                                                  {//非法输入return;}//创建新节点DlinkNode* new_node = CreateNode(value);//找到第一个节点DlinkNode* next_node = head->next;//需要修改四个指针的指向//head和new_nodehead->next = new_node;new_node->prev = head;//new_node和next_nodenew_node->next = next_node;next_node->prev = new_node;return;
}

6. 头删链表

(1)首先,判断链表是否为空,为空则删除失败

(2)链表不为空:

        1)根据头结点找到要删除的第一个结点。

        2)使头结点指向要删除结点的下一个节点,使该下一个节点作为新的第一个节点

        3)此时,需要修改两个指针的指向,将头结点的next指向下一个节点,下一个节点的prev指向头结点

        4)最后,释放要删除的结点即可。

代码如下:

//头删,head为双向链表的头指针
void DlinklistPopFront(DlinkNode* head)
{if(head == NULL){//非法输入return;}if(head->next == head){//空链表return;}//根据头节点找到要删除的第一个节点DlinkNode* to_delete = head->next;//根据要删除的节点找到他的下一个节点DlinkNode* next_node = to_delete->next;//只需改变两个指针,将下一个节点作为新的第一个节点head->next = next_node;next_node->prev = head;//释放要删除的节点                                                                                                                DestoryNode(to_delete);return;
}

7. 在指定位置之前插入节点

        要在指定位置之前插入节点

(1)首先,根据指定位置处的节点找到它的前一个节点

(2)然后,将新节点插入到指定结点和它的前一个节点之间。

(3)此时,需要修改四个指针的指向:

        1)是前一个节点的next指向新节点,新节点的prev指向前一个节点

        2)使新节点的next指向指定结点,指定结点的prev指向新节点

代码如下:

//在指定位置pos之前插入节点
void DlinklistInsert(DlinkNode* head,DlinkNode* pos,DlinklistType value)
{if(head == NULL || pos == NULL){                                                                                                                                 //非法输入return;}//根据pos找到他的前一个节点DlinkNode* prev_node = pos->prev;DlinkNode* new_node = CreateNode(value);//创建新节点//prev_node/new_nodeprev_node->next = new_node;new_node->prev = prev_node;//new_node/pos new_node->next = pos;pos->prev = new_node;return;
}

8. 在指定位置之后插入节点

        与上述类似

(1)首先,根据指定位置处的节点找到它的下一个节点

(2)然后,将新节点插入到指定结点和它的下一个节点之间。

(3)此时,需要修改四个指针的指向:

        1)使下一个节点的prev指向新节点,新节点的next指向下一个节点

        2)使新节点的prev指向指定结点,指定结点的next指向新节点

代码如下:

//在指定位置之后插入节点
void DlinklistInsertAfter(DlinkNode* head,DlinkNode* pos,DlinklistType value)
{                                                                                                                                     if(head == NULL || pos == NULL){//非法输入return;}//创建新节点DlinkNode* new_node = CreateNode(value);//根据pos找到他的下一个节点DlinkNode* next_node = pos->next;//pos/new_nodepos->next = new_node;new_node->prev = pos;//new_node/next_nodenew_node->next = next_node;next_node->prev = new_node;return;
}

9. 查找指定值所在的位置

(1)如果链表为空,则查找失败

(2)若链表非空,则对链表从头开始遍历。使指定值和链表的每个节点的数据域进行对比,如果相同,返回该结点的地址,如果不同,指针后移

(3)当链表遍历结束时,还未找到指定值,说明,该值不存在与链表中,则查找失败。注意,链表遍历结束时,即又回到头结点处时。

代码如下:

//查找指定值所在的位置
DlinkNode* DlinklistFind(DlinkNode* head,DlinklistType to_find)
{if(head == NULL){//非法输入return NULL;}if(head->next == head){//空链表return NULL;}//定义cur为当前指针遍历来遍历链表,初始从真正的第一个节点开始DlinkNode* cur = head->next;while(cur != head)//当未遍历完链表时{if(cur->data == to_find)//当找到指定值时{return cur;//返回指定值所在节点的位置}cur = cur->next;//若没找到,继续后移}return NULL;//遍历完链表还没找到                                                                                                  
}

10 . 删除指定位置处的节点

(1)首先要判断链表是否为空,为空在删除失败

(2)判断要删除的位置是不是头结点所在的位置。因为头结点代表着整个链表,除销毁链表外不能对其进行删除

(3)若不为(1)(2),根据指定位置找到它的前一个和后一个节点

(4)修改前,后节点的相应指针:使前一个节点的next指向后一个节点,后一个节点的prev指向前一个节点

(5)最后,释放指定位置处的结点。

代码如下:

//删除指定位置的节点
void DlinklistErase(DlinkNode* head,DlinkNode* pos)
{if(head == NULL || pos == NULL){//非法输入return;}   if(head->next == head){//空链表return;}   if(head == pos){//不能删除头节点return;}   //找到指定位置处的下一个节点DlinkNode* next_node = pos->next;//找到指定位置处的上一个节点DlinkNode* prev_node = pos->prev;//修改相应指针的指向next_node->prev = prev_node;prev_node->next = next_node;//释放指定位置处的节点                                                                                                            DestoryNode(pos);return;
}

11. 按值删除节点

(1)如果链表为空,则删除失败

(2)根据上述的find函数先找到该值所在的位置,如果没找到,则删除失败

(3)若找到了,则根据该位置上述的Erase函数对其进行删除

代码如下:

//按值删除
void DlinklistRemove(DlinkNode* head,DlinklistType to_delete_value)
{if(head == NULL){//非法输入return;}if(head->next == head){//空链表return;}//找到指定值所在的位置DlinkNode* to_delete =  DlinklistFind(head,to_delete_value);if(to_delete == NULL){//没找到该节点return;}//根据指定位置删除节点                                                                                                            DlinklistErase(head,to_delete);return;
}

12. 删除指定值所在的所有节点

        与上述类似

(1)链表为空,删除失败

(2)链表不为空,先找到指定值所在的位置,然后根据位置删除节点

(3)一直循环过程(2),直到找不到指定值所在的位置,说明已经删除指定值处的所有结点

代码如下:

//按值删除所有
void DlinklistRemoveAll(DlinkNode* head,DlinklistType to_delete_value)
{if(head == NULL){//非法输入return;}if(head->next == head){//空链表return;}//根据指定值找所在的位置DlinkNode* to_delete = DlinklistFind(head,to_delete_value);while(to_delete != NULL)//如果一直能找到指定值所在的位置{DlinklistErase(head,to_delete);//则对其进行删除to_delete = DlinklistFind(head,to_delete_value);}return;//此时,所有指定值所在的节点已全部删除                                                                                     
}

13. 销毁链表

        在对链表初始化前,链表只用一个头指针进行表示,所以销毁链表后,即恢复到初始化前的状态。

(1)首先,遍历链表,将链表的结点一个个进行释放

(2)然后,在对头结点进行释放

(3)因为头结点未释放前,由头指针指向。而释放后,头指针指向的是一个无效的 地址,即头指针变成了野指针。所以头结点释放后,要对头指针置空。此时,要改变头指针的指向,所以,函数传参时要传头指针的地址即二级指针。

代码如下:

//销毁链表
void DestoryDlinklist(DlinkNode** phead)
{if(phead == NULL){//非法输入return;}//遍历链表对各节点进行释放DlinkNode* to_delete = (*phead)->next;while(to_delete != *phead){DlinkNode* next_node = to_delete->next;DestoryNode(to_delete);to_delete = next_node;}//释放头节点DestoryNode(*phead);//头指针置空                                                                                                                      *phead = NULL;}

14. 统计双向链表的长度

(1)首先判定链表是否为空,为空,则返回0

(2)然后从第一个结点开始遍历,遍历到一个结点,长度加1,直到回到头结点

代码如下:

//统计双向链表的长度
int DlinklistSize(DlinkNode*head)
{if(head == NULL){   //非法输入return -1; }   if(head->next == head)//该部分代码可包含在下述代码中{   //空链表return 0;}   //从第一个节点开始遍历,遍历一个,长度加1,直到回到头节点                                                                         DlinkNode* cur = head->next;int size = 0;while(cur != head){   size++;cur = cur->next;}   return size;
}

这篇关于双向,带头节点,带环链表的基本操作的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



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

相关文章

【前端学习】AntV G6-08 深入图形与图形分组、自定义节点、节点动画(下)

【课程链接】 AntV G6:深入图形与图形分组、自定义节点、节点动画(下)_哔哩哔哩_bilibili 本章十吾老师讲解了一个复杂的自定义节点中,应该怎样去计算和绘制图形,如何给一个图形制作不间断的动画,以及在鼠标事件之后产生动画。(有点难,需要好好理解) <!DOCTYPE html><html><head><meta charset="UTF-8"><title>06

csu1329(双向链表)

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

day-51 合并零之间的节点

思路 直接遍历链表即可,遇到val=0跳过,val非零则加在一起,最后返回即可 解题过程 返回链表可以有头结点,方便插入,返回head.next Code /*** Definition for singly-linked list.* public class ListNode {* int val;* ListNode next;* ListNode() {}*

深入手撕链表

链表 分类概念单链表增尾插头插插入 删尾删头删删除 查完整实现带头不带头 双向链表初始化增尾插头插插入 删查完整代码 数组 分类 #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个整数建立升序链表,之后遍历链表并输出。 样例输

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

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

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

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

JS和jQuery获取节点的兄弟,父级,子级元素

原文转自http://blog.csdn.net/duanshuyong/article/details/7562423 先说一下JS的获取方法,其要比JQUERY的方法麻烦很多,后面以JQUERY的方法作对比。 JS的方法会比JQUERY麻烦很多,主要则是因为FF浏览器,FF浏览器会把你的换行也当最DOM元素。 <div id="test"><div></div><div></div

驱动(RK3588S)第七课时:单节点设备树

目录 需求一、设备树的概念1、设备树的后缀名:2、设备树的语法格式3、设备树的属性(重要)4、设备树格式举例 二、设备树所用函数1、如何在内核层种获取设备树节点:2、从设备树上获取 gpio 口的属性3、获取节点上的属性只针对于字符串属性的4、函数读取 np 结点中的 propname 属性的值,并将读取到的 u32 类型的值保存在 out_value 指向的内存中,函数的返回值表示读取到的

react笔记 8-19 事件对象、获取dom元素、双向绑定

1、事件对象event 通过事件的event对象获取它的dom元素 run=(event)=>{event.target.style="background:yellowgreen" //event的父级为他本身event.target.getAttribute("aid") //这样便获取到了它的自定义属性aid}render() {return (<div><h2>{