數據結構淺淺析之(二)——棧和隊列(Stack && Queue 附C++代碼)

一.寫在前面

  • 棧(stack)是限定僅在表尾進行插入和刪除操作的線性表(List)。   棧先進後出,後進先出(Last In First OUt)
  • 隊列(queue)是隻允許在一端進行插入操作,而在另一端進行刪除操作的線性表(List)。  隊列先進先出,後進後出(First In First Out)

二.棧(Stack)

  1. 基本概念

        我們在進行棧操作時,將允許插入和刪除的一端我們稱之爲棧頂(top),另一端稱之爲棧底(bottom),不含有任何數據元素的棧稱之爲空棧。對棧進行的插入操作,我們稱之爲進棧,也叫壓棧入棧。對棧的刪除操作,叫做出棧。如圖所示: 

                                                                                   棧基本概念圖示

 

2.進棧與出棧

         我們說棧是先進先出的,那麼是不是最先進棧的就最後出棧,最後進棧的最先出棧呢?當然不是,這我們要看具體情況。我們每次進棧和出棧都是在棧頂進行的操作。

      舉個簡單的例子,我們有1,2,3三個元素依次進棧出棧會有那些情況呢?

  • 1進,2進,3進 ,3出,2出,3出        進棧順序:1,2,3    出棧順序: 3,2,1
  • 1進,2進,2出,3進,3出,1出         進棧說序:1,2,3    出棧順序: 2,3,1
  • 1進,2進,2出,1出,3進,3出         進棧說序:1,2,3    出棧順序: 2,1,3
  • 1進,1出,2進,2出,3進,3出         進棧說序:1,2,3    出棧順序: 1,2,3
  • 1進,1出,2進,3進,3出 ,2出        進棧說序:1,2,3    出棧順序: 1,3,2

分析上面五種情況,進棧順序都是1,2,3,出棧順序卻又五種情況。還有沒有其他出棧情況呢?答案是否定的,沒有了。

我們說“棧是限定僅在表尾進行插入和刪除操作的線性表(List)”,那麼棧也是線性表的一種,所以線性表的順序存儲方式鏈式存儲方式對棧也是同樣適用的。(有關線性表的特性可參考上一篇:《數據結構淺淺析之(一)——線性表(List)》一文)。在對棧進行插入和刪除時,我們一般使用push和pop方法。

3.棧的順序存儲方式

  我們把棧的順序存儲稱爲順序棧,順序棧的存儲情況如下: 

                                                                                 順序棧存儲示意圖

   實例代碼(順序棧的進棧與出棧)

const int STACKSIZE = 5;
struct stackTag
{
	int data[STACKSIZE];
	int top;
};
class StackList
{
public:
	const bool push(stackTag* stack,int e)
	{
		if(stack->top == STACKSIZE - 1)
		{
			return false;
		}
		stack->top++;
		stack->data[stack->top] = e;
		return true;
	}
	const bool pop(stackTag* stack,int* e)
	{
		if(stack->top == - 1)
		{
			return false;
		}
		*e = stack->data[stack->top];
		stack->top--;
		return true;
	}
};

順序棧在一般情況下非常好用,但在定義的時候如果初始化空間太大,將會造成空間的浪費,如果空間太小,很可能出現存儲空間不夠的情況,這時可以考慮用兩棧共享內存的方式進行解決。

4.棧的鏈式存儲結構

棧的鏈式存儲結構簡稱爲鏈棧,鏈棧的存儲方式如下:

                                                                                        鏈棧存儲示例 

實例代碼:(鏈棧的進棧與出棧)

typedef struct stackNode
{
	int data;
	stackNode* next;
}*LinkStackPtr;

struct LinkStackTag
{
	LinkStackPtr top;
	int count;
};
class StackLink
{
public:
	const bool push(LinkStackTag* linkstack,int e)
	{
		LinkStackPtr s = (LinkStackPtr)malloc(sizeof(stackNode));
		s->data = e;
		s->next = linkstack->top;
		linkstack->top= s;
		linkstack->count++;
		return true;
	}
	const bool pop(LinkStackTag* linkstack,int* e)
	{
		LinkStackPtr p;
		*e = linkstack->top->data;
		p= linkstack->top;
		linkstack->top = linkstack->top->next;
		free(p);
		linkstack->count--;
		return true;
	}
};

像現在高級語言像java,C#,C++、Qt等裏面都封裝了自己的棧,我們可以很方便的去調用push和pop方法。

三.隊列(Queue)

    1.基礎知識

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

       隊列是一種先進先出(First In First Out)的線性表,允許插入的一端稱爲隊尾,允許刪除的一端稱爲隊頭。如圖所示:

                                                                                          隊列基本結構

     跟棧一樣,同樣都屬於線性表,隊列也有類似線性表的各種操作,不同的是插入數據只能在隊尾進行,刪除操作只能在隊頭進行。線性表有順序存儲和鏈式存儲兩種方式,同樣隊列也有這兩種存儲方式。

