數據結構-棧

先簡單聊聊

以前學習的線性表以及線性表在物理結構中順序存儲和鏈式存儲兩種方式的代碼實現。今天咱們來學習一下一種特殊的線性表---棧,以及棧的順序存儲和鏈式存儲。node

一、棧是什麼

棧是一種特殊的線性表,特殊點在於棧具備「先進後出」的一種形式。markdown

1.一、何爲「先進後出」

以前咱們在學習線性表的時候,實現過相關的插入和刪除方法。在線性表中,你能夠在任意的位置插入和刪除數據結點。而棧,只能在指定的位置插入和刪除數據結點,咱們一般叫這個指定位置爲「棧頂」。對應的另外一端,咱們叫「棧底」。數據結構

概念比較簡單,接下來咱們直接上代碼app

二、相關代碼

既然棧是線性表的一種,根據往期的學習,一下的代碼要分爲順序存儲和鏈式存儲兩種方式的實現函數

2.一、順序存儲

#include <stdio.h>
#include <stdlib.h>

#define OK 1
#define ERROR 0
#define TRUE 1
#define FALSE 0
#define MAXSIZE 20

typedef int Status;
typedef int SElemType;

typedef struct Stack{
    SElemType *data;
    int top;
}SqStack;

// init
Status initStack(SqStack *stack) {
    stack->data = (SElemType*)malloc(sizeof(SElemType) * MAXSIZE);
    stack->top = -1;
    return OK;
}

// clear
Status clearStack(SqStack *stack) {
    stack->top = -1;
    return OK;
}

// is empty
Status isEmptyStack(SqStack stack) {
    if (stack.top == -1) {
        return TRUE;
    } else {
        return FALSE;
    }
}

// stack長度
int length(SqStack stack) {
    return stack.top + 1;
}

//獲取棧頂元素
Status getTopElem(SqStack stack, SElemType *elem) {
    if (stack.top == -1) {
        return ERROR;
    } else {
        *elem = stack.data[stack.top];
        return OK;
    }
}

// 入棧
Status pushElem2Stack(SqStack *stack, SElemType elem) {
    if (stack->top == MAXSIZE - 1) {
        return ERROR;
    }
    
    stack->top ++;
    stack->data[stack->top] = elem;
    return OK;
}

// 出棧
Status popElemFromStack(SqStack *stack, SElemType *elem) {
    if (stack->top == -1) {
        return ERROR;
    }
    
    *elem = stack->data[stack->top];
    stack->top--;
    return OK;
}

// 打印棧
Status displayStack(SqStack stack) {
    if (stack.top == -1) {
        return ERROR;
    }
    
    for (int i = 0; i <= stack.top; i++) {
        printf("%5d", stack.data[i]);
    }
    printf("\n");
    return OK;
}
複製代碼

2.二、鏈式存儲

#include <stdio.h>
#include <stdlib.h>

#define OK 1
#define ERROR 0
#define TRUE 1
#define FALSE 0

typedef int Status;
typedef int ElemType;

typedef struct StackNode{
    ElemType data;
    struct StackNode *next;
    
}StackNode, *StackNodePtr;

typedef struct LinkStack{
    StackNodePtr top;
    int count;
}LinkStack;

//init
Status initStack(LinkStack *stack) {
    stack->top = NULL;
    stack->count = 0;
    return OK;
}

//clear
Status clearStack(LinkStack *stack) {
    StackNodePtr p = stack->top;
    StackNodePtr q;
    while (p) {
        q = p->next;
        free(p);
        p = q;
    }
    stack->top = NULL;
    stack->count = 0;
    
    return OK;
}

//isEmpty
Status isEmptyStack(LinkStack stack) {
    if (stack.count == 0) {
        return TRUE;
    } else {
        return FALSE;
    }
}

//length
int length(LinkStack stack) {
    return stack.count;
}

//getTopElem
Status getTopElem(LinkStack stack, ElemType *elem) {
    if (stack.top == NULL) {
        return ERROR;
    }
    *elem = stack.top->data;
    return OK;
}

//pushElem2Stack
Status pushElem2Stack(LinkStack *stack, ElemType elem) {
    StackNodePtr node = (StackNodePtr)malloc(sizeof(StackNode));
    if (node == NULL) {
        return ERROR;
    }
    node->data = elem;
    node->next = stack->top;
    stack->top = node;
    stack->count ++;
    return OK;
}

//popElemFromStack
Status popElemFromStack(LinkStack *stack, ElemType *elem) {
    if (stack->top == NULL) {
        return ERROR;
    }
    StackNodePtr p = stack->top;
    *elem = p->data;
    stack->top = stack->top->next;
    free(p);
    stack->count --;
    return OK;
}

//displayStack
Status displayStack(LinkStack stack) {
    StackNodePtr p = stack.top;
    while (p) {
        printf("%5d", p->data);
        p = p->next;
    }
    printf("\n");
    return OK;
}
複製代碼

三、棧的應用

