【数据结构】双向带头(哨兵位)循环链表 —详细讲解(赋源码)

简介: 【数据结构】双向带头(哨兵位)循环链表 —详细讲解(赋源码)

前言

双向循环列表是一种特殊的数据结构,它结合了双向链表和循环链表的特点。在双向循环列表中,每个节点除了拥有指向下一个节点的指针外,还拥有指向上一个节点的指针。此外,列表的头节点和尾节点通过这些指针相互连接,形成一个闭环。这种结构允许从任何一个节点开始,既可以向前遍历,也可以向后遍历,直到回到起点,从而实现高效的双向遍历。

一、双向带头循环链表的基本介绍

  • 定义节点结构:每个节点包含数据部分和两个指针部分,分别指向前一个节点和后一个节点。
  • 创建头节点:头节点是双向循环列表的起始点,它的前驱指针指向自己,后继指针也指向自己,形成一个闭环。
  • 插入节点:在特定位置插入新节点时,需要更新新旧节点的前驱和后继指针,确保列表的完整性。
  • 删除节点:删除特定节点时,同样需要更新前后节点的指针,避免出现悬空指针。
  • 遍历列表:可以通过前驱指针或后继指针从任意节点开始,向前或向后遍历整个列表。

双向带头链表的操作

常见操作包括初始化插入删除查找遍历。这些操作通常涉及到对链表节点的指针进行操作,以实现数据的动态管理。

双向带头循环链表基本理解

image.png

二、双向带头循环链表的实现

2.1 双向带头循环链表的功能

//链表初始化创建哨兵位
ListNode* ListInit();
//连接表增加节点
ListNode* BuyNewNode(ListDataType x);
//链表打印
void DlistPrint(ListNode* phead);
//链表尾插
void DLlistPushBack(ListNode* phead, ListDataType x);
//链表尾删
void DLlistPopBack(ListNode* phead);
//链表头插
void DLlistPushFront(ListNode* phead, ListDataType x);
//链表头删
void DLlistPopFront(ListNode* phead);
//链表查找
ListNode* DListFind(ListNode* phead, ListDataType pos);
//链表pos插入
void DListInset(ListNode* pos, ListDataType);
//链表pos删除
void DListErase(ListNode* phead, ListNode* pos);
//链表销毁
void DListDestory(ListNode* phead);

2.2 定义节点结构体

由于是双向带头,需要定义一个nextprev

typedef int ListDataType;

typedef struct DList
{
  int data;
  struct Dlist* prev;
  struct Dlist* next;

}ListNode;

2.3 链表的增加节点

这里可以相比较于单链表的操作,进行操作。

ListNode* BuyNewNode(ListDataType x)
{
  ListNode* newcode = (ListNode*)malloc(sizeof(ListNode));
  if (newcode == NULL)
  {
    perror("malloc fail");
    return NULL;
  }

  newcode->data = x;
  newcode->next = NULL;
  newcode->prev = NULL;
  return newcode;

}

2.4 链表的初始化

这个操作就是带头

//链表初始化创建哨兵卫
ListNode* ListInit()
{
  ListNode* head = BuyNewNode(-1);
  head->next = head;
  head->prev = head;
  return head;
}

2.5 链表的遍历

//链表打印
void DlistPrint(ListNode* phead)
{
  ListNode* cur = phead->next;
  printf("<-head->");
  while (cur != phead)
  {
    printf("%d<->", cur->data);
    cur = cur->next;
  }
  printf("\n");
}

2.6 链表的尾插

由于双向链表结构的特殊性,相比于单链表的效率更加高。

操作就是哨兵位的上一个节点,进行插入。

//链表尾插
void DLlistPushBack(ListNode* phead, ListDataType x)
{
  assert(phead);
  ListNode* newcode = BuyNewNode(x);
  ListNode* tail = phead->prev;
  tail->next = newcode;
  newcode->prev = tail;
  newcode->next = phead;
  phead->prev = newcode;

}

2,7 链表的尾删

单链表进行尾部删的时候会进行断言,判断链表是否为空,双向链表也是如此,但是进行的操作是哨兵位的上一个节点或者下一个节点是否指向自己。

判断是否为空链表

我们利用bool类型判断真或者假

bool ListEmpty(ListNode* phead)
{
  assert(phead);
  return phead->prev == phead;
}

尾删操作

//链表尾删
void DLlistPopBack(ListNode* phead)
{
  assert(phead);
  assert(!ListEmpty(phead));
  ListNode* tail = phead->prev;
  ListNode* tailprev = tail->prev;

  tailprev->next = phead;
  phead->prev = tailprev;
  free(tail);
  tail = NULL;
}

2.8 链表的头插

记录哨兵位下一个节点,进行头插入。

//链表头插
void DLlistPushFront(ListNode* phead, ListDataType x)
{
  assert(phead);

  ListNode* newcode = BuyNewNode(x);
  ListNode* cur = phead->next;
  phead->next = newcode;
  newcode->prev = phead;
  newcode->next = cur;
  cur->prev = newcode;
}