2.循環隊列  

  • 隊列順序存儲的不足:假設我們一個隊列有n個元素, 我們的入隊操作,其實就是在隊尾添加一個元素,不需要移動任何一個元素,因此時間複雜度爲O(1)。而在刪除操作時,元素的刪除操作是對對壘的隊頭元素進行操作,那也就意味着,我們刪除一個元素,隊列中 剩下的所有元素都要向前移動,以保證隊列的隊頭,也就是下標爲0的位置不爲空,此時時間複雜度爲O(n)。如圖所示:

                                                                                       隊列插入元素

                                                                                         隊列刪除元素

假設隊列裏只有一個元素,在進行刪除操作之後,隊頭和隊尾是同一元素導致操作變得繁瑣,爲了避免這種情況,我們引入了兩個指針,front指針指向隊頭元素,rear指針指向隊尾元素的下一個位置。這樣當front和rear相同時,此時隊列爲空隊列。

假設我們有一個隊列裏有五個元素,當我們刪除兩個元素之後,front指向第三個元素,而rear保持不變,這時繼續入隊ch插入元素,便會出現假溢出的現象,爲了解決這一問題,我們引入了循環隊列的概念。

爲了解決“假溢出”,我們在當隊列後面滿了的時候,就從頭開始,也就是頭尾相連的循環。把這種頭尾相連的順序存儲隊列稱爲循環隊列。如圖所示:

                                                                                           循環隊列 

3.鏈隊列

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

將隊頭指針指向鏈隊列的頭結點,而隊尾指針指向終端結點。如圖:

總體來說,在可以確定隊列長度最大值的情況下,我們使用循環隊列,在無法預估隊列的長度時,我們使用鏈隊列。

四.C++中的棧與隊列

1.棧(stack)

在C++中,棧(stack)基本的方法有:

  • push(): 向棧內壓入一個成員;
  • pop(): 從棧頂彈出一個成員;
  • empty(): 如果棧爲空返回true,否則返回false;
  • top(): 返回棧頂,但不刪除成員;
  • size(): 返回棧內元素的大小;

具體用法實例: 

#include <stdlib.h>
#include <stack>
#include <iostream>

using namespace std;

int _tmain(int argc, _TCHAR* argv[])
{
	stack<int> stackTest;
	for(int i = 0;i < 20;i++)
	{
		stackTest.push(i);      //壓棧
	}
	std::cout << "壓棧之後棧的大小:" << stackTest.size() << std::endl;
	std::cout << "************************************"<< std::endl;
	std::cout << "棧元素:"<< std::endl;
	while (!stackTest.empty())
	{
		std::cout <<  stackTest.top() << "  ";
		stackTest.pop();              //出棧
	}
	std::cout << std::endl << "出棧之後棧的大小:" << stackTest.size() << std::endl;
	system("pause");
	return 0;
}

輸出結果如下:

可以看出,棧按照先進後出的順序依次打印。

2.隊列(queue)

在C++中,隊列(queue)基本的方法有:

  •    front()      取隊首元素
  •    back()     取隊尾元素
  •    push()    入隊
  •     pop()    出隊
  •    size()    返回隊列的長度
  •   empty()  判斷隊列是否爲空,若爲空則返回1,否則返回0

具體用法代碼實例:

#include <stdlib.h>
#include <queue>
#include <iostream>

using namespace std;

int _tmain(int argc, _TCHAR* argv[])
{
	queue<int> queueTest;
	for(int i = 0;i < 20;i++)
	{
		queueTest.push(i);      //入隊
	}
	std::cout << "入隊之後隊列的大小:" << queueTest.size() << std::endl;    //隊列大小
	std::cout << "************************************"<< std::endl;       

	std::cout <<  "隊列首元素 :"  << queueTest.front() << std::endl;        //隊首元素
	std::cout <<  "隊列尾元素 :"  << queueTest.back() << std::endl;		//隊尾元素

	std::cout << "隊列元素:"<< std::endl;
	while (!queueTest.empty())
	{
		std::cout <<  queueTest.front() << "  ";
		queueTest.pop();              //出隊
	}
	std::cout << std::endl << "出隊之後隊的大小:" << queueTest.size() << std::endl;
	system("pause");
	return 0;
}

輸出結果:

 可以看出,隊列按照先進後出的順序依次打印。

 

【上一篇:】數據結構淺淺析之(一)——線性表(List):

https://blog.csdn.net/weixin_39951988/article/details/86477696

【下一篇:】數據結構淺淺析之(三)——樹(Tree)(上篇——基礎知識):

https://blog.csdn.net/weixin_39951988/article/details/86534298

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