陣列模擬雙連結串列,你get到了嗎?

時間最考驗人發表於2021-11-01

陣列模擬雙連結串列

通過前面的學習我們知道單連結串列是單個指標指向操作,那麼通過類比我們可以把指標設定為兩個,並且讓它們分別指向前後資料,這就是“雙向連結串列”。使用這種連結串列,不僅可以從前往後, 還可以從後往前遍歷資料,十分方便。

1.使用陣列模擬雙連結串列

// e[i] 表示節點i的值
// l[i] 指向當前節點的上一節點(左節點)的指標:即存的是左邊一個節點的位置(下標)
// r[i] 指向當前節點的下一節點(右節點)的指標:即存的是右邊一個節點的位置(下標)
// idx 儲存當前已經用到的節點(當前已經用到哪個節點了)——同單連結串列
int e[N], l[N], r[N], idx;

2.初始化雙連結串列

我們預設初始化:0表示左端點(首),1表示右端點(尾),這兩個是邊界點。

初始化兩個哨兵節點(0、1分別是首和尾),R[0]=1,L[1]=0,分別表示將首節點0向右連線到尾節點1、將尾節點1向左連線到首節點0。R[0]表示0的右,L[1]表示1的左

因為初始化就用了兩個,因此idx = 2

//初始化雙連結串列
void init()
{
    r[0] = 1, l[1] = 0;
    idx = 2;// 因為初始化就用了兩個,因此idx = 2下標是從二開始的
}

image

3.正下標為k的節點的右邊插入x

//在下標為k的節點的右邊,插入x
void add(int k ,int x)
{
    e[idx] = x;
    r[idx] = r[k];
    l[idx] = k;
    l[r[k]] = idx;
    r[k] = idx;
    idx ++;
}

image

那麼問題來了如果我們想在下標為k的左邊插入x該怎麼實現呢?

你可能會說可以向上述在右邊插入操作一樣實現,那當然是可以的,但是程式碼就多寫了。在下標為k的節點的左邊插入x,其實就是在l[k]的右邊(l[k]為下標k位置節點的左邊節點的下標!)插入x,即我們可以直接呼叫上述add()函式操作即可,減少了一定的程式碼量。

// 在下標為k的節點的左邊插入x
	add(l[k] , x);

4.刪除下標為k的節點

// 刪除第k個節點
void remove(int k)
{
    r[l[k]] = r[k];
    l[r[k]] = l[k];
}

image

5.例題

實現一個雙連結串列,雙連結串列初始為空,支援 5 種操作:

  1. 在最左側插入一個數;
  2. 在最右側插入一個數;
  3. 將第 k 個插入的數刪除;
  4. 在第 k 個插入的數左側插入一個數;
  5. 在第 k 個插入的數右側插入一個數

現在要對該連結串列進行 MM 次操作,進行完所有操作後,從左到右輸出整個連結串列。

注意:題目中第 k 個插入的數並不是指當前連結串列的第 k 個數。例如操作過程中一共插入了 n 個數,則按照插入的時間順序,這 n 個數依次為:第 1 個插入的數,第 2 個插入的數,…第 n 個插入的數。

輸入格式

第一行包含整數 M,表示操作次數。

接下來 M 行,每行包含一個操作命令,操作命令可能為以下幾種:

  1. L x,表示在連結串列的最左端插入數 x。
  2. R x,表示在連結串列的最右端插入數 x。
  3. D k,表示將第 k 個插入的數刪除。
  4. IL k x,表示在第 k 個插入的數左側插入一個數。
  5. IR k x,表示在第 k 個插入的數右側插入一個數。

輸出格式

共一行,將整個連結串列從左到右輸出。

資料範圍

1≤M≤100000
所有操作保證合法。

輸入樣例:

10
R 7
D 1
L 3
IL 2 10
D 3
IL 2 7
L 8
R 9
IL 4 7
IR 2 2

輸出樣例:

8 7 7 3 2 9

【參考程式碼】

#include<iostream>

using namespace std;

const int N = 100000+10;
int l[N], r[N], e[N], idx;

//初始化雙連結串列
void init()
{
    r[0] = 1, l[1] = 0;
    idx = 2;// 下標是從二開始的,因此第k個數的下標:k + 1
}

//在下標為k的節點的右邊,插入x
void add(int k ,int x)
{
    e[idx] = x;
    r[idx] = r[k];
    l[idx] = k;
    l[r[k]] = idx;
    r[k] = idx;
    idx ++;
}
// 刪除第k個節點
void remove(int k)
{
    r[l[k]] = r[k];
    l[r[k]] = l[k];
}
int main()
{   
    int m;
    cin>>m;
    
    //初始化雙連結串列
    init();
    
    while(m --)
    {
        int k, x;
        string opt;
        cin>>opt;
        //表示在連結串列的最左端插入數 x(邊界節點0的右邊)
        if(opt == "L")
        {
            cin>>x;
            add(0, x);
        }
        //表示在連結串列的最右端插入數 xx(邊界節點1的左邊邊)
        else if(opt == "R")
        {
            cin>>x;
            add(l[1],x);
        }
        //表示將第 k 個插入的數刪除
        else if(opt == "D")
        {
            cin>>k;
            remove(k + 1);
        }
        //表示在第 k 個插入的數左側插入一個數
        else if(opt == "IL")
        {
            cin>>k>>x;
            add(l[k + 1], x);
            
        }
        else if(opt == "IR")
        {
            cin>>k>>x;
            add(k + 1, x);           
        }
    }
    
    // 遍歷雙連結串列
    for(int i=r[0]; i!=1; i=r[i]) cout << e[i] << ' ';

    return 0;
}

下標是從二開始的,因此第k個數的下標:k + 1

6.總結

雙連結串列的操作與單連結串列存在著很大的聯絡,兩個指標使得插入操作變得相對來說繁瑣了一些,在寫程式碼的過程中一定要手動畫圖!畫圖!畫圖!

注:如果文章有任何錯誤或不足,請各位大佬盡情指出,評論留言留下您寶貴的建議!如果這篇文章對你有些許幫助,希望可愛親切的您點個贊推薦一手,非常感謝啦

相關文章