堆疊和佇列
- 前言
- 一、堆疊
- 1.堆疊的結構
- 2.初始化堆疊
- 3.壓堆疊操作
- 4.出堆疊操作
- 堆疊是否為空
- 5.獲取堆疊頂元素
- 6.獲取堆疊中有效元素個數
- 7.銷毀堆疊
- 二、佇列
- 1.佇列的結構
- 2.佇列的初始化
- 3.佇列的插入
- 4.佇列的洗掉
- 5.獲取佇列頭/尾部元素
- 6.獲取佇列中有效元素個數
- 7.檢測佇列是否為空
- 8.銷毀佇列
- 三、回圈佇列
- 回圈鏈表的結構
- 回圈鏈表的實作
- 四、一些習題
- 1.有效的括號
- 2. 用佇列實作堆疊
- 3.用堆疊實作佇列
- 附上堆疊和佇列的代碼
- 總結
文章很長,如有需要,請先收藏喲??????
前言
堆疊和佇列在資料結構中也是重要的一部分,他們都是特殊的線性表,許多需要用到我們的堆疊和佇列,回想一下,我們以前寫過的遞回,其實都是可以通過我們的堆疊去模擬實作,而佇列的使用也是十分多的,讓我們一起學習學習他們的實作
一、堆疊
1.堆疊的結構
我們模擬實作堆疊的結構非常簡單,只是在我們的線性表的基礎上更改,不允許了我們的隨機訪問,而是得通過介面來實作資料的插入和洗掉,
我們的資料都是得從堆疊頂入堆疊,從堆疊頂出堆疊
注釋:
壓堆疊/進堆疊/入堆疊:堆疊的插入操作,入資料在堆疊頂
出堆疊: 站的洗掉操作,出資料也在堆疊頂
來看看下面這個動圖加深理解吧:
代碼展示堆疊的結構:
typedef int STDataType;
typedef struct Stack
{
STDataType* _a;
int _top; // 堆疊頂
int _capacity; // 容量
}Stack;
解釋:這上面_a陣串列示我們要的是一個動態開辟的陣列,_top表示我們圖中所示的陣列,_capacity表示我們的容量大小,當_top與_capacity相同的時候我們就可以進行擴容操作了
首先我們創建一個stack.h,stack.c,和一個用來測驗介面的test_stack_queue.c檔案

常見介面:
// 初始化堆疊
void StackInit(Stack* ps);
// 入堆疊
void StackPush(Stack* ps, STDataType data);
// 出堆疊
void StackPop(Stack* ps);
// 獲取堆疊頂元素
STDataType StackTop(Stack* ps);
// 獲取堆疊中有效元素個數
int StackSize(Stack* ps);
// 檢測堆疊是否為空,如果為慷訓傳非零結果,如果不為慷訓傳0
int StackEmpty(Stack* ps);
// 銷毀堆疊
void StackDestroy(Stack* ps);
2.初始化堆疊
聽過我之前講的函式堆疊幀的同學肯定知道我要寫的第一個介面是什么
–初始化,不清楚的同學點擊跳轉函式堆疊幀
看完之后你就能明白為什么要初始化?不初始化的后果?
void StackInit(Stack* ps)
{
assert(ps);
ps->_a = NULL;
ps->_capacity = ps->_top = 0;
}
3.壓堆疊操作
每次只是需要將top位置更新,因為是底層實作用的陣列,所以說記憶體不夠需要擴容,我們就分開一個函式CheckCapacity(),每次檢測容量是否用完,
這里的增容是可以1.5或者2倍,但是增容意味著空間開太大會造成浪費,增容太少又造成頻繁增容效率低下,所以我們一般選擇適中的2倍,
void CheckCapacity(Stack* ps)
{
assert(ps);
if (ps->_capacity == ps->_top)
{
int newcapacity = ps->_capacity == 0 ? 4 : ps->_capacity * 2;
STDataType* tmp= (STDataType*)realloc(ps->_a,sizeof(STDataType) * newcapacity);
if (tmp == NULL)
{
printf("增容失敗\n");
exit(-1);
}
ps->_a = tmp;
ps->_capacity = newcapacity;
}
}
void StackPush(Stack* ps, STDataType data)
{
assert(ps);
CheckCapacity(ps);
ps->_a[ps->_top] = data;
ps->_top++;
}
4.出堆疊操作
出堆疊的時候需要檢測是否堆疊內有無元素,沒有元素的話我們就報錯處理 ,當然這里判斷堆疊是否為空我們也可以封裝一個函式
堆疊是否為空
int StackEmpty(Stack* ps)
{
assert(ps);
return ps->_top ==0;//0為空
}
void StackPop(Stack* ps)
{
assert(ps);
assert(!StackEmpty(ps));
ps->_top--;
}
5.獲取堆疊頂元素
也是要判斷一下是否在堆疊內有無元素
STDataType StackTop(Stack* ps)
{
assert(ps);
assert(!StackEmpty(ps));
return ps->_a[ps->_top-1];
}
6.獲取堆疊中有效元素個數
_top的位置就是元素的總數

