数据结构——线性表(链表C++)

数据结构------线性表(链表)

我们在上一节了解了线性表的顺序实现,如果还没有看过的小伙伴可以点击这里:

https://blog.csdn.net/qq_67693066/article/details/137581598

我们今天来看线性表的链式实现:

单链表

单链表(Singly Linked List)是一种基本的数据结构,它采用链式存储的方式来存储一组具有先后关系的数据元素。在单链表中,每一个元素称为节点(Node),包含两部分信息:数据域(Data)和指针域(Next Pointer)。数据域用于存储元素的实际数据,而指针域用于存储指向下一个节点的地址。

单链表的基本操作包括:

  1. 插入节点:在链表的任意位置插入一个新的节点,包括头部插入、尾部插入和中间插入。
  2. 删除节点:根据指定的条件(如节点值或位置)删除链表中的一个节点。
  3. 查找节点:根据指定的条件(如节点值)查找链表中是否存在这样的节点,并返回该节点或其位置。
  4. 遍历链表:从头节点开始,依次访问每个节点,直到到达尾节点。
  5. 更新节点:修改链表中某个节点的数据域的值。

单链表的特点:

  • 插入和删除操作相对数组来说更为灵活高效,时间复杂度一般为O(1)至O(n),具体取决于操作位置。
  • 单链表无法直接随机访问,若想访问第i个节点,需要从头节点开始连续遍历i次才能达到,因此随机访问的时间复杂度为O(n)。
  • 只需存储每个节点的后继节点地址,不需要像数组那样预留连续的内存空间,所以在内存利用率上较灵活,但存储相同数量的数据,所需的空间可能大于数组(每个节点都需要额外的指针空间)。

单链表有两种常见的特殊形式:带头结点的单链表和不带头结点的单链表。带头结点的单链表便于实现一些算法,例如在链表头部插入新节点时,无需判断特殊情况。

封装结点类

其实,单链表的实现是依靠一个一个的结点实现的,所以我们可以先把结点封装起来:

这里我们用C++来实现:

cpp 复制代码
template<class T> //使用模板
struct linknode //结点类的定义
{

    linknode()
        :_data(T())
        ,_next(nullptr)
    {

    }

    linknode(const T& data)
            :_data(data)
            ,_next(nullptr)
    {

    }

    //结点数据
    T _data;

    //存放下一个结点的指针
    linknode<T>* _next;

};

单链表的封装

我们通过结点,来封装一个只有一个头结点的单链表:

cpp 复制代码
template<class T>
class SLink
{
    typedef linknode<T> Node; //重命名类型
public:



private:
    Node* _head; //头结点
};

这样我们的单链表一来就会有一个结点,我们把这个结点当做头结点。

头插

我们来完成头插:

cpp 复制代码
    //创建结点
    Node* createNode(const T& data)
    {
        Node* newnode = new Node(data);
        if(newnode == nullptr)
        {
            perror("new fail");
            return nullptr;
        }

        return newnode;
    }

    //头插
    void FrontInsert(const T& data)
    {
        Node* newnode = createNode(data); //创建新结点

        if(newnode == nullptr)
        {
            exit(EXIT_FAILURE);
        }

        if(_head ->_next == nullptr) //如果只有头结点
        {
            _head ->_next = newnode;
        }
        else
        {
            newnode->_next =_head->_next;
            _head ->_next = newnode;
        }
    }

然后我们实现一个打印函数:

cpp 复制代码
    //打印
    void PrintList()
    {
        Node* cur = _head->_next;

        while(cur != nullptr)
        {
            std::cout<<cur->_data<<" ";
            cur = cur -> _next;
        }

        std::cout<<"END"<<std::endl;
    }

在main函数里面执行一下:

cpp 复制代码
#include"SLink.h"

int main()
{
    SLink<int> list;

    list.FrontInsert(234);
    list.FrontInsert(1);
    list.FrontInsert(24);
    list.FrontInsert(89);

    list.PrintList();

    return 0;
}

尾插

因为我们没有设置尾指针,所以我们得移动到最后一个结点,再完成尾插:

cpp 复制代码
    //尾插
    void TailInsert(const T& data)
    {
        Node* newnode = createNode(data); //创建新结点

        Node* cur = _head->_next;

        while(cur->_next)
        {
            cur = cur -> _next;
        }

        cur->_next = newnode;
    }
cpp 复制代码
#include"SLink.h"

int main()
{
    SLink<int> list;

    list.FrontInsert(234);
    list.FrontInsert(1);
    list.FrontInsert(24);
    list.FrontInsert(89);

    list.PrintList();

    list.TailInsert(234);
    list.TailInsert(1);
    list.TailInsert(4);

    list.PrintList();

    return 0;
}

在任意位置插入

在任意位置插入,我们首先的要知道链表长度,我们可以让头结点储存结点的个数

我们写一个函数来计算链表长度:

cpp 复制代码
    //链表长度
    size_t LenghthOfList()
    {
        size_t count = 0;

        Node* cur = _head -> _next;
        while(cur)
        {
            count++;
        }
        return count;
    }

在pos之后插入

cpp 复制代码
    //在pos之后插入
    void InsertPosAfter(const size_t& pos,const T& data)
    {
        size_t leghth = LenghthOfList();
        assert(pos >= 0 && pos <= leghth);

        if(pos == 0) //pos等于0,等于头插
        {
            FrontInsert(data);
            return;
        }
        else if(pos == leghth) //pos等于长度为尾插
        {
            TailInsert(data);
            return;
        }
        else
        {
            Node* cur = _head;

            size_t cur_pos = 0;
            while(cur_pos < pos)
            {
                cur = cur -> _next;
                cur_pos++;
            }

            Node* after = cur -> _next;

            Node* newnode = new Node(data);
            newnode->_next = after;
            cur->_next = newnode;
        }
    }

在pos之前插入

cpp 复制代码
   //在pos之前插入
    void InsertPosBefore(const size_t& pos,const T& data)
    {
        size_t length = LenghthOfList();
        assert(length > 0);

        //前后指针
        Node* prve = nullptr;
        Node* cur = _head;

        size_t cur_pos = 0;
        while(cur_pos < pos)
        {
            prve = cur;
            cur = cur->_next;
            cur_pos++;
        }

        Node* newnode = new Node(data);
        newnode->_next=cur;
        prve->_next=newnode;
    }

我们可以测试一下:

cpp 复制代码
#include"SLink.h"

int main()
{
    SLink<int> list;

    list.FrontInsert(234);
    list.FrontInsert(1);
    list.FrontInsert(24);
    list.FrontInsert(89);

    list.PrintList();

    list.TailInsert(234);
    list.TailInsert(1);
    list.TailInsert(4);


    list.PrintList();
    std::cout<<"lenghth of list "<<list.LenghthOfList()<<std::endl;

    list.InsertPosAfter(2,899);
    list.PrintList();

    list.InsertPosAfter(0,999);
    list.PrintList();

    list.InsertPosBefore(1,76);
    list.PrintList();

    list.InsertPosBefore(2,176);
    list.PrintList();
    return 0;
}

删除

头删

头删就是删除第一个元素:

cpp 复制代码
    //头删
    void FrontErease()
    {
        Node* deleteNode = _head->_next;
        Node* after =  deleteNode ->_next; //第二个元素

        _head ->_next = after;

        delete deleteNode;
    }
cpp 复制代码
#include"SLink.h"

int main()
{
    SLink<int> list;

    list.FrontInsert(234);
    list.FrontInsert(1);
    list.FrontInsert(24);
    list.FrontInsert(89);

    list.PrintList();

    list.TailInsert(234);
    list.TailInsert(1);
    list.TailInsert(4);


    list.PrintList();
    std::cout<<"lenghth of list "<<list.LenghthOfList()<<std::endl;

    list.InsertPosAfter(2,899);
    list.PrintList();

    list.InsertPosAfter(0,999);
    list.PrintList();

    list.InsertPosBefore(1,76);
    list.PrintList();

    list.InsertPosBefore(2,176);
    list.PrintList();

    list.FrontErease();
    list.PrintList();
    return 0;
}

尾删

尾删是删除最后一个元素:

