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

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

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

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

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

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

@[TOC]swift

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

  • 本篇博客的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 順序棧壓棧

入棧前以下圖所示: 函數

入棧前

入棧後以下圖所示: post

入棧後
入棧代碼實現:

//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

循環隊列的操做

相關文章
相關標籤/搜索