int StackSize(Stack* ps)
{
assert(ps);
return ps->_top;
}
7.銷毀堆疊
銷毀堆疊的程序,銷毀堆疊是重要的程序,我們的_a陣列是動態開辟的,沒有釋放會造成記憶體泄漏
當然,注意看這里的ps指標是不能置空的,在之前的詳解指標這一節的常見錯誤中有提及!
void StackDestroy(Stack* ps)
{
assert(ps);
free(ps->_a);
ps->_a = NULL;
//free(ps); 錯誤
ps->_capacity = ps->_top = 0;
}
二、佇列
1.佇列的結構
佇列的結構會比堆疊的難懂一些,因為佇列的屬性就是先入先出,所以采用順序表的話我們的頭刪就要去移動資料覆寫第一個位置,或者用一個指標記錄頭的位置,頭刪的時候用移動頭節點來表示,但是這都不如鏈表來的方便,鏈表在我們的頭部洗掉是O(1)的時間復雜度,并且不用洗掉隊尾元素,所以我們這里使用我們的單鏈表結構來實作佇列!
鏈表的初階
鏈表的進階
// 鏈式結構:表示佇列
typedef int QDataType;
typedef struct QListNode
{
struct QListNode* _next;
QDataType _data;
}QNode;//佇列中結點的結構
// 佇列的結構
typedef struct Queue
{
QNode* _front;//頭指標
QNode* _rear;//尾指標
}Queue;
佇列:只允許在一端進行插入資料操作,在另一端進行洗掉資料操作的特殊線性表,佇列具有先進先出 FIFO(First In First Out) 入佇列:進行插入操作的一端稱為隊尾 出佇列:進行洗掉操作的一端稱為隊頭
動圖:
我們先封裝佇列中的一個結點,結點中有指向下一個元素的指標,和自身的值,然后因為我們的佇列是用單鏈表實作的,而佇列有一個獲取隊尾元素的介面,所以我們設定尾指標_rear能夠是我們不用每次遍歷找到尾在進行插入
2.佇列的初始化
void QueueInit(Queue* q)
{
assert(q);
q->_front = NULL;
q->_rear = NULL;
}
3.佇列的插入
佇列的插入就和單鏈表的頭插一模一樣,就是記得判斷一開始我們的隊頭和隊尾都是NULL,后續只需要移動_rear(隊尾指標)!!!
動圖:
void QueuePush(Queue* q, QDataType data)
{
assert(q);
QNode* tmp = (QNode*)malloc(sizeof(QNode));
tmp->_next = NULL;
tmp->_data = data;
if (q->_rear == NULL)
{
q->_front = q->_rear = tmp;
}
else
{
q->_rear->_next = tmp;
q->_rear = tmp;
}
}
4.佇列的洗掉
動圖:(當洗掉最后一個結點時易忽略處理_front 指標)

尾刪需要注意是否佇列中存在元素!!這里
的邏輯比較簡單,但要注意當佇列元素只剩一個的時候我們的隊尾指標也要置成NULL,不然就會造成野指標
void QueuePop(Queue* q)
{
assert(q);
assert(!QueueEmpty(q));
QNode* first = q->_front->_next;
if (first == NULL)
q->_rear = NULL;//處理這一步
free(q->_front);
q->_front = first;
}
5.獲取佇列頭/尾部元素
這個就很簡單啦!!
QDataType QueueFront(Queue* q)
{
assert(q);
assert(!QueueEmpty(q));
return q->_front->_data;
}
QDataType QueueBack(Queue* q)
{
assert(q);
assert(!QueueEmpty(q));
return q->_rear->_data;
}
6.獲取佇列中有效元素個數
int QueueSize(Queue* q)
{
assert(q);
int n = 0;
QNode* cur = q->_front;
while (cur)
{
n++;
cur = cur->_next;
}
return n;
}
7.檢測佇列是否為空
如果為慷訓傳非零結果,如果非慷訓傳0
int QueueEmpty(Queue* q)
{
assert(q);
return q->_front == NULL;
}
8.銷毀佇列
這里要遍歷佇列,因為每一個結點都是我們從堆上開辟的,我們就要一個個的釋放
void QueueDestroy(Queue* q)
{
assert(q);
while (!QueueEmpty(q))
{
QNode* tmp = q->_front->_next;//保存下一個結點
free(q->_front);
q->_front = tmp;
}
q->_front = q->_rear = NULL;
}
從上面的圖我們就能看出來,出堆疊的時候我們只能從堆疊頂出,出來的結果剛好與入的值相反,其實同一組資料入堆疊在出堆疊順序不同的時候是有不同的結果的,
三、回圈佇列
解釋:
回圈佇列的使用場景:當我們在去打疫苗的時候排隊,注射疫苗的位置有限,我們前面排隊的先打,后面的后進去的后打.
回圈佇列就是固定了佇列的大小空間
設計回圈佇列
題目分析:這道題目是讓我們設計一個回圈佇列,先說結論我們這里的回圈佇列所用的是循序表實作,因為我們在這個場景下不需要擴容,順序表隨機訪問率高.其次如果使用單鏈表,在進行洗掉頭部元素的時候我們需要找到上一個指標,為了效率就得有雙向鏈表,但有簡單又高效的結構我們優先去使用順序表.

回圈鏈表的結構
typedef struct {
int *a;//指向我們的陣列
int front;//隊頭
int rear;//隊尾,指向的位置是我們存放的位置
int k;
} MyCircularQueue;
MyCircularQueue* myCircularQueueCreate(int k) {
MyCircularQueue* obj =(MyCircularQueue*)malloc(sizeof(MyCircularQueue));
obj->a =(int*)malloc(sizeof(int)*(k+1));
obj->front =obj->rear=0;
obj->k = k;
return obj;
}
回圈鏈表的實作
題目看起來并不難,但有一個點確實需要琢磨,我們用順序表的時候,一開始我們的隊頭元素的指標和隊首元素的指標是指向同一處的,這個時候我們是認為他是題目看起來并不難,但有一個點確實需要琢磨,我們用順序表的時候,一開始我們的隊頭元素的指標和隊首元素的指標是指向同一處的,
這個時候是整個陣列我們都已經放滿了呢還是一個元素都沒放入,這就會出現分歧

解決方案:
方案1.
聰明的同學可能一下子就想著說我們可能可以開一個標記矩陣,放過元素的我們標記一下,洗掉的時候我們把他的狀態也標記一下,這個方案毫無疑問是可行的,但是空間開的就比較多了,下面有一種更好的
方案2.
我們在原來的基礎上多開一個陣列,當我們的隊頭指標與隊尾指針相遇時是我們的陣列還沒元素,當_rear的下一個元素是我們的_front時才是我們整個陣列放滿了,下圖為放滿的情況

