1. 程式人生 > 實用技巧 >LeetCode 3. 無重複字元的最長子串

LeetCode 3. 無重複字元的最長子串

棧的概念

在開始前,請牢記這句話:棧是一種先進後出的資料結構。

棧(stack)是限定僅在表的一端進行操作的資料結構,請聯絡我們前文所學的,設想一個單鏈表我們只能夠對其連結串列的表尾結點進行操作,而操作也只能夠進行插入一個新的結點與刪除最末尾的這個結點兩個操作,而這樣強限制性的‘連結串列’,就是我們所說的棧。

讓我們重新理順一下定義:棧是一個線性的資料結構,規定這個資料結構只允許在其中一端進行操作,並禁止直接訪問除這一端以外的資料。

如圖:棧就像一個放球的單管桶,只允許球從桶的開口這一端取出,並且球先放入桶中則後從桶中拿出。

棧的結點設計

棧分為陣列棧和連結串列棧,其區別是陣列棧使用陣列進行功能的模擬,實現較為快速和便利,而連結串列棧使用連結串列的思路去設計,實現較為麻煩,但是其穩定不易出錯;在連結串列棧中又分為靜態連結串列棧和動態連結串列棧,靜態連結串列棧給定棧的空間大小,不允許超過儲存超過給定資料大小的元素,而動態棧使用的是自動建立空間的方法進行建立,只要符合機器的硬體要求以及編譯器的控制,其理論上是極大的。

說了那麼多,我們以連結串列棧的動態連結串列棧為例子,進行棧的設計,在後文直接以棧一名字稱呼動態連結串列棧,這也是各類語言標準模板中棧的實現方式。

首先是棧的結點設計,我們可以設計出兩個結構體,一個結構體Node表示結點,其中包含有一個data域和next指標。

其中data表示資料,其可以是簡單的型別(如int,double等等),也可以是複雜的結構體(struct型別);

next指標表示,下一個的指標,其指向下一個結點,通過next指標將各個結點連結。

目前的設計如同單鏈表,接下來,為這個進行限制性的設計,我們額外新增一個結構體,其包括了一個永遠指向棧頭的指標top和一個計數器count記錄元素個數,(也可以設計成一個指標top和一個指標bottom分別指向棧頭和棧尾)其主要功效就是設定允許操作元素的指標以及確定棧何時為空(count的方法是當count為0時為空,top和bottom方法就是兩者指向同一個空間時為棧為空)

這裡我採用的是top和count組合的方法。其程式碼可以表示為:

//棧的結點設計
//單個結點設計,資料和下一個指標
typedef struct node     
{
    int data; 
    struct node *next;
} Node;
//利用上面的結點建立棧,分為指向頭結點的top指標和計數用的count
typedef struct stack    
{
    Node *top;
    int count;
} Link_Stack;

棧的基本操作—入棧

如圖:

入棧(push)操作時,我們只需要找到top所指向的空間,建立一個新的結點,將新的結點的next指標指向我們的top指標指向的空間,再將top指標轉移,指向新的結點,即是入棧操作

其程式碼可以表示為:

//入棧 push
Link_Stack *Push_stack(Link_Stack *p, int elem)
{
    if (p == NULL)
        return NULL;
    Node *temp;
    temp=(Node*)malloc(sizeof(Node));
    //temp = new Node;
    temp->data = elem;
    temp->next = p->top;
    p->top = temp;
    p->count++;
    return p;
}

棧的基本操作—出棧

如圖:

出棧(pop)操作,是在棧不為空的情況下(注意一定要進行判空操作),將棧頂的元素刪除,同時top指標,next向下進行移動即可的操作。

其程式碼可以表示為:

//出棧 pop
Link_Stack *Pop_stack(Link_Stack *p)
{
    Node *temp;
    temp = p->top;
    if (p->top == NULL)
    {
        printf("錯誤:棧為空");
        return p;
    }
    else
    {
        p->top = p->top->next;
        free(temp);
        //delete temp;
        p->count--;
        return p;
    }
}

棧的基本操作—遍歷

棧的遍歷相對而言比較複雜,由於棧的特殊性質,其只允許在一端進行操作,所以我們的遍歷操作永遠都是逆序的,其過程為,在棧不為空的情況下,一次從棧頂元素向下訪問,直到指標指向空(即到棧尾)為結束。

其程式碼可以表示為:

//遍歷棧:輸出棧中所有元素
int show_stack(Link_Stack *p)
{
    Node *temp;
    temp = p->top;
    if (p->top == NULL)
    {
        printf("");
        printf("錯誤:棧為空");
        return 0;
    }
    while (temp != NULL)
    {
        printf("%d\t", temp->data);
        temp = temp->next;
    }
    printf("\n");
    return 0;
}

快速棧實現--陣列棧

陣列棧是一種更為快速的模擬實現棧的方法,所謂模擬,就是不採用真實的連結串列設計,轉而採用陣列的方式進行“模擬操作”,這是一種模擬型別的操作,其可以快速的幫助我們構建程式碼,分析過程,相應的實現起來也更加的便捷。

其程式碼如下(請參考上文進行自主分析):

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#define maxn 10000
  
//結點設計
typedef struct stack{
    int data[maxn];
    int top;
}stack;
  
//建立
stack *init(){
    stack *s=(stack *)malloc(sizeof(stack));
    if(s==NULL){
        printf("分配記憶體空間失敗");
        exit(0);
    }
    memset(s->data,0,sizeof(s->data));
    //memset操作來自於庫檔案string.h,其表示將整個空間進行初始化
    //不理解可以查閱百度百科https://baike.baidu.com/item/memset/4747579?fr=aladdin
    s->top=0;     //棧的top和bottom均為0(表示為空)
    return s;
}
  
//入棧push
void push(stack *s,int data){
    s->data[s->top]=data;
    s->top++;
}
  
//出棧pop
void pop(stack *s){
    if(s->top!=0){
        s->data[s->top]=0;  //讓其迴歸0模擬表示未初始化即可
        s->top--;
    }
}
  
//模擬列印棧中元素
void print_stack(stack *s){
    for(int n=s->top-1;n>=0;n--){
        printf("%d\t",s->data[n]);
    }
    printf("\n");   //習慣性換行
}
  
int main(){
    stack *s=init();
    int input[5]={11,22,33,44,55};  //模擬五個輸入資料
    for(int i=0;i<5;i++){
        push(s,input[i]);
    }
    print_stack(s);
    /////////////
    pop(s);
    print_stack(s);
    return 0;
}