前篇討論了一下具體的鏈表結構以及常規操作。現在我們趁熱打鐵,感覺再來熟悉一下具體的操作。此時我們用鏈表的形式來實現之前的順序棧。
鏈棧
棧結構這裏不多說,我們直接進入正題。先考慮,當棧爲空的時候,如何表示?當然就是一個空指針了
將新元素推入堆棧時,該元素只會添加到鏈表的前端。因此,如果將元素e1 push到空棧上,該元素將存儲在一個新單元格中,該單元格將成爲鏈中唯一的鏈接:
將一個新元素push棧將該元素添加到鏈的開頭。所涉及的步驟與將字符插入鏈表列表緩衝區所需的步驟相同。 首先分配一個新的單元格,然後輸入數據,最後更新鏈接指針,使新單元格成爲鏈中的第一個元素。 因此,如果將元素e2推到棧上,將得到以下配置:
在鏈表表示中,pop操作包括刪除鏈中的第一個單元格並返回存儲在該列中的值。因此,來自上圖所示的棧的pop操作返回e2並恢復棧的先前狀態,如下所示:
對鏈棧中的pop和push的分析
push
我們以一個最基本的數字棧爲基礎分析,下面是需要的一些接口內容:
class StackInt { // in StackInt.h
public:
StackInt (); // constructor
void push(int value); // append a value
int pop(); // return the first-in value
private:
struct Node {
int value;
Node * link;
};
Node * data; // member variables
};
接下來看代碼分析:
void StackInt::push(int v) {
Node * temp = new Node;
temp->value = v;
temp->link = data; //這一步必不可少!
data = temp;
}
老辦法,圖解push過程,假設此時我們的鏈棧如下:
我們的目的是push(7),將7放入棧頂,也就是要這樣的結果:
下面解釋整個過程:
- 新建一個臨時節點,並用創建臨時指針指向該節點,接着向該節點寫入數字7,就像這樣:
- 然後,將頭指針data裏面的指針值,賦值給新節點的link字段,以便新節點可以連接到8這個節點:
- 將temp裏面的值,賦值給首指針data處。將data的指向正確改變:
- 函數返回,得出正確的push後的結構:
pop
先看代碼:
int StackInt::pop() {
int toReturn = data->value;
Node * temp = data;
data = temp->link;
delete temp;
return toReturn;
}
圖解過程如下:
- 先將要pop出去的值進行保存
- 我們這邊創建一個臨時的指針變量Temp,然後將其指向首指針data指向的節點(即棧頂元素),就像這樣:
- 然後改變首指針(即棧頂)的指向:
- 最後刪除temp指向的節點
- 此時函數返回。
注意,我們第二步不可以直接改變指針的指向而不使用臨時節點,因爲直接改變指針的指向會像下圖一樣:
這顯然不是我們希望看到的。
鏈隊列
隊列類也使用鏈表結構進行簡單的表示。爲了說明基本的方法,隊列的元素存儲在從隊列頭部開始並以尾部結尾的列表中。並且爲了保證入隊和出隊正確運行,Queue對象必須含有指向隊列兩端的指針。 因此,私有實例變量的定義在queuepriv.h的修改版本如下:
/*
*這個文件保存的是Queue.h的私有部分
*我們這個時候用鏈隊列。也就是用鏈表來實現我們的隊列
*這樣的隊列我們稱爲鏈隊列
*/
private:
/*隊列的鏈式結構*/
struct Cell{
ValueType data;
Cell *link;
};
/*實例化變量*/
Cell *head; //頭指針
Cell *tail; //尾指針
int count; //記錄隊列元素的總數
/* Make it illegal to copy queues */
Queue(const Queue & value) { }
const Queue & operator=(const Queue & rhs) { return *this; }
那麼爲什麼我們要設置一個尾指針呢?
首先我們假設一下我們初始化的隊列如下所示:
這個時候我們執行後入隊操作:
顯然,這個時候我們相當於在鏈表中插入一個元素,且位於頭結點的第一個,自然我們的複雜度爲O(1)。但是當我們執行出隊操作時,情況就不同了,我們必須驗證頭指針遍歷到最後一個節點,再執行delet操作,此時算法複雜度爲O(N)。
入隊(enqueue)
那麼是否有更好的辦法呢?因此我們考慮一下添加一個尾指針(因爲隊列多數在鏈表的兩端操作)。所以我們看下面一段代碼:
void QueueInt::enqueue(int v) {
Node * temp = new Node;
temp->value = v;
tail->link = temp;
tail = temp;
}
圖解代碼執行過程:
- 假設我們初始的隊列值爲123,並且我們添加的尾指針指向的是我們的3的節點,如圖:
- 此時我們在隊列尾部(3),這裏加入數值4,此時,我們的操作應該是,建立一個指針,指向新建立的節點,然後往這個新節點寫入數值:
- 接下來我們改變指針的指向,也就是將cp的內容複製到3下面的link字段(也就是tail指向的節點的link字段)。此時情況如下(分兩步):
- 最終我們返回我們的隊列就可以了,因此複雜度爲O(1)
出隊(dequeue)
我們就拿上面完成的例子爲例,進行我們的dequeue操作,我們先看2 3行代碼,建立一個臨時節點,指向與我們的head指針的指向相同,如下:
首先假設我們有完整的隊列
現在我們要進行的queue操作,於是將隊頭移除,以達到下圖的結果:
同理,看代碼:
int QueueInt::dequeue() {
int toReturn = head->value;
Node * temp = head;
head = temp->link;
delete temp;
return toReturn;
}
- 我們先將此時隊頭指向的數字給存儲起來,然後新建一個指針,跟隊頭指針一同指向首元素1.
- 然後改變頭指針的指向
- 釋放被dequeue掉的那個節點