C++实现线性表 - 03 双向循环链表

简介: 上一讲我们学会了如何创建一个单链表,这一讲我们来看看双向循环链表是如何进行操作的,我相信经过上面的学习,这一讲对你来说不会太吃力~
写在前面:
上一讲我们学会了如何创建一个单链表,这一讲我们来看看双向循环链表是如何进行操作的,我相信经过上面的学习,这一讲对你来说不会太吃力~

什么是双向链表 

9.jpg

正如上图所示,双向链表就只是在单向链表的基础上,增加了一个指向上一个结点的指针,操作上就只用多考虑一个指针罢了。而双向循环链表就是在双向链表的基础上将头尾结点也连接起来,如下图所示。

10.jpg

另外要注意的是,我们这里的头指针和尾指针指向的结点不存放数据,这样可能会更易于初学者去理解(我当初就是这样的doge),当然你也可以不用尾指针,因为头结点的指向上一个结点的指针就是最后一个结点,这里全凭个人喜好啦~

链表的创建

这里的结点的结构体就增加了一个front指针。

struct Node {
    int val;        //数据
    Node* front;    //指向上一个结点的指针
    Node* next;        //指向下一个结点的指针
};

我们这里初始化的时候顺带创建一个存储数据的新结点,这样我们的尾结点的front在头插法的时候也能一直指向最后一个结点啦。

void initnode(int key) {
    Node* new_node = new Node;
    new_node->val = key;
    head = new Node;
    last = new Node;
    //将新结点连接到头结点和尾结点当中
    head->next = new_node;
    last->front = new_node;
    //将头尾结点相连
    head->front = last;
    last->next = head;
    head->val = last->val = -1;    //将两个结点赋一个不可能的值
}

头插法

其实双向链表的插入操作相对于单向链表而言会更加方便,因为想要插入一个结点到链表中的话不用找到删除结点的上一个结点了,直接调用删除结点的front就可以找到,我们这里还是给大家讲解头插法和尾插法的做法。
11.jpg

12.jpg

13.jpg

 其实做法和单向链表差不了多少,直接来看代码。

void head_insert(int key) {
    Node* new_node = new Node;
    new_node->val = key;
    new_node->next = head->next;    //将新结点的next指向头结点的next
    new_node->front = head;            //将新结点的front指向头结点
    head->next->front = new_node;    //将头结点的下一个结点的front指向新结点
    head->next = new_node;            //将头结点的next指向新结点
}

尾插法

尾插法和头插法几乎没什么差别,同样直接来看代码。

void end_insert(int key) {
    Node* new_node = new Node;
    new_node->val = key;
    new_node->next = last;                //将新结点的next指向尾结点
    new_node->front = last->front;        //将新结点的front指向尾结点的上一个结点
    new_node->front->next = new_node;    //将新结点的上一个结点的下一个结点指向新结点
    last->front = new_node;                //将尾结点的front指向新结点
}

删除结点

删除结点的操作和单向链表几乎一样,而且上面提到过,咱不需要另外创建一个临时指针寻找删除结点的上一个结点,直接调用删除结点的front即可,不过这里方便大家理解,我们创建三个临时指针,分别指向删除结点的上一个结点、删除结点和删除结点的下一个结点。

void delete_node(int key) {
    Node* temp = head->next;
    while (temp->val != key){
        //判断是否有该结点
        if (temp->val == -1){
            cout << "没有此结点" << endl;
            return;
        }
        temp = temp->next;
    }

    Node* Prev = temp->front;    //创建临时结点指向删除结点的上一个结点
    Node* Next = temp->next;    //创建临时结点指向删除结点的下一个结点
    Prev->next = Next;            //删除结点的上一个结点的next指向删除结点的下一个结点
    Next->front = Prev;            //删除结点的下一个节点的front指向删除结点的上一个结点
    /*
    也可以不用创建Prev和Next指针
    temp->front->next = temp->next;
    temp->next->front = temp->front;
    */

    temp->front = temp->next = NULL;
    delete[]temp;
    temp = NULL;
}

遍历链表

双向链表的遍历和我们的单向链表的遍历是几乎一样的,直接看代码!

