数据结构8: 双向链表(双向循环链表)的建立及C语言实现

之前接触到的链表都只有一个指针,指向直接后继,整个链表只能单方向从表头访问到表尾,这种结构的链表统称为 “单向链表”或“单链表”。

如果算法中需要频繁地找某结点的前趋结点,单链表的解决方式是遍历整个链表,增加算法的时间复杂度,影响整体效率。
为了快速便捷地解决这类问题,在单向链表的基础上,给各个结点额外配备一个指针变量,用于指向每个结点的直接前趋元素。这样的链表被称为“双向链表”或者“双链表”。

双链表中的结点

双向链表中的结点有两个指针域,一个指向直接前趋,一个指向直接后继。(链表中第一个结点的前趋结点为NULL,最后一个结点的后继结点为NULL)

图1 双向链表中的结点

结点的具体构成:

typedef struct line{
  struct line *prior;   //指向直接前趋
  int data;
  struct line *next;    //指向直接后继
}line;

创建双向链表并初始化

双向链表创建的过程中,每一个结点需要初始化数据域和两个指针域,一个指向直接前趋结点,另一个指向直接后继结点。

例如,创建一个双向链表line(1,2,3):

图2 双向表(1,2,3)

实现代码:

line* initLine(line *head){
  head = (line*)malloc(sizeof(line));  //创建链表第一个结点(首元结点)
  head->prior = NULL;
  head->next = NULL;
  head->data = 1;
  line *list = head;
  for (int i=2; i<=3; i++)   {
    //创建并初始化一个新结点
    line *body = (line*)malloc(sizeof(line));
    body->prior = NULL;
    body->next = NULL;
    body->data = i;
    list->next = body;//直接前趋结点的next指针指向新结点
    body->prior = list;//新结点指向直接前趋结点
    list = list->next;
  }
  return head;
}

双向链表中插入结点

比如在(1,2,3)中插入一个结点 4,变成(1,4,2,3)。

实现效果图:

图3 插入结点4

在双向链表中插入数据时,首先完成图 3 中标注为 1 的两步操作,然后完成标注为 2 的两步操作;反之,如果先完成 2,就无法通过头指针访问结点 2,需要额外增设指针,虽然能实现,但较前一种麻烦。

实现代码:

line *insertLine(line * head, int data, int add){
  //新建数据域为data的结点
  line * temp = (line*)malloc(sizeof(line));
  temp->data = data;
  temp->prior = NULL;
  temp->next = NULL;
  //插入到链表头,要特殊考虑
  if (add == 1)   {
    temp->next = head;
    head->prior = temp;
    head = temp;
  }  else  {
    line *body = head;
    //找到要插入位置的前一个结点
    for (int i=1; i<add-1; i++)     {
      body = body->next;
    }
    //判断条件为真,说明插入位置为链表尾
    if (body->next == NULL)     {
      body->next = temp;
      temp->prior=body;
    }    else    {
      body->next->prior = temp;
      temp->next = body->next;
      body->next = temp;
      temp->prior = body;
    }
  }
  return head;
}

双向链表中删除节点

双链表删除结点时,直接遍历链表,找到要删除的结点,然后利用该结点的两个指针域完成删除操作。

例如,在(1,4,2,3)中删除结点 2:

//删除结点的函数,data为要删除结点的数据域的值
line *delLine(line *head, int data){
  line *temp = head;
  //遍历链表
  while (temp)   {
    //判断当前结点中数据域和data是否相等,若相等,摘除该结点
    if (temp->data == data)     {
      temp->prior->next = temp->next;
      temp->next->prior = temp->prior;
      free(temp);
      return head;
    }
    temp = temp->next;
  }
  printf("链表中无该数据元素");
  return head;
}

双向链表中的查找和更改操作

双向链表的查找操作和单链表的实现方法完全一样,从链表的头结点或者首元结点开始遍历,这里不做过多解释。

更改链表中某结点的数据域的操作是在查找的基础上完成的。通过遍历找到存储有该数据元素的结点后,直接更改其数据域就可以。

本节的完整代码

