數據結構和算法(五)棧的操作和實現

數據結構和算法(一)線性表實現

數據結構和算法(二)單向循環鏈表的創建插入刪除實現

數據結構和算法(三)雙向鏈表與雙向循環鏈表的實現

數據結構和算法(四)鏈表相關面試題

數據結構和算法(五)棧和隊列的操作和實現

@[TOC]

數據結構和算法(五)棧的操作和實現

  • 本篇博客的Demo下載:
  1. 順序棧的基本操作實現
  2. 鏈式棧的基本操作實現

1. 棧的簡介

棧是一種后進先出的結構,有一個棧底指針,一個棧頂指針,入棧只能從棧頂入棧,出棧也只能從棧頂出棧。它的結構示意圖如下:


棧的結構圖

我們可以對比一下隊列:隊列是一種先進先出的數據結構,只能從隊尾入隊,從對頭出隊,隊列的結構圖如下圖:


隊列的結構示意圖

2. 順序棧的實現

2.1 順序棧的基本操作

2.1.1 順序棧結構

//順序棧結構
typedef struct KSqueueStack{
    KStackElementType data[MAXSIZE];
    int top; //用于棧頂指針
}SqStack;

2.1.2 順序棧建棧

//1. 構建一個空棧S
KStatus initStack(SqStack *S) {
    S->top = -1;
    return OK;
}

2.1.3 順序棧置空

//2. 將棧置空
KStatus clearStack(SqStack *S) {
    S->top = -1;
    return OK;
}```
#### 2.1.4 順序棧判空
```swift
//3. 判斷順序棧是否為空
KStatus isEmpty(SqStack S) {
    return  S.top == -1 ;
}

2.1.5 順序棧獲取長度

//4. 獲取棧長度
int getLength(SqStack S) {
    return S.top + 1;
}

2.1.6 順序棧獲取棧頂元素

//5. 獲取棧頂
KStatus getTop(SqStack S, KStackElementType *e) {
    //棧空,則返回錯誤
    if (S.top == -1) return ERROR;
    *e = S.data[S.top];
    return OK;
}

2.1.7 順序棧壓棧

入棧前如下圖所示:


入棧前

入棧后如下圖所示:


入棧后

入棧代碼實現:

//6. 壓棧
KStatus push(SqStack *S, KStackElementType e) {
    //判斷是否 棧滿
    if (S->top == MAXSIZE -1) return ERROR;
    //1. 棧頂指針+1;
    //2. 將新插入的元素賦值給棧頂空間
    //S->top ++;
    //S->data[S->top] = e;
    S->data[++(S->top)] = e;
    return OK;
}

2.1.8 順序棧出棧

//7. 出棧
KStatus pop(SqStack *S, KStackElementType *e) {
    //判斷是否棧空
    if(S->top == -1) return ERROR;
    //1. 將要刪除的棧頂元素賦值給e
    //2. 棧頂指針--;
    //*e = S->data[S->top];
    //S->top--;
    *e = S->data[S->top--];
    return OK;
}

2.1.9 順序棧遍歷

//8. 棧遍歷
KStatus traverse(SqStack S) {
    int i = 0;
    printf("棧所有元素:");
    while (i < S.top) {
        printf("%d ",S.data[i++]);
    }
    printf("\n");
    return OK;
}

2.1.10 順序棧單元測試

//9. 測試
void test() {
    SqStack S;
    int e;
    
    if (initStack(&S) == OK) {
        for (int j = 1 ; j < 10; j++) {
            push(&S, j);
        }
    }
    
    printf("順序棧中元素為:\n");
    traverse(S);
    
    pop(&S, &e);
    printf("彈出棧頂元素為: %d\n",e);
    traverse(S);
    printf("是否為空棧:%d\n",isEmpty(S));
    getTop(S, &e);
    printf("棧頂元素:%d \n棧長度:%d\n",e,getLength(S));
    clearStack(&S);
    printf("是否已經清空棧 %d, 棧長度為:%d\n",isEmpty(S),getLength(S));
}
  • 輸出結果
Hello, World!
順序棧中元素為:
棧所有元素:1 2 3 4 5 6 7 8 
彈出棧頂元素為: 9
棧所有元素:1 2 3 4 5 6 7 
是否為空棧:0
棧頂元素:8 
棧長度:8
是否已經清空棧 1, 棧長度為:0
Program ended with exit code: 0

