大話數據結構-第4章 棧與隊列

第4章 棧與隊列
4.2 棧的定義
棧(stack) 是限定僅在表層進行插入和刪除操作的線性表
允許插入和刪除的一端稱為棧頂(top)
另一端稱為棧底(bottom)
后進先出(Last In First Out) 線性表, 簡稱 LIFO

棧的插入操作: 進棧, 壓棧, 入棧
棧的刪除操作: 出棧, 彈棧

棧本身是一個線性表, 之前討論的有關線性表的順序存儲和鏈式存儲對于棧同樣適用

4.4 棧的順序存儲結構及實現

4.5 兩棧共享空間

4.6 棧的鏈式存儲結構及實現

4.6.2 棧的鏈式結構-----進棧操作
棧是由棧元素構成的線性鏈表, 其中有一個棧元素為棧頂元素.
不存在棧溢出情況. 除非整個內存都用完了
空棧: 當 top = NULL 時候
棧元素:

        typedef struct StackNode
        {
            sElemType data;// 元素內容
            struct StackNode *next; //指向下一個元素地址
        } StackNode, *LinkStackPtr;

棧頂元素

        typedef struct LinkStack
        {
            LinkStackPtr top;// 指向棧頂元素位置
            int count;// 棧元素的數量
        }LinkStack;

進棧操作 (棧為鏈式存儲結構時, 不用考慮棧溢出)

過程:

  • 創建新的棧元素 s , s 的數據data 裝我們的新數據, s 的 next 指針 指向棧頂top元素

  • 把棧頂元素top指向這個新的棧元素 s, 讓s 稱為新的棧頂, 棧頂元素的cout屬性加一

代碼如下:

        // 先配置 新的棧元素
        // 再配置 棧頂元素
        Status push(LinkStack *S, sElemType e)
        {
            // 大寫 S 是整個棧, e 是單純的數據
            // 小寫 s 是棧元素 
            LinkStackPtr s = (LinkStackPtr)malloc(sizeof(StackNode));
            // 棧元素的數據 為 e
            s->data = e;
            // 棧元素的 next 指向之前的棧頂元素.
            s->>next = S->top;
            // 棧頂元素現在是 s
            S->top = s;
            // 棧元素數量加1
            S->count++;
            return OK;
        }
        

4.6.3 棧的鏈式存儲結構-----出棧結構

當棧頂 top = NULL 時 為空棧, 不能執行出棧操作(Pop)

過程:

  • 拿到棧頂數據

  • 取出棧頂元素

  • 棧頂元素更新為下一個棧元素

  • 釋放取出的棧頂元素

代碼如下:

        Status Pop(LinkStack *S, sElemType *e)
        {
            // 傳入參數 S: 整個棧
            // 傳入參數 *e : 用來存棧頂數據的存儲位置
            //p : 棧頂元素 
            LinkStackPtr p;
            // 先判斷是否是空棧
            if(StackEmpty( *s )){
            return ERROR;
            }
            // *e 獲取棧頂的數據
            *e = S->top-data;
            // 給 p 賦值為 棧頂元素
            p = S->top;
            // 棧頂元素指向下一個元素(出棧)
            S->top = S->top->next;
            // 釋放被出棧的棧頂元素p
            free(p);
            // 跟新棧元素數量
            S->count--;
            // 出棧操作結束
            return OK;
        }
        

總結: 如果棧的使用過程中元素變化不可預料, 有時很小, 有時非常大, 那么最好用鏈棧,

反之, 如果它的變化在可控范圍內, 建議使用順序棧會更好一些


4.8 棧的應用----遞歸

4.8.1 斐波那契數列實現

    F(n) = 0, 當n=0
    F(n) = 1, 當n=1
    F(n) = F(n-1) + F(n-2), 當n>1

打印前40位的斐波那契數列

