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