void show()
{
    Node* temp = head->next;  //创建一个临时指针
    if (temp->val == -1)
    {
        cout << "当前还没有创建结点" << endl;
        return;
    }
    while (temp->val != -1)      //遍历整个链表
    {
        cout << temp->val << " ";
        temp = temp->next;
    }
    cout << endl;
}

全部代码

#include <bits/stdc++.h>
using namespace std;

struct Node {
    int val;        //数据
    Node* front;    //指向上一个结点的指针
    Node* next;        //指向下一个结点的指针
};

Node* head;        //头指针,设为全局变量方便使用
Node* last;        //尾指针

void initnode(int);
void head_insert(int);
void end_insert(int);
void delete_node(int);
void show();

int main() {
    initnode(1);
    head_insert(5);
    head_insert(2);
    end_insert(3);
    show();
    delete_node(2);
    show();
}

void initnode(int key) {
    Node* new_node = new Node;
    new_node->val = key;
    head = new Node;
    last = new Node;
    //将新结点连接到头结点和尾结点当中
    head->next = new_node;
    last->front = new_node;
    new_node->next = last;
    new_node->front = head;
    //将头尾结点相连
    head->front = last;
    last->next = head;
    head->val = last->val = -1;    //将两个结点赋一个不可能的值
}

void head_insert(int key) {
    Node* new_node = new Node;
    new_node->val = key;
    new_node->next = head->next;    //将新结点的next指向头结点的next
    new_node->front = head;            //将新结点的front指向头结点
    head->next->front = new_node;    //将头结点的下一个结点的front指向新结点
    head->next = new_node;            //将头结点的next指向新结点
}

void end_insert(int key) {
    Node* new_node = new Node;
    new_node->val = key;
    new_node->next = last;                //将新结点的next指向尾结点
    new_node->front = last->front;        //将新结点的front指向尾结点的上一个结点
    new_node->front->next = new_node;    //将新结点的上一个结点的下一个结点指向新结点
    last->front = new_node;                //将尾结点的front指向新结点
}

void delete_node(int key) {
    Node* temp = head->next;
    while (temp->val != key){
        //判断是否有该结点
        if (temp->val == -1){
            cout << "没有此结点" << endl;
            return;
        }
        temp = temp->next;
    }

    Node* Prev = temp->front;    //创建临时结点指向删除结点的上一个结点
    Node* Next = temp->next;    //创建临时结点指向删除结点的下一个结点
    Prev->next = Next;            //删除结点的上一个结点的next指向删除结点的下一个结点
    Next->front = Prev;            //删除结点的下一个节点的front指向删除结点的上一个结点
    /*
    也可以不用创建Prev和Next指针
    temp->front->next = temp->next;
    temp->next->front = temp->front;
    */

    temp->front = temp->next = NULL;
    delete[]temp;
    temp = NULL;
}

void show()
{
    Node* temp = head->next;  //创建一个临时指针
    if (temp->val == -1)
    {
        cout << "当前还没有创建结点" << endl;
        return;
    }
    while (temp->val != -1)      //遍历整个链表
    {
        cout << temp->val << " ";
        temp = temp->next;
    }
    cout << endl;
}

如果大家有什么问题的话,欢迎在下方评论区进行讨论哦~