2.9 链表的头部删

头删的操作同,尾部删除一样,就是换了哥位置。(但是还是要注意进行判断链表为空的问题)

void DLlistPopFront(ListNode* phead)
{
  assert(phead);
  assert(!ListEmpty(phead));

  ListNode* cur = phead->next;
  ListNode* curnext = cur->next;
  phead->next = curnext;
  curnext->prev = phead;
  free(cur);
  cur = NULL;

}

2.10 链表的查找

链表的查找的时候返回写成结构体指针的形式,以便于进行指定位置的插入和删除。

//链表查找
ListNode* DListFind(ListNode* phead, ListDataType pos)
{
  assert(phead);

  ListNode* cur = phead->next;
  while (cur != phead && cur->data != pos)
  {
    if (cur->data == pos)
    {
      break;
    }
    cur = cur->next;
  }
  return cur;
}

2.11 链表指定位置的插入

在这里面不运用二级指针的原因就是我们改变的是结构体的变量,并不是结构体指针。

//链表pos插入
void DListInset( ListNode* pos,ListDataType x)
{
  assert(pos);
  
  ListNode* newcode = BuyNewNode(x);

  ListNode* cur = pos->prev;

  cur->next = newcode;
  newcode->prev = cur;
  newcode->next = pos;
  pos->prev = newcode;

}

2.12 链表指定位置的删除

//链表pos删除
void DListErase(ListNode* phead, ListNode* pos)
{
  assert(!ListEmpty(phead));
  assert(pos);

  ListNode* posprev = pos->prev;
  ListNode* posnext = pos->next;

  posprev->next = posnext;
  posnext->prev = posprev;
  free(pos);
    //pos = NULL;
  //由于一级指针,NULL改变不了形式参数
  
}

2.13 链表的销毁

//链表销毁
void DListDestory(ListNode* phead)
{
  assert(phead);

  ListNode* cur = phead->next;

  while (cur != phead)
  {
    ListNode* curnext = cur->next;
    free(cur);
    //在循环第一句自动下一步
    cur = curnext;
    
  }
  //释放哨兵位头节点
  //不需要置空,形参数
  free(phead);
}

源码

DList.h

#pragma once


#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
#include <stdbool.h>


typedef int ListDataType;

typedef struct DList
{
  int data;
  struct Dlist* prev;
  struct Dlist* next;

}ListNode;

//链表初始化创建哨兵卫
ListNode* ListInit();
//连接表增加节点
ListNode* BuyNewNode(ListDataType x);
//链表打印
void DlistPrint(ListNode* phead);
//链表尾插
void DLlistPushBack(ListNode* phead, ListDataType x);
//链表尾删
void DLlistPopBack(ListNode* phead);
//链表头插
void DLlistPushFront(ListNode* phead, ListDataType x);
//链表头删
void DLlistPopFront(ListNode* phead);
//链表查找
ListNode* DListFind(ListNode* phead, ListDataType pos);
//链表pos插入
void DListInset(ListNode* pos, ListDataType);
//链表pos删除
void DListErase(ListNode* phead, ListNode* pos);
//链表销毁
void DListDestory(ListNode* phead);

DList.c

#define  _CRT_SECURE_NO_WARNINGS   1

#include "DList.h"

ListNode* BuyNewNode(ListDataType x)
{
  ListNode* newcode = (ListNode*)malloc(sizeof(ListNode));
  if (newcode == NULL)
  {
    perror("malloc fail");
    return NULL;
  }

  newcode->data = x;
  newcode->next = NULL;
  newcode->prev = NULL;
  return newcode;

}

//链表初始化创建哨兵卫
ListNode* ListInit()
{
  ListNode* head = BuyNewNode(-1);
  head->next = head;
  head->prev = head;
  return head;
}

//链表打印
void DlistPrint(ListNode* phead)
{
  ListNode* cur = phead->next;
  printf("<-head->");
  while (cur != phead)
  {
    printf("%d<->", cur->data);
    cur = cur->next;
  }
  printf("\n");
}

//链表尾插
void DLlistPushBack(ListNode* phead, ListDataType x)
{
  assert(phead);
  ListNode* newcode = BuyNewNode(x);
  ListNode* tail = phead->prev;
  tail->next = newcode;
  newcode->prev = tail;
  newcode->next = phead;
  phead->prev = newcode;

}

//判断链表是不是空
bool ListEmpty(ListNode* phead)
{
  assert(phead);
  return phead->prev == phead;
}


//链表尾删
void DLlistPopBack(ListNode* phead)
{
  assert(phead);
  assert(!ListEmpty(phead));
  ListNode* tail = phead->prev;
  ListNode* tailprev = tail->prev;

  tailprev->next = phead;
  phead->prev = tailprev;
  free(tail);
  tail = NULL;
}

//链表头插
void DLlistPushFront(ListNode* phead, ListDataType x)
{
  assert(phead);

  ListNode* newcode = BuyNewNode(x);
  ListNode* cur = phead->next;
  phead->next = newcode;
  newcode->prev = phead;
  newcode->next = cur;
  cur->prev = newcode;
}

