1. 程式人生 > 其它 >資料結構(2)—— 棧與佇列

資料結構(2)—— 棧與佇列

寫在前面

為了考研,需要複習資料結構。而對於資料結構這門學科來說,寫程式碼是非常必要的。用程式碼把一些常見的資料結構與演算法實現一遍,非常有利於對於資料結構的理解。

關於第一篇:資料結構(1)—— 線性表

這一篇主要是棧與佇列的實現。棧與佇列都是操作受限的線性表,棧只能從一端進出,佇列只能一端出一端進。這兩種資料結構在我們的實際開發中應用的十分多,因此對這部分不管是出於考研還是工作都要掌握。

順序棧

注意點

關於順序棧的實現,要注意的地方其實並不多。最核心的就是top指標的初始化情況了。這裡我把top指標設在了-1,也可以設在0,不過設定為0時判空和進出棧的條件就不大一樣了,需要注意。

程式碼

/*
 * @Description: 順序棧的實現
 * @version: 1.0
 * @Author: Liuge
 * @Date: 2021-07-09 20:25:53
 */

#include<bits/stdc++.h>
#define maxSize 50

// 順序棧的儲存結構
typedef struct{
    int data[maxSize];
    // 棧頂指標
    int top;
}SqStack;

// 初始化棧
void initStack(SqStack &S){
    // 這裡把棧頂指標設為-1,也可以設為0
    S.top = -1;
}

// 判空
bool isStackEmpty(SqStack S){
    return S.top == -1;
}

// 進棧
bool push(SqStack &S, int e){
    // 棧滿
    if(S.top == maxSize -1){
        return false;
    }
    S.data[++S.top] = e;
    return false;
}

// 出棧
bool pop(SqStack &S,int &e){
    // 棧空
    if(S.top == -1){
        return false;
    }
    e = S.data[S.top--];
    return true;
}

// 讀棧頂元素
bool getTop(SqStack &S,int &e){
    if(S.top == -1){
        return false;
    }
    e = S.data[S.top];
    return true;
}

// 遍歷棧
void printStack(SqStack S){
    printf("棧中的元素為:\n");
    while(!isStackEmpty(S)){
        int topElem;
        getTop(S,topElem);
        printf("%d ",topElem);
        S.top--;
    }
    printf("\n");
}

// 主函式測試
int main(){
    SqStack S;
    // 建立一個棧
    initStack(S);
    printStack(S);
    // 放幾個元素試試
    push(S,3);
    push(S,4);
    push(S,5);
    // 輸出應該是5 4 3
    printStack(S);
    // 出一個
    int popElem;
    pop(S,popElem);
    printf("\n出棧的元素為 -> %d\n",popElem);
    printStack(S);
    // 讀一個
    int topElem;
    getTop(S,topElem);
    printf("\n棧頂的元素為 -> %d\n",topElem);
    return 0;
}

迴圈佇列

注意點

由於佇列的特殊性,順序佇列實現的價值並不高。這裡直接實現迴圈佇列。迴圈佇列最核心的地方便在於判斷當前佇列是否為滿,這裡我的程式碼使用的是犧牲一個記憶體單元來區分隊滿和隊空,當然你也可以採用別的方式,如王道書上提到的增設一個size變數,或增加一個tag標記,都可以實現對於隊滿的判斷,這裡不再贅述。

程式碼

/*
 * @Description: 迴圈佇列的實現——犧牲一個記憶體單元來區分隊滿和隊空
 * @version: 1.0
 * @Author: Liuge
 * @Date: 2021-07-10 19:13:15
 */

#include<bits/stdc++.h>
#define maxSize 50

// 定義迴圈佇列的結構
typedef struct{
    int data[maxSize];
    // 隊頭指標
    int front;
    // 隊尾指標
    int rear;
}SqQueue;

// 初始化佇列
void initQueue(SqQueue &Q){
    // 將隊首和隊尾指標都指向0
    Q.rear = Q.front = 0;
}

// 判隊空 rear==front
bool isEmpty(SqQueue Q){
    return Q.rear==Q.front;
}

// 求當前佇列長度
int getQueueLength(SqQueue Q){
    return (Q.rear + maxSize - Q.front) % maxSize;
}

// 入隊
bool enQueue(SqQueue &Q,int x){
    // 判隊滿,rear + 1 % maxSize = front
    if((Q.rear + 1) % maxSize == Q.front){
        return false;
    }
    Q.data[Q.rear] = x;
    // 隊尾指標+1取模,起到迴圈效果
    Q.rear = (Q.rear + 1) % maxSize;
    return true;
}