目录
相关文章
|
存储 监控 算法
公司监控上网软件架构:基于 C++ 链表算法的数据关联机制探讨
在数字化办公时代,公司监控上网软件成为企业管理网络资源和保障信息安全的关键工具。本文深入剖析C++中的链表数据结构及其在该软件中的应用。链表通过节点存储网络访问记录,具备高效插入、删除操作及节省内存的优势,助力企业实时追踪员工上网行为,提升运营效率并降低安全风险。示例代码展示了如何用C++实现链表记录上网行为,并模拟发送至服务器。链表为公司监控上网软件提供了灵活高效的数据管理方式,但实际开发还需考虑安全性、隐私保护等多方面因素。
333 0
公司监控上网软件架构:基于 C++ 链表算法的数据关联机制探讨
|
存储 算法 测试技术
【C++数据结构——线性表】求集合的并、交和差运算(头歌实践教学平台习题)【合集】
本任务要求编写程序求两个集合的并集、交集和差集。主要内容包括: 1. **单链表表示集合**:使用单链表存储集合元素,确保元素唯一且无序。 2. **求并集**:遍历两个集合,将所有不同元素加入新链表。 3. **求交集**:遍历集合A,检查元素是否在集合B中存在,若存在则加入结果链表。 4. **求差集**:遍历集合A,检查元素是否不在集合B中,若满足条件则加入结果链表。 通过C++代码实现上述操作,并提供测试用例验证结果。测试输入为两个集合的元素,输出为有序集合A、B,以及它们的并集、交集和差集。 示例测试输入: ``` a c e f a b d e h i ``` 预期输出:
546 7
|
机器学习/深度学习 存储 C++
【C++数据结构——线性表】单链表的基本运算(头歌实践教学平台习题)【合集】
本内容介绍了单链表的基本运算任务,涵盖线性表的基本概念、初始化、销毁、判定是否为空表、求长度、输出、求元素值、按元素值查找、插入和删除数据元素等操作。通过C++代码示例详细解释了顺序表和链表的实现方法,并提供了测试说明、通 - **任务描述**:实现单链表的基本运算。 - **相关知识**:包括线性表的概念、初始化、销毁、判断空表、求长度、输出、求元素值、查找、插入和删除等操作。 - **测试说明**:平台会对你编写的代码进行测试,提供测试输入和预期输出。 - **通关代码**:给出了完整的C++代码实现。 - **测试结果**:展示了测试通过后的预期输出结果。 开始你的任务吧,祝你成功!
710 5
|
机器学习/深度学习 存储 C++
【C++数据结构——线性表】顺序表的基本运算(头歌实践教学平台习题)【合集】
本文档介绍了线性表的基本运算任务,涵盖顺序表和链表的初始化、销毁、判定是否为空、求长度、输出、查找元素、插入和删除元素等内容。通过C++代码示例详细展示了每一步骤的具体实现方法,并提供了测试说明和通关代码。 主要内容包括: - **任务描述**:实现顺序表的基本运算。 - **相关知识**:介绍线性表的基本概念及操作,如初始化、销毁、判定是否为空表等。 - **具体操作**:详述顺序表和链表的初始化、求长度、输出、查找、插入和删除元素的方法,并附有代码示例。 - **测试说明**:提供测试输入和预期输出,确保代码正确性。 - **通关代码**:给出完整的C++代码实现,帮助完成任务。 文档
538 5
|
存储 C++
C++的list-map链表与映射表
```markdown C++ 中的`list`和`map`提供链表和映射表功能。`list`是双向链表,支持头尾插入删除(`push_front/push_back/pop_front/pop_back`),迭代器遍历及任意位置插入删除。`map`是键值对集合,自动按键排序,支持直接通过键来添加、修改和删除元素。两者均能使用范围for循环遍历,`map`的`count`函数用于统计键值出现次数。 ```
329 1
|
C++ Python
UE C++ 链表
UE C++ 链表
|
C++ 容器
【C++进阶】深入STL之list:高效双向链表的使用技巧
【C++进阶】深入STL之list:高效双向链表的使用技巧
|
编译器 C++ 开发者
【C++篇】深度解析类与对象(下)
在上一篇博客中,我们学习了C++的基础类与对象概念,包括类的定义、对象的使用和构造函数的作用。在这一篇,我们将深入探讨C++类的一些重要特性,如构造函数的高级用法、类型转换、static成员、友元、内部类、匿名对象,以及对象拷贝优化等。这些内容可以帮助你更好地理解和应用面向对象编程的核心理念,提升代码的健壮性、灵活性和可维护性。
|
编译器 C++ 容器
【c++11】c++11新特性(上)(列表初始化、右值引用和移动语义、类的新默认成员函数、lambda表达式)
C++11为C++带来了革命性变化,引入了列表初始化、右值引用、移动语义、类的新默认成员函数和lambda表达式等特性。列表初始化统一了对象初始化方式,initializer_list简化了容器多元素初始化;右值引用和移动语义优化了资源管理,减少拷贝开销;类新增移动构造和移动赋值函数提升性能;lambda表达式提供匿名函数对象,增强代码简洁性和灵活性。这些特性共同推动了现代C++编程的发展,提升了开发效率与程序性能。
567 12
|
编译器 C语言 C++
类和对象的简述(c++篇)
类和对象的简述(c++篇)