【圖解數據結構】 棧&隊列


勤於總結,持續輸出!

1.棧

1.1棧的定義

棧(stack)是限定在表尾進行插入和刪除的操作的線性表

我們把允許插入和刪除的一端稱為棧頂(top),另一端稱為棧底(bottom),不包含任何數據元素的棧稱為空棧。棧又稱為后進先出(Last In First Out)的線性表,簡稱LIFO結構。

棧的插入操作,叫做進棧,也稱壓棧、入棧。

mark

棧的刪除操作,叫做出棧,也稱彈棧。

mark

1.2棧的順序存儲結構及實現

既然棧是線性表的特例,那么棧的順序存儲其實也是線性表順序存儲的簡化。

用數組實現,下標為0的一端作為棧底比較好,因為首元素都存在棧底。

棧的結構定義:

定義一個top變量來指示棧頂元素在數組中的位置,若存儲棧的長度為SackSize,則棧頂位置top必須小於SackSize。當棧存在一個元素時,top等於0,因此通常把空棧的判定條件為top=-1。

typedef int SElemType;
typedef struct 
{
	SElemType data[MAXSIZE];
	int top;		/*用於棧頂指針*/
} SqStack;

1.3棧的順序存儲結構——進棧操作

代碼實現:

#define MAXSIZE 5
#define OK 1
#define ERROR 0

/*插入元素e為新的棧頂元素*/
Status Push(SqStack *S, SElemType e)
{
	if (S->top == MAXSIZE - 1)	/*棧滿*/
	{
		return ERROR;
	}
	S->top++;
	S->data[S->top] = e;
	return OK;
}

測試代碼:

int main()
{
	SqStack stack = { {1,2},1 }; /*初始化棧內有兩個元素,top=1*/
	Push(&stack, 3);
}

運行結果:

mark

1.4棧的順序存儲結構——出棧操作

代碼實現:

#define MAXSIZE 5
#define OK 1
#define ERROR 0

/*若棧不為空,則刪除S的棧頂元素,用e返回其值*/
Status Pop(SqStack *S, SElemType *E)
{
	if (S->top == -1)
	{
		return ERROR;
	}
	*E = S->data[S->top];
	S->data[S->top] = NULL;
	S->top--;
	return OK;
}

測試代碼:

int main()
{
	SElemType e;
	SqStack stack = { {1,2},1 }; /*初始化棧內有兩個元素,top=1*/
	Push(&stack, 3);
	Pop(&stack, &e);
}

運行結果:

mark

驗證了后進先出的結構。

1.5棧的鏈式存儲結構及實現

棧的鏈式存儲結構,簡稱為棧鏈。由於單鏈表有頭指針,而棧頂指針也是必須的,那么便可以讓它倆合二為一。以為就是說棧頂放在單鏈表的頭部。

mark

對於鏈棧來說,基本不存在棧滿的情況,除非內存已經沒有可以使用的空間。但是對於空棧來說,鏈表原定義是頭指針指向空,那么鏈棧的空其實就是top=NULL。

棧鏈的結構代碼如下:

typedef int SElemType;
typedef struct StackNode
{
	SElemType data;
	struct StackNode  *next;
} StackNode;
typedef struct StackNode *LinkStackPtr;

typedef struct LinkStatck
{
	LinkStackPtr top;
	int count;
}LinkStatck;

1.6棧的鏈式存儲結構——進棧操作

mark

代碼實現:

#define OK 1
#define ERROR 0
typedef int Status;
typedef int SElemType;

/*插入元素e為新的棧頂元素*/
Status Push(LinkStatck *S, SElemType e)
{
	LinkStackPtr s = (LinkStackPtr)malloc(sizeof(StackNode));
	s->data = e;
	s->next = S->top;
	S->top = s; /*將新的節點s賦值給棧頂指針*/
	S->count++;
	return OK;
}

測試代碼:

int main()
{
	LinkStatck stack = { NULL ,0}; /*初始化一個空鏈棧*/
	Push(&stack, 1);
	Push(&stack, 2);
	Push(&stack, 3);
}

運行結果:

mark

動畫模擬:

mark

1.7棧的鏈式存儲結構——出棧操作

mark

代碼實現:

#define OK 1
#define ERROR 0
typedef int Status;
typedef int SElemType;

/*若棧不為空,則刪除S的棧頂元素,用e返回其值*/
Status Pop(LinkStatck *S, SElemType *e)
{
	LinkStackPtr p;
	if (S->count == 0)
	{
		return ERROR;
	}
	*e = S->top->data;
	p = S->top; /*將棧頂節點賦值給p*/
	S->top = S->top->next;	/*使棧頂指針下移一位,指向后一節點*/
	free(p);		/*釋放節點p*/
	S->count--;
	return OK;
}

測試代碼:

