文章目錄
- 堆疊和佇列
- 1 堆疊
- 1.1 堆疊的定義和結構
- 堆疊結構體定義
- 1.2 堆疊的實作
- 堆疊初始化和銷毀
- 堆疊的壓入和彈出
- 獲取堆疊頂元素
- 其他基本介面
- 2 佇列
- 2.1 佇列的定義和結構
- 佇列結構體定義
- 2.2 佇列的實作
- 佇列初始化和銷毀
- 隊尾入隊和隊頭出隊
- 獲取隊頭隊尾元素
- 其他基本介面
- 3 堆疊和佇列面試題
- Example 1 判斷有效括號
- Example 2 佇列實作堆疊
- Example 3 堆疊實作佇列
- Example 4 實作回圈佇列
- 陣列實作
- 鏈表實作
堆疊和佇列
堆疊和佇列是兩種資料結構,堆疊和佇列這兩種結構也是線性表,特殊在于它們的操作受到一些限制,
1 堆疊
1.1 堆疊的定義和結構
堆疊是一種特殊的線性表,堆疊只允許在其固定的一端進行插入和洗掉元素的操作,進行資料插入洗掉操作的一端被稱為堆疊頂,另一端被稱為堆疊底,堆疊中的資料元素遵循后進先出的原則,
后進先出,先進后出,即
LIFO原則(Last In First Out),
壓堆疊:堆疊的插入操作被稱為壓堆疊,也可以叫做進堆疊、入堆疊,
出堆疊:堆疊的插入操作被稱為出堆疊,或稱彈堆疊,

資料的出入都在堆疊頂,類似于子彈上膛和發射的程序,元素像子彈一樣一個個地被壓入彈夾,再一個個地打出去,這個程序便是壓堆疊和出堆疊,彈夾便是堆疊,
堆疊結構體定義
和線性表類似,堆疊結構可以使用陣列堆疊和鏈式堆疊實作,相對來說,陣列堆疊比鏈式堆疊的結構優勢更大一點,
動態陣列進行尾插尾刪的效率高其次快取命中率高,缺點是動態增容有一定的記憶體消耗,鏈式堆疊需使用雙向鏈表,否則進行尾刪的效率低,但以鏈表頭作堆疊頂進行頭插頭刪的效率要更高,
typedef int STDataType;
typedef struct Stack
{
STDataType* a;
int top;
int capacity;
}ST;
a指向為堆疊開辟的空間,top指向堆疊頂,相當于順序表的size,
1.2 堆疊的實作
//初始化堆疊
void StackInit(ST* ps);
//入堆疊
void StackPush(ST* ps, STDataType data);
//出堆疊
void StackPop(ST* ps);
//獲取堆疊頂元素
STDataType StackTop(ST* ps);
//獲取堆疊元素個數
int StackSize(ST* ps);
//檢測空堆疊
bool StackEmpty(ST* ps);
//銷毀堆疊
void StackDestroy(ST* ps);
堆疊初始化和銷毀
void StackInit(ST* ps) {
ps->a = NULL;
ps->capacity = ps->top = 0;
}
void StackDestroy(ST* ps) {
assert(ps);
free(ps->a);
ps->a = NULL;
ps->capacity = ps->top = 0;
}
top可以初始化為0,也可以為-1,
- 若為0,則
top總是即將插入元素的下標,或者說是堆疊頂的后一塊空間,其值代表堆疊元素個, - 若置為-1,則代表當前堆疊頂位置下標,其值加1代表元素個數,
堆疊的壓入和彈出
void StackPush(ST* ps, STDataType data) {
assert(ps);
//檢測容量
if (ps->capacity == ps->top) {
int newCapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
STDataType* ptr = realloc(ps->a, sizeof(STDataType) * newCapacity);
if (ptr == NULL) {
perror("StackPush::realloc");
exit(-1);
}
ps->a = ptr;
ps->capacity = newCapacity;
}
ps->a[ps->top] = data;
ps->top++;
}
void StackPop(ST* ps) {
assert(ps);
assert(!StackEmpty(ps));
ps->top--;
}
入堆疊一定記得最后把
newCapacity再賦值給capacity,還有擴容時的大小應是陣列元素的大小而不是結構體的大小,
洗掉資料前需要保證堆疊的非空狀態,
獲取堆疊頂元素
STDataType StackTop(ST* ps) {
assert(ps);
assert(!StackEmpty(ps));
return ps->a[ps->top - 1];
}
void test() {
while (!StackEmpty(&stack)) {
printf("%d ", StackTop(&stack));
StackPop(&stack);
}
}
top-1為當前堆疊頂的下標,同樣要保證堆疊非空,
加上該測驗函式,可以實作回圈列印堆疊元素的功能,
其他基本介面
//獲取堆疊元素個數
int StackSize(ST* ps) {
assert(ps);
return ps->top;
}
//檢測空堆疊
bool StackEmpty(ST* ps) {
assert(ps);
return !ps->top;
}
!top的值正好可以表示堆疊的有無元素的狀態,當然這樣top必須初始化為0,
2 佇列
2.1 佇列的定義和結構
佇列同樣是一種特殊的線性表,和堆疊相反,佇列只允許在其一端進行插入而在另一端進行洗掉元素的操作,進行資料插入操作的一端被稱為隊尾,進行洗掉操作的另一端被稱為隊頭,佇列中的資料元素遵循先進先出的原則,
入佇列即在隊尾插入資料,出佇列則是在隊頭洗掉資料,
先進先出,后進后出,即
FIFO原則(First In First Out),

