程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C >> 關於C >> 棧的存儲結構和常見操作(c 語言實現)

棧的存儲結構和常見操作(c 語言實現)

編輯:關於C
俗話說得好,線性表(尤其是鏈表)是一切數據結構和算法的基礎,很多復雜甚至是高級的數據結構和算法,細節處,除去數學和計算機程序基礎的知識,大量的都在應用線性表。   一、棧   其實本質還是線性表:限定僅在表尾進行插入或刪除操作。 俗稱:後進先出 (LIFO=last in first out結構),也可說是先進後出(FILO)。       同樣的,棧也分為順序和鏈式兩大類。其實和線性表大同小異,只不過限制在表尾進行操作的線性表的特殊表現形式。   1、順序棧:利用一組地址連續的存儲單元依次存放自棧底到棧頂的數據元素,同時附設指針 top 指示棧頂元素在順序棧中的位置,附設指針 base 指示棧底的位置。 同樣,應該采用可以動態增長存儲容量的結構。且注意,如果棧已經空了,再繼續出棧操作,則發生元素下溢,如果棧滿了,再繼續入棧操作,則發生元素上溢。棧底指針 base 初始為空,說明棧不存在,棧頂指針 top 初始指向 base,則說明棧空,元素入棧,則 top++,元素出棧,則 top--,故,棧頂指針指示的位置其實是棧頂元素的下一位(不是棧頂元素的位置)。   復制代碼   1 #ifndef _____ADT__   2 #define _____ADT__   3 #include <stdbool.h>   4 #include <stdio.h>   5 #include <stdlib.h>   6 #define STACK_SIZE 50   7 #define STACK_INCREMENT 10   8    9 typedef struct{  10     int stackSize;//棧容量  11     char *base;//棧底指針  12     char *top;//棧頂指針  13 } SqStack;  14   15 //初始化  16 //本質還是使用動態數組  17 void initStack(SqStack *s)  18 {  19     s->base = (char *)malloc(STACK_SIZE * sizeof(char));  20     //分配成功  21     if (s->base != NULL) {  22         //空棧  23         s->top = s->base;  24         s->stackSize = STACK_SIZE;  25     }  26     else  27     {  28         puts("分配失敗!");  29     }  30 }  31   32 //判空  33 bool isEmpty(SqStack s)  34 {  35     return s.top == s.base ? true : false;  36 }  37   38 //判滿  39 bool isFull(SqStack s)  40 {  41     return (s.top - s.base) >= STACK_SIZE ? true : false;  42 }  43   44 //求當前長度  45 int getLength(SqStack s)  46 {  47     int i = 0;  48     char *q = s.top;  49       50     while (q != s.base) {  51         q--;  52         i++;  53     }  54       55     return i;  56 }  57   58 //求棧頂元素  59 char getTop(SqStack s, char topElement)  60 {  61     if (isEmpty(s)) {  62         puts("棧空!");  63     }  64       65     topElement = *(s.top - 1);  66     return topElement;  67 }  68   69 //入棧  70 void push(SqStack *s, char topElement)  71 {  72     char *q = NULL;  73       74     if (isFull(*s)) {  75         q = (char *)realloc(s->base, STACK_INCREMENT * sizeof(char));  76           77         if (NULL == q) {  78             exit(0);  79         }  80           81         s->base = q;  82         s->stackSize = s->stackSize + STACK_INCREMENT;  83     }  84     //進棧  85     *s->top++ = topElement;  86 }  87   88 //出棧  89 void pop(SqStack *s, char *topElement)  90 {  91     if (isEmpty(*s)) {  92         exit(0);  93     }  94       95     s->top--;  96     *topElement = *s->top;  97 }  98   99 //遍歷 100 void traversal(SqStack s) 101 { 102     for (int i = 0; i < getLength(s); i++) { 103         printf("棧中元素遍歷:%c \n", s.base[i]); 104     } 105 } 106  107 //清空 108 void cleanStack(SqStack *s) 109 { 110     if (!isEmpty(*s)) { 111         s->top = s->base; 112         puts("棧已經清空!"); 113     } 114 } 115  116 //銷毀 117 void destroyStack(SqStack *s) 118 { 119     if (s->base != NULL) { 120         free(s->base); 121         s->base = NULL; 122         s->top = NULL; 123         s->stackSize = 0; 124         puts("棧成功銷毀!"); 125     } 126 } 127  128 #endif /* defined(_____ADT__) */ 復制代碼  函數: void exit(int status);    所在頭文件:stdlib.h    功 能: 關閉所有文件,終止正在執行的進程。    exit(1)表示異常退出.這個1是返回給操作系統的。    exit(x)(x不為0)都表示異常退出    exit(0)表示正常退出    exit()的參數會被傳遞給一些操作系統,包括UNIX,Linux,和MS DOS,以供其他程序使用。        exit()和return的區別:    按照ANSI C,在最初調用的main()中使用return和exit()的效果相同。 但要注意這裡所說的是“最初調用”。如果main()在一個遞歸程序中,exit()仍然會終止程序;但return將控制權移交給遞歸的前一級,直到最初的那一級,此時return才會終止程序。   return和exit()的另一個區別在於,即使在除main()之外的函數中調用exit(),它也將終止程序。        _exit()與exit的區別:    頭文件不同:    exit:#include<stdlib.h>    _exit:#include<unistd.h>    _exit()函數:直接使進程停止運行,清除其使用的內存空間,並銷毀其在內核中的各種數據結構;    exit()函數則在這些基礎上作了一些包裝,在執行退出之前加了若干道工序。比如系統調用之前exit()要檢查文件的打開情況,把文件緩沖區中的內容寫回文件。       復制代碼  1 #include "ADT.h"  2   3 int main(void) {  4     char temp = '0';  5     SqStack stack;  6     initStack(&stack);  7       8     printf("%d\n", getLength(stack));  9      10     push(&stack, 'b'); 11     push(&stack, 'k'); 12     push(&stack, 'y'); 13      14     printf("%d\n", getLength(stack)); 15     // 函數使用temp之前必須初始化 16     temp = getTop(stack, temp); 17     printf("%c\n", temp); 18      19     traversal(stack); 20      21     pop(&stack, &temp); 22     printf("%d\n", getLength(stack)); 23      24     traversal(stack); 25      26     cleanStack(&stack); 27      28     destroyStack(&stack); 29      30     return 0; 31 } 復制代碼 測試結果:   0   3   y   棧中元素遍歷:b    棧中元素遍歷:k    棧中元素遍歷:y    2   棧中元素遍歷:b    棧中元素遍歷:k    棧已經清空!   棧成功銷毀!   Program ended with exit code: 0        順序棧的小結:   1)、盡量使用指向結構的指針做函數參數,這樣的操作比結構體變量作函數參數效率高,因為無需傳遞各個成員的值,只需傳遞一個結構的地址,且函數中的結構體成員並不占據新的內存單元,而與主調函數中的成員共享存儲單元。這種方式還可通過修改形參所指成員影響實參所對應的成員值。   2)、棧清空,一定是棧頂指向棧底,不可顛倒,否則析構出錯!   3)、再次注意二級指針和一級指針做函數參數的不同   4)、使用一個指針變量之前,必須初始化,不為指針分配內存,即指針沒有指向一塊合法的內存,那麼指針無法使用,強行運行出錯,這就是野指針的危害。類似其他類型變量都是如此,(這也是為什麼建議聲明變量的同時就立即初始化,哪怕是一個不相干的數值),就怕程序寫的復雜的話,一時忘記變量是否初始化,導致出錯。   5)、為什麼順序棧(包括順序表)在初始化函數的傳入參數裡不用二級指針傳參?       個人理解:     首先清楚函數傳參的類型,值傳遞,引用(c++)傳遞,和指針傳遞,且函數修改的是實參的一份拷貝,並不是直接去修改實參。        問題是,在之前的鏈表裡,定義結點結構( Node)和指向結點的指針 p,有 struct Node *p;為了給結點分配內存,把這個指針(p本身占據一份內存空間,在棧區操作系統分配,但是 p 的指向是沒有初始化的)p 傳入初始化函數內,等於是傳入的指向結點Node的一個指針 p 的拷貝 _p,而這個拷貝 _p 本身(假設指針變量p自己在內存的地址是0x1111)和拷貝 _p 存儲的(指針 p指向的 內存區域)內容是不一樣的,此時給 拷貝 _p 使用malloc函數分配內存,看似是修改了 _p ,實際上是把 _p 指向的內存區域改變了, p 本身在內存的地址(0x1111)沒有被改變,故函數執行完畢,棧分配的內存被操作系統回收,然後參數返回給主調函數,那份拷貝 _p 還是以前傳入的那份拷貝 _p, 高矮胖瘦那是紋絲未動,故不會對實參起到修改的作用,完全類似值傳遞,在值傳遞,就是把實參的本身的值傳入函數,如果函數內部對其拷貝進行修改,其實傳入的參數本身並沒有被改變,雖然函數體內,他的值被修改了,但是一旦函數執行完,棧的內存被系統回收,修改就變得徒勞。           順序棧裡,一般是定義的整個表List結構,struct List p;變量p 就是一個實例化的棧結構,注意 p 已經分配了內存(主調函數 main ,操作系統在棧區給p分配),這和主調函數裡鏈表的指針 p 不一樣,鏈表的指針 p 在 main 函數,只是給指針本身分配了內存空間,但是對 其指向的表結點沒有分配,需要在初始化函數初始化!故到了順序棧裡,當給函數傳入&p(因為開始main 已經給棧結構分配了內存空間,而&p 是棧結構 p 在內存的地址0x1111,也就是棧本身的首地址,也是 base指向的棧的基址),同樣是傳入一份拷貝 _&p ,且不是給 _&p  malloc 內存,而是給 _&p 指向的內容分配空間—— (&p)->base(表的基地址)分配內存,也就是說,這裡堆 p 修改也是沒用的,但是對 p 的指向修改,也就是 base,是有用的。而 base 本身就是一個指針,p 其實和 base 值相等,只不過變量的類型不一樣,故不需要再傳入二級指針。       6)、初始化那裡,其實寫的不好,主調函數裡 s 分配了內存,如果沒有對這個結構體初始化,就不代表 s 的成員 base 或者 top 等就是有指向的,更別說 NULL 了。很大程度是指向是垃圾值,不確定的內存區域,故這裡的判斷    if (s->base != NULL)   在這個前提下,是沒有用處的語句。故還是聲明變量的同時,最好是初始化,哪怕是0或者 NULL。        2、鏈棧   其實就是鏈表的特殊情形,一個鏈表,帶頭結點,棧頂在表頭,插入和刪除(出棧和入棧)都在表頭進行,也就是頭插法建表和頭刪除元素的算法。顯然,鏈棧還是插入刪除的效率較高,且能共享存儲空間。       是棧頂在表頭!棧頂指針指向表頭結點。棧底是鏈表的尾部,base 就是尾指針。還有,理論上,鏈式結構沒有滿這一說,但是理論上是這樣的,也要結合具體的內存,操作系統等環境因素。   復制代碼   1 #ifndef _____ADT__   2 #define _____ADT__   3 #include <stdbool.h>   4 #include <stdio.h>   5 #include <stdlib.h>   6    7 typedef struct Node{   8     char data;   9     struct Node *next; //next 指針  10 } Node, *ListStack;  11   12 //初始化頭結點,top 指針指向頭結點  13 void initStack(ListStack *top)  14 {  15     //top就是頭指針!也就是棧頂指針  16     *top = (ListStack)malloc(sizeof(Node));  17     //就一個結點,也就是空棧,其實和鏈表一樣,沒啥意思  18     (*top)->next = NULL;  19     //內容初始化  20     (*top)->data = '0';  21 }  22   23 //判空  24 bool isEmpty(ListStack top)  25 {  26     //棧頂指針的next==NULL 就是空棧,沒有滿的判斷,但是也要悠著點。小心內存受不了。  27     return top->next == NULL ? true : false;  28 }  29   30 //入棧  31 void push(ListStack top, char topElement)  32 {  33     ListStack q = NULL;  34     q = (ListStack)malloc(sizeof(Node));  35       36     if (NULL == q) {  37         exit(0);  38     }  39     //類似頭插法建表,進棧  40     q->next = top->next;  41     top->next = q;  42     //賦值  43     top->data = topElement;  44     //棧底永遠是表尾指針  45 }  46   47 //出棧  48 void pop(ListStack top, char *topElement)  49 {  50     ListStack p = NULL;  51       52     if (isEmpty(top)) {  53         exit(0);  54     }  55     //棧頂元素出棧,記住,棧頂指針永遠是指向棧頂元素的下一位,p 指向棧頂元素  56     p = top->next;  57     *topElement = p->data;  58     //刪除這個元素  59     top->next = p->next;  60     free(p);  61 }  62   63 //求當前長度  64 int getLength(ListStack top)  65 {  66     int i = 0;  67     ListStack q = top->next;  68       69     while (q != NULL) {  70         i++;  71         q = q->next;  72     }  73       74     return i;  75 }  76   77 //求棧頂元素  78 char getTop(ListStack top, char topElement)  79 {  80     if (isEmpty(top)) {  81         puts("棧空!");  82     }  83       84     topElement = top->next->data;  85     return topElement;  86 }  87   88 //遍歷  89 void traversal(ListStack top)  90 {  91     ListStack p = top->next;  92       93     for (int i = 0; i < getLength(top); i++) {  94         printf("棧中元素遍歷:%c \n", p->data);  95         p = p->next;  96     }  97 }  98   99 //銷毀 100 void destroyLinkStack(ListStack *top) 101 { 102     ListStack p = *top; 103     ListStack pn = (*top)->next; 104      105     while (pn != NULL) 106     { 107         free(p); 108         p = pn; 109         pn = pn->next; 110     } 111     //銷毀最後一個 112     free(p); 113     p = NULL; 114     puts("棧成功銷毀!"); 115 } 116  117 #endif /* defined(_____ADT__) */ 復制代碼 main 函數   復制代碼  1 #include "ADT.h"  2   3 int main(void) {  4     ListStack stack = NULL;  5     initStack(&stack);  6       7     printf("棧長度 = %d\n", getLength(stack));  8       9     push(stack, 'a'); 10     push(stack, 'b'); 11     push(stack, 'c'); 12     push(stack, 'd'); 13      14     printf("棧長度 = %d\n", getLength(stack)); 15      16     traversal(stack); 17      18     char temp = '0'; 19     printf("棧頂元素 = %c\n", getTop(stack, temp)); 20     pop(stack, &temp); 21      22     printf("棧長度 = %d\n",getLength(stack)); 23      24     traversal(stack); 25      26     destroyLinkStack(&stack); 27      28     return 0; 29 } 復制代碼 其實鏈棧和鏈表是一樣的,沒什麼新鮮的東西。可見,線性表,尤其是鏈表,是數據結構和算法裡的重中之重,後續很多復雜高級的數據結構和算法,都會無數次的用到鏈表的相關知識和概念。
  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved