带头循环双向链表详解 1

简介: 带头循环双向链表详解

一、什么是带头循环双向链表?

1.特点:

1.带头:有哨兵位节点,它不用存储数据。对链表进行插入删除操作时也不会影响改节点。


2.双向:组成链表的结构体中的结构体成员有数据,上一个节点的地址和下一个节点的地址


3.循环:链表的头结点存储了尾结点的地址,链表的尾结点存储了头节点的地址。


2.优点:

相比单向链表,双向循环链表的优点在于它的尾插找尾巴非常的快    因为它的头节点同时存储了上一个节点的地址,头的上一个即尾。相比无头链表,带头链表的好处在于当没有节点的时候,可以直接通过访问结构体成员的方式来增加相应的指针,而无头的话需要直接对地址进行修改,传变量的时候还需要传递二级指针   不仅不好理解,还易在实现的时候出错。


二、实现接口

1.前置准备

1.1需要的三个文件

先创建两个.c文件,再创建一个头文件,分别命名为test.c,list.c,list.h


test.c用来测试写好的接口                                   list.c存放实现接口的代码


list.h则存放对应函数,头文件,结构体的声明,这样在想使用链表的接口时,直接引用list.h即可,不需要引用别的头文件。


创建好的环境如图


1a7cabd794444545b510ebc98ef8a078.png


1.2结构体的创建和头文件的引用

这些内容放在list.h的文件中,到时引用就可以一条龙带走,不需要再引用别的内容

#pragma once//防止头文件二次引用
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
typedef int LTDateType;
//这样创建结构体数据类型,不仅是为了和int做区分
//也是为了到时更好的替换,想换直接把int改了就行
typedef struct listnode
{
  struct listnode* prev;//存放上一个节点的地址
  struct listnode* next;//存放下一个节点的地址
  LTDateType data;//该节点存放的数据
}listnode;

2.接口实现

2.1函数创建新节点

创建节点,虽然简单,但我们在很多操作中都会用到,因此把它单独分装成一个接口

listnode* buy_listnode(LTDateType x)
{
  listnode*newnode=(listnode*)malloc(sizeof(listnode));
  if (newnode == NULL)
  {
    perror("buy_listnode");//错误提示
    exit(-1);//节点都没创建出来,直接退出程序
  }
  newnode->data = x;//将新节点的数据初始化成我们需要的
  newnode->next = NULL;//不清楚插入的方式,先初始化成空
  newnode->prev = NULL;
}

2.2打印链表内容

非常经典的操作,遍历一遍链表即可,唯一需要注意的便是,哨兵节点不是链表中真正的成员,它只能算是实现链表的辅助,因此跳过哨兵节点进行打印

void print_list(listnode* phead)
{
  assert(phead);//哨兵节点地址不可能为空
  listnode* head = phead->next;
  //哨兵位节点不存储有效数据,因此phead->next才是头节点
  printf("head<=>");//纯属美观
  while (head != phead)//当head和phead相等意味着已经遍历完一遍链表
  {
    printf("%d<=>", head->data);
    head = head->next;
  }
  printf("\n");
}

2.3尾插新节点

void list_pushback(listnode*phead,LTDateType x)
//尾插一个新节点,此节点存储x
{
  listnode* newnode = buy_listnode(x);
  //创建一个我们需要的新节点
  listnode* tail = phead->prev;
  //先找尾,尾很显然是哨兵位节点的上一个节点
  tail->next = newnode;
  newnode->prev = tail;
  newnode->next = phead;
  phead->prev = newnode;
}

后面的4行代码是核心,单独在文章中解释,创建了一个新节点,要把它放到链表的末端,尾节点我们已经找到了,接下来就是链接即可


首先明确,新的尾巴是创建出来的新节点,但还没进行链接之前,尾巴还是之前的尾巴

原始链表

第一步:

第二步:

 

第三步:

第四步:

测试代码:

#include"list.h"
void test1()
{
  listnode* plist=NULL;
  plist=init_listnode(plist);
  list_pushback(plist,1);
  list_pushback(plist,2);
  list_pushback(plist,3);
  list_pushback(plist,4);
  print_list(plist);
}
int main()
{
  test1();
}