#include <stdio.h>
#include <stdlib.h>
typedef struct line{
  struct line *prior;
  int data;
  struct line *next;
}line;
line *initLine(line *head);
line *insertLine(line *head, int data, int add);
line *delLine(line *head, int data);
void display(line *head);
int main() {
  line *head = NULL;
  head=initLine(head);
  head=insertLine(head, 4, 2);
  display(head);
  head=delLine(head, 2);
  display(head);
  return 0;
}
line *initLine(line * head){
  head = (line*)malloc(sizeof(line));
  head->prior = NULL;
  head->next = NULL;
  head->data = 1;
  line *list = head;
  for (int i=2; i<=3; i++)   {
    line *body = (line*)malloc(sizeof(line));
    body->prior = NULL;
    body->next = NULL;
    body->data = i;
    list->next = body;
    body->prior = list;
    list = list->next;
  }
  return head;
}
line *insertLine(line *head, int data, int add){
  //新建数据域为data的结点
  line *temp = (line*)malloc(sizeof(line));
  temp->data = data;
  temp->prior = NULL;
  temp->next = NULL;
  //插入到链表头,要特殊考虑
  if (add == 1)   {
    temp->next = head;
    head->prior = temp;
    head = temp;
  }  else  {
    line *body = head;
    //找到要插入位置的前一个结点
    for (int i=1; i<add-1; i++)    {
      body = body->next;
    }
    //判断条件为真,说明插入位置为链表尾
    if (body->next == NULL)     {
      body->next = temp;
      temp->prior = body;
    }    else    {
      body->next->prior = temp;
      temp->next = body->next;
      body->next = temp;
      temp->prior = body;
    }
  }
  return head;
}
line *delLine(line *head, int data){
  line *temp = head;
  //遍历链表
  while (temp)   {
    //判断当前结点中数据域和data是否相等,若相等,摘除该结点
    if (temp->data == data)     {
      temp->prior->next = temp->next;
      temp->next->prior = temp->prior;
      free(temp);
      return head;
    }
    temp = temp->next;
  }
  printf("链表中无该数据元素");
  return head;
}
//输出链表的功能函数
void display(line *head){
  line *temp = head;
  while (temp)   {
    if (temp->next == NULL)     {
      printf("%d\n",temp->data);
    }    else    {
      printf("%d->",temp->data);
    }
    temp=temp->next;
  }
}

总结双向链表和单链表唯一的不同在于结构中多了一个指向直接前趋的指针,其他完全一样。如果问题中需要频繁的调取当前结点的前趋结点,那使用双向链表的数据结构为最佳方案。

补:双向链表和循环链表的结合体

约瑟夫环问题其实还可以这样玩:如果顺时针报数,有人出列后,顺时针找出出列位置的下一个人,开始反方向(也就是逆时针)报数,有人出列后,逆时针找出出列位置的下一个人,开始顺时针报数。依次重复,直至最后一个出列。
例如,还是从编号为 3 的开始数,数到 2 的人出列:

图4 约瑟夫环

新玩法的出列顺序为:
首先顺时针转,4 数 2,所以 4 出列;
顺时针找到下一个人为 5,开始逆时针转,3 数 2,所以 3 出列;
逆时针找到下一个人为 2,开始顺时针转,5 数 2,所以 5 出列;
顺时针找到下一个人为 1,开始逆时针转,2 数 2,所以 2 出列;
最后只剩下 1,所以 1 自己出列。

对于新的约瑟夫环问题,需要将循环链表和双向链表结合使用,组成:双向循环链表。

有兴趣的可以尝试编码解决新的约瑟夫环问题。

原文地址:https://www.cnblogs.com/ciyeer/p/9028143.html

时间: 2024-07-29 11:24:35

数据结构8: 双向链表(双向循环链表)的建立及C语言实现的相关文章

双向循环链表的建立

单链表的缺点是只能往前,不能后退,虽然有循环单链表,但后退的成本还是很高的,需要跑一圈.在这个时候呢,双向链表就应运而生了,再加上循环即双向循环 链表就更加不错了.所谓双向链表只不过是添加了一个指向前驱结点的指针,双向循环链表是将最后一个结点的后继指针指向头结点,这在遍历时很关键. 程序: #include<stdio.h>#include<stdlib.h>#define OK 1 #define ERROR 0#define OVERFLOW 0typedef struct D

数据结构基础(12) --双向循环链表的设计与实现