然后這題要對下標進行判斷,下標若走到-1,則更新成k,若走到k+1則更新成0
typedef struct {
int *a;//指向我們的陣列
int front;//隊頭
int rear;//隊尾,指向的位置是我們存放的位置
int k;
} MyCircularQueue;
MyCircularQueue* myCircularQueueCreate(int k) {
MyCircularQueue* obj =(MyCircularQueue*)malloc(sizeof(MyCircularQueue));
obj->a =(int*)malloc(sizeof(int)*(k+1));
obj->front =obj->rear=0;
obj->k = k;
return obj;
}
bool myCircularQueueEnQueue(MyCircularQueue* obj, int value) {
if(((obj->rear+1)%(obj->k+1)) ==obj->front)
return false;
//插入元素
obj->a[obj->rear] = value;
++obj->rear;
obj->rear %= (obj->k+1);
return true;
}
bool myCircularQueueDeQueue(MyCircularQueue* obj) {
if(obj->front == obj->rear)//洗掉元素front指標往前走
return false;
obj->front++;
if(obj->front == obj->k+1)
obj->front =0;
return true;
}
int myCircularQueueFront(MyCircularQueue* obj) {
if(obj->front == obj->rear)
return -1;
return obj->a[obj->front];
}
int myCircularQueueRear(MyCircularQueue* obj) {
if(obj->front ==obj->rear)
return -1;
int x = obj->rear -1;
if(x ==-1)
x= obj->k;
return obj->a[x];
}
bool myCircularQueueIsEmpty(MyCircularQueue* obj) {
return obj->rear ==obj->front;
}
bool myCircularQueueIsFull(MyCircularQueue* obj) {
return ((obj->rear+1)%(obj->k+1)) ==obj->front;
}
void myCircularQueueFree(MyCircularQueue* obj) {
free(obj->a);
obj->a =NULL;
free(obj);
}
四、一些習題
1.有效的括號
鏈接:有效的括號
這題要用到我們的堆疊,對于C語言的學習者,我們并沒有庫,大家可以用下面我在碼云發布的堆疊的代碼來寫
思路:我們遇到左括號的時候就入堆疊,遇到右括號的時候就拿堆疊頂資料比較,只要不符合就回傳false,若字符遍歷完且堆疊里的資料都出完了,則括號都能一一匹配
class Solution {
public:
bool isValid(string s) {
//只要是左括號我們就入堆疊,遇到右括號我們就從堆疊頂pop一個資料看與他是否匹配
stack<char> st;
int len =s.size();
for(int i =0;i<len;i++)
{
char ch =s[i];
if(ch =='('||ch =='{'||ch=='[')
{
st.push(ch);
}
else
{
if(st.empty())
{
return false;
}
else
{
char ch2 = st.top();
st.pop();
if((ch ==')'&&ch2 !='(')
|| (ch =='}'&&ch2 !='{')
|| (ch ==']'&&ch2!='['))
return false;
}
}
}
if(st.empty())
return true;
return false;
}
};
2. 用佇列實作堆疊
看到前面的不要慌,只是佇列的實作而已,我們為了練習佇列可以就用我們實作的佇列
思路:用兩個佇列分別為q1,q2,每次push資料往其中為有資料的入,都沒有就隨便選一個入,pop資料的時候就將有資料的匯入到沒資料的,在判斷是否為最后一個,是的話我們就pop掉且不用入有空的佇列了
myStackPush資料的時候只用往nonempty(不為空)的佇列入,動圖:
myStackPop都要導到另一個為空的佇列在pop掉最后一個資料,動圖:
typedef int QDataType;
typedef struct QListNode
{
struct QListNode* _next;
QDataType _data;
}QNode;//佇列中結點的結構
// 佇列的結構
typedef struct Queue
{
QNode* _front;
QNode* _rear;
}Queue,queue;
// 初始化佇列
void QueueInit(Queue* q);
// 隊尾入佇列
void QueuePush(Queue* q, QDataType data);
// 隊頭出佇列
void QueuePop(Queue* q);
// 獲取佇列頭部元素
QDataType QueueFront(Queue* q);
// 獲取佇列隊尾元素
QDataType QueueBack(Queue* q);
// 獲取佇列中有效元素個數
int QueueSize(Queue* q);
// 檢測佇列是否為空,如果為慷訓傳非零結果,如果非慷訓傳0
int QueueEmpty(Queue* q);
// 銷毀佇列
void QueueDestroy(Queue* q);
void QueueInit(Queue* q)
{
assert(q);
q->_front = NULL;
q->_rear = NULL;
}
void QueuePush(Queue* q, QDataType data)
{
assert(q);
QNode* tmp = (QNode*)malloc(sizeof(QNode));
tmp->_next = NULL;
tmp->_data = data;
if (q->_rear == NULL)
{
q->_front = q->_rear = tmp;
}
else
{
q->_rear->_next = tmp;
q->_rear = tmp;
}
}
void QueuePop(Queue* q)
{
assert(q);
assert(!QueueEmpty(q));
QNode* first = q->_front->_next;
if (first == NULL)
q->_rear = NULL;//處理這一步
free(q->_front);
q->_front = first;
}
QDataType QueueFront(Queue* q)
{
assert(q);
assert(!QueueEmpty(q));
return q->_front->_data;
}
QDataType QueueBack(Queue* q)
{
assert(q);
assert(!QueueEmpty(q));
return q->_rear->_data;
}
int QueueSize(Queue* q)
{
assert(q);
int n = 0;
QNode* cur = q->_front;
while (cur)
{
n++;
cur = cur->_next;
}
return n;
}
int QueueEmpty(Queue* q)
{
assert(q);
return q->_front == NULL;
}
void QueueDestroy(Queue* q)
{
assert(q);
while (!QueueEmpty(q))
{
QNode* tmp = q->_front->_next;
free(q->_front);
q->_front = tmp;
}
q->_front = q->_rear = NULL;
}
typedef struct {
queue q1;
queue q2;
} MyStack;
/** Initialize your data structure here. */
MyStack* myStackCreate() {
MyStack* mtk = (MyStack*) malloc(sizeof(MyStack));
QueueInit(&mtk->q1);
QueueInit(&mtk->q2);
return mtk;
}
/** Push element x onto stack. */
void myStackPush(MyStack* obj, int x) {
MyStack* empty = &obj->q1;
MyStack* nonempty =&obj->q2;
if(!QueueEmpty(&obj->q1))
{
empty =&obj->q2;
nonempty =&obj->q1;
}
QueuePush(nonempty,x);
}
/** Removes the element on top of the stack and returns that element. */
int myStackPop(MyStack* obj) {
MyStack* empty = &obj->q1;
MyStack* nonempty =&obj->q2;
if(!QueueEmpty(&obj->q1))//這段邏輯大家可以思考一下
{
empty =&obj->q2;
nonempty =&obj->q1;
}
int x;
while(!QueueEmpty(nonempty))
{
x =QueueFront(nonempty);
QueuePop(nonempty);
if(!QueueEmpty(nonempty))
QueuePush(empty,x);
}
return x;
}
/** Get the top element. */
int myStackTop(MyStack* obj) {
MyStack* empty = &obj->q1;
MyStack* nonempty =&obj->q2;
if(!QueueEmpty(&obj->q1))
{
empty =&obj->q2;
nonempty =&obj->q1;
}
return QueueBack(nonempty);
}
/** Returns whether the stack is empty. */
bool myStackEmpty(MyStack* obj) {
return QueueEmpty(&obj->q1)&&QueueEmpty(&obj->q2);
}
void myStackFree(MyStack* obj) {
QueueDestroy(&obj->q1);
QueueDestroy(&obj->q2);
free(obj);
obj=NULL;
}
3.用堆疊實作佇列
鏈接:用堆疊實作佇列
這道題之前的類似
我們用兩個堆疊解決問題,一個為pushst,用來存放資料,popst用來出資料
StackPush資料我們可以一直往我們的pushst入,當我們要出的時候popst沒有資料則匯入資料,有就直接出,并且注意我們導資料到popst的時候正常的pop資料就可以保證他是按照佇列先進先出的順序了,一些小細節在代碼有注釋
動圖展示:
從入堆疊123變換popst出堆疊時也123的順序