佇列結構體定義
typedef int QDataType;
typedef struct QueueNode
{
QDataType data;
struct QueueNode* next;
} QueueNode;
typedef struct Queue
{
struct QueueNode* head;
struct QueueNode* tail;
} Queue;
佇列需要兩個指標標識隊頭和隊尾,以便管理佇列的元素,而佇列元素即結點用單鏈表的結構實作即可,把結點封裝成一個結構體,佇列再封裝成一個結構體存入指向結點結構體的指標,
隊尾指標是根據佇列這個物件只在隊尾進行插入操作的特點而設計的,原先的單鏈表表尾有插入洗掉兩種操作,故定義尾指標的意義不大,將存盤的功能交給雙向鏈表完成,
結構的定義是很靈活的,不定義
Queue結構體或者說不將頭尾指標封裝起來也是可以的,那么函式就需要定義成:void QueueInit(QueueNode** pphead, QueueNode** pptail);顯然,封裝成結構體不失為一種良好的代碼風格,
2.2 佇列的實作
//佇列初始化
void QueueInit(Queue* pq);
//佇列銷毀
void QueueDestroy(Queue* pq);
//佇列入隊
void QueuePush(Queue* pq, QDataType x);
//佇列出隊
void QueuePop(Queue* pq);
//獲取隊頭資料
QDataType QueueFront(Queue* pq);
//獲取隊尾資料
QDataType QueueBack(Queue* pq);
//獲取佇列元素個數
int QueueSize(Queue* pq);
//檢測佇列是否為空
bool QueueEmpty(Queue* pq);
佇列初始化和銷毀
void QueueInit(Queue* pq) {
assert(pq);
pq->head = NULL;
pq->tail = NULL;
}
void QueueDestroy(Queue* pq) {
assert(pq);
QueueNode* cur = pq->head;
while (cur) {
QueueNode* next = cur->next;
free(cur);
cur = next;
}
}
初始化和銷毀并沒有傳二級指標,因為傳遞結構體的地址,而兩個指標是封裝在結構體里的,創建佇列在函式外,所以傳其地址就行,同時加上斷言以防空指標,
隊尾入隊和隊頭出隊
void QueuePush(Queue* pq, QDataType x) {//Enqueue
assert(pq);
QueueNode* newNode = (QueueNode*)malloc(sizeof(QueueNode));
if (newNode == NULL) {
perror("Queue::malloc");
exit(-1);
}
newNode->data = x;
newNode->next = NULL;
//佇列為空
if (pq->head == NULL) {
pq->head = pq->tail = newNode;
}
else {
pq->tail->next = newNode;
pq->tail = newNode;
}
}
void QueuePop(Queue* pq) {//Dequeue
assert(pq);
assert(!QueueEmpty(pq));
QueueNode* next = pq->head->next;
free(pq->head);
pq->head = next;
//鏈表為空時尾指標置空
if (pq->head == NULL) {
pq->tail = NULL;
}
}
在tail所指的隊尾后再新建并鏈上一個結點,再將tail指標指向新結點,這便是入隊的原理,出隊只能在隊頭洗掉結點,也就是將head指標指向下一個結點并將前一個釋放掉即可,
head->next對頭指標解參考,就一定要保證head有next,也就是保證鏈表非空,

