浅谈【数据结构】链表之双链表

目录

1、删除结点

2、双向链表

2.1增加结点

2.2删除结点


谢谢帅气美丽且优秀的你看完我的文章还要点赞、收藏加关注

没错,说的就是你,不用再怀疑!!!

希望我的文章内容能对你有帮助,一起努力吧!!!


本文主要以图示+代码为主

数据结构最多的操作:增删改查

1、删除结点

但是我们发现单链表存在一个弊端:不能回溯(没法直接回到前一个结点)

有木有方法可以解决这个问题?

双向链表它可以解决单向链表只有一个指针指向下一个结点没法回溯的这么一个问题。

2、双向链表

双向链表它包含数据域和指针域(但是指针域相对单链表而言多出了一个指针:prev指针)

cpp 复制代码
struct node
{
// 数据域
    ElementType data;
// 指针域
    struct node *prev; // 前指针
    struct node *next; // 后指针
};

2.1增加结点

图示:

头插:

尾插:

中间位置插入:

2.2删除结点

图示:删除头结点 删除中间节点 删除尾结点 图示示例+代码示例

***双链表示例***

cpp 复制代码
/*
    双向链表
*/
#include <iostream>
// 换一种方式来实现双向链表:对象

template <typename DataType>
class ddl
{
public:
    ddl(); // 创建一个双向链表
    ~ddl(); // 销毁一个双向链表

    // 增删改查

    // 增加
    bool addNode(DataType d)
    {
        // 中、头、尾插入...

        // 尾插法
        // 如果链表是空的那么需要将新节点作为第一个结点增加
        if(firstNode == nullptr)
        {
            firstNode = new struct node;
            firstNode->data = d;
            firstNode->next = nullptr;
            firstNode->prev = nullptr;
        }
        else
        {
            // 先找尾结点
            struct node *tail_ptr = firstNode;
            while(tail_ptr->next)tail_ptr = tail_ptr->next;

            // 进行插入
            struct node *newNode = new struct node;
            newNode->data = d;
            newNode->next = nullptr;
            newNode->prev = nullptr;
            tail_ptr->next = newNode;
            newNode->prev  = tail_ptr;
        }
        length ++;
        return true;
    }

    // 删除
    bool delNode(DataType d)
    {
        if(length == 0)
            return false;
        
        // 查找要删除的结点
        struct node *delNode_ptr = firstNode;
        while(delNode_ptr)
        {
            // 进行数据比较,是否尾需要删除的结点
            if(delNode_ptr->data == d)
            {
                // 进行删除
                // 需要判断一下是不是只有一个元素
                if(length == 1)
                {
                    // 断开当前结点的指向,让它脱离链表
                    firstNode->next = nullptr;
                    firstNode->prev = nullptr;
                    delete firstNode;
                    length -- ;
                    return true;
                }

                // 第一步:把前驱和后继的指向更新
                    // 更新前驱结点指向:让前驱结点的next指针指向当前结点的后继结点
                    // delNode_ptr 当前结点,delNode_ptr->prev 就是当前结点的前驱结点
                    if(delNode_ptr->prev != nullptr)
                        (delNode_ptr->prev)->next = (delNode_ptr->next);

                    // 更新后继结点的指向:让后继结点的prev指针指向当前结点的前驱结点
                    // delNode_ptr 当前结点,delNode_ptr->next 就是当前结点的后继结点
                    if(delNode_ptr->next != nullptr)
                        (delNode_ptr->next)->prev = (delNode_ptr->prev);

                // 第二步:断开当前结点的指向,让它脱离链表
                delNode_ptr->next = nullptr;
                delNode_ptr->prev = nullptr;

                // 第三步:释放当前结点的空间
                delete delNode_ptr;

                length -- ;
                return true;
            }

            // 更新指针
            delNode_ptr = delNode_ptr->next;
        }
        return false;
    }

    // 修改
    bool chgNode(DataType od,DataType nd)
    {
        if(length == 0)
        {
            std::cout << "修改失败:空的" << std::endl;
            return false;
        }

        // 查找需要修改的元素
        struct node *chgNode_ptr = firstNode;
        while(chgNode_ptr)
        {
            // 判断是不是需要修改的元素
            if(chgNode_ptr->data == od)
            {
                chgNode_ptr->data = nd;
                return true;
            }

            chgNode_ptr = chgNode_ptr->next;
        }
        std::cout << "修改失败:没有找到指定元素" << std::endl;
        return false;
    }

