【数据结构】带头双向循环链表的实现(C语言)

简介: 【数据结构】带头双向循环链表的实现(C语言)

前言


之前我们讲了单链表的实现【数据结构】单链表定义的介绍及增删查改的实现,带头双向循环链表就是在单链表的基础之上增加了一些功能使结构更加完善。可以直接用两个字来形容它,就是无敌。

image.png

image.png

增加了头结点使得在插入结点时不再需要对原链表是否为空进行判断,就可以直接插入到头结点之后。增加循环功能实现了链表对前一结点的访问,弥补了单链表只能沿一个方向迭代的缺陷。而增加循环功能则是回避了每次链表访问尾结点都需要遍历一遍链表的弊端。虽然这样的结构看起来可能有些许复杂,但从实际运用上来讲,这个结构确实是无懈可击。


也正是这个结构的优势,在这个链表的实现中不再需要更改头结点,也就是说只要一级指针就可以完成整个链表的实现。

初始化


结点的初始化

image.png

结合上述结构,带头循环双向链表的结点的有一个 prev 指向上一个结点同时有一个 next 指向下一给结点,并在 data 存储数据


先使用malloc开辟结构体的空间,之后对开辟空间成功与否进行判断之后,对其赋值以及指向地址的初始化。 最后返回开辟的地址,完成一个结点的开辟。

image.png

链表的初始化

这是个带头的链表,所以在初始化的时候就要完成头结点的开辟,完成初步的循环。


这样初步循环的意义在于在之后要插入新结点时即便整个链表只有头结点一个但使用的插入方式不用发生变化。即保证循环的进程。

image.png

增删及打印


尾插

这边就可以直接感受到这个结构给我们带来的便利,我们只需对头结点进行断言之后通过头结点找到尾节点,并在二者之间插入新节点。值得注意的是,尾插是在尾结点跟头结点之间插入,而不是在尾结点前插入,要避免出现这样的误区。

image.png

书接上文,想象一下当我们链表中只有一个头结点时,对头结点的上一个结点访问仍然还是头结点,之后头结点的上一个结点为新的这个结点,由于在这之前头结点的上一个结点还是自己,经过链接头结点上一个跟下一个都是新结点,就相当于是头插,这就是前面头结点初始化时要让头结点的上下结点都指向自己的原因。

image.png

尾删

经过结构的升级,原本单链表原本不完善的结构已经完善,尾删一样变得非常简单通过头结点找到尾结点,再寻找到尾结点的上一个结点并将其于头结点链接起来就可以实现。


既然是删除就要保证在链表为空的时候不能继续删除,在这里再用 NULL 判断链表数量已经不管用了,这个链表不断循环根本不会出现空指针,而是通过头结点来判断。即只要链表中至少存在一个非头结点的结点,头指针的下一个结点便不会指向自己。所以以此断言。

image.png

打印

为了方便分块调试,这里就先讲打印,以便于能直接判断前面的代码写得是否正确。

需要注意的也就只有结束循环的条件,通过头结点来判断链表循环一周便可以结束循环。

image.png

头插

头插跟尾插的步骤相差不大,先断言传入的指针,之后需要找到第一个结点,在其于头结点之间插入新节点。

image.png

头删

同样考虑一下链表是否只有头结点,之后找到第一跟第二结点,把第一结点释放后把头结点跟第二结点链接起来。


写到这里,各位可能感觉怪怪的,感觉要思考的东西好像变少了,这正是带头双向循环链表带给我们的便利。正如这里,如果链表只有一个结点也不用担心会对空指针进行解引用,因为唯一的那个结点指向的是头指针,即将第一个结点删除后,头结点还会继续跟自己链接起来,这就是前面为什么说这个结构无敌的原因。各各方面都非常完美,简直无懈可击,让我在实现程序的时候都不寒而栗。

image.png

image.png

查找


对于查找并没有更好的方法,找到就返回目标的地址,没找到就继续迭代直到链表循环一次

0cfa5b457f394f9e845a10b8d2fc7440.png

指定位置后插入


从某种层面上来讲,只要实现了这个跟下面两个函数就可以实现前面的增删操作了。


接收一个结点的地址,操作前先判断是否是空指针避免发生错误,因为要在 pos 的下一位插入结点,所以找到 pos 的下一位,新建结点后将其插入到二者之间。若要在 pos 前插入只要 pos 的上一个结点就可以了,这也是单链表十分困难才能做到的操作。

image.png

实现了指定位置插入结点的操作,前面的头插跟尾插就可以变成这样。

image.png

image.png

删除指定位置


接收一个指针,判断一下删除两件套(传入指针是否为空及链表是否只有一个头结点),然后找到 pos 的前后结点将其链接起来后并将 pos 释放就完成目标。即便只有一个结点也能完成工作。

