線性表、連結串列、棧、佇列的關係
一、前言
程式設計師在程式設計實戰操作面前有兩個攔路虎,第一個是用遞迴的思想去解決問題,第二個是資料結構與演算法的應用。對於遞迴,由於其神奇的薄面紗總是然我們覺得難以理解,而大多數遞迴解法還是需要承擔一定的計算負擔的,因此我覺得能理解其思想與用法固然好,但是實在無法理解也只能慢慢適應。而對於第二個難題,資料結構與演算法基本上每一個程式設計師都需要了解,因為,演算法是解答一個問題的基本邏輯,如果你可以證明這個邏輯是正確的,那接下來的問題就是應該採用什麼樣的資料結構來保證你的程式能有最高的執行效率,即比較好的時間複雜性,同樣記憶體空間消耗也需要儘可能小。
二、線性表
作為了解資料結構與演算法的基礎,線性表通常是第一個拿來作為介紹與研究的。但到底線性表
首先,什麼是線性表?就是一種連續或間斷儲存的陣列,這裡的連續和間斷是針對實體記憶體空間中線性表元素之間是否連續,其中連續陣列對應內建陣列的實現方式,間斷陣列對應的是指標的實現方式,這種方式也稱為連結串列實現。
也就是說,線性表有兩種實現方式,一種是內建陣列實現,另一種是連結串列實現,最好把連結串列就理解為線性表的一種實現方式,而且從連結串列的定義來看,它的本質是一種資料結構,其定義如下
而我們通常自定義的連結串列中,一般使用typedef ListNode* List; 這其實就是一個ListNode節點的next域,正是由於有next域的存在,所以我們通常會以一種邏輯結構理解的連結串列(大多數資料結構教程中確實也是這麼表示的)。struct ListNode { ElementType value; //節點值 ListNode *next; //指向下一個節點 };
三、棧
首先,棧就是一種線性結構的表,也就是線性表,那麼也就是說它有兩種實現方式,一種是用內建陣列實現,一種是以連結串列實現。
其次,棧有自己的結構特性:棧可以動態增長和縮減,即(一般)可以向一個棧新增或從一個棧刪除元素,但這種新增和刪除操作只能從棧的棧頂進行操作,這種限制也造就了棧的先進後出特性。
最後,注意上面只是說一般我們可以用內建陣列和連結串列兩種方式來實現棧,但是,根據棧的特性,其實還可以用其他結構來實現棧,只要這種結構能實現棧的先進後出,而且只能從棧的棧頂進行插入和刪除操作,最常見的就是我們可以用兩個佇列來實現棧(當然,後面會解釋,其實佇列也是用陣列和連結串列來實現的)。
比如,用內建陣列來實現棧,則可以這樣定義棧的結構:
struct stack_array
{
int TopofStack; //表示棧頂
int capacity; //表示棧的容量(固定值)
Elemtype *arr; //指向陣列首元素的指標,capacity和*arr兩個引數就表示了陣列
};
棧的連結串列實現:
struct stack_list
{
ElemType value;
stack_list *next;
};
如果採用連結串列方式來實現棧,則一般需要有一個表頭,因為棧需要在表頭位置來進行插入和刪除操作。
三、佇列
首先,佇列也是一種線性表(線性的資料結構),則佇列也可以用陣列和連結串列兩種方式來實現;
其次,佇列也有自己的結構特性:佇列是先進先出的線性表,它同時維護表的兩端,但只能在表尾進行插入,在表頭進行刪除操作(這是有佇列的先進先出特性決定的)。
所以,不管是用陣列還是用連結串列實現佇列,都需要遵循佇列的先進先出特性。
用內建陣列實現佇列,則佇列結構定義為:
struct queue_array
{
int capacity; //佇列最大容量
int size; //佇列中當前元素個數
ElemType *arr; //陣列
int front; //佇列頭
int rear; //佇列尾
};
用連結串列實現佇列,則佇列結構定義為:
struct node
{
ElemType value;
node *next;
};
struct queue_list
{
node *front; //佇列頭節點
node *rear; //佇列尾節點
int size; //佇列中當前元素個數
};
不管是佇列的陣列還是連結串列實現,在進行刪除和插入操作之後都需要維護佇列頭和尾的指向性元素(即front和real),以及隊列當前元素個數size。