链表小结
介绍
必须说在前面的是,链表的详细介绍肯定书本或者论文或者百科介绍的更具体,更详细,这里我只是根据我做题学习到的和我认为必须要知道的东西做了一次介绍。
什么是链表
链表:由若干个结点组成的一种在内存空间中非连续非顺序的存储结构。其中结点是由存储数据元素的数据域和存储下一个(上一个)结点地址的指针域组成。因此,链表插入和删除结点都可以达到O(1)的时间复杂度,但是查找一个节点却要遍历整个链表,O(n)的时间复杂度。
结点程序表述:
struct ListNode{
int val;
ListNode*next;
//ListNode*pre;
ListNode():val(0),next(nullptr){}
ListNode(int x):val(x),next(nullptr){}
ListNode(int x,ListNode*next):val(x),next(next){}
}
链表类型
-
单向链表
做题一般都是单向链表,所谓单向就是结点中指针域只包含指向其后继结点的指针,如图示: -
双向链表
相较于单向链表,有一个指向前驱结点的指针,如图示
-
循环链表
首尾结点相接的链表。暂时不做过多介绍,想要详细了解的可以参考书本。
链表之STL
源码
这部分依然是根据侯捷老师的视频+书本来进行表述的。C++里面详细介绍。
STL使用
相对于源码来说,我们常常做的是使用STL,这里列出了常见的函数,更多函数可以参考源码。
- list的创建
list<int>values;//创建空的list容器
list<int>values(n);//创建一个包含n个元素的list容器
list<int>values(n,data);//创建一个包含n个元素的容器并为每个元素指定初始值
list<int>values1(values);//通过拷贝list容器来创建新的list
int a[]={1,2,3};
list<int>values2(a,a+3);
vector<int>arr{0,1,2};
list<int>values2(arr.begin(),arr.end());//拷贝其他类型的元素(可以指定区域)来创建一个List容器
- list的迭代器(常用)
begin():返回指向容器中第一个元素的双向迭代器
end():返回指向容器中最后一个元素所在位置的下一个位置的双向迭代器
rbegin():返回指向最后一个元素的反向双向迭代器
rend():返回指向第一个元素所在位置前一个元素的反向双向迭代器 - list中结点的访问
两种方式:一是通过front()和back()成员函数,二是使用迭代器遍历。
front():获取list容器中第一个元素的引用
back():获取list容器中最后一个元素的引用 - list中结点的插入
push_front():
push_back():
emplace_back():
emplace_front():
insert(): - list中结点的删除
pop_front():
pop_back():
erase():
clear():
remove(val): - 其他常用函数
empty():
基本操作
- 创建结点
ListNode cur=new ListNode(data);
- 遍历链表
假设当前结点是cur,那下一个结点就是
tmp=cur->next;
- 插入结点
知道插入结点的前一个结点即可,一般即是插入到哪个结点之后,这个tmp的表示不是必须的。
tmp=p->next;//先把后面的部分链表给标记下总没错
cur->next=tmp;//修改指向,从后向前考虑结点也总没错
p->next=cur;//修改指向,注意和结点遍历和结点更新的区别,pre=pre->next;//结点的遍历。pre=tmp;//结点的更新
- 删除结点
tmp=cur->next;//必须先保存要删除结点的下一个结点,想走必须先留下点东西
delete cur;//删除结点
p->next=tmp;//重新连接,这里我区分了修改指向和重新连接。修改指向:指针由一个指向的结点转为指向另一个结点,两个结点都存在
//重新连接,指针从指向一个结点到执行的结点删除之后 重新又指向一个结点
常用技巧
- 虚拟头节点
- 双指针之快慢指针
- 修改指向
- 递归-链表
- 哈希表-链表
经典题目
- 设计链表
- 删除排序链表中的重复元素
- 反转链表
- 分隔链表
- 合并两个有序链表
- 求两个链表交点
- 复制带随机指针的链表
- 环形链表
- 交换两个链表中的结点
- 对链表进行插入排序
- 重排链表
参考:leetcode