image.png

这个实现完,头删尾删也可以转换成

image.png

image.png

销毁


首先断言确保不是空指针,通过迭代一个一个释放链表的结点,最后别忘记头结点也要释放掉!!!

image.png

尾言


如此带头双向循环链表的实现就完成了,也就只有初始化以及链接时候麻烦一点,其他方面完美胜过其他类型的链表。把源码放在下面,有需要的自取。使用前记得对链表进行初始化,接收头结点之后再使用。

Dlist.h

#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
typedef  int LTdatatype;
typedef struct Listnode
{
  struct Listnode* prev;
  LTdatatype data;
  struct Listnode* next;
}Listnode;
Listnode* ListCreate(void);
Listnode* buynewnode(LTdatatype x);
void Listpushback(Listnode* head, LTdatatype x);
void Listprint(Listnode* head);
void Listpopback(Listnode* head);
void Listpushfront(Listnode* head, LTdatatype x);
void Listpopfront(Listnode* head);
Listnode* ListFind(Listnode* head, LTdatatype x);
void ListInsert(Listnode* pos, LTdatatype x);
void ListErase(Listnode* pos);
void Listdestroy(Listnode* pos);
#include"Dlist.h"
Listnode* buynewnode(LTdatatype x)   //开辟新结点
{
  Listnode* newnode = (Listnode*)malloc(sizeof(Listnode));   //开辟空间
  if (newnode == NULL)                        //判断开辟是否成功
  {
    perror(malloc);
    exit(-1);
  }
  newnode->data = x;                          //赋值
  newnode->next = NULL;                       //下一结点初始制空
  newnode->prev = NULL;                       //上一结点初始制空
  return newnode;
}
Listnode* ListCreate()  //链表的初始化
{
  Listnode* head = buynewnode(-1);      //开辟头结点
  head->next = head;                    //下一结点指向自己
  head->prev = head;                    //上一结点也指向自己
  return head;
}
void Listpushback(Listnode* head, LTdatatype x)
{
  assert(head);
  通过头结点找到尾结点
  //Listnode* back = head->prev;
  //Listnode* newnode = buynewnode(x);
  三者链接
  //head->prev = newnode;
  //newnode->next = head;
  //back->next = newnode;
  //newnode->prev = back;
  ListInsert(head->prev, x);
}
void Listpopback(Listnode* head)
{
  assert(head);
  assert(head->next != head);  //判断链表是否只有头指针
  //Listnode* back = head->prev; //找尾结点
  //back->prev->next = head;   //跨过尾结点链接
  //head->prev = back->prev;
  //free(back);
  ListErase(head->prev);
}
void Listprint(Listnode* head)
{
  Listnode* cur = head->next;
  while (cur!=head)        //回到头结点说明已经循环一遍结束循环
  {
    printf("%d ", cur->data);
    cur = cur->next;
  }
  printf("\n");
}
void Listpushfront(Listnode* head, LTdatatype x)
{
  //assert(head);
  找到第一个结点进行头插
  //Listnode* newnode = buynewnode(x);
  //Listnode* first = head->next;
  三者链接
  //head->next = newnode;
  //newnode->prev = head;
  //newnode->next = first;
  //first->prev = newnode;
  ListInsert(head, x);
}
void Listpopfront(Listnode* head)
{
  assert(head);
  assert(head->next != head);
  //Listnode* first = head->next; //找第一个结点
  //Listnode* sec = first->next; //找第二个结点
  //head->next = sec;
  //sec->prev = head;
  //free(first);
  ListErase(head->next);
}
Listnode* ListFind(Listnode* head, LTdatatype x)
{
  assert(head);
  Listnode* cur = head->next;
  while (cur != head)
  {
    if (cur->data == x)
    {
      break;
    }
    cur = cur->next;
  }
  if (cur == head)
  {
    printf("找不到\n");
  }
  else
  {
    return cur;
  }
  return NULL;
}
void ListInsert(Listnode* pos, LTdatatype x)
{
  assert(pos);
  Listnode* next = pos->next;   //找pos的下一个结点
  Listnode* newnode = buynewnode(x);
  //三点链接
  pos->next = newnode;
  newnode->prev = pos;
  newnode->next = next;
  next->prev = newnode;
}
void ListErase(Listnode* pos)
{
  assert(pos);
  assert(pos->next != pos);
  //找到前后
  Listnode* bef = pos->prev;
  Listnode* next = pos->next;
  bef->next = next;
  next->prev = bef;
  free(pos);
}
void Listdestroy(Listnode* head)
{
  assert(head);
  Listnode* cur = head->next;
  while (cur != head)
  {
    Listnode* next = cur->next;
    free(cur);
    cur = next;
  }
  free(head);
}
目录
相关文章
|
1月前
|
C语言
【数据结构】栈和队列(c语言实现)(附源码)
本文介绍了栈和队列两种数据结构。栈是一种只能在一端进行插入和删除操作的线性表,遵循“先进后出”原则;队列则在一端插入、另一端删除,遵循“先进先出”原则。文章详细讲解了栈和队列的结构定义、方法声明及实现,并提供了完整的代码示例。栈和队列在实际应用中非常广泛,如二叉树的层序遍历和快速排序的非递归实现等。
159 9
|
25天前
|
存储 算法 Perl
数据结构实验之链表
本实验旨在掌握线性表中元素的前驱、后续概念及链表的建立、插入、删除等算法,并分析时间复杂度,理解链表特点。实验内容包括循环链表应用(约瑟夫回环问题)、删除单链表中重复节点及双向循环链表的设计与实现。通过编程实践,加深对链表数据结构的理解和应用能力。
51 4
|
18天前
|
存储 缓存 算法
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式,强调了合理选择数据结构的重要性,并通过案例分析展示了其在实际项目中的应用,旨在帮助读者提升编程能力。
40 5
|
25天前
|
存储 人工智能 算法
数据结构实验之C 语言的函数数组指针结构体知识
本实验旨在复习C语言中的函数、数组、指针、结构体与共用体等核心概念,并通过具体编程任务加深理解。任务包括输出100以内所有素数、逆序排列一维数组、查找二维数组中的鞍点、利用指针输出二维数组元素,以及使用结构体和共用体处理教师与学生信息。每个任务不仅强化了基本语法的应用,还涉及到了算法逻辑的设计与优化。实验结果显示,学生能够有效掌握并运用这些知识完成指定任务。
44 4
|
1月前
|
存储 搜索推荐 算法
【数据结构】树型结构详解 + 堆的实现(c语言)(附源码)
本文介绍了树和二叉树的基本概念及结构,重点讲解了堆这一重要的数据结构。堆是一种特殊的完全二叉树,常用于实现优先队列和高效的排序算法(如堆排序)。文章详细描述了堆的性质、存储方式及其实现方法,包括插入、删除和取堆顶数据等操作的具体实现。通过这些内容,读者可以全面了解堆的原理和应用。
71 16
|
1月前
|
C语言
【数据结构】二叉树(c语言)(附源码)
本文介绍了如何使用链式结构实现二叉树的基本功能,包括前序、中序、后序和层序遍历,统计节点个数和树的高度,查找节点,判断是否为完全二叉树,以及销毁二叉树。通过手动创建一棵二叉树,详细讲解了每个功能的实现方法和代码示例,帮助读者深入理解递归和数据结构的应用。
120 8
|
1月前
|
存储 C语言
【数据结构】手把手教你单链表(c语言)(附源码)
本文介绍了单链表的基本概念、结构定义及其实现方法。单链表是一种内存地址不连续但逻辑顺序连续的数据结构,每个节点包含数据域和指针域。文章详细讲解了单链表的常见操作,如头插、尾插、头删、尾删、查找、指定位置插入和删除等,并提供了完整的C语言代码示例。通过学习单链表,可以更好地理解数据结构的底层逻辑,提高编程能力。
68 4
|
1月前
|
存储 C语言
【数据结构】顺序表(c语言实现)(附源码)
本文介绍了线性表和顺序表的基本概念及其实现。线性表是一种有限序列,常见的线性表有顺序表、链表、栈、队列等。顺序表是一种基于连续内存地址存储数据的数据结构,其底层逻辑是数组。文章详细讲解了静态顺序表和动态顺序表的区别,并重点介绍了动态顺序表的实现,包括初始化、销毁、打印、增删查改等操作。最后,文章总结了顺序表的时间复杂度和局限性,并预告了后续关于链表的内容。
71 3
|
1月前
|
算法 安全 搜索推荐
2024重生之回溯数据结构与算法系列学习之单双链表精题详解(9)【无论是王道考研人还是IKUN都能包会的;不然别给我家鸽鸽丢脸好嘛?】
数据结构王道第2.3章之IKUN和I原达人之数据结构与算法系列学习x单双链表精题详解、数据结构、C++、排序算法、java、动态规划你个小黑子;这都学不会;能不能不要给我家鸽鸽丢脸啊~除了会黑我家鸽鸽还会干嘛?!!!
|
1月前
|
存储 Web App开发 算法
2024重生之回溯数据结构与算法系列学习之单双链表【无论是王道考研人还是IKUN都能包会的;不然别给我家鸽鸽丢脸好嘛?】
数据结构之单双链表按位、值查找;[前后]插入;删除指定节点;求表长、静态链表等代码及具体思路详解步骤;举例说明、注意点及常见报错问题所对应的解决方法

热门文章

最新文章