数据结构学习之路————栈和队列

       栈和队列是两种重要的线性结构。从数据结构的角度看,栈和队列也是线性表,其特殊性在于栈和队列的基本操作是线性表基本操作的子集。,它们是操作受限的线性表,因此可称为限定性的数据结构。

1.栈

       栈是限定仅在表尾进行插入或删除操作的线性表。因此对栈来说,表尾端有其特殊含义,称为栈顶,表头端称为栈底。不含元素的空表称为空栈。栈顶实现元素的进出,栈的修改遵循后进先出的原则。因此,栈又称为后进先出(last in first out)的线性表(简称LIFO结构)。

1.1栈的表示和实现

       和线性表类似,栈也有两种存储表示方法。

       顺序栈,即栈的顺序存储结构是利用一组地址连续的存储单元依次存放自栈底到栈顶的数据元素,同时附设指针top指示zhan栈顶元素在顺序栈中的位置。通常习惯做法是以top=0表示空栈,鉴于c中数组下标以0开始,则用c作描述语言时,如此设定会带来很大的不便(不太清楚哪里不便??);另一方面,由于栈在使用的过程中所需最大空间的大小很难估计,因此,一般来说,在初始化设空栈是不应限定栈的最大容量。一个比较合理的做法是,先为栈分配一个基本容量,然后在应用的过程中,当栈的空间不够使用时za再逐段扩大。为此可设定两个常量:STACK_INIT_SIZE(存储空间初始分配量)和STACKINCREMENT(存储空间分配增量),并以下述类型说明作为顺序栈的定义。

typedef struct {
	int *base;//类型要看元素是什么类型
	int *top;
	int stacksize;
}Sqstack; //最大容量

top=base可作为栈空的标记。每当插入新元素时,指针top+1;删除时,指针top-1;因此,非空栈中的栈顶指针始终在栈顶元素的下一个位置上。

以下是顺序栈的基本操作的展示:

#include
#include 
#include 
#define STACK_INIT_SIZE 100
#define STACKICREAMENT 10
typedef struct {
	int *base;//类型要看元素是什么类型
	int *top;
	int stacksize;
}Sqstack;



//--------------基本操作算法----------
bool InitStack(Sqstack &s)
{
	//创建一个空栈
	s.base = new int;
	s.top = s.base;
	s.stacksize = STACK_INIT_SIZE;
	return 1;
}

bool GetTop(Sqstack &s, int &e) {
	//若栈不为空,则用e返回s的栈顶元素,并返回1;否则返回0
	if (s.top == s.base)return 0;
	e = *(s.top - 1);
	return 1;
}

bool Push(Sqstack &s, int e) {
	//插入元素e为新栈顶元素
	*s.top++ = e;//由于new为动态分配储存空间,故省略空间已满的情况,此处与malloc函数处理不同
	return 1;
	
}

bool pop(Sqstack &s, int &e) {
	//若栈不空,则删除s的栈顶元素,用e返回其值,并返回1;否则返回0
	if (s.top == s.base)return 0;
	e = *--s.top;
	return 1;

}
void Destory(Sqstack &s)
{
	Sqstack e;
	while (s.top != s.base) {
		e.top = s.top;
		--s.top;
		delete e.top;
	}
	delete s.base;   //此处会显示有断点,暂时找不出原因
}

int main()
{
	Sqstack s;
	int e;
	int e1;
	InitStack(s);
	Push(s, 1);
	Push(s, 2);
	if (GetTop(s, e)) { std::cout << e << std::endl;
	std::cin.get();
	std::cin.get();
	}
	if (pop(s, e1)) { std::cout << e1 << std::endl; }
	Destory(s);
	return 0;
}

栈的链式显示,由于栈的操作是线性操作的特例,易于实现,此处为简单讨论,不贴出代码,代码参考单链表的基本操作。

栈的操作即在单链表操作基础上,push则是在头结点前添加元素,pop则是删除头结点。

1.2 栈的应用及相关代码

1.2.1 数制转换

void conversion()
{
	Sqstack s;
	int e;
	int n;
	InitStack(s);
	std::cin >> n;
	while (n)
	{
		Push(s, n % 8);//转换为8进制
		n /= 8;
	}
	while (!StackEmpty(s)) {
		pop(s, e);
		std::cout << e << endl;
	}
}

1.2.2 括号匹配的检验