// 出隊
bool deQueue(SqQueue &Q,int &x){
    // 判隊空
    if(isEmpty(Q)){
        return false;
    }
    x = Q.data[Q.front];
    // front+1取模
    Q.front = (Q.front + 1) % maxSize;
    return true;
}

// 列印
// 這裡可以這麼寫,是因為Q裡的是陣列
// 這樣這裡就是一個新的陣列了,不會影響原本的陣列,順序棧那裡同理
void printQueue(SqQueue Q){
    printf("佇列中的元素為:\n");
    while(!isEmpty(Q)){
        int deQueueElem;
        deQueue(Q,deQueueElem);
        printf("%d ",deQueueElem);
    }
    printf("\n");
}
// 主函式測試

int main(){
    SqQueue Q;
    // 初始化佇列
    initQueue(Q);
    printQueue(Q);
    // 入隊幾個元素
    enQueue(Q,3);
    enQueue(Q,4);
    enQueue(Q,5);
    // 列印應該為 3 4 5
    printQueue(Q);
    // 出隊幾個試試
    int deElem;
    deQueue(Q,deElem);
    printf("出隊元素為 -> %d\n",deElem);
    printf("當前佇列長度為 -> %d\n",getQueueLength(Q));
    printQueue(Q);
}

鏈式佇列

注意點

鏈式佇列的實現,需要注意隊頭的設定。這裡我把隊頭設定在了連結串列的頭部,那麼隊尾就是連結串列的尾部了。我實現的是帶有頭結點的鏈式佇列,這樣的比較好實現,讀者也可以自己試試如何實現不帶頭結點的。

程式碼

/*
 * @Description: 鏈式佇列的實現
 * @version: 1.0
 * @Author: Liuge
 * @Date: 2021-07-10 20:45:24
 */
#include<bits/stdc++.h>

// 定義結點的結構
typedef struct LinkNode{
    int data;
    struct LinkNode *next;
}LinkNode;
// 定義鏈式佇列的結構
typedef struct{
    LinkNode *front;
    LinkNode *rear;
}LinkQueue;

// 初始化
void initQueue(LinkQueue &Q){
    // 建立頭結點
    Q.front = Q.rear = (LinkNode *)malloc(sizeof(LinkNode));
    Q.front->next = NULL;
}

// 判隊空
bool isEmpty(LinkQueue Q){
    return Q.front == Q.rear;
}

// 入隊
void enQueue(LinkQueue &Q,int x){
    LinkNode *s = (LinkNode *)malloc(sizeof(LinkNode));
    s->data = x;
    s->next = NULL;
    Q.rear->next = s;
    // 隊尾指標移動到新的尾部
    Q.rear = s;
}

// 出隊
bool deQueue(LinkQueue &Q,int &x){
    if(isEmpty(Q)){
        return false;
    }
    LinkNode *p = Q.front->next;
    x = p->data;
    Q.front->next = p->next;
    // 如果原佇列只有一個結點,刪除後變空
    if(Q.rear == p){
        Q.rear = Q.front;
    }
    free(p);
    return true;
}

// 列印 
// 註釋掉的寫法中,雖然這裡的Q和main裡的Q不是同一個
// 但後續連線的結點都是同一個(即同一塊記憶體空間)
// 如果呼叫了deQueue函式,會把結點free掉,導致main裡接下來的deQueue無法使用,報segement fault異常(記憶體無法訪問,因為已經被釋放了)
// 正確的做法應該是拿另一個指標去遍歷這個連結串列
void printQueue(LinkQueue Q){
    printf("佇列中的元素為:\n");
    // while(!isEmpty(Q)){
    //     int deElem;
    //     deQueue(Q,deElem);
    //     printf("%d ",deElem);
    // }
    LinkNode *p = Q.front->next;
    while (p){
        printf("%d ",p->data);
        p = p->next;
    }
    printf("\n");
}

// 主函式測試
int main(){
    LinkQueue Q;
    // 初始化
    initQueue(Q);
    printQueue(Q);
    // 放幾個元素
    enQueue(Q,1);
    enQueue(Q,3);
    enQueue(Q,5);
    // 應該輸出:1 3 5
    printQueue(Q);
    // 出隊一個元素
    int deElem;
    deQueue(Q,deElem);
    printQueue(Q);
    return 0;
}

棧的應用——表示式求值

演算法簡述

