一.寫在前面
- 棧(stack)是限定僅在表尾進行插入和刪除操作的線性表(List)。 棧先進後出,後進先出(Last In First OUt)
- 隊列(queue)是隻允許在一端進行插入操作,而在另一端進行刪除操作的線性表(List)。 隊列先進先出,後進後出(First In First Out)
二.棧(Stack)
- 基本概念
我們在進行棧操作時,將允許插入和刪除的一端我們稱之爲棧頂(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