上一节里实现的是最简单的链表,在实际中那种链表不会单独用来存储数据,更多是作为其他数据结构的子结构,如图的邻接表等。而比较常用的就是带头双向循环链表。


通过对比我们可以看出有三个不同,多了头节点,链表中的元素之间不再是单向而是双向,头尾节点相连,构成循环链表。
虽然结构比起之前的复杂了一点,但是优势却十分明显。
如普通链表要访问最后的元素时,只能通过遍历链表来取得,而这个可以直接取头节点的前一个来找到尾巴。链表之间是双向的,所有可以实现回溯等操作。多了头节点可以直接在头节点上对链表就行修改,不需要再使用二级指针等

数据结构
typedef struct ListNode
{DATATYPE data;struct ListNode* next;struct ListNode* prev;
}ListNode;

我们需要用到两个指针,一个指向当前位置的前面,一个指向后面

实现的接口
// 创建返回链表的头结点.
ListNode* ListCreate();
// 双向链表销毁
void ListDestory(ListNode* list);
// 双向链表打印
void ListPrint(ListNode* list);
// 双向链表尾插
void ListPushBack(ListNode* list, DATATYPE x);
// 双向链表尾删
void ListPopBack(ListNode* list);
// 双向链表头插
void ListPushFront(ListNode* list, DATATYPE x);
// 双向链表头删
void ListPopFront(ListNode* list);
// 双向链表查找
ListNode* ListFind(ListNode* list, DATATYPE x);
// 双向链表在pos的前面进行插入
void ListInsert(ListNode* pos, DATATYPE x);
// 双向链表删除pos位置的节点
void ListErase(ListNode* pos);

创建返回链表的头结点

ListNode* ListCreate()
{ListNode* head = (ListNode*)malloc(sizeof(ListNode));head->data = 0;head->next = head;head->prev = head;return head;
}

我们首先要创建头节点

在一开始我们就要确保双向循环,所以前后指针都指向自己

在pos的前面进行插入

我先不讲头插和尾插,而是直接讲插入,因为当我们能实现在pos位置插入时,就可以通过这个函数来实现头插和尾插



如图,如果我们要让他在pos前面插入,就必须先让cur处理好他与pos的prev的关系,因为如果我们先让他变成pos的前一个,那么原本的前一个节点的位置就会丢失,所以我们必须要注意好顺序

void ListInsert(ListNode* pos, DATATYPE x)
{assert(pos);ListNode* cur = (ListNode*)malloc(sizeof(ListNode));cur->data = x;pos->prev->next = cur;cur->prev = pos->prev;cur->next = pos;pos->prev = cur;}

头插

void ListPushFront(ListNode* list, DATATYPE x)
{assert(list);ListNode* head = list;ListNode* tail = head->prev;ListNode* cur = (ListNode*)malloc(sizeof(ListNode));cur->data = x;cur->next = head->next;head->next->prev = cur;head->next = cur;cur->prev = head;//ListInsert(list->next, x);
}

这里的实现思路一样,要处理好head的下一个节点与cur的关系,我们也可以用刚刚的插入函数直接实现,插入的位置就是头节点的下一个位置

尾插

void ListPushBack(ListNode* list, DATATYPE x)
{assert(list);ListNode* head = list;ListNode* tail = head->prev;ListNode* cur = (ListNode*)malloc(sizeof(ListNode));cur->data = x;cur->prev = tail;cur->next = head;tail->next = cur;head->prev = cur;//ListInsert(list, x);
}

这里与前两个一样,就不罗嗦了,这里也可以用之前的函数,插入位置就是list也就是头节点,头节点的前一个位置就是尾节点,在这插入就是尾插

删除pos位置的节点

删除的操作和插入的思路基本一样,就不多说了

void ListErase(ListNode* pos)
{assert(pos);pos->next->prev = pos->prev;pos->prev->next = pos->next;free(pos);
}

头删

void ListPopFront(ListNode* list)
{assert(list);ListNode* head = list;ListNode* next = head->next;next->next->prev = head;head->next = next->next;free(next);//ListErase(list->next);
}

尾删

void ListPopBack(ListNode* list)
{assert(list);ListNode* head = list;ListNode* tail = list->prev;head->prev = tail->prev;tail->prev->next = head;free(tail);//ListErase(list->prev);
}

打印链表

遍历输出即可

void ListPrint(ListNode* list)
{assert(list);ListNode* cur = list->next;while (cur != list){printf("%d ", cur->data);cur = cur->next;}printf("NULL\n");
}