3. 鏈式棧的實現

鏈式棧是有鏈表來實現的一種棧結構,它的結構示意圖如下圖:


鏈式棧的結構圖

3.1 鏈式棧的基本操作

棧的入棧出棧過程圖

3.1.1 鏈式棧結構


//鏈棧結點
typedef struct KStackNode {
    KStackElementType data;    //結點數據
    struct KStackNode *next;   //指向下一個結點的指針
}StackNode, *LinkStackPtr;

//鏈棧結構
typedef struct KLinkStack {
    LinkStackPtr top;   //棧頂結點
    int count;          //棧大小
}LinkStack;

3.1.2 鏈式棧建棧

//1. 構造一個空棧S
KStatus initStack(LinkStack *S) {
    S->top = NULL;
    S->count = 0;
    return OK;
}

3.1.3 鏈式棧置空

//2. 鏈棧置空
KStatus clearStack(LinkStack *S) {
    LinkStackPtr p,q;
    //p指向棧頂結點
    p = S->top;
    while (p) {
        //保存要刪除的結點p
        q = p;
        //然p指向它的下一個結點
        p = p->next;
        //刪除 p結點
        free(q);
    }
    return OK;
}

3.1.4 鏈式棧判空

//3. 判斷棧是否為空
KStatus isEmpty(LinkStack S) {
    return S.count == 0;
}

3.1.5 鏈式棧獲取長度

//4. 獲取棧長度
int getLength(LinkStack S) {
    return S.count;
}

3.1.6 鏈式棧獲取棧頂元素

//5. 獲取棧頂元素
KStatus getTop(LinkStack S, KStackElementType *e) {
    //判斷是否棧空
    if (S.top == NULL) return ERROR;
    *e = S.top->data;
    return OK;
}

3.1.7 鏈式棧壓棧

鏈式棧結構,入棧示意圖如下:


鏈式棧入棧示意圖
//6. 壓棧
KStatus push(LinkStack *S, KStackElementType e) {
    //1. 創建一個新結點,
    LinkStackPtr newNode = (LinkStackPtr)malloc(sizeof(StackNode));
    //2. 賦值給新結點
    newNode->data = e;
    
    //3. 插入新結點到棧頂結點后面
    //3.1 把當前的棧頂元素的結點指針指向直接后繼結點
    newNode->next = S->top;
    //3.2 將新結點賦值給棧頂指針
    S->top = newNode;
    //棧大小+1
    S->count++;
    
    return OK;
}

3.1.8 鏈式棧出棧

鏈式棧結構,出棧示意圖如下圖:


鏈式棧出棧圖
//7. 出棧
KStatus pop(LinkStack *S, KStackElementType *e) {
    LinkStackPtr p;
    if (isEmpty(*S)) return ERROR;
    //1. 將棧頂元素賦值給*e
    *e = S->top->data;
    //2. 將棧頂結點賦值給p
    p = S->top;
    //3. 使得棧頂指針下移一位, 指向后一結點
    S->top = S->top->next;
    //4. 釋放p結點
    free(p);
    //棧大小減1
    S->count--;
    
    return OK;
}

3.1.9 鏈式棧遍歷

//8. 遍歷棧
KStatus traverse(LinkStack S) {
    LinkStackPtr p = S.top;
    printf("遍歷棧元素:");
    while (p) {
        printf("%d ", p->data);
        p = p->next;
    }
    printf("\n");
    return OK;
}

3.1.10 鏈式棧單元測試

//9. 單元測試
void test() {
    int j;
    LinkStack s;
    int e;
    if(initStack(&s)==OK)
        for(j=1;j<=10;j++)
            push(&s,j);
    printf("棧中元素依次為:");
    traverse(s);
    pop(&s,&e);
    printf("彈出的棧頂元素 e=%d\n",e);
    traverse(s);
    printf("棧空否:%d(1:空 0:否)\n",isEmpty(s));
    getTop(s,&e);
    printf("棧頂元素 e=%d 棧的長度為%d\n",e,getLength(s));
    clearStack(&s);
    printf("清空棧后,棧空否:%d(1:空 0:否)\n",isEmpty(s));
}

  • 輸出結果
