单链表常见的实现方法有两种,一种方式是定义一个结构体表示链表节点。比如:
struct node{
int val;
node* next;
};
然后就是通过next
指针将链表的所有节点连接起来。如果涉及到链表节点的插入和删除操作,则只需要修改链表节点的指针即可。
这种方式有个明显的缺点,就是不能随机存取。如果要在某个节点之后插入或者删除节点,复杂度是O(n),因为要从头开始逐个遍历到需要插入或者删除的节点(通过next指针找)。所以用结构体实现的单链表缺点就是太慢了。当然还有一个原因就是C++里要new一个node比较慢。总之就是慢~
因此算法题中使用单链表往往是通过数组实现,数组实现的单链表可以通过下标来索引节点,可以直接通过下标找到某个节点的值和下一个节点的,因此数组实现的单链表的最大优点就是快(插入和删除操作都是O(1)的时间复杂度),毕竟数组的特点就是随机存取嘛。
这里结合一道题目来看数组实现单链表。
原题链接
这道题题意非常直白。就是需要实现一个单链表,这个链表支持三个操作:(1)在链表的表头插入一个节点;(2)删除某个数后面的数;(3)在某个数后面加入一个数(即插入节点)。
输入有若干行,每行都有可能是三个操作的其中之一,最后的输出就是经过所有操作之后从头到尾将链表输出。
来模拟一下样例吧。
来看一下实现。
要实现三个操作,我们可以额外定义三个函数分别表示上面三个操作。
这里分别命名为insertBeforeHead(int x)、 Insert(int k, int x)、Delete(int k)。这里的x就是要插入的值,k就是要插入/删除节点的前一个位置(也就是在第k个节点之后进行插入/删除)。
这里要注意,第k个节点并不是当前链表从前往后数,而是从最开始计算,插入的第k个节点(也就是说前面插入的节点被删除了,并不会重新计算k)。
要用数组表示单链表,和结构体实现链表一样,每个节点都要有值和记录下一个节点的“指针”,因此我们可以开两个数组val和nex分别表示节点的数值和下一个节点的位置。
由于需要对链表头做操作(在链表头插入节点)和记录已经插入的节点的个数(因为要在第k个节点之后进行插入/删除操作),因此我们需要用两个“指针”记录头节点和当前操作的节点是第几个节点。
简单的说,head
指向的就是链表头节点(第一个节点,不是附加头节点)的下标,idx
表示当前用到的是第几个节点。
const int N = 1e5 + 5;
int val[N], nex[N], head, idx; //head表示头节点的下标,idx表示已经插入的节点的个数
链表需要有初始化操作,不然head
和idx
的值可能是不确定的。
void init() {
head = -1; //注意:这里的head并不是虚拟头节点,指向的就是链表的第一个节点,这样做主要是为了方便在头节点插入数据,最开始没有插入节点,head指向-1
idx = 0; //还没插入节点,idx为0
}
然后看一下在链表头节点(之前)插入节点。
由于head
记录了原来的头节点,我们希望新插入一个节点,这个节点的值为x
,且这个节点的**next指针**
指向原来的head
。
看一下代码:
void insertBeforeHead(int x) {
val[idx] = x; //新插入一个值为x的节点
nex[idx] = head; //x的下一个节点是原来的头节点,表示在头节点之前插入x
head = idx; //head指针指向新节点
++idx; //更新插入节点的个数
}
看一下如何在第k个节点之后插入一个节点。
首先肯定要创建一个值为x的节点:val[idx] = x;
且这个新节点的next指针指向第k+1个节点:nex[idx] = nex[k]. (nex[k]是原来的第k+1个节点,让新插入的节点指向这个节点)。
然后k的next指针也要更新,指向x: nex[k] = idx;
再更新一下已经插入的节点的个数:++idx;
这样就得到在第k个节点之后插入节点x的代码:
void Insert(int k, int x) {
val[idx] = x;
nex[idx] = nex[k];
nex[k] = idx;
++idx;
}
然后就剩下在第k个节点之后删除节点的函数啦:
这个很简单,要让第k个节点之后的值不存在,就直接让第k个节点的next指针
指向第k+2个节点,也就是跳过了第k+1个节点,这样第k+1个节点就社会性死亡了。
void Delete(int k) {
nex[k] = nex[nex[k]];
}
这道题的完整代码如下:
#include<bits/stdc++.h>
using namespace std;
const int N = 1e5 + 5;
int val[N], nex[N], head, idx;
void init() {
head = -1;
idx = 0;
}
void insertBeforeHead(int x) {
val[idx] = x;
nex[idx] = head;
head = idx;
++idx;
}
//在下标为k的节点后插入x
void Insert(int k, int x) {
val[idx] = x;
nex[idx] = nex[k];
nex[k] = idx;
++idx;
}
//删除下标为k的节点的下一个节点
void Delete(int k) {
nex[k] = nex[nex[k]];
}
int main() {
int M;
cin >> M; //操作的个数
init(); //操作之前需要对head和idx指针进行初始化
while(M--) {
char op; //当前进行的操作
int k, x;
cin >> op;
if(op == 'H') { //在头节点之前插入一个节点
cin >> x;
insertBeforeHead(x);
} else if(op == 'I') { //在第k个节点之后插入一个节点
cin >> k >> x;
Insert(k - 1, x); //由于idx初始为0,所以第k个节点的下标为k - 1
} else if(op == 'D') {
cin >> k;
if(k == 0) {
head = nex[head];
} else {
Delete(k - 1); //由于idx初始为0,所以第k个节点的下标为k - 1
}
}
}
for(int i = head; i != -1; i = nex[i]) {
cout << val[i] << ' ';
}
cout << endl;
return 0;
}