bool check_(Sqstack &s, Sqstack &s1,std::string str)
{

	int e;
	char exp;
	for (int i = 0; i < str.size(); i++)
	{
		switch (str[i])
		{
		case '[':Push(s, str[i]);
			exp = ']';
			Push(s1, exp); break;
		case ']':if (GetTop(s1, e)&&s1.top!=s1.base) exp = e;
				 else return 0;
			if (exp = str[i]) { pop(s, e); pop(s1, e); }
				 else return 0; break;
		case '(':Push(s, str[i]);
			exp = ')';
			Push(s1, exp); break;
		case ')':if (GetTop(s1, e) && s1.top != s1.base) exp = e;
				 else return 0;
			if (exp = str[i]) { pop(s, e); pop(s1, e); }
				 else return 0; break;
		case '{':Push(s, str[i]);
			exp = '}';
			Push(s1, exp); break;
		case '}':if (GetTop(s1, e) && s1.top != s1.base) exp = e;
				 else return 0;
			if (exp = str[i]) { pop(s, e); pop(s1, e); }
				 else return 0; break;
		}
	}
	if(s.top=s.base)
	return 1;
	else return 0;
	
}

1.2.3 迷宫求解

算法简单描述:

设定当前位置的初值为入口位置:
do{若当前位置可通,
则{将当前位置插入栈顶;
若该位置是出口位置,则结束;
否则切换当前位置的东邻方块为新的当前位置;
}
否则,
若栈不空且栈顶位置尚有其他方向未经探索,
则设定新的当前位置为沿顺时针方向旋转找到的栈顶位置的下一相邻方块;
若栈不空但栈顶位置的四周均不可通,
则{
删去栈顶位置;
若栈不空,则重新测试新的栈顶位置,
直至找到一个可通的相邻块或出栈至栈空}
}while(栈不空)
bool MazePath(MazeType maze, std::map start, std::map end)
{
	//若迷宫maze中存在从入口start到出口end的通道,则求得一条存放在栈中(从栈底到栈顶),并返回true;否则false
	Sqstack s;
	InitStack(s); std::map curpos = start;//设定当前位置为入口位置
	int curstep = 1;//探索第一步
	do {
		if (Pass(curpos)) {//当前位置可以通过,即是未曾走到过的通道块
			FootPrint(curpos);//留下足迹
			e = (curstep, curpos, 1);
			Push(s, e);//加入路径
			if (curpos == end)return 1;
			curpos = Next(curpos, 1);//下一位置是当前位置的东邻
			curstep++;//探索下一步
		}//if
		else {
			//位置不能通过
			if (!StackEmpty(s)) {
				pop(s, e);
				while (e.di == 4 && !StackEmpty(s)) {
					MarkPrint(e.seat); pop(s, e);//留下不能通过标记,并退回一步
				}//while
				if (e.di < 4) {
					e.di++; Push(s, e);//换下一个方向探索
					curpos = NextPos(e.seat, e.di);
				}
			}
		}
	} while (!StackEmpty(s));
}

以上贴出代码为伪代码。 

 

1.3 栈与递归实现

 在高级语言编制的程序中,调用函数与被调用函数之间的链接及信息交换需通过栈来进行。

 通常在一个函数运行期间调用另一个函数时,在运行被调用函数之前,系统需完成三件事:

       (1)将所有的实在参数,返回地址等信息传递给被调用函数保存。

       (2)为被调用函数的局部变量分配储存区。

       (3)将控制转移到被调函数入口

 从被调函数返回调用函数之前,系统完成三件工作:

        (1)保存被调函数计算结果

         (2)释放被调用函数的数据区

          (3)依照被调用函数保存的返回地址将控制转移到调用函数。

递归函数运行过程类似多个函数的嵌套调用,只是调用与被调用函数为同一个,因此,和每次调用相关的一个重要概念是递归函数运行的层次。

从0层开始调用则进入1层,从i层调用进入i+1层,返回则是返回上一层,即层级递减。为了保证递归函数的正确执行,系统需设立一个“递归工作栈”作为整个递归函数运行期间使用的数据存储区。每层递归所需信息构成一个“工作记录”,其中包括所有的实在参数、所有局部变量以及上一层的返回地址。每进入一层,则产生一个新的记录压入栈顶。没退出一层,则弹出一个。当前执行层的工作记录必须是递归栈顶的工作记录,称这个记录为“活动的记录”。

   2. 队列

队列与栈相反,遵循先进先出原则。允许插入的一端为队尾,允许删除的一端为队头。