查找链表

ListNode* ListFind(ListNode* list, DATATYPE x)
{assert(list);ListNode* cur = list->next;while (cur != list){if (cur->data == x)return cur;elsecur = cur->next;}return NULL;
}

销毁链表

void ListDestory(ListNode* list)
{assert(list);ListNode* head = list;ListNode* cur = list->next;while (cur != head){ListNode* next = cur->next;free(cur);cur = next;}free(head);head = NULL;}


完整代码
头文件

#pragma once
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
#define _CRT_SECURE_NO_WARNINGS
#define LISTSIZE 10typedef int DATATYPE;typedef struct ListNode
{DATATYPE data;struct ListNode* next;struct ListNode* prev;
}ListNode;// 创建返回链表的头结点.
ListNode* ListCreate();
// 双向链表销毁
void ListDestory(ListNode* list);
// 双向链表打印
void ListPrint(ListNode* list);
// 双向链表尾插
void ListPushBack(ListNode* list, DATATYPE x);
// 双向链表尾删
void ListPopBack(ListNode* list);
// 双向链表头插
void ListPushFront(ListNode* list, DATATYPE x);
// 双向链表头删
void ListPopFront(ListNode* list);
// 双向链表查找
ListNode* ListFind(ListNode* list, DATATYPE x);
// 双向链表在pos的前面进行插入
void ListInsert(ListNode* pos, DATATYPE x);
// 双向链表删除pos位置的节点
void ListErase(ListNode* pos);

函数实现

#include "Double-linked circular list.h"ListNode* ListCreate()
{ListNode* head = (ListNode*)malloc(sizeof(ListNode));head->data = 0;head->next = head;head->prev = head;return head;
}void ListPrint(ListNode* list)
{assert(list);ListNode* cur = list->next;while (cur != list){printf("%d ", cur->data);cur = cur->next;}printf("NULL\n");
}void ListPushBack(ListNode* list, DATATYPE x)
{assert(list);ListNode* head = list;ListNode* tail = head->prev;ListNode* cur = (ListNode*)malloc(sizeof(ListNode));cur->data = x;cur->prev = tail;cur->next = head;tail->next = cur;head->prev = cur;//ListInsert(list, x);
}void ListPushFront(ListNode* list, DATATYPE x)
{assert(list);ListNode* head = list;ListNode* tail = head->prev;ListNode* cur = (ListNode*)malloc(sizeof(ListNode));cur->data = x;cur->next = head->next;head->next->prev = cur;head->next = cur;cur->prev = head;//ListInsert(list->next, x);
}void ListDestory(ListNode* list)
{assert(list);ListNode* head = list;ListNode* cur = list->next;while (cur != head){ListNode* next = cur->next;free(cur);cur = next;}free(head);head = NULL;}ListNode* ListFind(ListNode* list, DATATYPE x)
{assert(list);ListNode* cur = list->next;while (cur != list){if (cur->data == x)return cur;elsecur = cur->next;}return NULL;
}void ListInsert(ListNode* pos, DATATYPE x)
{assert(pos);ListNode* cur = (ListNode*)malloc(sizeof(ListNode));cur->data = x;pos->prev->next = cur;cur->prev = pos->prev;cur->next = pos;pos->prev = cur;}void ListErase(ListNode* pos)
{assert(pos);pos->next->prev = pos->prev;pos->prev->next = pos->next;free(pos);
}void ListPopBack(ListNode* list)
{assert(list);ListNode* head = list;ListNode* tail = list->prev;head->prev = tail->prev;tail->prev->next = head;free(tail);//ListErase(list->prev);
}void ListPopFront(ListNode* list)
{assert(list);ListNode* head = list;ListNode* next = head->next;next->next->prev = head;head->next = next->next;free(next);//ListErase(list->next);
}

