重要的线性结构:栈和队列

栈和队列

栈和队列是两种重要的线性结构。
数据结构角度看,它们也是线性表,不过它们的基本操作是线性表操作的子集,所以属于限定性的线性表;从数据类型角度看,它们是和线性表不一样的抽象数据类型;在面向对象的程序设计中属于多型数据类型。

栈(Stack)

栈:后进先出的线性表(LIFO结构),限定仅在表尾插入或删除的线性表。表尾端是栈顶(top),表头端是栈底(bottom);不含元素的栈叫空栈。

栈的表示和实现

栈有两种存储结构的表示方法,一种是顺序栈(顺序存储结构),另一种是链栈(链式存储结构)。

顺序栈

顺序栈:利用一组地址连续的存储单元存放从栈底到栈顶的数据元素。结构体设置栈底指针(* base)、栈顶指针(*top)、当前可用最大容量(stacksize)3个变量。
base指针为NULL时,表示栈结构不存在。
top指针=base指针时,说明栈空。
每次新增元素,top指针加1,指向下一个位置。所以非空栈中,top指针始终在栈顶元素的下一个位置。
在这里插入图片描述

#include<stdio.h>
#include<stdlib.h>
#define STACK_INIT_SIZE 100//存储空间初始分配量 
#define STACKINCREMENT 10//分配增量
typedef int SElemType;
typedef int Status;
typedef struct{
	SElemType *base;
	SElemType *top;
	int stacksize;
}sqStack; 
Status InitStack(sqStack &s){
	s.base = (SElemType *)malloc(STACK_INIT_SIZE*sizeof(SElemType));
	if(!s.base){
		exit(1);
	}
	s.top=s.base;
	s.stacksize=STACK_INIT_SIZE;
		return 0;
}
Status GetTop(sqStack s,SElemType &e){//获取栈顶元素 
	if(s.top==s.base){
		return 1;
	}
	e=*(s.top-1);
	return 0;
}
Status Push(sqStack &s,SElemType e){//插入元素e为新的栈顶元素 

	if(s.top-s.base>=s.stacksize){//如果栈满,扩充空间 
		s.base = (SElemType *)realloc(s.base,(s.stacksize+STACKINCREMENT)*sizeof(SElemType));
		if(!s.base){
		exit(1);
		}
		s.top=s.base+s.stacksize; 
		s.stacksize+=STACKINCREMENT;
	}
	*s.top++=e;//赋值后栈顶指针+1 
	return 0;
}
Status Pop(sqStack &s){//删除栈顶元素 
	if(s.top==s.base){
		return 1;
	}
	s.top--;//栈顶指针-1,给e赋值 
}
void display(sqStack s){
	while(s.base<s.top){
		printf("%d ",*(s.base));
		s.base++;
	}
}
int main(){
	sqStack s;
	SElemType p;
	InitStack(s);
	Push(s,1);
	Push(s,2);
	Push(s,3);
	Push(s,4);
	GetTop(s,p);
	printf("%d\n",p);
	display(s);
	Pop(s);
	printf("\n");
	display(s);
	return 0;
} 
链式栈

采用链式存储结构的栈,如下图所示。
在这里插入图片描述

栈的应用示例

由于栈的后进先出的特性,栈可以在程序设计中广泛应用。

数制转换

数制转换:进制转换问题,比如十进制到二进制。
从N进制到d进制转换公式:N=(N div d)*d+ N mod d(div为取整运算,mod为取余运算)。
在这里插入图片描述

void conversion(){
	sqStack h;
	SElemType N;
	
	InitStack(h);
	scanf("%d",&N);
	while(N){
		 Push(h,N%8);
		 N=N/8;
	}
	display(h);
} 
括号匹配检验

在这里插入图片描述

void matching(sqStack &s,SElemType e){//括号匹配 
	SElemType q;
	GetTop(s,q);///获取栈顶元素 q
	
	if(s.base==NULL){//如果栈空,直接插入 
		if(e==')'||e==']'){
			printf("匹配表达式不正确"); 
			exit(1);
		}
		else if(e=='('||e=='['){
			Push(s,e);
			printf("插入了%c\n",e);
		}
	}
	else{
		if(e=='('||e=='['){
			Push(s,e);
			printf("插入了%c\n",e);
		}
		else if(e==')'){
			if(q=='('){
				Pop(s);//删除栈顶元素  
				printf("插入%c时,匹配并取出了%c\n",e,q);
			}else{
				printf("匹配表达式不正确"); 
				exit(1);
			}
		}
		else if(e==']'){
			if(q=='['){
				Pop(s); 
				printf("插入%c时,匹配并取出了%c\n",e,q);
			}else{
				printf("匹配表达式不正确"); 
				exit(1);
			}
		}
		else if(e=='#'){//#作为结束标志 
			if(s.top>s.base){
				printf("匹配表达式不正确"); 
			}
			else if(s.top=s.base){
				printf("匹配表达式正确");
			}
		}
	}
} 
int main(){
	sqStack p;
	InitStack(p);
	matching(p,'[');
	matching(p,'(');
	matching(p,'[');
	matching(p,']');
	matching(p,'[');
	matching(p,']');
	matching(p,')');
	matching(p,']');
	matching(p,'#');//输入#作为结束标志 
	return 0;
} 
行编辑程序

