服务器之家:专注于VPS、云服务器配置技术及软件下载分享
分类导航

PHP教程|ASP.NET教程|Java教程|ASP教程|编程技术|正则表达式|C/C++|IOS|C#|Swift|Android|VB|R语言|JavaScript|易语言|vb.net|

服务器之家 - 编程语言 - C/C++ - C语言 超详细介绍与实现线性表中的带头双向循环链表

C语言 超详细介绍与实现线性表中的带头双向循环链表

2022-11-01 15:29李逢溪 C/C++

带头双向循环链表:结构最复杂,一般用在单独存储数据。实际中使用的链表数据结构,都是带头双向循环链表。另外这个结构虽然结构复杂,但是使用代码实现以后会发现结构会带来很多优势,实现反而简单

一、本章重点

  • 带头双向循环链表介绍
  • 带头双向循环链表常用接口实现
  • 实现接口总结
  • 在线oj训练与详解

二、带头双向循环链表介绍

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

  • 带头:存在一个哨兵位的头节点,该节点是个无效节点,不存储任何有效信息,但使用它可以方便我们头尾插和头尾删时不用判断头节点指向NULL的情况,同时也不需要改变头指针的指向,也就不需要传二级指针了。 
  • 双向:每个结构体有两个指针,分别指向前一个结构体和后一个结构体。
  • 循环:最后一个结构体的指针不再指向NULL,而是指向第一个结构体。(单向)
  • 第一个结构体的前指针指向最后一个结构体,最后一个结构体的后指针指向第一个结构体(双向)。

图解 

C语言 超详细介绍与实现线性表中的带头双向循环链表

2.2最常用的两种链表结构

  • 更具有无头,单双向,是否循环组合起来有8种结构,但最长用的还是无头单向非循环链表和带头双向循环链表
  • 无头单向非循环链表:结构简单,一般不会单独用来存数据。实际中更多是作为其他数据结构的子结构,如哈希桶、图的邻接表等等。另外这种结构在笔试面试中出现很多。 
  • 带头双向循环链表:结构最复杂,一般用在单独存储数据。实际中使用的链表数据结构,都是带头双向循环链表。另外这个结构虽然结构复杂,但是使用代码实现以后会发现结构会带来很多优势,实现反而简单了,后面我们代码实现了就知道了。

三、带头双向循环链表常用接口实现 

3.1结构体创建

?
1
2
3
4
5
6
7
typedef int DataType;
typedef struct DListNode
{
    DataType data;
    DListNode* prev;
    DListNode* next;
}DListNode;

3.2带头双向循环链表的初始化 

?
1
2
3
4
5
6
void DListInint(DListNode** pphead)
{
    *pphead = (DListNode*)malloc(sizeof(DListNode));
    (*pphead)->next = (*pphead);
    (*pphead)->prev = (*pphead);
}

 或者使用返回节点的方法也能实现初始化

?
1
2
3
4
5
6
7
DListNode* DListInit()
    {
        DListNode* phead = (DListNode*)malloc(sizeof(DListNode));
        phead->next = phead;
        phead->prev = phead;
        return phead;
    }

3.3创建新节点

?
1
2
3
4
5
6
7
8
9
10
11
12
13
DListNode* BuyDListNode(DataType x)
{
    DListNode* temp = (DListNode*)malloc(sizeof(DListNode));
    if (temp == NULL)
    {
        printf("malloc fail\n");
        exit(-1);
    }
    temp->prev = NULL;
    temp->next = NULL;
    temp->data = x;
    return temp;
}

3.4尾插

?
1
2
3
4
5
6
7
8
9
void DListPushBack(DListNode* phead,DataType x)
{
    DListNode* newnode = BuyDListNode(x);
    DListNode* tail = phead->prev;
    tail->next = newnode;
    newnode->prev = tail;
    newnode->next = phead;
    phead->prev = newnode;
}

3.5打印链表

?
1
2
3
4
5
6
7
8
9
10
void DListNodePrint(DListNode* phead)
{
    DListNode* cur = phead->next;
    while (cur != phead)
    {
        printf("%d->", cur->data);
        cur = cur->next;
    }
    printf("NULL\n");
}

3.6头插

?
1
2
3
4
5
6
7
8
9
void DListNodePushFront(DListNode* phead, DataType x)
{
    DListNode* next = phead->next;
    DListNode* newnode = BuyDListNode(x);
    next->prev = newnode;
    newnode->next = next;
    newnode->prev = phead;
    phead->next = newnode;
}

3.7尾删

?
1
2
3
4
5
6
7
8
9
10
11
12
13
void DListNodePopBack(DListNode* phead)
{
    if (phead->next == phead)
    {
        return;
    }
    DListNode* tail = phead->prev;
    DListNode* prev = tail->prev;
    prev->next = phead;
    phead->prev = prev;
    free(tail);
    tail = NULL;
}

3.8头删

?
1
2
3
4
5
6
7
8
9
10
11
12
13
void DListNodePopFront(DListNode* phead)
{
    if (phead->next == phead)
    {
        return;
    }
    DListNode* firstnode = phead->next;
    DListNode* secondnode = firstnode->next;
    secondnode->prev = phead;
    phead->next = secondnode;
    free(firstnode);
    firstnode = NULL;
}

3.9查找data(返回data的节点地址)

