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

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

双链表中的结点

双向链表中的结点有两个指针域,一个指向直接前趋,一个指向直接后继。(链表中第一个结点的前趋结点为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 = ;
  line *list = head;
  for (int i=; i<=; 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 == )
  {
    temp->next = head;
    head->prior = temp;
    head = temp;
  }
  else
  {
    line *body = head;
    //找到要插入位置的前一个结点
    for (int i=; i<add-; 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, , );
  display(head);
  head=delLine(head, );
  display(head);
  return ;
}
line *initLine(line * head)
{
  head = (line*)malloc(sizeof(line));
  head->prior = NULL;
  head->next = NULL;
  head->data = ;
  line *list = head;
  for (int i=; i<=; 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 == )
  {
    temp->next = head;
    head->prior = temp;
    head = temp;
  }
  else
  {
    line *body = head;
    //找到要插入位置的前一个结点
    for (int i=; i<add-; 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 自己出列。

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

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

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

  1. 【C语言教程】“双向循环链表”学习总结和C语言代码实现!

    双向循环链表 定义 双向循环链表和它名字的表意一样,就是把双向链表的两头连接,使其成为了一个环状链表.只需要将表中最后一个节点的next指针指向头节点,头节点的prior指针指向尾节点,链表就能成环儿 ...

  2. JS数据结构第三篇---双向链表和循环链表之约瑟夫问题

    一.双向链表 在上文<JS数据结构第二篇---链表>中描述的是单向链表.单向链表是指每个节点都存有指向下一个节点的地址,双向链表则是在单向链表的基础上,给每个节点增加一个指向上一个节点的地 ...

  3. 双向链表、双向循环链表的JS实现

    关于链表简介.单链表.单向循环链表.JS中的使用以及扩充方法:  单链表.循环链表的JS实现 关于四种链表的完整封装: https://github.com/zhuwq585/Data-Structu ...

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

    1.1.copy函数 通过copy函数可以把一个切片内容复制到另一个切片中 (1)把长切片拷贝到短切片中 package main import "fmt" func main() ...

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

    1.1.copy函数 通过copy函数可以把一个切片内容复制到另一个切片中 (1)把长切片拷贝到短切片中 ? 1 2 3 4 5 6 7 8 9 10 11 12 package main   imp ...

  6. java与数据结构(4)---java实现双向循环链表

    线性表之链式存储结构双向循环链表 双向循环链表:每个结点包含了数据.直接前驱地址指针和直接后驱地址指针,头结点的直接前驱指向尾结点,尾结点的直接后驱指向头结点,头尾相连构成一个可正可反的圆环.可以形象 ...

  7. java数据结构-06双向循环链表

    双向循环链表跟单向链表一样,都是头尾相连,不过单向是尾指向头,双向是头尾互相指,可以从前往后查,也可以从后往前查 无头结点的双向循环链表 public class CircleLinkedList&l ...

  8. C++实现双向循环链表

    本次博文是关于利用C++模板的方式实现的双向循环链表以及双向循环链表的基本操作,在之前的博文C++语言实现双向链表中,已经给大家分析了双向循环链表的结构,并以图示的方式给大家解释了双向循环链表的基本操 ...

  9. C语言通用双向循环链表操作函数集

    说明 相比Linux内核链表宿主结构可有多个链表结构的优点,本函数集侧重封装性和易用性,而灵活性和效率有所降低.     可基于该函数集方便地构造栈或队列集.     本函数集暂未考虑并发保护. 一  ...

随机推荐

  1. 第十四章 Spring MVC的工作机制与设计模式(待续)

    Spring MVC的总体设计 Control设计 Model设计 View设计 框架设计的思考 设计模式解析之模版模式

  2. logstash日志写入kafka

    安装kafka curl -L -O https://mirrors.cnnic.cn/apache/kafka/0.10.2.1/kafka_2.10-0.10.2.1.tgz tar xf kaf ...

  3. ORA-00600:内部错误代码,参数:[kpnxdcbk-2],[],[],[],[],[],[],[],[],[],[],[]

    由于最近工作中常出现ORA-00600:内部错误代码,参数:[kpnxdcbk-2],[],[],[],[],[],[],[],[],[],[],[]这种异常!所以在这里讲一下我的处理方法. 笔者所遇 ...

  4. 如何实现1080P延迟低于500ms的实时超清直播传输技术<转>

    转载地址:http://www.yunweipai.com/archives/9037.html 最近由于公司业务关系,需要一个在公网上能实时互动超清视频的架构和技术方案.众所周知,视频直播用 CDN ...

  5. 用JS,打印99乘法表

    <!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title&g ...

  6. LaTeX数学公式基础

    LaTeX数学公式 参考:https://www.cnblogs.com/Sinte-Beuve/p/6160905.html 原博客显示有点问题,重新搬运整理LaTeX数学公式部分的基本用法 基础 ...

  7. 【总结整理】WMS、WMTS、WFS

    参考:http://www.cnblogs.com/naaoveGIS/p/5508882.html WMTS:WMTS是OGC制定的一种发布瓦块地图的Web服务规范,wms主要是动态地图,wmts是 ...

  8. 简单的jQuery前端验证码校验

    简单的jQuery前端验证码校验2 html; <!DOCTYPE html> <html lang="zh-cn"> <head> <m ...

  9. 电子模块 001 --- 遥杆 JoyStick

    电子模块 001 - 遥杆 JoyStick - Ongoing - 2016年8月31日 星期三 遥杆 JoyStick 模块 今天介绍:JoyStick 电子模块. 模块名称: 双轴按键摇杆 PS ...

  10. Java-马士兵设计模式学习笔记-迭代器模式-模仿Collectin ArrayList LinckedList

    Java Iterator模式 Java Iterator模式, 模仿Collectin ArrayList LinckedList 一.有如下几个类 1.接口Collection.java 2.接口 ...