在咱們平時開發中,不多直接使用棧。可是棧又是相對比較基礎的數據結構。學習

不少時候系統會給咱們封裝提供以棧的方式實現的模塊,例如iOS中的UINavigationViewController,可能一些非客戶端app的開發者不知道這個東西。它就是從一個界面中,點擊一個按鈕,進入到另外一個界面,至關於棧的入棧;當你點擊返回的時候,回到前一個界面,至關於出棧。spa

其實,還有一個咱們更經常使用的,並且每一個人都會用的,但平時不會太多去注意到的一個點,就是函數調用。在高級語言的程序中,調用函數和被調用函數之間的連接和信息交換都是經過棧的形式來實現的。指針

3.一、函數調用

在一個函數(如下用「調用函數」表示)內,調用另外一個函數(如下用「被調用函數」表示),在運行被調用函數以前,系統會先完成下面的幾件事code

  • 將實參、返回地址等相關信息傳遞給被調用函數中保存
  • 爲被調用函數的局部變量分配存儲空間
  • 將控制轉移到被調用函數的入口

從被調用函數返回到調用函數以前,系統一樣會作一些事情orm

  • 保存被調用函數的結果
  • 釋放被調用函數的數據區
  • 根據被調用函數保留的返回地址,將控制權轉移回調用函數中。

當多個函數構成嵌套條用時,按照「先調用後返回」方式,也就是先進後出的棧的形式實現的。

好比代碼實現:

int second(int d){ 
int x,y;
//...
}
int first(int s ,int t){ 
int i;
//... 
second(i)//2.⼊入棧 
//...
}
void main( ){ 
int m,n;
first(m ,n); //1.⼊入棧
//...
}
複製代碼

當main函數中,調用了first函數,main函數就是調用函數,first函數就是被調用函數。方法調用的棧結構如圖:

first函數中又調用了second函數,如圖:

函數調用時,棧空間中保存了方法名,參數,和相關臨時變量。

3.二、遞歸函數

遞歸函數運行過程相似多個函數嵌套調用,只是調用函數和被調用函數是同一個函數。

爲了保證遞歸函數的正確調用,系統須要設立一個「遞歸工做棧」做爲整個遞歸函數運行期間的數據存儲區。每一層遞歸所需信息構成一個工做記錄,其中包括全部的實參,全部的局部變量以及上一層的返回地址。每進入一層遞歸,就產生一個新的工做記錄壓入棧頂。沒退出一個遞歸,就從棧頂彈出一個記錄。

擴展內容

什麼是遞歸?

在一個函數,過程或數據結構敵營的內部又直接或間接出現定義自己的應用;則稱爲他們是遞歸的或者是遞歸定義。

在下面3中狀況下,咱們會使用到遞歸來解決問題

  1. 定義是遞歸的

階乘

// Fact(n)
// 若n=0, 則返回1;
// 若n > 1, 則返回n*Fact(n-1);
 long Fact(Long n){
 if(n=0) return -1;
 else return n * Fact(n-1);
}
複製代碼

斐波拉契數列

//Fib(n)
// 若n=1或者n=2,則返回1;
// 若n > 2, 則Fib(n-1) + Fib(n-2);
long Fib(Long n){
    if(n == 1 || n == 2) return 1;
    else return Fib(n-1)+Fib(n-2);
}
複製代碼
  1. 數據是遞歸的 以前咱們學習過的鏈表,就是一種數據遞歸。鏈表結點中包含data和next指針,next指向的是鏈表結點的類型。遍歷鏈表時可使用遞歸的方式
void TraverseList(LinkList p){
    if(p == NULL) return; 
    else{
        printf("%d",p->data); 
        p TraverseList(p->next);
    }
}
複製代碼
  1. 問題的解法是遞歸的

有一類問題,雖然問題自己並無明顯的遞歸結構,可是採起遞歸求解比迭代更簡單,如Hanoi塔問題、八皇后問題、迷宮問題

//漢諾塔 Hanoi
void Hanoi(int n, char a, char b, char c) {
    if (n == 1) {
        moves(a, 1, c);
    } else {
        Hanoi(n-1, a, c, b);
        moves(a, n, c);
        Hanoi(n-1, b, a, c);
    }
}

int main(int argc, const char * argv[]) {
    // insert code here...
    printf("Hello, Hanoi!\n");
    Hanoi(4, 'a', 'b', 'c');
    return 0;
}
複製代碼
使用遞歸的條件
  • 能講問題轉變成小問題,而小問題和原問題解法相同或類同,不一樣的僅僅是處理的對象數據,而且這些處理更小且變化有規律
  • 能夠經過轉換而使得問題簡化
  • 必須有一個明確的遞歸出口或稱爲遞歸邊界值。

總結

這節課的學習是在原有的基礎上擴展出來的,可是注意到了平時不注意的點(函數調用是棧的邏輯)。但願以上的總結,能夠給你帶來幫組。記住:沿途的風景要比目的地更彎的否!!!

相關文章
相關標籤/搜索