C++:链表-理论基础

C++:链表-理论基础

链表的理论基础:
单链表、双链表、循环链表、链表的存储方式、链表定义及其初始、链表的删除节点、添加节点操作、
链表与数组的性能差异



链表类型

链表是一种通过指针串联在一起的线性结构,每一个节点由两部分组成,一个是数据域一个是指针域(存放指向下一个节点的指针),最后一个节点的指针域指向null(空指针的意思)。

链表的入口节点称为链表的头结点也就是head。

单链表

单链表

双链表

单链表中的指针域只能指向节点的下一个节点。

双链表:每一个节点有两个指针域,一个指向下一个节点,一个指向上一个节点。

双链表 既可以向前查询也可以向后查询。

双链表

循环链表

循环链表,顾名思义,就是链表首尾相连。

循环链表可以用来解决约瑟夫环问题。
循环链表

链表存储方式

数组是在内存中是连续分布的,但是链表在内存中可不是连续分布的。

链表是通过指针域的指针链接在内存中各个节点。

所以链表中的节点在内存中不是连续分布的 ,而是散乱分布在内存中的某地址上,分配机制取决于操作系统的内存管理。

即链表与数组相比具有较高的内存利用率

链表存储方式


链表的定义及初始化

常用构造函数、构造函数重载、构造函数初始化列表的写法进行定义

//链表的节点
class ListNode
{
    
    
public:
    int val;                                //存储的元素
    ListNode *next;                         //指向下一个节点的指针
    ListNode() : val(0), next(NULL) {
    
    }      //构造函数重载
    ListNode(int x) : val(x), next(NULL) {
    
    } //节点的构造函数,构造函数的初始化列表写法
    ListNode(int x, ListNode *l_next) : val(x), next(l_next) {
    
    }
};

链表初始化,常用vector转链表

//创建链表
//将vector转成链表
ListNode *createList(vector<int> &nums)
{
    
    
    ListNode *head = new ListNode(nums[0]); //初始化头结点
    ListNode *p = head;                     //赋值给p进行链表创建操作,防止改变head值
    for (int i = 1; i < nums.size(); i++)
    {
    
    
        p->next = new ListNode(nums[i]); //最开始p是head,p指针指向ListNode(nums[1])
        p = p->next;
    }
    return head; //创建完毕返回链表头结点
}

此部分虽然C++有封装好链表可以直接使用,但还是要熟练掌握,达到能够手写程度,因为面试常考


链表的操作

删除节点

删除D节点,如图所示:
删除节点

只要将C节点的next指针 指向E节点就可以了。

改变了指针指向,D节点不是依然存留在内存里么?只不过是没有在这个链表里而已。

是这样的,所以在C++里最好是再手动释放这个D节点,释放这块内存。

其他语言例如Java、Python,就有自己的内存回收机制,就不用自己手动释放了。

实际进行删除节点操作时,常通过建立虚拟头结点,然后对目标节点的上一个节点进行操作

添加节点

添加节点
可以看出链表的增添和删除都是O(1)操作,也不会影响到其他节点。

但是要注意,要是删除第五个节点,需要从头节点查找到第四个节点通过next指针进行删除操作,查找的时间复杂度是O(n)。

数组与链表性能对比

链表的特性和数组的特性进行一个对比
数组与链表性能对比
数组定义时,长度基本固定,如果想改动数组的长度,就需要重新定义一个新的数组。
所以数组适用于数据量固定,较少增删,但频繁查询的场景

链表的长度是不固定的,可以进行动态的增减,所以链表适用于数据量不固定,有频繁增删,但较少查询的场景。


总结

掌握基本概念、定义初始化、还有基本操作。
重点熟练掌握删除节点时,采用虚拟头结点的方法进行相关操作
掌握数组与链表的性能分析,数组适用更改少查询多的场景,链表适用更改多查询少的场景

本文参考代码随想录

猜你喜欢

转载自blog.csdn.net/Bellwen/article/details/128402212