栈的简单实现及应用
棧的簡單實現及其應用
- 什么是棧?
- 棧的分類
- 棧的數據結構
- 棧的基本操作
- 棧的初始化
- 棧的銷毀
- 入棧操作
- 出棧和棧空的判斷
- 獲取棧頂元素
- 獲取棧的元素個數
- 頭文件
- 總結
- 棧的應用
什么是棧?
棧:一種特殊的線性表,其只允許在固定的一端進行插入和刪除元素操作。進行數據插入和刪除操作的一端稱為棧頂,另一端稱為棧底。棧中的數據元素遵守后進先出LIFO(Last In First Out)的原則。
入棧:棧的插入操作叫做進棧/壓棧/入棧;(從棧頂插入數據)
出棧:棧的刪除操作叫做出棧;(也是從棧頂刪除數據)
做個簡單的比喻:我們的棧就相當于一個彈夾,入棧操作就相當向彈夾之中壓入子彈;出棧操作就相當將彈夾里面的子彈取出來;而這個壓入子彈和取出子彈都只有一個口;這個口就是棧頂,出入數據都從棧頂進行;
壓棧和出棧:
棧的分類
棧主要分為兩類:
1、順序棧;(用順序表實現的棧)
2、鏈式棧;(用鏈表實現的棧)
日常情況下,我們通常會選用順序表來實現棧?
為什么?
因為如果我們將順序表的尾作為棧頂的話,入棧(尾插)、出棧(尾刪)效率很高就是O(1);
當然理論上我們也可以將順序表的頭作為棧頂,但是沒人會這樣高,因為入棧(頭插)、出棧(頭刪)效率很低,時間復雜度是O(N^2)是一種事倍功半的操作;
那既然這樣的話,我們為什么不選單鏈表的頭做為棧頂,這樣的話入棧(頭插)、出棧(頭刪)效率也很高啊,時間復雜度也是O(1);
那么為什么不選它嘞?
我們目前來看是這樣,但是具體實現起來,我們就得用一個棧頂指針來維護這個棧,那么就需要考慮很多特俗情況,同時參數這塊也需要傳二級指針,因為我的棧頂指針(鏈表頭節點指針)是會變的;
而如果利用順序表實現的話,就不會存在傳二級指針的問題,也不需要考慮過多的特殊情況;
為此我們強烈推薦利用順序表來實現棧!!!
棧的數據結構
為了維護這一個棧,我們利用一個結構體來維護棧;
這里我們只需與順序表區分一下top指針,top指針是專門用來維護棧頂元素的;
棧的基本操作
棧的初始化
剛開是一定是空棧,這點毋庸置疑;
所以我們結構體里面的nums置空,容量capcity也應該置空;
但是這里的top指針有兩種初始化方式:
1、top=-1;
2、top=0;
這兩種初始化方式對應著后面的操作有些不同;
如果top初始化為-1,那么我的棧頂元素是那個?
是不是就是nums[top],我的top每次都是指向上一次的空間的,為此我們每一次入棧,都要先將top++,在將數據放入top所指位置;為此我的top指針就指向最后一個空間(棧頂位置);
如果我的top指針初始化為0的話,我就不需要先++top了,直接在top位置插入數據就可以了,因為此時top位置就是待插入數據的位置,為此我們數據插入完畢過后需要++top,而此時的top表示的是棧頂元素的下一個位置top-1才表示棧頂元素的位置,這時的top也就相當于順序表里面元素個數也就相當于順序表里面的size;
本文采用top=0;的初始化方式:
棧的銷毀
銷毀與初始化操作通常都是連載一起的,我們在初始化操作實現完成過后便可直接實現銷毀操作:
//銷毀棧 void DestroyStack(ST*ps) {assert(ps);ps->capcity = 0;ps->top = 0;free(ps->nums); }入棧操作
入棧操作對于順序表來說就是尾插,既然是要插入數據,我們就要首先檢查一下容量夠不夠,不免容量不夠而出現插入失敗的情況:
檢查容量:
當然這個檢查容量的操作是由程序自己完成的,我們使用者是用不到的,為此我們可以將這個函數加以static關鍵字加以修飾,以此切斷它的外部鏈接屬性,不把它暴露給用戶,加強其封裝性!!!
容量問題現在解決了,接下來便是插入數據(入棧):
出棧和棧空的判斷
當然出棧的操作,必然伴隨著數據的刪除,為此我們必須保證有數據可刪才行!!數據都沒了,那還刪個der!為此我們需要先實現一下棧的判空:
由于我么初始化的時候選擇的是將top初始化為0,那么top的數據就是元素個數,為此top==0是便是棧為空,我們便返回真;棧不為空,便返回假:
接下來我們保證了數據有的刪,那我們直接移動top指針就行了,讓計算機訪問不到,不用真的刪除并釋放,同時free也不支持只釋放不完整的空間;
//出棧 void StackPop(ST* ps) {assert(ps);assert(!StackEmpty(ps));//判空ps->top--; }獲取棧頂元素
同理可得,我們獲取元素,那也得要棧里面有元素才行,棧都為空,就沒必要再去取元素了:
為此,我們首先要先判斷一下棧是不是為NULL:
獲取棧的元素個數
我們初始化的時候top初始化的0,為此,我們top所表示的意義和元素個數等價,為此我們只需返回top即可:
//統計棧的元素 size_t StackSize(ST* ps) {assert(ps);return ps->top; }頭文件
#pragma once #include<stdio.h> #include<stdbool.h> #include<stdlib.h> #include<assert.h> typedef char DataType; typedef struct Stack {DataType* nums;int capcity;int top; }ST; //初始化棧 void InitStack(ST* ps); //銷毀棧 void DestroyStack(ST* ps); //入棧 void StackPush(ST* ps,DataType x); //出棧 void StackPop(ST*ps); //判斷棧是否為NULL bool StackEmpty(ST* ps); //統計棧的元素 size_t StackSize(ST* ps); //獲取棧頂元素個數 DataType StackTop(ST*ps);總結
或許有的讀者會疑惑,我們為什么要單獨對棧頂元素和棧的元素個數專門寫個函數呢?我們直接一句printf不就搞定了嘛。比如:我直接printf(“%d\n”,st.top);不就是棧的元素嘛,為啥還要專門寫個函數;
我們現在是知道top是初始化為0,然后top就直接就表示元素個數,那如果我初始化top為-1,那么此時我的top就不再等價表示順序表元素個數了,top+1才是表示元素個數,這也就意味著我需要去查看源碼到底是怎么初始化top的,很是麻煩,而且難免有時候看錯😊😊😊,而我們直接使用函數接口去求棧的元素個數,則不需要關心top是怎么初始化的,直接調就行了,很是方便,也不會出錯!!就問這兩種方式你更青睞與哪一種,我比較喜歡第二種!!😊😊😊
棧的應用
上面介紹了什么是棧,同時也介紹了棧的基本操作,下面我們就來做上一道題,感受一下棧的威力!!
題目描述:
??挑戰鏈接??
分析:
思路:
我們可以利用棧先進后出的特點:
遇到左括號就進棧,遇到右括號就獲取棧頂元素,并且出棧;
舉個例子:
為此我們來寫我們的第一次代碼:
由于C語言沒有棧,所以我們需要將我們寫的棧copy過去,才行:
運行失敗!!
通過測試用例,我們在走讀代碼,發現當只有左括號的時候,就會出現一直進棧,沒有出棧的操作,自然也就不會走到false位置,最后直接就會來到treu位置,為此我們通過測試用例可以相當,如果全部能夠匹配成功的話,那么我的棧最后一定是沒有剩余元素的,如果有,那么說明一定還存在左括號沒有匹配成功;為此我們的代碼改進如下:
再次運行:
還是失敗!!
這次他給我們報的測試用例是全是右括號的情況,假設全是右括號,按照上面的邏輯,我就需要去進行出棧操作,可是由于全是右括號,沒有左括號給我們入棧,棧里面自然也就沒有元素給我們出,這種情況就是我遇到了右括號,但是沒有左括號與我匹配,我直接就返回false;
代碼改進:
這次代碼就過了:
時間復雜度:O(N)
空間復雜度:O(N)
具體代碼:
總結
- 上一篇: ORACLE数据库备份方法
- 下一篇: 数据结构之稀疏数组队列