//链表头删
void DLlistPopFront(ListNode* phead)
{
  assert(phead);
  assert(!ListEmpty(phead));

  ListNode* cur = phead->next;
  ListNode* curnext = cur->next;
  phead->next = curnext;
  curnext->prev = phead;
  free(cur);
  cur = NULL;

}

//链表查找
ListNode* DListFind(ListNode* phead, ListDataType pos)
{
  assert(phead);

  ListNode* cur = phead->next;
  while (cur != phead && cur->data != pos)
  {
    if (cur->data == pos)
    {
      break;
    }
    cur = cur->next;
  }
  return cur;
}

//链表pos插入
void DListInset( ListNode* pos,ListDataType x)
{
  assert(pos);
  
  ListNode* newcode = BuyNewNode(x);

  ListNode* cur = pos->prev;

  cur->next = newcode;
  newcode->prev = cur;
  newcode->next = pos;
  pos->prev = newcode;

}

//链表pos删除
void DListErase(ListNode* phead, ListNode* pos)
{
  assert(!ListEmpty(phead));
  assert(pos);

  ListNode* posprev = pos->prev;
  ListNode* posnext = pos->next;

  posprev->next = posnext;
  posnext->prev = posprev;
  free(pos);
  //由于一级指针,NULL改变不了形式参数
  
}

//链表销毁
void DListDestory(ListNode* phead)
{
  assert(phead);

  ListNode* cur = phead->next;

  while (cur != phead)
  {
    ListNode* curnext = cur->next;
    free(cur);
    //在循环第一句自动下一步
    cur = curnext;
    
  }
  //释放哨兵位头节点
  //不需要值空,形参数
  free(phead);
}

test.c

#define  _CRT_SECURE_NO_WARNINGS   1

#include "DList.h"

void testDlist1()
{
  ListNode* plist = ListInit();
  DLlistPushBack(plist, 1);
  DLlistPushBack(plist, 2);
  DLlistPushBack(plist, 3);
  DLlistPushBack(plist, 4);

  
  ListNode* ret = DListFind(plist, 2); 
  //ret->data = (ret->data) * 2;
  DListInset(ret, 40);
  DListErase(plist, ret);

  DLlistPopBack(plist);

  DlistPrint(plist);

}
void testDlist2()
{
  ListNode* plist = ListInit();
  DLlistPushFront(plist, 1);
  DLlistPushFront(plist, 2);
  DLlistPushFront(plist, 3);
  DLlistPushFront(plist, 4);
  DLlistPushFront(plist, 5);

  DLlistPopFront(plist);
  DLlistPopFront(plist);



  DlistPrint(plist);

}


int main()
{
  testDlist1();
  //testDlist2();


  return 0;
}

目录
相关文章
|
8天前
|
Java C++ 索引
让星星⭐月亮告诉你,LinkedList和ArrayList底层数据结构及方法源码说明
`LinkedList` 和 `ArrayList` 是 Java 中两种常见的列表实现。`LinkedList` 基于双向链表,适合频繁的插入和删除操作,但按索引访问元素效率较低。`ArrayList` 基于动态数组,支持快速随机访问,但在中间位置插入或删除元素时性能较差。两者均实现了 `List` 接口,`LinkedList` 还额外实现了 `Deque` 接口,提供了更多队列操作。
13 3
|
8天前
|
存储 Java
HashMap之链表转红黑树(树化 )-treefyBin方法源码解读(所有涉及到的方法均有详细解读,欢迎指正)
本文详细解析了Java HashMap中链表转红黑树的机制,包括树化条件(链表长度达8且数组长度≥64)及转换流程,确保高效处理大量数据。
24 1
|
12天前
|
存储 Java
数据结构第三篇【链表的相关知识点一及在线OJ习题】
数据结构第三篇【链表的相关知识点一及在线OJ习题】
21 7
|
11天前
|
算法 Java
数据结构与算法学习五:双链表的增、删、改、查
双链表的增、删、改、查操作及其Java实现,并通过实例演示了双向链表的优势和应用。
10 0
数据结构与算法学习五:双链表的增、删、改、查
|
5天前
|
存储
[数据结构] -- 双向循环链表
[数据结构] -- 双向循环链表
12 0
|
10天前
|
存储
探索数据结构:便捷的双向链表
探索数据结构:便捷的双向链表
|
10天前
|
存储
探索数据结构:单链表的实践和应用
探索数据结构:单链表的实践和应用
|
11天前
|
算法 Java
数据结构与算法学习六:单向环形链表应用实例的约瑟夫环问题
这篇文章通过单向环形链表的应用实例,详细讲解了约瑟夫环问题的解决方案,并提供了Java代码实现。
12 0
|
4月前
|
存储 SQL 算法
LeetCode力扣第114题:多种算法实现 将二叉树展开为链表
LeetCode力扣第114题:多种算法实现 将二叉树展开为链表
|
4月前
|
存储 SQL 算法
LeetCode 题目 86:分隔链表
LeetCode 题目 86:分隔链表