在这里插入图片描述

//为此,可设这个输人缓冲区为一个栈结构,每当从终端接受了1个字符之后先作如下
//判别:如果它既不是退格符也不是退行符,则将该字符压人栈顶;如果是一个退格符
//,则从栈顶删去一个字符;如果它是1个退行符,则将字符栈清为空栈。
void DestoryStack(sqStack &s){
	free(s.base);
    s.base = NULL;
    s.top = NULL;
    s.stacksize = 0;
}
void ClearStack(sqStack &s){
	while(s.top!=s.base){
	s.top--;
	}	
}
void LineEdit(){//行编辑 
	sqStack p;
	InitStack(p);
	char ch=getchar();
	while(ch!=EOF){
		while(ch!=EOF&&ch!='\n'){//EOF用ctrl+z表示在window中 
			switch(ch){
				case '#':Pop(p);break;
				case '@':ClearStack(p);break;
				default:Push(p,ch);break;
			}
			ch=getchar();
		}
		display(p);
			ClearStack(p);
		if(ch!=EOF){
			ch=getchar();
		}
	}
	DestoryStack(p);
}
int main(){
	LineEdit();
	return 0;
} 
迷宫求解

参考往期文章: 迷宫求解,用顺序栈结构实现.

表达式求值

参考往期文章: 表达式求值.

栈与递归的实现

栈还有一个重要的应用是在程序设计语言中实现递归,一个直接调用自己或通过一系列的调用语句间接调用自己的函数,就是递归函数。

递归函数

有很多数学函数都是通过递归定义的,比如阶乘函数、斐波那契数列、Ackerman函数。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
另外,二叉树、广义表等数据结构,由于结构本身的递归特性,它们也可以通过递归描述。
其外,某类问题,比如汉诺塔、八皇后问题使用递归求解比迭代更简单。

递归的应用:汉诺塔

(首先看下图描述)当n=1时,只需将圆盘从X移到Z即可;当n>1时,问题可拆解为:

  1. 设法将最底部圆盘上的n-1个圆盘移到Y,Z作辅助塔座。
  2. 将最底部圆盘移到Z
  3. 将n-1个圆盘从Y移到Z,X作辅助塔座。

其中第1步又可拆解为
4. 设法将第2底圆盘上的n-2个圆盘移到Z,Y作辅助塔座。
5. 将第2底圆盘移到Y
6. 将n-1个圆盘从Z移到Y,X作辅助塔座。

可以发现都具有相同特征属性,只是问题规模-1而已。
111111111

