單鏈表常見的實現方法有兩種,一種方式是定義一個結構體表示鏈表節點。比如:
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;
}