数据结构浅浅析之(二)——栈和队列(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

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