    // 查找
    bool srhNode(DataType d)
    {
        if(length == 0)
        {
            std::cout << "查找失败:空的" << std::endl;
            return false;
        }

        // 查找需要修改的元素
        struct node *srhNode_ptr = firstNode;
        while(srhNode_ptr)
        {
            // 判断是不是需要修改的元素
            if(srhNode_ptr->data == d)
                return true;

            srhNode_ptr = srhNode_ptr->next;
        }
        std::cout << "查找失败:没有找到指定元素" << std::endl;
        return false;
    }
    

    // 打印
    void prtList()
    {
        // for(int i = 0;i < length;i++)
        // {
        //     std::cout << node_ptr->data;
        //     node_ptr = node_ptr->next;
        // }

        struct node *node_ptr = firstNode;
        std::cout << "DoubleDirectionList( ";
        while(node_ptr)
        {
            std::cout << node_ptr->data << " ";
            node_ptr = node_ptr->next;
        }
        std::cout << ")"<<std::endl;
    }

private:

    // 首结点地址
    struct node
    {
        DataType data;
        struct node *prev;
        struct node *next;
    }*firstNode;

    // // 尾结点地址
    // struct node
    // {
    //     DataType data;
    //     struct node *prev;
    //     struct node *next;
    // }lastNode;

    // 统计一下个数
    int length;
};


template <typename DataType>
ddl<DataType>::ddl()
    : firstNode(nullptr)
    , length(0)
{
}

// 析构:用于销毁链表
template <typename DataType>
ddl<DataType>::~ddl()
{
    struct node *delNode_ptr = firstNode;
    while(delNode_ptr)
    {
        // 更新指针
        firstNode = firstNode->next;

        // 销毁结点
        delNode_ptr->next = nullptr;
        delNode_ptr->prev = nullptr;

        delete delNode_ptr;
        length--;
        delNode_ptr = firstNode;
    }
}


int main()
{
    ddl<int> d;
    d.addNode(10);
    d.addNode(11);
    d.addNode(13);

    d.prtList();

    d.chgNode(10,100);
    d.prtList();

    d.delNode(13);
    d.prtList();
    // d.delNode(13);
    // d.prtList();
    return 0;
}

***增删改查***

cpp 复制代码
/*
    无头结点的单链表
*/

#include <iostream>

/*
    结点类型
*/
struct node
{
    // 用来存储数据的空间(成员)称为:数据域
    int data;       

    // 用来保存其他结点的地址(关系)称为:指针域
    struct node *next;
};



//----------------------------------------------------------增加----------------------------------------------------
/*
    @brief:头部插入
    @param: list 需要增加新数据的链表指针
    @param: data 是需要存入链表的数据
    @return : 链表首地址
*/
struct node* addNodeHead(struct node *list,int data)
{
    // list 表示就是一个链表(链表首地址/首结点的地址)头插法其实就是在它的首结点前面插入
    struct node *newNode = new struct node;
    newNode->data = data; // 数据存入结点数据域
    newNode->next = list; // 因为新结点作为新的首结点存入链表,那么原有的首结点就变成了新节点后继结点
    return newNode; // 返回newNode因为newNode变成了新的首结点了。更新链表的首地址
}

/*
    @brief: 尾部插入
    @param: list 需要增加新数据的链表指针
    @param: data 是需要存入链表的数据
    @return : 链表首地址
*/
struct node* addNodeTail(struct node *list,int data)
{
    struct node *newNode = new struct node;
    newNode->data = data; // 将通过键盘获取到的数据存入结构体的数据域中
    newNode->next = nullptr; // 因为它是一个新结点,暂时是没有后继结点

    // 搞一个临时指针,来指向首结点
    struct node *node_ptr = list;

    // 找尾结点
    while(node_ptr->next)node_ptr = node_ptr->next;
    
    // 到这个位置 node_ptr 此时指向的结点是尾结点
    // 就可以把newNode作为尾结点的后继结点添加到链表里面去了
    node_ptr->next = newNode;

    return list;
}

/*
    @brief : 中间插入法
    @param : list 需要增加数据的链表首地址
    @param : data 需要增加到链表的数据
    @return : 链表首地址
*/
struct node *addNodeMid(struct node *list,int data)
{
    struct node *newNode = new struct node;
    newNode->data = data; // 将通过键盘获取到的数据存入结构体的数据域中
    newNode->next = nullptr; // 因为它是一个新结点,暂时是没有后继结点