獲取隊頭隊尾元素
QDataType QueueFront(Queue* pq) {
assert(pq);
assert(!QueueEmpty(pq));
return pq->head->data;
}
QDataType QueueBack(Queue* pq) {
assert(pq);
assert(!QueueEmpty(pq));
return pq->tail->data;
}
與出堆疊的實作類似,獲取佇列元素tail->data,對指標解參考訪問其所指空間,必然要檢查指標是否有效,也就是判斷鏈表是否為空,
void test() {
//...
while (!QueueEmpty(&q)) {
printf("%d ", QueueFront(&q));
QueuePop(&q);
}
}
配合上述函式可以模擬實作回圈出隊,
其他基本介面
//獲取佇列元素個數
int QueueSize(Queue* pq) {
assert(pq);
int count = 0;
QueueNode* cur = pq->head;
while (cur) {
count++;
cur = cur->next;
}
return count;
}
//檢測佇列是否為空
bool QueueEmpty(Queue* pq) {
assert(pq);
return !pq->head;
}
獲取佇列元素個數,除了遍歷計數的方式,還可以定義一個整型變數放在結點結構體中,
3 堆疊和佇列面試題
Example 1 判斷有效括號
判斷有效括號
給定一個只包括 (,),{,},[,] 的字串 s ,判斷字串是否有效,
bool isValid(char* s) {
ST st;
StackInit(&st);
while (*s) {
if (*s == '(' || *s == '[' || *s == '{') {
StackPush(&st, *s);
}
else {
//堆疊無元素,無法與右括號匹配
if (StackEmpty(&st)) {
StackDestroy(&st);
return false;
}
STDataType ret = StackTop(&st);
if ((ret == '(' && *s != ')') || (ret == '[' && *s != ']') || (ret == '{' && *s != '}')) {
StackDestroy(&st);
return false;
}
else {
StackPop(&st);
}
}
s++;
}
if (StackEmpty(&st)) {
StackDestroy(&st);
return true;
}
else {
StackDestroy(&st);
return false;
}
}
利用堆疊的先進后出,后進先出的特點,
- 將字串
s從前向后遍歷將其中所有左括號依次入堆疊, - 等待遇到右括號時再利用后進先出的特點就可將最近的左括號與右括號對比,
- 若匹配成功則出堆疊一次,下一次就可以找到前一個左括號與之后的右括號進行匹配,

Example 2 佇列實作堆疊
佇列實作堆疊
使用兩個佇列實作一個后入先出的堆疊,并支持普通堆疊的全部四種操作(push、top、pop 和 empty)
如用陣列用鏈表實作,換成用佇列實作堆疊,即利用佇列的結構和介面函式,也就是佇列的特點實作出一個結構,該結構具有堆疊的特點,
/**
* 結構體定義
**/
typedef struct QueueNode {
QDataType data;
struct QueueNode* next;
}QueueNode;
typedef struct Queue {
struct QueueNode* head;
struct QueueNode* tail;
}Queue;
typedef struct {
Queue q1;
Queue q2;
} MyStack;

/**
* 介面函式定義
**/
MyStack* myStackCreate() {
MyStack* st = (MyStack*)malloc(sizeof(MyStack));
if (st == NULL) {
exit(-1);
}
QueueInit(&st->q1);
QueueInit(&st->q2);
return st;
}
//呼叫函式創建堆區結構體并回傳
void myStackPush(MyStack* obj, int x) {
assert(obj);
//向非空佇列Push
if (!QueueEmpty(&obj->q1)) {
QueuePush(&obj->q1, x);
}
else {
QueuePush(&obj->q2, x);
}
}
int myStackPop(MyStack* obj) {
assert(obj);
//定義空與非空佇列
Queue* emptyQ = &obj->q1;
Queue* nonEmptyQ = &obj->q2;
if (!QueueEmpty(&obj->q1)) {
nonEmptyQ = &obj->q1;
emptyQ = &obj->q2;
}
//將非空佇列前n-1個元素Push到空佇列
while (QueueSize(nonEmptyQ) > 1) {
QueuePush(emptyQ, QueueFront(nonEmptyQ));
QueuePop(nonEmptyQ);
}
//Pop最后一個元素并回傳
int top = QueueFront(nonEmptyQ);
QueuePop(nonEmptyQ);
return top;
}
int myStackTop(MyStack* obj) {
assert(obj);
//回傳非空佇列隊尾元素
if (!QueueEmpty(&obj->q1)) {
return QueueBack(&obj->q1);
}
else {
return QueueBack(&obj->q2);
}
}
bool myStackEmpty(MyStack* obj) {
assert(obj);
//二者皆空才為空
return QueueEmpty(&obj->q1) && QueueEmpty(&obj->q2);
}
void myStackFree(MyStack* obj) {
assert(obj);
//釋放佇列結點
QueueDestroy(&obj->q1);
QueueDestroy(&obj->q2);
//釋放結構體
free(obj);
}
Push,由于堆疊和佇列都是從固定的一端入,故模擬入堆疊直接向非空佇列入即可,

