數據結構(7)棧結構之順序棧
前言
棧本質上來看也是線性表,但它是操作受限的線性表。所謂操作受限,顧名思義,在線性表中,我們可以在頭部、尾部和中間對線性表進行插入與刪除操作;但是在棧中,規定了只能在表的尾部進行插入與刪除。因此棧的操作比一般線性表的操作少了不少,但是實用性卻十分廣泛。
在棧中,表的尾部稱爲棧頂,表的頭部則稱爲棧底,不含任何元素的空表稱爲空棧。
順序棧的初始化
需要明確的是,順序棧是用一組地址連續的存儲單元依次存放從棧底到棧頂的數據元素。因此在設計棧的構造時,需要base一個指針來指向這片連續的存儲單元,它同時也是棧底元素所在的位置,始終不變。還需要附設一個top指針指示棧頂元素所在的位置,再附設一個capacity值來表示棧的容量大小。由於是在順序結構中,top指針可以直接看作數組的下標,因此直接使用int類型即可。
這樣,我們初始化設計的順序棧,需要經過三個步驟
- 申請一塊連續的內存空間,並將base指針指向它
- 設置top的初值
- 設置capacity的初值
初始化結束後,可以得到如下的結構:
當然,這個結構並不是唯一的。在有些資料中,使用了數組來代替base指針,這本質上沒有區別;或者不設置capacity值,這也無傷大雅;有的top初值賦爲-1,這樣會在實現插入和刪除操作時有些變化,但只要按棧的概念來實現即可。
順序棧的插入與刪除
前面提到,棧插入和刪除的位置是被規定的,只能在表的尾部(也就是棧頂)進行插入與刪除,這樣,一般稱棧的插入操作爲入棧,刪除操作爲出棧。這也就有了棧的特性:先進後出,或者說是後進先出。也就是說,第一個入棧的元素永遠被壓在最底端,當它上方還有元素時,是無法出棧的;它最先入棧,卻只能最後出棧。
這樣說明,棧的出入順序是有跡可循的,比如" 1 "註定會在最後一個出棧(它之後沒有其他元素進棧的前提下),假設棧中的元素出棧後不再進棧,那麼越接近棧底的元素其出棧次序一定在越接近棧頂的元素之後。在這裏不仔細討論。
前面有說過,在初始化棧時,有些資料會將top值設爲0,有些則設爲-1;這樣會在代碼的具體實現時有些差異。當top值初始化爲0時,它所指向的是當前可以插入的位置,因此要進行插入,只需要把數據存入結點中,再將top值加一;而top值初始化爲-1時,需要先將其加一,再把數據存入結點中。
這樣,在判斷棧滿棧空時也會有差異,自行注意即可。
由於是在順序結構中,我們要刪除某個結點就不需要釋放掉該結點的內存空間了,只需要將top指針回溯,就可以當做把這個空間回收。當下次有元素入棧時,會直接將原來的數據覆蓋掉。當然,這樣對原來的數據而言似乎不太安全,因此可以在指針回溯之前把它改成一個初值。假如結點的元素類型是指針類型,可以設置爲null,本文中是int型,就直接設爲0了。
增配內存空間
但凡是順序存儲結構,都需要考慮到空間的問題。初始化時設置的空間太大而實際上存儲的數據很少,會造成內存空間的浪費;如果初始化時設置的空間太小而實際存儲的數據很多,就會遇到存不下的問題。因此就實用性而言,實現動態增配內存空間是必要的。
一般的操作是:在棧初始化時分配一個合適的容量大小,當棧的容量不夠用時再去尋找另外的空間。由於我們希望增配完空間後,數據仍按原來的邏輯存放,因此需要調用relloc函數(關於malloc、relloc和calloc函數的區別可以自行去查詢)。
總而言之,我們用一個newBase指針來保存新申請到的空間,由於調用的是relloc方法,假設原有空間之後仍有滿足需求的內存,系統將直接在原有空間之後擴容,然後返回原有空間的基地址。假如原有空間之後沒有足夠內存,系統會去額外開闢一個空間,大小爲(當前棧的容量個數+需要增配的容量個數)*存儲的數據類型的大小;然後將原來的數據拷貝到這片區域,將原來的空間釋放,返回新空間的地址。假如此時內存中沒有這樣的額外空間可以開闢了,說明內存空間申請失敗,返回NULL,原有空間不會釋放。
全部代碼
SeqStack.h
#ifndef SeqStack_h
#define SeqStack_h
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#define ElemType int
//棧的初始大小
#define STACK_INIT_SIZE 8
//再次分配的大小
#define STACK_INC_SIZE 2
typedef struct SeqStack{
ElemType *base;
//棧容量
int capacity;
//表示棧頂所在的位置,也表示了當前棧內元素的個數
int top;
}SeqStack;
//初始化
void InitStack(SeqStack *s);
//增加棧空間 0->失敗 !0->成功
int Inc(SeqStack *s);
//1.入棧
void Push(SeqStack *s,ElemType x);
//2.出棧
void Pop(SeqStack *s);
//3.展示棧
void Show(SeqStack s);
void Show2(SeqStack *s);
//4.獲取棧頂元素
void GetTop(SeqStack s,ElemType *x);
void GetTop2(SeqStack *s,ElemType *x);
//5.獲取當前元素個數
int GetLength(SeqStack s);
int GetLength2(SeqStack *s);
//6.清除棧
void Clear(SeqStack *s);
//7.摧毀棧
void Destory(SeqStack *s);
//判斷棧是否已滿 0->未滿 !0->已滿
int IsFull(SeqStack *s);
//判斷棧是否已空 0->未空 !0->已空
int IsEmpty(SeqStack *s);
#endif /* SeqStack_h */
SeqStack.c
#include "SeqStack.h"
//初始化
void InitStack(SeqStack *s){
//1.申請棧的空間,初始空間爲(設定初始化棧的大小)*(數據類型所佔空間的大小)
s->base = (ElemType *)malloc(sizeof(ElemType)*STACK_INIT_SIZE);
assert(s->base != NULL);
//2.設置棧的容量
s->capacity = STACK_INIT_SIZE;
//3.設置棧頂位置
s->top = 0;
}
//增加棧空間
int Inc(SeqStack *s){
//申請一個新的空間->在原有空間的基礎上,調用realloc方法
ElemType *newBase = (ElemType *)realloc(s->base, sizeof(ElemType)*(s->capacity+STACK_INC_SIZE));
if (newBase == NULL) {
printf("內存空間已滿,無法再次分配\n");
return 0;
}
s->base = newBase;
s->capacity += STACK_INC_SIZE;
return 1;
}
//1.入棧
void Push(SeqStack *s,ElemType x){
//判斷棧是否已滿
// if (IsFull(s)) {
// //已滿,去申請增配空間
// if (!Inc(s)) {
// //增配空間不成功
// printf("棧空間已滿,%d不能入棧\n",x);
// return;
// }
// }
if (IsFull(s) && !Inc(s)) {
printf("棧空間已滿,%d不能入棧\n",x);
return;
}
//存入空間
s->base[s->top] = x;
//棧頂上移
s->top ++;
//寫法2:
//s->base[s->top++] = x;
}
//2.出棧
void Pop(SeqStack *s){
//判斷棧是否已空
if (IsEmpty(s)) {
printf("棧已空\n");
return;
}
s->top--;
s->base[s->top] = 0;
}
//3.展示棧
void Show(SeqStack s){
for (int i = s.top-1; i >= 0; i--) {
printf("%4d",s.base[i]);
}
printf("\n");
}
void Show2(SeqStack *s){
for (int i = s->top-1; i >= 0; i--) {
printf("%4d",s->base[i]);
}
printf("\n");
}
//4.獲取棧頂元素
void GetTop(SeqStack s,ElemType *x){
if (IsEmpty(&s)) {
printf("棧已空\n");
return;
}
*x = s.base[s.top-1];
}
void GetTop2(SeqStack *s,ElemType *x){
if (IsEmpty(s)) {
printf("棧已空\n");
return;
}
*x = s->base[s->top-1];
//這樣寫相當於獲取棧頂元素值的同時將它出棧了
//*x = s->base[--s->top];
}
//5.獲取當前元素個數
int GetLength(SeqStack s){
return s.top;
}
int GetLength2(SeqStack *s){
return s->top;
}
//6.清除棧
void Clear(SeqStack *s){
s->top = 0;
}
//7.摧毀棧
void Destory(SeqStack *s){
//釋放棧空間的指針
free(s->base);
s->base = NULL;
s->capacity = s->top = 0;
}
//判斷棧是否已滿
int IsFull(SeqStack *s){
//棧頂的值大於容量時,返回真,否則返回假
return s->top >= s->capacity;
}
//判斷棧是否已空
int IsEmpty(SeqStack *s){
//棧頂的值爲0時,返回真,否則返回假
return s->top == 0;
}
Main.c
#include "SeqStack.h"
int main(void) {
SeqStack st;
InitStack(&st);
ElemType top;
for (int i = 0; i < 10; i ++) {
Push(&st,i+1);
}
Show(st);
GetTop(st,&top);
printf("top = %d\n",top);
Pop(&st);
Show(st);
GetTop(st,&top);
printf("top = %d\n",top);
return 0;
}