双向链表的操作特点: (1) "查询" 和单链表相同; (2)"插入" 和"删除"时需要同时修改两个方向上的指针. 但是对于双向循环链表则在表尾插入非常的迅速, 只需O(1)的时间,因为有指向前面的指针, 因此双向循环链表会很容易的找到位于表尾的元素,因此双向循环链表比较适用于频繁在表尾插入的情况. 空链表: 双向循环链表节点构造: class DoubleListNode { private: Type data; DoubleListNode

数据结构-内核的双向循环链表-简单实现

list.h #ifndef LIST_H__ #define LIST_H__ struct list_head { struct list_head *prev ; struct list_head *next ; }; #define LIST_HEAD_INIT(name) { &(name), &(name) } #define LIST_HEAD(name) struct list_head name = LIST_HEAD_INIT(name) #define __list_

数据结构之双向链表(包含双向循环链表)

双向(循环)链表是线性表的链式存储结构的又一种形式. 在之前已经讲述了单向链表和循环链表.相比于单向链表只能从头结点出发遍历整个链表的局限性,循环链表使得可以从任意一个结点遍历整个链表. 但是,不管单向链表也好,循环链表也罢,都只能从一个方向遍历链表,即只能查找结点的下一个结点(后继结点),而不能查找结点的上一个结点(前驱结点).鉴于上述问题,引入了双向链表.由于双向循环链表包含双向链表的所有功能操作.因此,我们只讲述双向循环链表. 与单向链表不同,双向链表的结点构造如下图所示.即一个结点由三个

小猪的数据结构辅助教程——2.7 线性表中的双向循环链表

小猪的数据结构辅助教程--2.7 线性表中的双向循环链表 标签(空格分隔): 数据结构 本节学习路线图与学习要点 学习要点: 1.了解引入双向循环链表的原因 2.熟悉双向循环链表的特点以及存储结构 3.掌握双向循环链表的一些基本操作的实现逻辑 4.掌握逆序输出双向循环链表元素逻辑 1.双向循环链表的引入 2.双向循环链表的存储结构 双向循环链表的特点: 上面也说了,空间换时间,比起循环链表只是多了一个指向前驱的指针 特点的话: 判断空表:L ->next = L -> prior = L; 存

JS数据结构第三篇---双向链表和循环链表

一.双向链表 在上文<JS数据结构第二篇---链表>中描述的是单向链表.单向链表是指每个节点都存有指向下一个节点的地址,双向链表则是在单向链表的基础上,给每个节点增加一个指向上一个节点的地址.然后头结点的上一个节点,和尾结点的下一个节点都指向null.同时LinkedList类中再增加一个last内部属性,一直指向链表中最后一个节点.结构模拟如图: 同样对外暴露的方法和单向链表一样,只是内部实现稍有变化 双向链表完整设计代码: /** * 自定义双向链表:对外公开的方法有 * append(e

数据结构-双向循环链表(无头结点)相关算法

#include <stdio.h>#include <stdlib.h>#define OVERFLOW -2#define OK 1#define ERROR 0 //此双向循环链表无头结点typedef int ElemType;typedef struct DulNode {    ElemType data;    struct DulNode *prior;    struct DulNode *next;}DulNode,*DulLinkList; DulLinkLi

数据结构_线性表_链式存储_双向循环链表的基本操作

//双向链表,将头结点和尾结点链接起来,就构成了双向循环链表 //双向循环链表是将头结点的前驱指针指向了尾结点,同时将尾结点的后劲指针指向了头结点. //空表,头结点的前驱和后继指针均指向了自己,这也是判断双向循环链表是否为空的条件, //双向循环链表具有对称性 //缺点,是要付出空间代价的 双向链表也叫双链表,是链表的一种,它的每个数据结点中都有两个指针,分别指向直接后继和直接前驱.所以,从双向链表中的任意一个结点开始,都可以很方便地访问它的前驱结点和后继结点.一般我们都构造双向循环链表. 代

1.Go-copy函数、sort排序、双向链表、list操作和双向循环链表

1.1.copy函数 通过copy函数可以把一个切片内容复制到另一个切片中 (1)把长切片拷贝到短切片中 package main import "fmt" func main() { s1 := []int {1,2} s2 := []int{3,4,5,6} //copy的是角标,不会增加元切片的长度 copy(s1,s2) fmt.Println(s1) //[3 4] fmt.Println(s2) //[3 4 5 6] } (2)把短切片拷贝到长切片中 package ma