cpp 复制代码
    //尾删
    void TailErease()
    {
        //找到倒数第二个结点
        Node* cur = _head->_next;

        while(cur->_next->_next)
        {
            cur = cur->_next;
        }

        Node* deleteNode = cur->_next;
        cur->_next = deleteNode->_next;

        delete deleteNode; //释放结点
    }
cpp 复制代码
#include"SLink.h"

int main()
{
    SLink<int> list;

    list.FrontInsert(234);
    list.FrontInsert(1);
    list.FrontInsert(24);
    list.FrontInsert(89);

    list.PrintList();

    list.TailInsert(234);
    list.TailInsert(1);
    list.TailInsert(4);


    list.PrintList();
    std::cout<<"lenghth of list "<<list.LenghthOfList()<<std::endl;

    list.InsertPosAfter(2,899);
    list.PrintList();

    list.InsertPosAfter(0,999);
    list.PrintList();

    list.InsertPosBefore(1,76);
    list.PrintList();

    list.InsertPosBefore(2,176);
    list.PrintList();

    list.FrontErease(); //头删
    list.PrintList();

    list.TailErease(); //尾删
    list.PrintList();
    
    return 0;
}

任意位置删除

任意位置删除我们也可以通过两个指针来完成:

cpp 复制代码
    //任意位置删除
    void EreasePos(size_t pos)
    {
        size_t length = LenghthOfList();
        assert(length > 0 && pos <= length && pos > 0);

        if(pos == 1)
        {
            FrontErease();
            return;
        }
        else
        {
            Node* prve = nullptr;
            Node* deletNode = _head;

            size_t cur_pos = 0;
            while(cur_pos < pos)
            {
                prve = deletNode;
                deletNode = deletNode->_next;
                cur_pos++;
            }

            Node* after = deletNode->_next;
            prve->_next = after;
            delete deletNode;
        }
    }
cpp 复制代码
#include"SLink.h"

int main()
{
    SLink<int> list;

    list.FrontInsert(234);
    list.FrontInsert(1);
    list.FrontInsert(24);
    list.FrontInsert(89);

    list.PrintList();

    list.TailInsert(234);
    list.TailInsert(1);
    list.TailInsert(4);


    list.PrintList();
    std::cout<<"lenghth of list "<<list.LenghthOfList()<<std::endl;

    list.InsertPosAfter(2,899);
    list.PrintList();

    list.InsertPosAfter(0,999);
    list.PrintList();

    list.InsertPosBefore(1,76);
    list.PrintList();

    list.InsertPosBefore(2,176);
    list.PrintList();

    list.FrontErease(); //头删
    list.PrintList();

    list.TailErease(); //尾删
    list.PrintList();

    list.EreasePos(2); //任意位置删除
    list.PrintList();

    return 0;
}

到这里,单链表的主要操作都差不多写完了,单链表我之前也写过一篇,是用C语言写的,如果看这篇吃力的话可以点击这里,这篇更详细:

https://blog.csdn.net/qq_67693066/article/details/130446229

单链表我们只是复习,我们主要看单链表的几种变形:带尾指针,循环,双向,双向循环

相关推荐
van叶~42 分钟前
算法妙妙屋-------1.递归的深邃回响:二叉树的奇妙剪枝
c++·算法
knighthood20011 小时前
解决:ros进行gazebo仿真,rviz没有显示传感器数据
c++·ubuntu·ros
半盏茶香1 小时前
【C语言】分支和循环详解(下)猜数字游戏
c语言·开发语言·c++·算法·游戏
小堇不是码农1 小时前
在VScode中配置C_C++环境
c语言·c++·vscode
Jack黄从零学c++1 小时前
C++ 的异常处理详解
c++·经验分享
捕鲸叉7 小时前
创建线程时传递参数给线程
开发语言·c++·算法
A charmer7 小时前
【C++】vector 类深度解析:探索动态数组的奥秘
开发语言·c++·算法
Peter_chq7 小时前
【操作系统】基于环形队列的生产消费模型
linux·c语言·开发语言·c++·后端
wheeldown8 小时前
【数据结构】选择排序
数据结构·算法·排序算法
青花瓷9 小时前
C++__XCode工程中Debug版本库向Release版本库的切换
c++·xcode