    // 循环比较数据 大到小  小到大
    // struct node *node_previce = nullptr;
    // struct node *node_current = list;

    // 第一种方法两个指针
    // while(node_current)
    // {
    //     if(node_current->data > data)
    //         break; // 找到插入位置了
        
    //     // 把两个指针往后移
    //     node_previce = node_current;
    //     node_current = node_current->next;
    // }

    // // 进行插入
    // if(node_previce == nullptr) // 说明需要作为首结点插入(头插入)
    // {
    //     // 头插代码
    // }
    // else
    // {
    //     // 直接插入到node_previce的后面
    //     newNode->next = node_previce->next; // 要成为node_previce下一个结点的前驱结点
    //     node_previce->next = newNode; // 然后让newNode成为node_previce的后继结点
    // }

    // 第二种方法提前判断
    struct node *node_ptr = list;
    // 如果第一个结点就比data大说明要插入到最前面(头插法)
    if(node_ptr->data > data)
    {
        // 头插法
        list = addNodeHead(list,data);
        return list;
    } 
    while(node_ptr&&node_ptr->next)
    {
        if(node_ptr->data < data && node_ptr->next->data >= data) // 大于前一个小于后一个
        {
            // 将新结点的next指向当前结点的后继结点node_ptr->next
            newNode->next = node_ptr->next;

            // 让newNode成为node_ptr的后继结点
            node_ptr->next = newNode;
            return list; // 退出增加
        }
        // 把指针往后移
        node_ptr = node_ptr->next;
    }

    // 严谨判断一下node_ptr->next是不是为空
    if(node_ptr->next == nullptr) // 尾插法
    {
        node_ptr->next = newNode;
    }

    return list;
}

//----------------------------------------------------------删除----------------------------------------------------
/*
    @brief:删除指定结点数据
    @param:list 需要删除元素的链表首地址
    @param:data 需要删除的那个元素的值
    @return : 链表首地址
*/
struct node *delNodeData(struct node*list,int data)
{
    // 严谨判断链表是否存在
    if(list == nullptr)
        return list;
    
    // 找需要删除的元素
    // 是不是首结点
    if(list->data == data)
    {
        // 第一步:获取后继结点指针
        struct node *newHeadNode = list->next;

        // 第二步:将需要删除的首结点的next置空
        list->next = nullptr; // 此时的它和链表没有关系了

        // 第三步:将list结点删除/释放
        delete list;

        // 第四步:返回新的首结点
        return newHeadNode;
    }

    // 不是首结点
    struct node *node_ptr = list;
    while(node_ptr->next) // 判断下一个的原因:是能够找到需要删除的元素的前一个,next才是实际要比对的结点
    { 
        // 找到那个结点了
        /*
            node_ptr: 表示需要删除的结点的前驱结点
            (node_ptr->next): 表示需要删除的结点
            (node_ptr->next)->data : 表示需要删除的结点的数据
        */

        if((node_ptr->next)->data == data)
        {
            // 第一步:获取需要删除的结点指针
            struct node*needDelNode = node_ptr->next;

            // 第二步:将前驱结点的next指向当前删除结点的next
            node_ptr->next = needDelNode->next;

            // 第三步:将当前删除结点的next置空
            needDelNode->next = nullptr;

            // 第四步:将当前删除结点进行释放
            delete needDelNode;

            // 第五步:返回结点首地址
            return list;
        }

        // 移动指针
        node_ptr = node_ptr->next;
    }

    // 为空退出:表示没找到啊
    return list;
}

//----------------------------------------------------------修改----------------------------------------------------
/*
    @brief:修改指定数据的结点
    @param:list 需要修改元素的链表首地址
    @param:oldData 需要修改的那个元素的值
    @param:newData 新数据
*/
void changeNodeData(struct node*list,int oldData,int newData)
{
    // 严谨判断链表是否存在
    if(list == nullptr)
    {
        std::cout << "链表为空" << std::endl; 
        return;
    }
       
    // 查找需要修改的数据是否存在
    while(list)
    {
        // 判断
        if(list->data == oldData)
        {
            list->data = newData;
            return;
        }

        // 移动指针
        list = list->next;
    }
    
    // 如果循环都结束了还没找到,那么就是没有这个数据
    std::cout << "链表不存在" << std::endl;
    return;
}

