标题
前言
复习一下数据结构和算法,今天开始把这些复习好的东西都写成blog发出来吧。也顺便可以方便其他想要学习的小伙伴。环境如果没有特殊说明,就代表用的是C++20 MSVC。默认情况下直接使用了using namespace std;(如cin, cout, endl, STL定义好的数据结构如list, vector等)。只有少数情况——可能定义的函数有大概率重合(如std::move, std::sort, std::max, …)才不会直接使用namespace std;
什么是链表(List/LinkedList)
链表的核心在于其数据结构内部自己定义了一个指向自己结构的指针。
链表常见的一般有单向链表、双向链表、循环链表。
按照我个人理解,可以将链表理解成为:A给你一个锦囊,让你去找B解决问题,你找到B后发现他不是这个领域的专家,于是B跟你说让你去找C,C可能知道如何解决这个问题,于是你继续去找C……直到找到你想要的可以解决你要找的问题的人。在下面的实现当中,我们直接简单地将“需要解决的问题”当做int整形来处理。为了方便,也不写模板了。
链表总体来讲比较简单,唯二需要注意的点就是1. 哨兵头节点的应用,以及2. 删除操作的实现。
链表的特点
- 头尾删除插入一般较快(双向链表O(1)),单向链表头部删除插入较快(O(1)),单向链表尾部插入为O(n)
- 遍历速度为O(n)
单向链表的实现
凡是
- 凡是和头结点操作相关的操作,都最好使用哨兵/哑头结点(dummyhead),这样可以大量减少和头结点相关的判断操作。注意使用哨兵节点后,事实上链表的第一个节点应当是dummyhead->next.
- 凡是和删除相关的操作,一定需要有一个指针停留在待删除结点的前面。
由于链表大多数时间不会存放过多的数据(否则遍历和查找都会比较慢),因此下面代码不考虑申请空间失败的情况。
其中dummyhead的应用非常广泛并且重要,大家可以在下面的代码当中感受其作用。
结构操作
其核心在于要杜绝出现内存泄漏问题、对空指针访问问题。
插入
- 头插
- index插入
头插较为简单,分为下列步骤:
- 新建节点
- 将新建节点的next指针指向dummyhead的next。
- 将dummyhead的next指向新建的节点
index插入:
新建节点->创建指针p指向dummyhead->移动index次p->新建节点next为p的next,p的next=node
删除
和插入一样,先定义 一个指针p指向dummyhead,随后移动index次p,此时p即为要删除节点的前一个节点。如果这里理不清楚的可以画个图看一看。
在删除时,需要将被删除节点的前一个节点的(也就是p指向的节点)next指针指向被删除节点的next指针,随后将被删除节点的内存释放。
翻转
先让cur指针指向head.next,随后让head.next为空。随后在cur不为空的循环内定义q指向cur的next,然后将cur.next指向head.next,再把head.next指向cur,再让cur=q。这样一来,头结点就完成了翻转,注意不能将head指向cur,因为head是不参与他们之间的操作的,只有head.next才是链表真正的头结点。这一块如果想不明白的,拿个纸画一画就明白了。
其他操作
其他操作包括返回链表长度、链表遍历等,比较简单,就不多说了。
单向链表代码
#include<iostream>
#include<vector>
#include<queue>
#include<cstdio>
#include<format>
#include<ctime>
#include<list>
#include<algorithm>
using namespace std;
class List {
private:
struct listnode {
int mvalue;
listnode* next;
listnode() :mvalue(0), next(nullptr) {
}
listnode(int value) :mvalue(value), next(nullptr) {
}
};
private:
listnode head;//dummyhead
int mlength;
public:
List() :mlength(0) {
head.next = nullptr;
}
~List() {
listnode* cur = head.next;
while (cur) {
listnode* del = cur;
cur = cur->next;
delete del;
}
}
void push_front(int value) {
listnode* node = new listnode(value);
node->next = head.next;
head.next = node;
++mlength;
}
void insert(int value, int index) {
if (index<0 || index>mlength) return;//failed to insert
listnode* node = new listnode(value);
listnode* p = &head;
while (index--) {
p = p->next;
}
node->next = p->next;
p->next = node;
++mlength;
}
void erase(int index) {
if (index < 0 || index >= mlength) return;//The node needed to be deleted cannot be nullptr;
listnode* p = &head, * del;
while (index--) {
p = p->next;
}
del = p->next;
p->next = del->next;
delete del;
--mlength;
}
void reverse_list() {
listnode* cur = head.next, * next;
head.next = nullptr;
while (cur