测试效果:


28d2a384836343c584c689046ae77f93.png


2.4头插新节点

这里我就不再画图了,自己画一遍比看别人画一万遍都来的快

void list_pushfront(listnode* phead, LTDateType x)
{
  listnode* head = phead->next;//找到头节点
  listnode* newnode = buy_listnode(x);//创建新节点
  head->prev = newnode;
  newnode->next = head;
  phead->next = newnode;
  newnode->prev = phead;
}

测试代码:

void test2()
{
  listnode* plist = NULL;
  plist = init_listnode(plist);
  list_pushfront(plist, 1);
  list_pushfront(plist, 2);
  print_list(plist);
  list_pushfront(plist, 10086);
  print_list(plist);
}
int main()
{
  test2();
}

测试效果:  

3446a16f7b414808a7f883c6d5dfd240.png

2.5头删节点

需要注意的一点便是,我们删的节点不是哨兵节点,哨兵节点是不存放有效数据的,我们删除的是头节点

void list_popfront(listnode*phead)
{
  assert(phead);
  if (phead->next == phead)
  {
    printf("链表为空,操作失败\n");//为空就别删了
    return;
  }
  listnode* head = phead->next;//找到头节点
  phead->next = head->next;
  head->next->prev = phead;
  free(head);//链接完成,彻底删除
}

测试代码:

void test3()
{
  listnode* plist = NULL;
  plist = init_listnode(plist);
  list_pushfront(plist, 1);
  list_pushfront(plist, 2);
  print_list(plist);
  list_pushfront(plist, 10086);
  print_list(plist);
  list_popfront(plist);
  print_list(plist);
  list_popfront(plist);
  print_list(plist);
  list_popfront(plist);
  print_list(plist);
  list_popfront(plist);
  print_list(plist);
}
int main()
{
  test3();
}

测试效果:

735c0733d1504a37b9d6a1466d8bccc7.png


相关文章
|
4月前
|
存储 算法 C语言
C语言手撕实战代码_循环单链表和循环双链表
本文档详细介绍了用C语言实现循环单链表和循环双链表的相关算法。包括循环单链表的建立、逆转、左移、拆分及合并等操作;以及双链表的建立、遍历、排序和循环双链表的重组。通过具体示例和代码片段,展示了每种算法的实现思路与步骤,帮助读者深入理解并掌握这些数据结构的基本操作方法。
|
8月前
特殊链表(循环单链表,循环双链表,静态链表)
特殊链表(循环单链表,循环双链表,静态链表)
64 3
|
8月前
|
存储
【数据结构】【版本1.2】【线性时代】——链表之王(双向带头循环)
【数据结构】【版本1.2】【线性时代】——链表之王(双向带头循环)
图文版实现无头非循环单链表的增加和删除操作
图文版实现无头非循环单链表的增加和删除操作
不带头非循环的单向链表的增删查改的实现(代码版)
不带头非循环的单向链表的增删查改的实现(代码版)
|
8月前
|
Python Go Java
Golang每日一练(leetDay0042) 二叉树最大路径和、回文串、单词接龙II
Golang每日一练(leetDay0042) 二叉树最大路径和、回文串、单词接龙II
59 0
Golang每日一练(leetDay0042) 二叉树最大路径和、回文串、单词接龙II
|
8月前
|
Python C++ Java
C/C++每日一练(20230405) 数组元素循环右移、输出字符图形、移除链表元素
C/C++每日一练(20230405) 数组元素循环右移、输出字符图形、移除链表元素
54 0
C/C++每日一练(20230405) 数组元素循环右移、输出字符图形、移除链表元素
|
8月前
|
存储 缓存 程序员
手撕【双向链表】带头双向循环(2)
手撕【双向链表】带头双向循环(2)
39 0
|
存储 算法
数据结构单链表之检测和删除链表中的循环 | 第十三套
数据结构单链表之检测和删除链表中的循环 | 第十三套
60 0
|
存储 C++
【双向链表】带头双向循环(1)
【双向链表】带头双向循环(1)
66 0

热门文章

最新文章