int main()
{
	LinkStatck stack = { NULL ,0}; /*初始化一個空鏈棧*/
	Push(&stack, 1);
	Push(&stack, 2);
	Push(&stack, 3);
	SElemType e;
	Pop(&stack, &e);
	Pop(&stack, &e);
	Pop(&stack, &e);
}

運行結果:

mark

動畫模擬:

mark

2.隊列

2.1隊列的定義

隊列(queue)是只允許在一端進行插入操作,而在另一端進行刪除操作的線性表。

隊列是一種先進先出(First In First Out)的線性表,簡稱FIFO。允許插入的一端稱為隊尾,允許刪除的一端稱為隊頭。

2.2隊列的順序存儲結構

我們假設一個隊列有n個元素,則順序存儲的隊列需建立一個大於n的數組。

現在進行入隊操作,就是在隊尾插入一個元素,不需要移動任何元素,因此時間復雜度是O[1]。

mark

出隊操作是在隊頭,那么隊列中所有的元素都要向前移動一個位置,確保下標為0的位置不為空,時間復雜度是O[n],這是個問題。

mark

如果不限定出隊操作時所有的元素都要向前移動,也就是說隊頭不一定必須在下標為0 的位置,出隊的性能就會大大增加。

mark

但是這樣又會出現另一個問題——假溢出,就是假設隊列前面的位置是空着的,但是從隊尾入隊已經滿了。

mark

循環隊列可以解決這一個問題,后面滿了,就從頭再開始,也就是頭尾相接的循環,這種頭尾相接的順序存儲結構稱為循環隊列。

mark

但是循環隊列還是會面臨着數組溢出的問題。

2.3隊列的鏈式存儲結構及實現

隊列的鏈式存儲結構,其實就是線性表的單鏈表,只不過它能尾進頭出而已,簡稱鏈隊列。

隊頭指針指向鏈隊列的頭節點,而隊尾指針指向終端節點:

mark

空隊列時都指向頭節點:

mark

鏈隊列的結構如下:

typedef int QElemType;
typedef struct QNode /*結點結構*/
{
	QElemType data;
	struct QNode *next;
}QNode,*QueuePtr;

typedef struct /*隊列的鏈表結構*/
{
	QueuePtr front, rear;	/*隊頭、隊尾指針*/
} LinkQueue;

2.4隊列的鏈式存儲結構——入隊操作

入隊操作,在隊尾插入新元素。

代碼實現:

#define OK 1
#define ERROR 0

typedef int Status;
/*插入元素e為Q的新的隊尾元素*/
Status EnQueue(LinkQueue *Q, QElemType e)
{
	QueuePtr s = (QueuePtr)malloc(sizeof(QNode));
	s->data = e;
	s->next = NULL;

	Q->rear->next = s;		/*把擁有元素e新節點s賦值給原隊尾結點的后繼*/
	Q->rear = s;		/*把s設置為隊尾結點,rear指向s*/

	return OK;
}

測試代碼:

int main()
{
	/*頭結點*/
	QueuePtr head = (QueuePtr)malloc(sizeof(QNode));
	head->data = 0;
	head->next = NULL;

	LinkQueue q = { head ,head }; //空隊列,隊頭、隊尾指針都指向頭結點
	
	EnQueue(&q, 1);
	EnQueue(&q, 2);
}

運行結果:

mark

動畫模擬:

mark

2.4隊列的鏈式存儲結構——出隊操作

代碼實現:

#define OK 1
#define ERROR 0

typedef int Status;
/*若隊列不為空,刪除Q的隊頭元素,用e返回其值*/
Status DeQueue(LinkQueue *Q, QElemType *e)
{
	QueuePtr p;
	if (Q->front == Q->rear)
	{
		return ERROR;
	}
	p = Q->front->next;	 /*將欲刪除的隊頭節點暫存給p*/
	*e = p->data;
	Q->front->next = p->next;	 /*將原隊頭結點后繼賦值給頭結點后繼*/

	if (Q->rear == p) /*若隊頭是隊尾,則刪除后將rear指向頭結點*/
	{
		Q->rear = Q->front;
	}

	free(p);
	return OK;
}

測試代碼:

int main()
{
	/*頭結點*/
	QueuePtr head = (QueuePtr)malloc(sizeof(QNode));
	head->data = 0;
	head->next = NULL;

	LinkQueue q = { head ,head };
	
	EnQueue(&q, 1);
	EnQueue(&q, 2);
	QElemType e;
	DeQueue(&q, &e);
}

運行結果:

mark

動畫模擬:

mark

參考:《大話數據結構》


本文為博主學習感悟總結,水平有限,如果不當,歡迎指正。

如果您認為還不錯,不妨點擊一下下方的推薦按鈕,謝謝支持。

轉載與引用請注明出處。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM