本文由個人筆記整理得出,材料主要來自《大話數據結構》和網絡各博主
棧的定義:限定僅在表尾進行插入和刪除操作的線性表。
棧的插入操作叫作進棧,也稱壓棧、入棧。
棧的刪除操作,也叫出棧,彈棧。
棧底:下標爲0的一端作爲棧底。
ADT棧(stack)
Data
Operation
InitStack(*S):初始化操作,建立一個空棧S;
DestroyStack(*S):若棧存在,則銷燬它;
ClearStack(*S):清空棧
StackEmpty(S):若棧爲空,返回ture,否則返回false
GetTop(S,*e):若棧存在,用e返回S的棧頂元素
Push(*S,e):若棧存在,插入新的元素e到棧S中併成爲棧頂元素
Pop(*S,*e):刪除棧S中的棧頂元素,並用e返回其值
StackLength(S):返回棧S的元素個數
endADT
棧的結構定義:
typedef int SElemType;
typedef struct
{
SElemType data[MAXSIZE];//定義數組
int top;//設置棧頂
}SqStack;
Status Push(SqStack *S,SElemType)//進棧
{
if(S->top == MAXSIZE-1)//棧已滿
return ERROR;
S->top++;
S->data[S->top] = e;
return OK;
}
Status Pop(SqStack *S,SElemType *e)//出棧
{
if(S->top == -1)
return ERROR;
e= S->data[S->top];
S->top--;
return OK;
}
typedef strcut//定義兩個棧,佔用同一個數組;由兩邊向中間靠攏
{
SElemType data[MAXSIZE];
int top1;
int top2;
}SqDoubleStack;
Status Push(SaDoubleStack *S,SElemType e,int stackNumber)//進棧,先判斷要進哪一個棧;然後再進
{
if(S->top+1==S->top2)//棧滿
return ERROR;
if(stackNumber==1)//判斷是棧1
S->data[++S->top1] = e;
else if(stackNumber == 2)//判斷是棧2
S->data[--S->top2] = e;
return OK;
}
Status Pop(SqDoubleStack *S,SElemType *e,int stackNumber)
{
if(stackNumber == 1)
{
if(S->top == -1)
return ERROR;
*e = S->data[S->top1--];
else if(stackNumber == 2)
{
if(S->top2 == MAXSIZE)
return ERROR;
*e = S->data[S->top2++];
}
return OK;
}
}
棧的鏈式存儲結構及實現
上篇文章我們可以知道,線性表有順序表和鏈表;那麼對於棧也是一樣的;上面介紹的棧只有數組,沒有指針,屬於順序棧。一般情況下,棧只是用來做插入和刪除操作,但是上面我們談到的棧是有空間限制的;對比於上篇文章中寫到的線性表,那麼我們應該就不難理解鏈棧存在的意義了:最大的好處就是沒有空間的限制,通過指針指向將結點像一個鏈子一樣把結點鏈接。
先來個定義:
typdef struct StackNode
{
SElemType data;//數據
struct StackNode *next;//指針
}StackNode,*LinkStackPtr;
typdef struct LinkStack
{
LinkStackPtr top;//棧頂
int count;
}LinkStack;
Stauct Push(LinkStack *S,SElemType e)//插入元素e爲新的棧頂元素
{
LinkStackPtr s = (LinkStackPtr)malloc(sizeof(StackNode));//創建新結點
s->data = e;//數據
s->next = s->top;//把當前的棧頂數據賦值給新結點的後繼
s->top = s;//新結點s賦值給棧頂指針
s->count++;
return OK;
}
Stauct Pop(LinkStack *S,SElemType *e)//若棧不空,則刪除S的棧頂元素,用e返回其值;
{
LinkStackPtr p;
if(StackEmpty(*S))
return ERROR;
*e = S->top->data;//數據
p = S->top;//把當前的棧頂數據賦值給新結點的後繼
S->top = S->top->next;//新結點s賦值給棧頂指針
S->count++;
return OK;
}
其實我們很容易就可以看出,棧的時間複雜度爲O(1);對比順序棧和鏈棧,順序棧需要事先確定一個固定的長度,可能會存在內存空間浪費的問題,但它的優勢是存取定位很方便,而鏈棧則要求每一個元素都有指針域,壞處是增加了一些內存的開銷,但對於棧的長度,我們就可以不跟順序棧一樣死板定義了。
總結一下,從上篇文章我們知道,線性表的插入和刪除是可以在表中、表頭和表尾插入的;但棧不一樣,只能刪除和加入元素只能棧頂上操作;這個的本質意義上是簡化了程序設計的問題,將數據的處理劃分了不同的關注層次,使得思考範圍縮小,更加聚焦於我們要解決的問題。
補充:
遞歸(函數)
所謂遞歸就是一個直接或間接調用自己的函數。但是寫遞歸最怕的就是陷入永不結束的無窮遞歸中,所以,每一個遞歸至少有一個條件,就是當滿足條件再進行時,不再引用自身而是利用返回值退出。
如:
int Fbi(int i)
{
if(i<2)
return i == 0?0:1;//問號運算符的標準格式:表達式1?表達式2:表達式3
若表達式1爲真,則執行表達式2,爲假,則執行表達式3
對於本題,若i=0,則返回值0,否則返回值1
return Fbi(i-1)-Fbi(i-2);
}
int main()
{
int i;
for(int i = 0;i<40;i++)
printf("%d",Fbi(i));
return 0;
}
隊列定義:值允許在一端進行插入,而在另外一端進行刪除操作的線性表。
隊列,其實就跟排隊一樣,講究先來後到,先進先出,簡稱FIFO。允許插入的一端成爲隊尾,允許刪除的一端稱爲隊頭。
實際應用典例:電腦鍵盤的字母輸入
ADT Queue
Data
Operation
InitQueue(*Q):建立一個空隊列
DestroyQueue(*Q):如果隊列存在,則銷燬它
ClearQueue(*Q):清空隊列
QueueEmpty(Q):判斷隊列是否爲空,若爲空,則返回true;否則返回false
GetHead(Q,*e):若隊列Q存在且位非空,用e返回隊列Q的隊頭元素
EnQueue(*Q,e):刪除隊列Q中隊頭元素,並用e返回其值
QueueLength(Q):返回隊列Q的元素的個數
endADT
循環隊列:隊列中頭尾相接的順序存儲結構。
typedef int QElemType;
typedef struct
{
QElemType data[MAXSIZE];
int front;//頭
int rear;//尾
}SqQueue;
Status InitQueue()//初始化一個隊列
{
Q->front;
Q->rear = 0;
return OK;
}
int QueueLength(SqQueue Q)//求隊列的長度
{
return (Q.rear-Q.front+MAXSIZE)%MAXSIZE;
}
Status EnQueue(SqQueue *Q,QElemType e)//若隊列未滿,則插入元素e爲Q的新的隊尾元素
{
if((Q-rear+1)%MAXSIZE== Q->front)//已滿
return ERROR;
Q->data[Q->rear] = e;//把元素e賦值給隊尾
Q->rear = (Q->rear+1)%MAXSIZE;//rear向後移一位置
return OK;
}
Status DeQueue(SqQueue *Q,QElemType *e)//若隊列不空,則刪除元素e,用e返回其值
{
if(Q->front == Q->rear)
return ERROR;
*e = Q-data[Q-front];
Q-front = (Q-front+1)%MAXSIZE;
return OK;
}
隊列的鏈式存儲結構及實現
寫到隊列這裏其實我們都不用講了,鏈式?呵呵,不就是在結點再加指針嗎?不就是方便刪除和插入嗎?這誰不會?對吧
typedef int QElemType;
typedef struct QNode
{
QElemType data;//數據
struct QNode *next;//指針
}QNode,*QueuePtr;
typedef struct
{
QueuePtr front,rear;//隊頭、隊尾指針
}LinkQueue;
Status EnQueue(LinkQueue *Q,QElemtype e)//插入元素e爲Q的新的隊列元素
{
QueuePtr s = (QueuePtr)malloc(sizeof(QNode));
if(!s)//保險一點,加了防止分配內存不成功
{
exit(0);
}
s->data = e;
s-next = NULL;
Q->rear->next = s;//把擁有元素e新結點s賦值給原隊尾結點的後繼,就是讓隊尾的後繼指向s咯
Q-rear = s;//當前的s設置爲隊尾結點,rear指向s
return OK;
}
Status DeQueue(LinkQueue *Q,QElemtype *e)//刪除結點
{
QueuePtr p;
if(Q->front == Q->rear)
return ERROR;
p = Q-front->next;//把欲刪除的隊頭結點暫存給p,
*e = p->data;//把欲刪除的隊友結點的值賦值給e
Q->front->next = p-next;//把原隊頭結點後繼p->next賦值給頭結點後繼
if(Q-rear == p)//判斷改鏈表是否只剩下一個元素,是的話則將rear指向頭結點
Q-rear = Q-front;
free(p);
return OK;
}
再總結一下:
棧:僅限定在表尾插入和刪除操作的線性表;
隊列:只允許在一端進行插入操作,而在另一端進行刪除操作的線性表;
對於循環隊列,相對於隊列最大好處就是隊頭和隊尾可以在數組中循環變化,解決了移動數據的時間損耗。使得本來的時間複雜度從O(n)變爲了O(1)。