生活随笔
收集整理的這篇文章主要介紹了
【数据结构与算法】之栈与队列的应用和操作
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
棧
一、概念
- 棧(stack)又名堆棧,它是一種運算受限的線性表:
-
- 限定僅在表尾進行插入和刪除操作的線性表。允許插入和刪除的一端為棧頂,另一端是棧底。
-
- 向一個棧插入新元素又稱作進棧、入棧或壓棧,它是把新元素放到棧頂元素的上面,使之成為新的棧頂元素。
-
- 從一個棧刪除元素又稱作出棧或退棧,它是把棧頂元素刪除掉,使其相鄰的元素成為新的棧頂元素。
- 舉個例子,向 AK-47 的彈夾那樣,最后壓(存)入的子彈最先被打出去,最開始壓入的子彈,最后才能被射出。
- 再舉一個例子,一個空盤子,每烙好一張餅都放在盤子最上面(這是 push 壓入棧中),烙完后,盤子里堆了一疊餅,最下面的是最先烙好的。最上面的是剛烙好的,每一次吃只能從上面一張張拿,吃完一張拿下一張餅(這是 pop 出棧),直到盤子為空。
- 棧的性質:棧是一種 LIFO(Last In First Out) 的線性表,也就是數據元素遵循后進先出的原則。
- 棧的抽象數據類型:
ADT 棧
(Stack
)
Data具有線性表一樣的性質。
Operationtop:獲取棧頂元素count:棧的長度isEmpty:棧內元素是否為空
push(data
):元素進棧
pop():元素出棧
removeAll():清空棧內元素
EndADT
- 棧的分類(存儲結構):
① 棧的順序存儲結構:單棧、共享棧;
② 棧的鏈式存儲結構; - 共享棧:兩個順序棧共享存儲空間,棧1的棧頂在下標0處,棧2的棧頂在下標n-1處。
- 棧的順序結構和鏈式結構區別:
① 順序結構需要預先估算存儲空間大小,適合數據元素變化不大的情況;
② 鏈式結構不需要預先估算存儲空間,適合數據元素變化較大的情況; - 棧和線性表的不同之處在于,棧只有進棧和出棧操作,并且遵循后進先出的規則,也就是說數據元素順序進棧,逆序出棧。棧可以實現回退操作,遞歸和四則運算等。
二、棧的操作
- s.push(item) // 在棧頂壓入新元素
- s.pop() // 刪除棧頂元素但不返回其值
- s.empty() // 如果棧為空返回true,否則返回false
- s.size() // 返回棧中元素的個數
- s.top() // 返回棧頂的元素,但不刪除該元素
三、順序存儲棧
- 概念
-
- 順序存儲棧即物理結構是順序存儲,先開辟一塊內存空間(和順序存儲鏈表一樣有沒有),每 push 一個新元素,棧頂標記 top+1。
-
- 直到開辟的空間被存滿,每 pop 一個棧頂元素,top-1,也就是下一個元素變成棧頂元素。
- 定義數據結構:
typedef
int Status
;/* Status是函數的類型
,其值是函數結果狀態代碼,如OK等
*/
typedef
int ElemType
;/* ElemType類型根據實際情況而定,這里假設為
int *//* 順序棧結構
*/
typedef struct
{ElemType data
[MAXSIZE
];int top
; /* 用于棧頂指針
*/
}Stack
;
Status InitStack
(Stack
*S
) {S
->top
= -1;return OK
;
}
Status ClearStack
(Stack
*S
) {S
->top
= -1;return OK
;
}
Status GetTop
(Stack S
, ElemType
*e
) {if (S
.top
== -1) return ERROR
;*e
= S
.data
[S
.top
];return OK
;
}
int StackLength
(Stack S
) {return S
.top
+1;
}
Status PushStack
(Stack
*S
, ElemType e
) { if (S
->top
== MAXSIZE
-1) return ERROR
; S
->top
++; S
->data
[S
->top
] = e
; return OK
;
}
Status PopStack
(Stack
*S
, ElemType
*e
) {if (S
->top
== -1) {return ERROR
;}*e
= S
->data
[S
->top
];S
->top
--;return OK
;
}
int main
(int argc
, const char
* argv
[]) {// 創建棧Stack S
;InitStack
(&S
);for (int i
= 0; i
< 10; i
++) {PushStack
(&S
, i
);}StackPrint
(S
);// 出棧ElemType e
;PopStack
(&S
, &e
);printf
("出棧:%d",e
);StackPrint
(S
);// 獲取棧頂元素GetTop
(S
, &e
);printf
("棧頂:%d\n",e
);// 輸出長度printf
("棧長度:%d\n",StackLength
(S
));return 0;
}
棧
0 1 2 3 4 5 6 7 8 9
出棧:
9
棧
0 1 2 3 4 5 6 7 8
棧頂:
8
棧長度:
9
四、鏈式存儲棧
- 概念
-
- 鏈式存儲棧以鏈表的形式,新入棧的節點,next 指向原來的棧頂節點,插在鏈表的最前端,成為新的棧頂(和鏈表的頭插法像不像?)。
-
- 用 top 標記棧頂節點,而不是上面順序存儲的位置,每一次入棧新節點,top 指向新棧頂節點,count 也隨之 +1。出棧時,top 指向棧頂節點的 next 節點,count-1。
typedef
int Status
;/* Status是函數的類型
,其值是函數結果狀態代碼,如OK等
*/
typedef
int ElemType
;/* ElemType類型根據實際情況而定,這里假設為
int *//* 鏈棧的每一個節點,和單鏈表很像有沒有
*/
typedef struct StackNode
{ElemType data
;struct StackNode
*next;
}StackNode
;typedef struct StackNode
* StackNodePtr
;/* 棧結構
*/
typedef struct
{StackNodePtr top
;int count
;
}LinkStack
;
Status InitStack
(LinkStack
*S
) {S
->top
= NULL
;S
->count
= 0;return OK
;
}
Status ClearStack
(LinkStack
*S
) { if (S
->top
== NULL
) return ERROR
; StackNodePtr p
; while (S
->count
!= 0) { p
= S
->top
; S
->top
= S
->top
->next; free
(p
); S
->count
--; } return OK
;
}
Status GetTop
(LinkStack S
, ElemType
*e
) {if (S
.top
== NULL
) return ERROR
;// if (S
->count
== 0) return ERROR
; // 也可以
*e
= S
.top
->data
;return OK
;
}
int StackLength
(LinkStack S
) {return S
.count
;
}
Status PushStack
(LinkStack
*S
, ElemType e
)
{// 創建新元素StackNodePtr p
= (StackNodePtr
)malloc
(sizeof
(StackNode
));if (p
== NULL
) return ERROR
;p
->data
= e
;p
->next = S
->top
;S
->top
= p
;S
->count
++;return OK
;
}
Status PopStack
(LinkStack
*S
, ElemType
*e
) { if (S
->top
== NULL
) return ERROR
; // if (S
->count
== 0) return ERROR
; // 也可以
/* 將棧頂指針指向新的棧頂
*/ StackNodePtr temp
= S
->top
; S
->top
= S
->top
->next; *e
= temp
->data
; free
(temp
); S
->count
--; return OK
;
}
nt main
(int argc
, const char
* argv
[]) {// 創建棧LinkStack S
;InitStack
(&S
);for (int i
= 0; i
< 10; i
++) {PushStack
(&S
, i
);}StackPrint
(S
);ElemType e
;PopStack
(&S
, &e
);printf
("出棧:%d\n",e
);StackPrint
(S
);// 獲取棧頂元素GetTop
(S
, &e
);printf
("棧頂:%d\n",e
);// 輸出長度printf
("棧長度:%d\n",StackLength
(S
));return 0;
}
9 8 7 6 5 4 3 2 1 0
出棧:
9
8 7 6 5 4 3 2 1 0
棧頂:
8
棧長度:
9
隊列
一、概念
- 只允許在一段進行插入操作,而在另一端進行刪除操作的線性表;
- 性質:先進先出;
- 隊列的抽象數據類型:
ADT 隊列
(Queue
)
Data具有線性表一樣的性質。
Operationfront:隊列第一個數據count:隊列的長度
isEmpty():隊列是否為空
enQueue(data
):數據進隊列
deQueue():數據出隊列
removeAll():清空隊列內的所有數據
EndADT
- 順序隊列:就是使用數組來模擬隊列,但是數組的插入和刪除需要移動數據,比較繁瑣。
- 循環順序隊列:在順序隊列的基礎上改造,使隊列的隊頭和隊尾可以在數組中循環變化,在數據插入和刪除就不需要頻繁移動數據了。但是順序隊列,都需要提前申請存儲空間,還有溢出的可能。
- 鏈式隊列:為了解決順序隊列的不足,引用鏈式隊列。不需要提前申請空間,只不過會引入頻繁申請和釋放的操作。
- 隊列有什么作用?在開發過程中,接觸最多的應該是全局并發隊列。為什么要用隊列實現呢?在線程的優先級一樣的情況下,不應該先申請系統資源的先被滿足嗎?這和在銀行排隊取錢是一個道理。
二、隊列的操作
- push(item)
- q.pop()
- q.front()
- q.back()
- q.size()
- q.empty()
三、鏈隊列
- 區別:棧,新的元素添加在棧頂,而且棧頂先出;隊列,隊尾進,隊首出。
- 二者相反,所以,鏈隊列的操作簡單來說就是:
-
- 進入隊列:Q.rear 尾節點后追加新節點,將 Q.rear 指向新節點,新節點成隊尾;
-
- 出隊列:Q.front 指向的首元節點出隊列,Q.front 指向首元節點的下一個節點。
- 先定義數據結構:
typedef
int Status
;/* Status是函數的類型
,其值是函數結果狀態代碼,如OK等
*/
typedef
int ElemType
;/* ElemType類型根據實際情況而定,這里假設為
int */typedef struct QueueNode
{ElemType data
;struct QueueNode
*next;
} QueueNode
, *QueueNodePtr
;typedef struct
{QueueNodePtr front
;QueueNodePtr rear
;
} LinkQueue
;
- 初始化:先創建一個頭節點,讓 Q.front 和 Q.rear 指向頭節點,頭節點的 next 為 NULL:
// 初始化Status
InitQueue
(LinkQueue
*Q
) { // 初始隊列為空
,只有頭節點,不是有效數據的節點
*Q
->front
= *Q
->rear
= (QueueNodePtr
)malloc
(sizeof
(QueueNode
)); if (*Q
->front
== NULL
) return ERROR
; // 頭節點的后面為空
*Q
->front
->next = NULL
; return OK
;
}
- 判斷隊列為空:當隊列為空時,恰入上圖初始化的狀態,只剩一個頭節點,此時 Q.front == Q.rear;
// 判斷是否為空Status
QueueEmpty
(LinkQueue Q
) { if (Q
.front
== Q
.rear
) return TRUE
; return FALSE
;
}
- 進入隊列:
-
- 進入隊列的操作,是將新元素,追加到 rear 指向的隊尾之后,rear->next = 新元素,再將 rear 指向新元素,此時,新元素成為隊尾。因為不受存儲空間限制(內存占滿另說),所以不需要一開始就判斷是否隊滿,也沒有隊滿的操作。
-
-
-
Status EnQueue
(LinkQueue
*Q
, ElemType e
) {QueueNodePtr p
= (QueueNodePtr
)malloc
(sizeof
(QueueNode
));if (p
== NULL
) return ERROR
;p
->data
= e
;p
->next = NULL
;// 追加到隊尾
*Q
->rear
->next = p
;// 標記成隊尾
*Q
->rear
= p
;return OK
;
}
- 出隊列:出隊列操作,是將首元節點從鏈隊刪除。
-
-
- 找到隊首節點 head(此時已拿到節點,將該節點的 data 回調出去),等待刪除;
-
- 更改標記 head 后面的節點 s 為首元節點,即 head->next;
-
- 判斷是否是最后一個節點,是的話,rear 也指向頭節點;
-
Status DeQueue
(LinkQueue
*Q
, ElemType
*e
) {if (QueueEmpty
(*Q
)) return ERROR
;QueueNodePtr head
;// 找到要刪除的節點head
= *Q
->front
->next;// 回調到函數外
*e
= head
->data
;// 更改頭節點
*Q
->front
->next = head
->next;// 如果刪到了隊尾最后一個元素
if (*Q
->rear
== head
)*Q
->rear
= *Q
->front
;// 刪除臨時指針指向的頭節點free
(head
);return OK
;
}
- 清空:僅清空頭節點以外的全部節點,有頭節點在,清空完,還能繼續 EnQueue() 操作,又回到初始化后的狀態;
// 清空隊列
Status ClearQueue
(LinkQueue
*Q
) {if (*Q
->front
->next == NULL
) return ERROR
;QueueNodePtr temp
, p
; // 首元節點
*Q
->rear
= *Q
->front
;p
= *Q
->front
->next;*Q
->front
->next = NULL
;// 此時只有temp指向首元節點
while (p
) {temp
= p
;p
= p
->next;free
(temp
);}return OK
;
}
- 銷毀:銷毀操作,和清空不一樣,清空僅僅刪除除頭節點以外的所有節點,清空后還可以再入隊。但是銷毀已經徹底不使用,需要連頭節點也一并 free 掉,所以代碼中是從 front 開始,而非 front->next,已 free 所有的節點;
// 銷毀隊列
Status DestoryQueue
(LinkQueue
*Q
) {if (*Q
->front
->next == NULL
) return ERROR
;/*說明,頭節點也是個malloc開辟的,也需要釋放
*/while (*Q
->front
) {*Q
->rear
= *Q
->front
->next;free
(*Q
->front
);*Q
->front
= *Q
->rear
;}return OK
;
}
- 獲取隊首:不更改隊列,不破壞隊列現有結構,僅查找,所以首元節的數據直接讀取 Q.front->next->data;
Status GetHead
(LinkQueue Q
, ElemType
*e
) {if (QueueEmpty
(*Q
)) return ERROR
;*e
= Q
.front
->next->data
;return OK
;
}
總結
以上是生活随笔為你收集整理的【数据结构与算法】之栈与队列的应用和操作的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。