這個演算法,稱得上是一個非常經典的演算法了。這裡我先用常規的語言描述一下這個演算法:

  1. 初始化兩個棧,運算元棧與運算子棧。
  2. 若掃描到運算元,壓入運算元棧。
  3. 若掃描到運算子或界限符(這裡主要指的就是()),那麼就比對運算子棧頂與掃描到的運算子或界限符的優先順序,如果棧頂符號的優先順序大於等於掃描到的,那麼就彈出運算子棧,運算元棧彈出兩個值,按照彈出的運算子進行運算後再次壓入到運算元棧。
  4. 掃描到字串結尾,將運算元棧的棧頂元素彈出就是結果。

這個演算法具體的演示與操作步驟,大家可以百度搜一下。

而我這裡實現的這個演算法,實現了多位數的求值,如100*100+100這種的,主要是使用了一個counter變數來判斷是否為多位數。具體邏輯直接看程式碼吧。

程式碼

/*
 * @Description: 表示式求值
 * @version: 1.0
 * @Author: Liuge
 * @Date: 2021-07-12 19:30:34
 */
#include <bits/stdc++.h>
using namespace std;

// 運算子棧
stack<char> opter;   
// 運算元棧
stack<double> opval; 

// 獲取theta所對應的索引
int getIndex(char theta)
{
    int index = 0;
    switch (theta)
    {
    case '+':
        index = 0;
        break;
    case '-':
        index = 1;
        break;
    case '*':
        index = 2;
        break;
    case '/':
        index = 3;
        break;
    case '(':
        index = 4;
        break;
    case ')':
        index = 5;
        break;
    case '#':
        index = 6;
    default:
        break;
    }
    return index;
}

// 獲取theta1與theta2之間的優先順序
char getPriority(char theta1, char theta2)
{
    // 算符間的優先順序關係
    const char priority[][7] = 
        {
            {'>', '>', '<', '<', '<', '>', '>'},
            {'>', '>', '<', '<', '<', '>', '>'},
            {'>', '>', '>', '>', '<', '>', '>'},
            {'>', '>', '>', '>', '<', '>', '>'},
            {'<', '<', '<', '<', '<', '=', '0'},
            {'>', '>', '>', '>', '0', '>', '>'},
            {'<', '<', '<', '<', '<', '0', '='},
        };

    int index1 = getIndex(theta1);
    int index2 = getIndex(theta2);
    return priority[index1][index2];
}

// 計算a theta b
double calculate(double a, char theta, double b)
{
    switch (theta)
    {
    case '+':
        return a + b;
    case '-':
        return a - b;
    case '*':
        return a * b;
    case '/':
        return a / b;
    default:
        return 0;
    }
}

// 表示式求值
double getAnswer() 
{
    // 首先將'#'入棧opter
    opter.push('#'); 
    // 新增變數counter表示有多少個數字相繼入棧,實現多位數的四則運算
    int counter = 0; 
    char c = getchar();
    // 終止條件
    while (c != '#' || opter.top() != '#') 
    {
        // 如果c在'0'~'9'之間 使用isdigit函式用來判斷字元型變數是否是數字
        if (isdigit(c)) 
        {
            // counter==1表示上一字元也是數字,所以要合併,比如12*12,要算12,而不是單獨的1和2
            if (counter == 1) 
            {
                double t = opval.top();
                opval.pop();
                opval.push(t * 10 + (c - '0'));
                counter = 1;
            }
            else
            {
                // 將c對應的數值入棧opval
                opval.push(c - '0'); 
                counter++;
            }
            c = getchar();
        }
        else
        {
            // counter置零
            counter = 0;                        
            // 獲取運算子棧opter棧頂元素與c之間的優先順序,用'>','<','='表示 
            switch (getPriority(opter.top(), c)) 
            {
            // <則將c入棧opter
            case '<': 
                opter.push(c);
                c = getchar();
                break;
            // =將opter棧頂元素彈出,用於括號的處理
            case '=': 
                opter.pop();
                c = getchar();
                break;
            // >則計算
            case '>': 
                char theta = opter.top();
                opter.pop();
                double a = opval.top();
                opval.pop();
                double b = opval.top();
                opval.pop();
                opval.push(calculate(b, theta, a));
            }
        }
    }
    // 返回opval棧頂元素的值
    return opval.top(); 
}

int main()
{
    cout << "請輸入算數運算式(以#結尾)" << endl;
    while (!opter.empty())
        opter.pop();
    while (!opval.empty())
        opval.pop();
    double ans = getAnswer();
    cout << ans << endl;
    return 0;
}

總結

總的來說,這塊一定要好好掌握。參考書籍依然是王道的資料結構輔導書。參考程式碼來自網路。