数据结构与算法 | 带头双向循环链表相关推荐

  1. 【数据结构】-关于带头双向循环链表的增删查改

    作者:低调 作者宣言:写好每一篇博客 文章目录 前言 一.带头双向循环链表的实现 1.1创建返回链表的头结点 1.2开辟一个新的结点 1.3双向链表的销毁 1.4双向链表的打印 1.5双向链表尾插 1 ...

  2. 比特数据结构与算法(第二章收尾)带头双向循环链表的实现

    1.链表的分类 链表的分类 ① 单向或者双向 ② 带头或者不带头 ③ 循环或者非循环 常用的链表: 根据上面的分类我们可以细分出8种不同类型的链表,这么多链表我们一个个讲解这并没有意义.我们实际中最常 ...

  3. 【数据结构】链表:带头双向循环链表的增删查改

    本篇要分享的内容是带头双向链表,以下为本片目录 目录 一.链表的所有结构 二.带头双向链表 2.1尾部插入 2.2哨兵位的初始化 2.3头部插入 2.4 打印链表 2.5尾部删除 2.6头部删除 2. ...

  4. 初阶数据结构之带头+双向+循环链表增删查实现(三)

    文章目录 @[TOC](文章目录) 前言 一.带头双向循环链表的初始化 1.1带头双向循环链表的结构体定义 1.2初始化代码的实现 二.带头+双向+循环链表的增功能实现 2.1头插代码的实现 2.2尾 ...

  5. 数据结构:带头双向循环链表——增加、删除、查找、修改,详细解析

    读者可以先阅读这一篇:数据结构--单链表的增加.删除.查找.修改,详细解析_昵称就是昵称吧的博客-CSDN博客,可以更好的理解带头双向循环链表. 目录 一.带头双向循环链表的处理和介绍 1.带头双向循 ...

  6. 【数据结构初阶】链表(下)——带头双向循环链表的实现

    目录 带头双向循环链表的实现 1.带头双向循环链表的节点类型 2.创建带头双向循环链表的节点 3.向带头双向循环链表中插入数据 <3.1>从链表尾部插入数据 <3.2>从链表头 ...

  7. 数据结构-带头双向循环链表(增删查改详解)

    在上一篇博客中,详细介绍了单链表的增删查改,虽然单链表的结构简单,但是用起来却不是那么顺手.因此根据单链表的种种缺点,这篇博客所介绍的带头双向循环链表将会带来极大的优化. 上图就是带头双向循环链表的主 ...

  8. 【数据结构】双向链表(带头双向循环链表)——超详细代码

    文章目录 1. 双链表 1.1 前言 1.2 带头双向循环链表 2. 带头双向循环链表的实现 2.1 双向链表的定义声明 2.2 双向链表的初始化 2.3 释放双向链表 2.4 打印双向链表 2.5 ...

  9. 【数据结构】带头双向循环链表

    各位读者们好久不见了,咋们接着上一期链表来,今天来实现一下链表最难的结构,同时也是实现起来最简单的结构--带头双向循环链表.话不多说,进入主题 文章目录 前言 实现带头双向循环链表 DList.h头文 ...

最新文章

  1. Windows下通过Python 3.x的ctypes调用C接口
  2. 快手2020校园招聘秋招笔试--工程B试卷
  3. 提高篇 第五部分 动态规划 第4章 状态压缩类动态规划
  4. C++ 里利用 std::ios::sync_with_stdio(false) 解决TLE问题
  5. DB2行转列(多维度)
  6. 交互系统的构建之(四)手掌与拳头检测加盟TLD
  7. Linux服务-bind
  8. 运算符重载为类的友元函数
  9. java arraystoreexception_java基础面试
  10. Python数据分析(二):DataFrame基本操作
  11. iOS定时器NSTimer的使用方法
  12. 微信小游戏排行榜设计技术梳理
  13. Java最新面试题大全
  14. 软件开发过程中需要的文档汇总
  15. 虹科解决方案 | 如何快速解决CAN与CAN FD之间通信的问题
  16. python生成单位阵或者对角阵的三种方法
  17. 日语学习的一些网站推荐
  18. 第一篇图像处理论文审稿意见修改说明
  19. 使用xiaopiu常见技巧
  20. 100人坐飞机,第一个乘客在座位中随便选一个坐下,第100人正确坐到自己坐位的概率是?

热门文章

  1. 字典-字典和列表组合的应用场景
  2. 科技文明等级那一级有量子计算机,人类科技在宇宙中属于几级文明,最高级文明多强?...
  3. AJAX框架眼镜店美瞳,PS完成对照片中人物的美瞳效果
  4. 前端进阶 -css的弱化与js的强化(11)
  5. 自己动手写一个 SimpleVue
  6. 想自己造无人机吗?Intel推出基于 Linux x86的自助无人机开发板
  7. Python 中的模块和包
  8. 深入浅出Node.js (2) - 模块机制
  9. 【原创】MySQL 返回更新值(RETURNING)
  10. finally块的问题(finally block does not complete normally) (转)