數據結構與算法&&線性表的變異體——棧與隊列

本文由個人筆記整理得出,材料主要來自《大話數據結構》和網絡各博主

棧的定義:限定僅在表尾進行插入和刪除操作的線性表。

棧的插入操作叫作進棧,也稱壓棧、入棧。
棧的刪除操作,也叫出棧,彈棧。
棧底:下標爲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)。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章