Hello, World!
棧中元素依次為:遍歷棧元素:10 9 8 7 6 5 4 3 2 1 
彈出的棧頂元素 e=10
遍歷棧元素:9 8 7 6 5 4 3 2 1 
棧空否:0(1:空 0:否)
棧頂元素 e=9 棧的長度為9
清空棧后,棧空否:0(1:空 0:否)
Program ended with exit code: 0

3. 棧的應用

3.1 遞歸

3.1.1 函數調用及遞歸實現

下?面3種情況下,我們會使?用到遞歸來解決問題

  1. 定義是遞歸的
  2. 數據結構是遞歸的
  3. 問題的解法是遞歸的
遞歸函數調用分析
函數遞歸入棧過程

3.1.2 深度優先搜索

3.1.3 回溯算法

3.1.4 Hanoi塔問題

問題描述: 假如有3個分別命名為A,B,C的塔座,在塔座A上插有n個直接?大?小各不不相同的,從?小到?大的 編號為1,2,3...n的圓盤. 現在要求將塔座A上的n個圓盤移動到塔座C上. 并仍然按照同樣的順序疊 排. 圓盤移動時必須按照以下的規則:1. 每次只能移動?一個圓盤;2. 圓盤可以插在A,B,C的任?一塔座 上;3. 任何時刻都不不能將?一個較?大的圓盤壓在?小的圓盤之上.

hanoi塔問題

求解過程圖如下:

hanoi塔問題求解過程圖

3.2 表達式求值

在編譯系統中,算術表達式可以分為三類:算術表達式,關系表達式,邏輯表達式。

任何一個算術表達式都是由:操作數,運算符和分界符組成。我們把操作數,運算符和分界符(分界符標志了一個算術表達式的結束)稱為一個算術表達式的單詞。

  • 中綴表達式:算術表達式中的運算符總是出現在兩個操作數之間(除單目運算符外)例如:A+(B-C/D)*E
  • 后綴表達式:表達式中的運算符出現在操作數之后。編譯系統對于中綴表達式處理方法是將其變成后綴表達式,例如:ABCD/-E*+