//----------------------------------------------------------查找----------------------------------------------------
/*
    @brief:修改指定数据的结点
    @param:list 需要查找元素的链表首地址
    @param:data 需要查找的那个元素的值
*/
void searchNodeData(struct node*list,int data)
{
    // 严谨判断链表是否存在
    if(list == nullptr)
    {
        std::cout << "链表为空" << std::endl; 
        return;
    }
       
    // 查找需要修改的数据是否存在
    while(list)
    {
        // 判断
        if(list->data == data)
        {
            std::cout << "找到链表" << std::endl;
            return;
        }

        // 移动指针
        list = list->next;
    }
    
    // 如果循环都结束了还没找到,那么就是没有这个数据
    std::cout << "链表不存在" << std::endl;
    return;
}


/*
    @brief:创建一个新链表
    @return:返回新链表的首结点的地址
*/
struct node *createNewList()
{
    // 新链表的首结点指针
    struct node *newList = nullptr;

    // 循环通过数据不断的去增加新结点到链表中
    while(1)
    {
        int data = -1;
        // 通过键盘获取数据
        std::cin >> data;

        // 判断退出条件
        if(data == -1)
            break;

        // 做第一次判断:链表中有没有结点
        if(newList == nullptr)
        {
            struct node *newNode = new struct node;
            newNode->data = data; // 将通过键盘获取到的数据存入结构体的数据域中
            newNode->next = nullptr; // 因为它是一个新结点,暂时是没有后继结点

            // 如果newList是nullptr说明该链表里面为空,当前的新节点就是首届点
            newList = newNode;
            continue;
        }

        // 通过中间插入法增加结点
        newList = addNodeMid(newList,data);

        // 通过尾插法增加结点
        // newList = addNodeTail(newList,data);
    }
    return newList;
}


/*
    @brief: 销毁一个链表
    @list : 需要销毁的链表
    @return : 销毁成功返回空指针
*/
struct node *destoryList(struct node *list)
{
    while(list)
    {
        // 保存当前结点的后继结点,以防等会找不到了
        struct node * next_node = list->next;

        // 将当前结点的next指针置空
        list->next = nullptr;

        // 释放当前结点
        delete list;

        // 移动下一个待删除结点
        list = next_node;
    }

    return list;
}


// 打印链表(遍历方法)
void printList(struct node *list)
{
    // 判断是不是空链表
    if(list == nullptr)
    {
        std::cout << "链表为空" << std::endl;
        return;
    }

    std::cout << "List( ";
    // 如果不为空打印链表元素
    while(list) // 只要list不为空就一直循环
    {
        // list本来就可以表示首结点
        std::cout << list->data << " ";

        // 让list移动到下一个结点
        list = list->next;
    }
    std::cout << ")" << std::endl;
}

int main()
{
    // 不在栈空间里面申请结点空间
    // 创建一个链表
    struct node *newList = createNewList();

    // 打印链表元素
    printList(newList); // 传入的值是newList存储的地址,并非newList自己的地址

    // 打印链表元素
    printList(newList); // 传入的值是newList存储的地址,并非newList自己的地址

    int data;
    std::cin >> data;

    // 销毁链表
    destoryList(newList);
    return 0;
}
相关推荐
Wangx_wang4 分钟前
C++——list常见函数的使用和模拟实现(2)
开发语言·c++·list
MessiGo7 分钟前
Qt 实战(10)模型视图 | 10.4、模型角色
开发语言·qt
A.A呐8 分钟前
LeetCode 692.前K个高频单词
c++·算法·leetcode
搁浅°87911 分钟前
Collection
java·开发语言·windows
月夕花晨37415 分钟前
C++学习笔记(21)
c++·笔记·学习
nice6666020 分钟前
xml基础
xml·java·开发语言·前端·css·bootstrap·idea
Evand J24 分钟前
【逐行注释】自适应观测协方差R的AUKF(自适应无迹卡尔曼滤波,MATLAB语言编写),附下载链接
开发语言·matlab·r语言
小白学大数据27 分钟前
利用R语言进行头条主页内容的自动化下载
开发语言·r语言·自动化
御风_2130 分钟前
数据结构——栈和队列(队列的定义、顺序队列以及链式队列的基本操作)
数据结构
小冉在学习31 分钟前
leetcode刷题day17|二叉树Part05(654.最大二叉树、617.合并二叉树、700.二叉搜索树中的搜索、98.验证二叉搜索树)
数据结构·算法·leetcode