簡單的實現:

    int main()
    {
        // 從斐波那契數列這個名字上, 我們就可以看到, 可以用數組來實現.
        // 根據公式的規律把數列和數組的每個元素對應存起來.
        // 直接給 a[0] 設值 為 0
        // 直接給 a[1] 設值 為 1
        // a[i] = a[i-1] + a[i-2] 和公式 F(n) = F(n-1) + F(n-2) 對應起來.
        int i;
        int a[40];
        a[0] = 0;
        a[1] = 1;
        printf("%d", a[0]);
        printf("%d", a[1]);
        for(i = 2; i< 40; i++){
            a[i] = a[i-1] + a[i-2];
            printf("%d", a[i])
        }
        return 0;
    }

遞歸實現方法:


    /* 上一個實現方法的思路是 從頭到尾 的推斷方式
        先考慮 i=0 再考慮 i=1 一直到 i = 40
    */

    /* 這里的遞歸采用的是 從尾到頭 的推斷方式
       先是 i = 40, i = 39, i = 38, 一直到 i = 1, i = 0  
    */
    // 斐波那契的遞歸函數
    int Fbi( int i )
    {
        if (i<2)
        {
            // i=0 時 Fbi(0) = 0
            // i=1 時 Fbi(1) = 1
            return i==0 ? 0 : 1; 
        }
        // i >= 2時: Fib(i) = Fib(i-1) + Fib(i-2)
        return Fbi(i - 1) + Fbi(i - 2);// 這里Fbi 就是函數自己, 它在調用自己
    }

    int main ()
    {
        int i;
        for (int i = 0; i < 40; i++){
            // 輸出 每一個斐波那契數列元素
            printf("%d", Fbi(i));
        }
        return 0;
    }

4.8.2 遞歸定義
我們把一個直接調用自己或通過一系列的調用語句間接地調用自己的函數, 稱作遞歸函數
每個遞歸定義, 必須至少有一個條件, 滿足時遞歸不再進行, 即不再引用自身而是返回值退出

  • 遞歸和迭代:

    • 迭代: 使用循環結構, 不需要反復調用函數和占用額外的內存,
    • 遞歸: 使用選擇結構, 使程序的結構更清晰, 更簡潔, 更容易讓人理解, 從而減少讀懂代碼的時間. 但是大量的遞歸調用會建立函數的副本, 會耗費大量的時間和內存.

    遞歸是用棧這種數據結構實現的. 簡單的說, 在執行過程中, 對于每一層遞歸, 函數的局部變量, 參數以及返回地址都被壓入棧中, 在退回階段, 位于棧頂的局部變量, 參數值和返回地址被彈出, 用于返回調用層次中執行代碼的其余部分, 也就是恢復了調用的狀態.

4.9 棧的應用 -- 四則運算表達式求值

4.9.1 后綴 (逆波蘭)表示法定義

4.9.2 后綴表達式計算結構

4.9.3 中綴表達式轉后綴表達式

4.10 隊列的定義
定義: 隊列(queue) 是只允許在一端進行插入操作. 而在另一端進行刪除操作的線性表.

隊列是一種先進先出(First In First Out)線性表, 簡稱FIFO. 允許插入的一端稱為隊尾, 允許刪除的一端稱為隊頭. 假設隊列是 q = (a1, a2, ......, an) , 那么 a1 就是隊頭元素, 而 an 是隊尾元素, 這樣我們就可以刪除時, 總是從 a1 開始, 插入時, 列在最后.

           隊頭                 隊尾
出隊列 <--- a1, a2, a3, ...... an <---入隊列

4.12 循環隊列

  • 順序隊列的不足: 造成假溢出

  • 循環隊列: 后面滿了, 就再從頭開始, 也就是頭尾相接的循環. 我們把隊列的這種頭尾相接的順序存儲結構稱為循環隊列

c 語言中 區域運算符%

比如 5%4 = 1
4%5 = 4

循環隊列的順序存儲結構代碼

        //隊列結構體
        typedef int QElemType; // QElemType 為 int 類型(類型按照實際情況來定, 這里假設為 int)
        typedef struct
        {
            QElemType data[MAXSIZE];// int 類型數組
            int front;              // 頭指針
            int rear;               // 尾指針
        }sqQueue
        

