抽象資料型別ADT(棧與佇列)
抽象資料型別 abstract data type,ADT:
指只通過介面進行訪問的資料型別,我們將那些使用ADT的程式叫做客戶,將那些確定資料型別的程式叫做實現。
資料的表示和實現操作函式都在介面的實現裡面,和客戶完全分離。介面對於我們來說是不透明的:客戶不能通過介面看到方法的實現。
優點:
- 將程式設計時對資料的概念轉換從任何特定的資料表示和演算法實現中分離出來。
- 使用抽象機制可以讓我們關心程式如何實現的細節上得到解放。
- 當程式的效能重要時,我們需要知道基本操作的開銷。
棧ADT的實現
棧ADT的實現有兩種方式,一是陣列實現,二是連結串列實現。
使用陣列實現時,將資料項放進陣列中,記錄棧頂位置的下標。進行進棧push操作時,只要把項存放到棧頂下標所指示的位置即可,然後下標增1;進行彈棧pop操作時,使下標減1,並返回它所指示的項。初始Initialize操作包含分配指定大小陣列,測試是否為空empty操作包含檢查下標是否為0.
陣列實現的缺點是在使用之前需要知道陣列的最大長度,這樣才能分配記憶體。
使用連結串列實現時,對於pop操作,刪除連結串列表頭元素,並返回其項;對於push操作,建立一個節點,然後把其新增到表頭。
下推棧的陣列實現
當棧中有N個項時,這一實現把這些項儲存在s[0],...,s[N-1]中,按照最近插入的順序排列。棧頂(下一個進棧元素將要佔據的位置)元素就是s[N].
static Item *s; static int N; void STACHKinit(init maxN) { s=malloc(maxN * sizeof(Item));//為陣列分配固定maxN個空間,首地址記錄在s中。 N=0;//當前棧頂位置 } int STACHKempty() { return N == 0; } void STACHKpush(Item item) { s[N++] = item;//先加入,再++ } Item STACKpop() { return s[--N];//先彈出,再-- }
下推棧的連結串列實現
typedef struct STACKnode* link; struct STACKnode { Item item; link next; }; static link head; link NEW(Item item,link next) { link x = malloc(sizeof *x);//分配一個節點 x->item = item;//節點中填充資料 x->next = next;//節點中填充下一個指標 return x; } void STACKinit() { head = NULL; } int STACKempty() { return head == NULL;//check head 是否為 NULL } STACKpush(Item item) { head = NEW(item,head);//分配一個節點,填入資料,放入連結串列頭(下一個指標為head),並更新成為新的表頭。 } Item STACKpop() { Item item = head->item;//需要pop的元素 link temp = head->next;//記錄下一個,防止free(head)的時候把下一個丟失。 free(head);//釋放棧頂節點(該節點資料已經被記錄在item中) head=temp;//下下個節點成為新的head return item; }
FIFO佇列的實現
FIFO(first in first out,先進先出):包含兩個基本操作,插入(put)一個新的項,刪除(get)一個最早插入的項。
FIFO佇列連結串列實現
我們在連結串列上維持兩個指標,一個在開始(我們可以刪除第一個元素),一個在末尾(我們可以插入一個新的元素)。
typedef struct QUEUEnode* link;
struct QUEUEnode
{
Item item;
link next;
};
static link head,tail;
link NEW(Item item,link next)
{
link x = malloc(sizeof *x);//分配一個節點
x->item = item;//節點中填充資料
x->next = next;//節點中填充下一個指標
return x;
}
void QUEUEinit()
{
head = NULL;
}
int QUEUEempty()
{
return head == NULL;//check head 是否為NULL
}
QUEUEput(Item item)
{
if (head == NULL)
{
head = tail = NEW(item,head);//對於插入空佇列,頭尾指標指向同一個新插入的元素。
return;
}
tail->next = NEW(item,tail>next);//對於非空佇列,在尾部插入元素
tail = tail->next;//插入的元素更新為新的尾部。
}
Item QUEUEget()
{
Item item = head->item;//需要pop的元素
link temp = head->next;//記錄下一個,防止free(head)的時候把下一個丟失。
free(head);//釋放棧頂節點(該節點資料已經被記錄在item中)
head=temp;//下下個節點成為新的head
return item;
}
FIFO佇列陣列實現
給陣列維持兩個索引的下標:一個在佇列頭,一個在佇列尾。我們把佇列的內容看成兩個下標之間的元素。為了刪除一個元素,我們把它從佇列的開始(頭部)刪除,然後使頭索引增1。為了插入一個元素,我們把它加到佇列的最後(尾部),然後使尾索引增1。
當到達陣列的末尾時,我們使它回到陣列的開始部分。
如果head和tail重合,我們認為此時佇列為空。
如果put操作使它們相等,則認為佇列滿。
static Item *q;
static int N,head,tail;
void QUEUEinit(int maxN)
{
q = malloc((maxN + 1) * sizeof(Item));//事先為陣列分配maxN + 1個節點空間
N = maxN+1;
head = N;
tail = 0;
}
int QUEUEempty()
{
return head % N == tail;
}
void QUEUEput(Item item)
{
q[tail++] = item;
tail = tail % N;//若tail < N,則tail % N == tail; 若tail >= N,則tail % N == 回捲到頭部開始計算
}
Item QUEUEget()
{
head = head % N;//若head < N,則head % N == tail; 若head >= N,則head % N == 回捲到頭部開始計算
return q[head++];//返回節點後推進一個
}