2.1链队列----队列的链式表示和实现

一个链队列显然需要两个分别指示队头和队尾的指针才能唯一确定。为了操作方便,我们也给队列添加一个头结点,并令头指针指向头结点。由此,空的链队列的判决条件是头指针和尾指针均指向头结点

以下代码为其基本操作:

#include
#include 
#include 

typedef struct QNode {
	int data;
	struct QNode *next;
}QNode,*QueuePtr;
typedef struct {
	QueuePtr front;
	QueuePtr rear;
}LinkQueue;

//----------------------基本操作--------------------
bool InitQueue(LinkQueue &Q)
{
	//构造一个空队列Q
	Q.front =Q.rear= new QNode;
	Q.front->next = NULL;
	return 1;

}

bool DestoryQueue(LinkQueue &Q)
{
	//销毁队列Q
	while (Q.front) {
		Q.rear = Q.front->next;
		delete Q.front;
		Q.front = Q.rear;
	}//队头到队尾逐个删除
	return	1;
}

bool EnQueue(LinkQueue &Q,int e)
{
	//插入元素e为Q的新的队尾元素
	QueuePtr p = new QNode;
	p->data = e;
	p->next = NULL;
	Q.rear->next = p;//前队尾元素的next指向新队尾元素
	Q.rear = p;//尾指针指向新队尾元素
	return 1;
}

bool DeQueue(LinkQueue &Q, int &e)
{
	//若队列不为空,则删除Q的队头元素,并用e返回其值,并返回1
	if (Q.front == Q.rear)return 0;
	QueuePtr p = Q.front->next;
	e = p->data;
	Q.front->next = p->next;
	if (Q.rear == p)Q.rear = Q.front;//如果只有一个元素,则删除唯一的这个元素前头尾指针指向其next,即NULL
	delete p;
	return 1;
}

int main()
{
	LinkQueue Q;
	int e;
	if(InitQueue(Q)){};
	if (EnQueue(Q, 1)) {};
	if (EnQueue(Q, 2)) {};
	std::cout << Q.front->next->data <next->data << std::endl;
	std::cin.get();
	std::cin.get();
	DestoryQueue(Q);
	return 0;
}

2.2 循环队列

与顺序栈类似,在队列的顺序结构中,除了用一组地址连续的存储单元依次存放从队列头到队列尾的元素之外,尚需附设两个指针front和rear分贝指示队列头元素及队列尾元素的位置。

数据结构学习之路————栈和队列_第1张图片

用此方法表示循环队列,就无法用Q.front=Q.rear来判断队列空间是空还是满。可有两种处理方法:其一是另设一个标志位以区别队列是空还是满;其二是少用一个元素空间,约定以“队列头指针在队列尾指针的下一位置上”作为队列呈满状态的标志。

从上述分析可见,C语言中不能用动态分配的一维数组来实现循环队列。如果要用,就必须为它设定一个最大队列长度;若无法预估所用队列的最大长度,建议使用链队列。

循环队列的类型模块说明如下:

#include
#include 
#include 
#include
#define MAXSIZE 100 //最大队列长度
typedef struct {
	int *base;//初始化的动态分配储存空间,int可以换成元素的类型
	int front;
	int rear;        //非链式,应该是顺序存储
}SqQueue;
//---------------------循环队列的基本操作的算法描述

bool InitQueue(SqQueue &Q) {
	//构造一个空队列Q
	Q.base = (int *)malloc(MAXSIZE * sizeof(int));
	if (!Q.base)exit(OVERFLOW);
	Q.front = Q.rear=0;
	return 1;
}

int QueueLength(SqQueue &Q)
{
	//返回Q的元素个数,即队列长度
	return((Q.rear - Q.front + MAXSIZE) % MAXSIZE);
}

bool EnQueue(SqQueue &Q, int e)
{
	if ((Q.rear + 1) % MAXSIZE == Q.front)return 0;
	Q.base[Q.rear] = e;
	Q.rear = (Q.rear + 1) % MAXSIZE;
	return 1;
}

bool DeQueue(SqQueue &Q,int &e)
{
	//若队列不空,则删除Q的队头元素,用e返回其值
	if (Q.front == Q.rear)return 0;
	e = Q.base[Q.front];
	Q.front = (Q.front + 1) % MAXSIZE;//由于下标是动态的,可能对应+1就到了开头的情况
	return 1;
}

 

你可能感兴趣的:(数据结构学习之路————栈和队列)