循環隊列的初始化代碼

        Status InitQueue(sqQueue *Q)
        {
            Q->front=0;
            Q->rear=0;
            return OK;
        }

循環隊列求隊列長度代碼如下:

當 rear > front 時, 隊列長度為: QueueLength = rear - front
當 rear < front 時, 隊列長度為: QueueLength = (QueueSize - front) + (0 + rear)
把上面兩個條件結合在一起得到公式: QueueLength = (rear - front + QueueSize) % QueueSize

        int QueueLength(sqQueue Q)
        {
            return (Q.rear - Q.front + MAXSIZE) % MAXSIZE;
        }

循環隊列的入隊列操作代碼如下:

        Status EnQueue(SqQueue *Q, QElemType d)
        {
            // 判斷是否隊列已經滿了
            if((Q->rear + 1) % MAXSIZE == Q->front)
            {
                return ERROR;
            }
            // 隊列中插入值
            Q->data[Q->rear] = e;
            // 后移隊尾指針
            Q->rear = (Q->rear+1)%MAXSIZE;
            return OK;
        }

循環隊列的出隊列操作代碼如下:

        Status DeQueue(sqQueue *Q, QElemType *e)
        {
            // 判斷隊列是否為空
            if(Q->front == Q->rear)
            {
                return ERROR;
            }
            // 取出隊首數據
            *e = Q->data[Q->front];
            // 隊首 指針后移
            Q->front = (Q->front+!)%MAXSIZE;
            return OK;
        }
        

4.13 隊列的鏈式存儲結構及實現
隊列的鏈式存儲結構, 其實就是線性表的單鏈表, 只不過它只能尾進頭出而已, 我們把它簡稱為鏈隊列
空隊列時, front 和 rear 都指向頭結點

鏈隊列的結構:
- 頭指針指向鏈隊列的頭結點.
- 隊尾指針指向終點結點

    typedef int QElemType; // QElemType 是int 類型, (可以根據實際情況自己設定類型)

    typedef struct QNode  // 結點結構 
    {
        QElemType data;   // 結點數據
        struct QNode *next;//存放下一個結點的指針
    }

    typedef struct
    {
        QueuePtr front, rear; // 隊頭, 隊尾 指針

    } LinkQueue;
    

隊列的鏈式存儲結構-----入隊操作

    Status EnQueue(LinkQueue *Q, QElemType e)
    {
        // 給新的隊列元素分配內存
        QueuePtr s = (QueuePtr)malloc(sizeof(QNode));
        if(!s)
        {
            exit(OVERFLOW); // 退出程序, 并返回 OVERFLOW 的值給主程序.(當運算溢出時)
        }
        s->data = e;        // 新的隊列元素賦值
        s->next = Null;     // 新的隊列元素的下一個元素為 Null
        Q->rear->next = s;  // 隊尾指針的下一個元素 為 s

        Q->rear = s;        // 隊尾指針指向 s
        return OK;
    }

隊列的鏈式存儲結構---出隊操作

    // 若隊列不空, 刪除Q的隊頭元素, 用e返回其值, 并返回OK, 否則返回ERROR
    Status DeQueue(LinkQueue *Q, QElemType *e)
    {
        QueuePtr p;
        // 判斷隊列是否為空
        if(Q->front == Q->rear)
        {
            return ERROR;
        }
        // p 為隊首
        p = Q->front->next;
        // 取出隊首的值
        *e = p->data;
        // 隊頭指針后移
        Q->front->next = p->next
        // 如果隊列被清空
        if(Q->rear == p)
        {
            Q->rear = Q -> front;
        }
        // 釋放p
        free(p);
        return OK
    }
    
  • 循環隊列和鏈隊列比較:
    • 時間上: 沒有什么大的區別
    • 空間上: 循環隊列必須有一個固定的長度, 就有了存儲元素個數和空間浪費的問題.空間上,鏈隊列會更加靈活.

    可以確定隊列長度最大值時,使用循環隊列
    不能預估隊列長度時, 使用鏈隊列

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

推薦閱讀更多精彩內容