?
1
2
3
4
5
6
7
8
9
10
11
12
13
DListNode* DListNodeFind(DListNode* phead, DataType x)
{
    DListNode* firstnode = phead->next;
    while (firstnode != phead)
    {
        if (firstnode->data == x)
        {
            return firstnode;
        }
        firstnode = firstnode->next;
    }
    return NULL;
}

3.10在pos位置之前插入节点

?
1
2
3
4
5
6
7
8
9
void DListNodeInsert(DListNode* pos, DataType x)
{
    DListNode* prev = pos->prev;
    DListNode* newnode = BuyDListNode(x);
    newnode->next = pos;
    newnode->prev = prev;
    prev->next = newnode;
    pos->prev = newnode;
}

3.11删除pos位置的节点

?
1
2
3
4
5
6
7
8
9
void DListNodeErase(DListNode* pos)
{
    DListNode* prev = pos->prev;
    DListNode* next = pos->next;
    prev->next = next;
    next->prev = prev;
    free(pos);
    pos = NULL;
}

四、实现接口总结

  • 多画图:能给清晰展示变化的过程,有利于实现编程。
  • 小知识:head->next既可表示前一个结构体的成员变量,有可表示后一个结构体的地址。当head->next作为左值时代表的是成员变量,作右值时代表的是后一个结构体的地址。对于链表来说理解这一点非常重要。
  • 实践:实践出真知
  • 带头双向循环链表:相比于单链表,它实现起来更简单,不用向单链表一样分情况讨论链表的长度。虽然结构较复杂,但使用起来更简单,更方便。  

五、在线oj训练与详解

链表的中间节点(力扣)

给定一个头结点为 head 的非空单链表,返回链表的中间结点。

如果有两个中间结点,则返回第二个中间结点。

输入:[1,2,3,4,5]

输出:此列表中的结点 3 (序列化形式:[3,4,5])

返回的结点值为 3 。 (测评系统对该结点序列化表述是 [3,4,5])。

注意,我们返回了一个 ListNode 类型的对象 ans,

这样:

ans.val = 3, ans.next.val = 4, ans.next.next.val = 5, 以及 ans.next.next.next = NULL.

来源:力扣(LeetCode)

 思路:快慢指针

取两个指针,初始时均指向head,一个为快指针(fast)一次走两步,另一个为慢指针(slow)一次走一步,当快指针满足fast==NULL(偶数个节点)或者fast->next==NULL(奇数个节点)时,slow指向中间节点,返回slow即可。

?
1
2
3
4
5
6
7
8
9
10
11
struct ListNode* middleNode(struct ListNode* head)
{
    struct ListNode* fast=head;
    struct ListNode* slow=head;
    while(fast&&fast->next)
    {
        fast=fast->next->next;
        slow=slow->next;
    }
    return slow;
}

到此这篇关于C语言 超详细介绍与实现线性表中的带头双向循环链表的文章就介绍到这了,更多相关C语言 双向循环链表内容请搜索服务器之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持服务器之家!

原文链接:https://blog.csdn.net/m0_62171658/article/details/123315681

延伸 · 阅读

精彩推荐
  • C/C++详解计数排序算法及C语言程序中的实现

    详解计数排序算法及C语言程序中的实现

    技术排序算法与我们普通接触的冒泡排序和快速排序等基于元素比较的算法不同,在编程中通过C语言的数组能够清除地表达出来,这里我们就来详解计数排序...

    zhoutk10692021-04-08
  • C/C++浅谈C++类型转化(运算符重载函数)和基本运算符重载(自增自减)

    浅谈C++类型转化(运算符重载函数)和基本运算符重载(自增自减

    下面小编就为大家带来一篇浅谈C++类型转化(运算符重载函数)和基本运算符重载(自增自减)。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一...

    C++教程网11172021-05-17
  • C/C++Qt自定义控件实现线条型加载条

    Qt自定义控件实现线条型加载条

    这篇文章主要为大家详细介绍了Qt自定义控件实现线条型加载条,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...

    parkchorong11422021-08-10
  • C/C++用C语言实现扫雷小游戏

    用C语言实现扫雷小游戏

    这篇文章主要为大家详细介绍了用C语言实现扫雷小游戏,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...

    技术新人王小明4312021-11-15
  • C/C++基于MFC实现自定义复选框效果

    基于MFC实现自定义复选框效果

    复选框是一种可同时选中多项的基础控件,主要是有两种明显的状态:选中与非选中。本文将通过MFC框架实现自定义复选框效果,感兴趣的可以了解一下...

    中国好公民st6712022-09-15
  • C/C++C++迭代器iterator详解

    C++迭代器iterator详解

    这篇文章主要为大家详细介绍了C++迭代器模式Iterator,具有一定的参考价值,感兴趣的小伙伴们可以参考一下希望能给你带来帮助...

    久病成良医7052021-12-18
  • C/C++详解为什么指针被誉为C语言灵魂

    详解为什么指针被誉为C语言灵魂

    说到指针,就不可能脱离开内存,学会指针的人分为两种,一种是不了解内存模型,另外一种则是了解。不了解的对指针的理解就停留在“指针就是变量的...

    RioTian8902021-11-16
  • C/C++C++中新手容易犯的十种编程错误汇总

    C++中新手容易犯的十种编程错误汇总

    一段C语言代码,在编译、链接和运行的各个阶段都可能会出现问题,下面这篇文章主要给大家介绍了关于C++中新手容易犯的十种编程错误的相关资料,需要的...

    IT老张4222022-02-13