一.写在前面
- 栈(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