后綴表達式的特點:

  1. 后綴表達式的操作數和中綴表達式的操作數先后次序完全相同(上面ABCDE),只是運算符的先后次序改變了(+-/*);
  2. 后綴表達式中沒有括號,后綴表達式的運算次序就是其執行次序

應用堆棧實現后綴表達式求值的基本過程:

從左到右讀入后綴表達式的各項(運算符或運算數):

  1. 運算數:入棧
  2. 運算符:從堆棧中彈出適當數量的運算數,計算并結果入棧
  3. 最后,堆棧頂上的元素就是表達式的結果值

3.2.1 中綴表達式求值

基本策略:將中綴表達式轉換為后綴表達式,然后求值。

  • 如何將中綴表達式轉換為后綴表達式?
    例如:2+9/3-5 -> 2 9 3 / +5 -

過程:

  1. 運算數相對順序不變
  2. 運算符號順序發生改變
  3. 需要存儲“等待中”的運算符號
  4. 要將當前運算符號與“等待中”的最后一個運算符號比較

3.2.2 中綴表達式如何轉換為后綴表達式

從頭到尾讀取中綴表達式的每個對象,對不同對象按不同的情況處理。

  1. 運算數:直接輸出
  2. 左括號:壓入堆棧
  3. 右括號:將棧頂的運算符彈出并輸出,直到遇到左括號(出棧,不輸出)
  4. 運算符:
    (1) 若優先級大于棧頂運算符時,則把它壓棧
    (2) 若優先級小于等于棧頂運算符時,將棧頂運算符彈出并輸出;再比較新的棧頂運算符,直到該運算符大于棧頂運算符優先級為止,然后將該運算符壓棧
  5. 若各對象處理完畢,則把堆棧中存留的運算符一并輸出
中綴表達式如何轉換為后綴表達式

3.2.3 后綴表達式的實現過程

編譯系統設置一個存放運算符的堆棧,初始時棧頂置一個分界符“#”。編譯系統從左到右依次掃描中綴表達式,每讀到一個操作數就把它作為后綴表達式的一部分輸出,每讀到一個運算符(分界符也看作運算符)就將其優先級與棧頂運算符優先級運算符進行比較,以決定是就所讀到的運算符進棧,還是將棧頂運算符作為最為后綴算術表達式的一部分輸出。

  • 運算符優先級別注意: 若把O1看成棧頂運算符,O2看成當前掃描讀到的運算符。
  1. 當O1為“+”或“-”,O2為“*”或“/”時,O1的優先級 < O2的優先級(滿足先乘除,后加減)
  2. 當O1為“+”“-”“*”或“/”,O2為“(”時,O1的優先級 < O2的優先級(滿足先括號內,后括號外的規則)
  3. 當O1的運算符和O2的運算符同級別時,O1的優先級 > O2的優先級別(同級別先左后右規則)
  4. 由于后綴表達式無括號,當O1為“(”,O2為“)”時,用標記“=”使算法在此時去掉該對算法;
  5. 當O1為“#”時,O2為“#”時,用標記“=”使算法在此時結束處理
  6. 若表中的值為空,則不允許出現這種情況,一旦出現即為中綴算術表達式語法出錯,如O1為“)”,而O2為“(”情況,即為中綴表達式語法錯誤!)。
  • 算法步驟:
  1. 設置一個堆棧,初始時將棧頂元素置為#
  2. 順序讀入中綴算術表達式,當讀到的單詞為操作數是就將其輸出,并接著讀下一個單詞
  3. 單讀到的單詞為運算符時,令a為當前棧頂運算符的變量,b為當前掃描讀到運算符的變量,把當前讀到的運算符賦給b,然后比較變量a的優先級和b的優先級。若a的優先級高于b的優先級,則將a退棧并作為后綴表達式的一個單詞輸出,,然后比較新的棧頂元素運算符a的優先級與b的優先級。
  1. 若優先級 a<b,則將b的值進棧,然后接著讀下一個單詞

  2. 若優先級 a>b,則將a退棧并作為后綴表達式的一個單詞輸出,然后比較新的棧頂元素運算符a的優先級與b的優先級。

  3. 若優先級 a=b且a為“(”,b為“)”。則將a退棧,接著讀下一個單詞

  4. 若優先級 a=b且a為“#”,b為“#”。算法結束。

  • 代碼實現:
int PostExp(char str[])  //借助堆棧計算后綴表達式str的值
{
    KStackElementType x,x1,x2;
    int I;
    KNode *head;    //定義頭指針變量head
    initStack(&head);   //初始化鏈式堆棧head
    for(i-0;str[i]!=#;i++)   //循環直到輸入為#
    {
        if(isdigit(str[i]))   //當str[i]為操作數時
        {
            x=(int)(str[i]-48);  //轉換成int類型數據存于變量x中
            push(head,x);   //x入棧
        }
        else                     //當str[i]為運算符時
        {  
            pop(head,&x2);  //退棧的操作數,存于變量x2中
            pop(head,&x1);  //退棧的被操作數,存于變量x1中
            switch(str[i])      //執行str[i]所表示的運算
            {
            case '+':
                {
                    x1+=x2; break;
                }
            case '-':
                {
                    x1-=x2; break;
                }
            case '*':
                {
                    x1*=x2; break;
                }
            case '/':
                {
                    if(x2==0.0)
                    {
                        printf("除數為0錯誤!\n");
                        exit(0);
                    }
                    else
                    {
                        x1/=x2;
                        break;
                    }
                }
            }
            push(head,x1);    //運算結果入棧
        }
    }
    pop(head,&x);     //得到計算結果存于x
    return x;             //返回計算結果
}

4. 隊列

隊列:具有一定操作約束的線性表
有以下特點:

  1. 插入和刪除操作:只能在一端插入,而在另一端刪除
  2. 數據插入:入隊(AddQ)
  3. 數據刪除:出隊列(DeleteQ)
  4. 先進先出:FIFO

4.1 隊列基本操作

隊列基本操作

4.2 循環隊列

循環隊列基本結構
  • 循環隊列中頭尾指針和元素之間的關系
循環隊列中頭尾指針和元素之間的關系1
循環隊列中頭尾指針和元素之間的關系2
循環隊列的操作
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,156評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,401評論 3 415
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,069評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,873評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,635評論 6 408
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,128評論 1 323
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,203評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,365評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,881評論 1 334
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,733評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,935評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,475評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,172評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,582評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,821評論 1 282
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,595評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,908評論 2 372

推薦閱讀更多精彩內容