1. 程式人生 > 實用技巧 >陣列實現單鏈表

陣列實現單鏈表

單鏈表常見的實現方法有兩種,一種方式是定義一個結構體表示連結串列節點。比如:

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表示已經插入的節點的個數

連結串列需要有初始化操作,不然headidx的值可能是不確定的。

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;
}