typedef int STDataType;
typedef struct Stack
{
STDataType* _a;
int _top; // 堆疊頂
int _capacity; // 容量
}Stack;
// 初始化堆疊
void StackInit(Stack* ps);
// 入堆疊
void StackPush(Stack* ps, STDataType data);
// 出堆疊
void StackPop(Stack* ps);
// 獲取堆疊頂元素
STDataType StackTop(Stack* ps);
// 獲取堆疊中有效元素個數
int StackSize(Stack* ps);
// 檢測堆疊是否為空,如果為慷訓傳非零結果,如果不為慷訓傳0
int StackEmpty(Stack* ps);
// 銷毀堆疊
void StackDestroy(Stack* ps);
void StackInit(Stack* ps)
{
assert(ps);
ps->_a = NULL;
ps->_capacity = ps->_top = 0;
}
void CheckCapacity(Stack* ps)
{
assert(ps);
if (ps->_capacity == ps->_top)
{
int newcapacity = ps->_capacity == 0 ? 4 : ps->_capacity * 2;
STDataType* tmp= (STDataType*)realloc(ps->_a,sizeof(STDataType) * newcapacity);
if (tmp == NULL)
{
printf("增容失敗\n");
exit(-1);
}
ps->_a = tmp;
ps->_capacity = newcapacity;
}
}
void StackPush(Stack* ps, STDataType data)
{
assert(ps);
CheckCapacity(ps);
ps->_a[ps->_top] = data;
ps->_top++;
}
void StackPop(Stack* ps)
{
assert(ps);
assert(!StackEmpty(ps));
ps->_top--;
}
STDataType StackTop(Stack* ps)
{
assert(ps);
assert(!StackEmpty(ps));
return ps->_a[ps->_top-1];
}
int StackEmpty(Stack* ps)
{
assert(ps);
return ps->_top ==0;//0為空
}
void StackDestroy(Stack* ps)
{
assert(ps);
free(ps->_a);
ps->_a = NULL;
//free(ps); 錯誤
ps->_capacity = ps->_top = 0;
}
int StackSize(Stack* ps)
{
assert(ps);
return ps->_top;
}
typedef struct {
Stack pushst;
Stack popst;
} MyQueue;
/** Initialize your data structure here. */
MyQueue* myQueueCreate() {
MyQueue* mq= (MyQueue*)malloc(sizeof(MyQueue));
StackInit(&mq->pushst);
StackInit(&mq->popst);
return mq;
}
/** Push element x to the back of queue. */
void myQueuePush(MyQueue* obj, int x) {
StackPush(&obj->pushst,x);
}
/** Get the front element. */
int myQueuePeek(MyQueue* obj) {
if(!StackEmpty(&obj->popst))
{
return StackTop(&obj->popst);
}
//如果是popst為空就入,不然就直接出
while(!StackEmpty(&obj->pushst))
{
int x =StackTop(&obj->pushst);
StackPop(&obj->pushst);
StackPush(&obj->popst,x);
}
return StackTop(&obj->popst);
}
/** Removes the element from in front of queue and returns that element. */
int myQueuePop(MyQueue* obj) {
int tmp =myQueuePeek(obj);
StackPop(&obj->popst);
return tmp;
}
/** Returns whether the queue is empty. */
bool myQueueEmpty(MyQueue* obj) {
return StackEmpty(&obj->pushst)&&StackEmpty(&obj->popst);
}
void myQueueFree(MyQueue* obj) {
StackDestroy(&obj->pushst);
StackDestroy(&obj->popst);
free(obj);
obj=NULL;
}
附上堆疊和佇列的代碼
碼云: <代碼鏈接>
三道習題若大家沒有動手實作可以用這里的!!
總結
本篇文章講述了堆疊和佇列,其中鏈表實作還有問題一定要去看看我之前寫的兩篇鏈表文章,這篇文章的細節有很多,但弄懂之后其實都還好,對于文章有任何問題的都歡迎私信我.
看到這里不妨一鍵三連!!!一鍵三連!!!一鍵三連!!!
上集回顧:
鏈表的初階
鏈表的進階
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/293179.html
標籤:其他