#include <stdio.h>
#include <windows.h>
void Hanoi(int n, char a,char b,char c);
void Move(int n, char a, char b);
int count;
int main()
{
    int n;
    printf("汉诺塔的层数:\n");
    scanf("%d",&n);
    Hanoi(n, 'A', 'B', 'C');
    Sleep(20000);
    return 0;
}
void Hanoi(int n, char a, char b, char c)		
{
    if (n==1)
    {
        Move(n, a, c);//只有一个圆盘时,直接从a挪到c 
    }
    else
    {
        Hanoi(n - 1, a, c, b);//将n-1个圆盘从a挪到b 
        Move(n, a, c);//将第n个圆盘挪到c 
        Hanoi(n - 1, b, a, c);//将n-1个圆盘从b挪到c 
    }
}
void Move(int n, char a, char b)
{
    count++;
    printf("第%d次移动 Move %d: Move from %c to %c !\n",count,n,a,b);
栈在递归中的作用

在高级语言中,调用函数与被调用函数之间的链接和信息交换是通过栈来进行的。递归函数也涉及多层函数的嵌套,只是调用函数和被调用函数是一个函数罢了。
一个函数调用另一个函数,在运行被调函数前,要:

  1. 将函数的实在参数、返回地址等信息传给被调函数保存。
  2. 为被调函数的局部变量分配空间。
  3. 将控制转移到被调函数入口。

被调函数运行完之后,在回到调用函数之前,要:

  1. 保存被调函数的计算结果。
  2. 释放之前分配的空间。
  3. 根据返回地址,将控制转到调用函数的相应位置。

因为“后调用(函数)先返回(值)”;系统将整个程序运行时所需的空间安排在一个栈中,每调用一个函数时,系统在栈底分配(push)一个存储区,存储信息。每返回一个函数,就释放(Pop)它的存储区。

比如汉诺塔示例,系统在程序运行时的分配:
在这里插入图片描述
在这里插入图片描述

队列(Queue)

队列:和栈相反,先进先出的线性表(FIFO结构),限定仅在一端插入另一端删除的线性表。允许插入的一端叫队尾(rear),允许删除的一端叫队头(front)
在这里插入图片描述
除了栈和队列以外。还有一种限定性的线性表叫双端队列,是限定插入和删除可以在表的两端进行的数据结构。

队列的表示和实现

队列有两种存储结构的表示方法,一种是循环队列(顺序存储结构),另一种是链队列(链式存储结构)。

链队列

链队列:用链表来表示,一个链队列显然需要2个分别指向队头和队尾的指针(头指针和尾指针)。
另外为了操作方便,加一个头结点,头指针指向头结点。
所以空队列时,头指针和尾指针都指向头结点。
在这里插入图片描述

#include<stdio.h>
#include<stdlib.h> 
typedef int Status;
typedef int QElemType;
typedef struct QNode{
	QElemType data;
	struct QNode *next;
}QNode,*QueuePtr;
typedef struct{
	QueuePtr front;//队头指针 
	QueuePtr rear;//队尾指针 
}LinkQueue;
Status InitQueue(LinkQueue &q){//构造一个空队列q
	q.front=q.rear=(QueuePtr)malloc(sizeof(QNode)); 
	if(!q.front){
		exit(0);
	}
	q.front->next=NULL;
	return 0; 
}
Status DestroyQueue(LinkQueue &q){//销毁队列q
	while(q.front){//因为先进先出 
		q.rear=q.front->next;
		free(q.front);//所以删除头指针指的结点 
		q.front=q.rear;
	}
	return 0; 
}
Status EnQueue(LinkQueue &q,QElemType e){//在队尾插入元素 
	QueuePtr p = (QueuePtr)malloc(sizeof(QNode)); 
	if(!p){
		exit(0);
	}
	p->data = e;
	p->next=NULL;
	
	q.rear->next = p;
	q.rear = p;
	return 0; 
}
Status DeQueue(LinkQueue &q,QElemType &e){//在队头删除元素 
	if(q.front == q.rear){
		return 1;
	}
	QueuePtr p=q.front->next;//p指向第一个结点(除头结点) 
	e=p->data;
	q.front->next=p->next;//头结点的下个结点是第二个结点 
	if(q.rear==p){//if p指向尾结点,删掉p后(尾结点后) 
		q.rear=q.front;// 尾指针要赋值,指向头指针恢复空队列状态 
	}
	free(p);
	return 0; 
}
void display(LinkQueue q){
	QueuePtr p=q.front->next;
	printf("队列:");
	while(p!=NULL){
		printf("%d ",p->data);
		p=p->next;
	}
} 
int main(){
	LinkQueue q;
	QElemType e;
	InitQueue(q);
	EnQueue(q,1);
	EnQueue(q,2);
	EnQueue(q,3);
	EnQueue(q,4);
	DeQueue(q,e);
	display(q);
	return 0;
} 
循环队列

顺序队列:采用顺序存储结构的队列,和链队列相似,需要front和rear这2个指针;初始化队列时,front=rear=0;增加元素时,尾指针+1;删除元素时,头指针+1。所以循环队列中,头指针front一直指向队列头元素,尾指针始终指向队列尾元素的下一位
在这里插入图片描述
所谓循环队列,我们可以看到上图图d中还有部分空间未利用,我们可以将上图臆想为一个如下图环状的空间。
在这里插入图片描述
由上图可知,队空和队满时都是q.front=q.rear无法区分。有2种方法,一种是另设一个变量区分队空和队满。另一种是少用一个元素的空间(最常用)。

#include<stdio.h>
#include<stdlib.h>
#define MAXQSIZE 100
typedef int Status;
typedef int QElemType;
typedef struct{
	QElemType *base;
	int front;//头指针,if队列不空,指向队列头元素 
	int rear;//尾指针,if队列不空,指向尾元素下一个位置 
}SqQueue;
Status InitQueue(SqQueue &q){//构造一个空队列q
	q.base=(QElemType *)malloc(MAXQSIZE*sizeof(QElemType));
	if(!q.base){
		exit(0);
	}
	q.front=q.rear=0;
	return 0;
}
int QueueLength(SqQueue q){
	return (q.rear-q.front+MAXQSIZE)%MAXQSIZE;
}
Status EnQueue(SqQueue &q,QElemType e){//在队尾插入元素 
	if((q.rear+1)%MAXQSIZE == q.front){
		return 1;
	}
	q.base[q.rear]=e;
	q.rear=(q.rear+1) % MAXQSIZE;
	return 0; 
}
Status DeQueue(SqQueue &q,QElemType &e){//在队头删除元素 
	if(q.front == q.rear){
		return 1;
	}
	e = q.base[q.front];
	q.front=(q.front+1) % MAXQSIZE;
	return 0; 
}
void display(SqQueue q){
	for(int i= q.front;i<q.rear;i++){
		printf("%d ",q.base[i]);
	}
} 
int main(){
	SqQueue q;
	QElemType e;
	InitQueue(q);
	EnQueue(q,1);
	EnQueue(q,2);
	EnQueue(q,3);
	EnQueue(q,4);
	DeQueue(q,e);
	display(q);
	return 0;
} 
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Carry_Cui

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值