Pop,模擬出堆疊時,就要考慮到二者的不同,先洗掉佇列中的前 n ? 1 n-1 n?1個元素并將其入到另一個空佇列中,直至第 n n n個元素再將其洗掉,
隊頭出資料,隊尾入資料,正好能將非空佇列前 n n n個元素按照原順序插入到空佇列中,非空佇列僅剩最后一個元素再洗掉掉,將所插佇列視為出堆疊后的堆疊,便實作模擬出堆疊的程序,

Top,直接呼叫佇列讀取隊尾元素的介面函式即可,

完成任意操作后都會產生一個空佇列和一個非空佇列,通過加以判斷可以將非空佇列視為待操作物件,也就是每次操作都是操作非空佇列,
Example 3 堆疊實作佇列
堆疊實作佇列
使用兩個堆疊實作先入先出佇列,佇列應當支持一般佇列支持的所有操作(push、pop、peek、empty)
/**
* 結構體定義
**/
typedef int STDataType;
typedef struct Stack {
STDataType* a;
int top;
int capacity;
}ST;
typedef struct {
ST pushST;
ST popST;
} MyQueue;

/**
* 介面函式定義
**/
MyQueue* myQueueCreate() {
MyQueue* pq= (MyQueue*)malloc(sizeof(MyQueue));
if (pq == NULL) {
exit(-1);
}
StackInit(&pq->pushST);
StackInit(&pq->popST);
return pq;
}
void myQueuePush(MyQueue* obj, int x) {
StackPush(&obj->pushST, x);
}
int myQueuePop(MyQueue* obj) {
//出堆疊為空,將入堆疊所有元素移到出堆疊
if (StackEmpty(&obj->popST)) {
while (!StackEmpty(&obj->pushST)) {
StackPush(&obj->popST, StackTop(&obj->pushST));
StackPop(&obj->pushST);
}
}
//洗掉堆疊頂即隊頭元素
int front = StackTop(&obj->popST);
StackPop(&obj->popST);
return front;
}
int myQueuePeek(MyQueue* obj) {
//出堆疊為空,將入堆疊所有元素移到出堆疊
if (StackEmpty(&obj->popST)) {
while (!StackEmpty(&obj->pushST)) {
StackPush(&obj->popST, StackTop(&obj->pushST));
StackPop(&obj->pushST);
}
}
//回傳堆疊頂即隊頭元素
return StackTop(&obj->popST);
}
bool myQueueEmpty(MyQueue* obj) {
return StackEmpty(&obj->pushST) && StackEmpty(&obj->popST);
}
void myQueueFree(MyQueue* obj) {
free(&obj->pushST);
free(&obj->popST);
free(obj);
}
由于堆疊的特點后進先出,將堆疊中元素移入另一個堆疊中會造成“逆置”的現象,此時將元素插入到非空堆疊中會導致順序發生錯誤,故指定兩個堆疊分別為入堆疊
pushST和出堆疊popST,分別負責入堆疊和出堆疊,
Push,將直接將元素插入到負責入堆疊的pushST中,

Pop,popST為空,就將pushST的元素移到popST,再從popST堆疊頂出堆疊就等于洗掉pushST的堆疊底元素,
將元素從“入堆疊”移到“出堆疊”,等于將元素逆置,逆置后的堆疊出堆疊操作而言已然和佇列的出隊一樣,相當于將堆疊底的元素從堆疊底出堆疊,也就是從先進后出變成先進先出,模擬實作了出隊,

入堆疊pushST和出堆疊popST互不影響,分別完成入隊的出隊的任務,只要popST為空,就將pushST中元素移入即可,
Example 4 實作回圈佇列
實作回圈佇列
回圈佇列可由陣列和鏈表兩種方式實作,陣列則是通過下標回圈,鏈表是回圈鏈表,

- 回圈佇列同樣是佇列,滿足佇列的所有性質,
- 回圈佇列空間大小固定且可重復利用,
- 需要多開辟一個元素的空間以用于判滿,

由圖可得
tail->next==head即佇列滿,tail==head即佇列空,陣列利用下標控制即可,利多開一塊的空間來判滿,
陣列實作
/**
* 結構體定義
**/
typedef struct {
int* a;
int front;
int tail;
int k;
} MyCircularQueue;
/**
* 介面函式定義
**/
MyCircularQueue* myCircularQueueCreate(int k) {
//為結構體開辟空間
MyCircularQueue* cq = (MyCircularQueue*)malloc(sizeof(MyCircularQueue));
//開辟陣列
cq->a = (int*)malloc((k + 1) * sizeof(int));
cq->k = k;
cq->front = cq->tail = 0;
return cq;
}
bool myCircularQueueEnQueue(MyCircularQueue* obj, int value) {
assert(obj);
//插入判斷非滿
if (myCircularQueueIsFull(obj)) {
return false;
}
obj->a[obj->tail] = value;
obj->tail++;
//回到陣列中對應位置
obj->tail %= obj->k + 1;
return true;
}
bool myCircularQueueDeQueue(MyCircularQueue* obj) {
assert(obj);
//洗掉判斷非空
if (myCircularQueueIsEmpty(obj)) {
return false;
}
obj->front++;
obj->front %= obj->k + 1;
return true;
}
int myCircularQueueFront(MyCircularQueue* obj) {
assert(obj);
if (myCircularQueueIsEmpty(obj)) {
return -1;
}
return obj->a[obj->front];
}
int myCircularQueueRear(MyCircularQueue* obj) {
assert(obj);
if (myCircularQueueIsEmpty(obj)) {
return -1;
}
/*
if (obj->tail == 0)
return obj->a[obj->k];
else
return obj->a[obj->tail - 1];
*/
int i = (obj->tail + obj->k) % (obj->k + 1);
return obj->a[i];
}
bool myCircularQueueIsEmpty(MyCircularQueue* obj) {
assert(obj);
return obj->front == obj->tail;
}
bool myCircularQueueIsFull(MyCircularQueue* obj) {
assert(obj);
/*
if (obj->tail == obj->k)
return obj->front == 0;
else
return obj->tail + 1 == obj->front;
*/
return obj->tail % (obj->k + 1) == obj->front;
//tail % (K + 1) + 1 == front
}
void myCircularQueueFree(MyCircularQueue* obj) {
assert(obj);
free(obj->a);
free(obj);
}
陣列實作,必須要考慮下標溢位的問題,當下標在陣列內時,模等上陣列長度其值不受影響,當下標溢位時,模等上陣列長度就回到陣列內對應位置,
下標溢位幾個單位,模等陣列長度后就會回到陣列內第幾位,

鏈表實作
/**
* 結構體定義
**/
typedef int SLTDataType;
typedef struct SLTNode {
SLTDataType data;
struct SLTNode* next;
}SLTNode;
typedef struct {
SLTNode* head;
SLTNode* tail;
int k;
} MyCircularQueue;
/**
* 介面函式定義
**/
MyCircularQueue* myCircularQueueCreate(int k) {
MyCircularQueue* pq = (MyCircularQueue*)malloc(sizeof(MyCircularQueue));
if (pq == NULL)
exit(-1);
pq->k = k;
//開辟鏈表
pq->head = SListNewNode();
pq->tail = pq->head;
while (k--) {
pq->tail->next = SListNewNode();
pq->tail = pq->tail->next;
}
//頭尾相連
pq->tail->next = pq->head;
//初始化指標
pq->tail = pq->head;
return pq;
}
bool myCircularQueueEnQueue(MyCircularQueue* obj, int value) {
assert(obj);
if (myCircularQueueIsFull(obj)) {
return false;
}
SListInsert(obj->tail, value);
obj->tail = obj->tail->next;
return true;
}
bool myCircularQueueDeQueue(MyCircularQueue* obj) {
assert(obj);
if (myCircularQueueIsEmpty(obj)) {
return false;
}
obj->head = obj->head->next;
return true;
}
int myCircularQueueFront(MyCircularQueue* obj) {
assert(obj);
if (myCircularQueueIsEmpty(obj)) {
return -1;
}
return obj->head->data;
}
int myCircularQueueRear(MyCircularQueue* obj) {
assert(obj);
if (myCircularQueueIsEmpty(obj)) {
return -1;
}
SLTNode* cur = obj->head;
while (cur->next != obj->tail) {
cur = cur->next;
}
return cur->data;
}
bool myCircularQueueIsEmpty(MyCircularQueue* obj) {
assert(obj);
return obj->head == obj->tail;
}
bool myCircularQueueIsFull(MyCircularQueue* obj) {
assert(obj);
return obj->tail->next == obj->head;
}
void myCircularQueueFree(MyCircularQueue* obj) {
assert(obj);
SLTNode* cur = obj->head;
while (cur->next != obj->head) {
SLTNode* next = cur->next;
free(cur);
cur = next;
}
free(cur);
free(obj);
}
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/350938.html
標籤:其他
上一篇:【資料結構】堆
