考研数据结构之动态顺序队列(指针标记头尾)

本文详细讲解了队列的基本概念,包括队列的定义、操作及其顺序存储结构(含假溢出及循环队列解决方案)。通过结构体定义和代码示例,逐步展示了初始化、判空、进队、出队、获取队头值和销毁队列的过程。适合初学者理解队列数据结构的实战应用。
摘要由CSDN通过智能技术生成

提示:非知之艰,行之惟艰

队列其实也就是我们平时排队 我们只能从一端排队 接完水你就从从另外一端走了走了
请添加图片描述


前言

提示:这一篇我们就不适用思维导图来开始今天的闹剧了 今天我们直接开始讲解可能还更清晰一点需要思维导图的可以向我留言 尽管我也不会写


一、队列的基本概念

1、队列的定义

  • 是一种操作受限制的线性表,只允许在表的一端进行另外一端进行删除
  • 向队列中插入元素称为入队,删除元素称为出队 或者离队
  • 先进先出
  • 结构:
    队头(Front):允许删除的一端又称为队首,先取元素在++
    队尾(Rear):允许插入的一端,先插入在++
    空队列:不含任何元素的空表

2、队列基本操作

不要问为什么写接下来几个基本操作 因为考试的时候 尽量也写成这样的英文形式 给阅卷老师方便 阅卷老师才会给你方便

  • InitQueue(&Q):初始化队列 构建一个空队列Q
  • QueueEmpty(Q):判断队列是否为空若是队列为空 返回true 否则返回false
  • EnQueue(&Q,x) 若是栈未满 则加入使之称为新的那个队尾
  • DeQueue(&Q,&x) 出队 若是队列非空 删除队头元素,并用x 返回
  • GetHead(Q,&x):读队头元素 若是未空,则将队头元素赋值给x

3、队列的顺序存储结构

3.1、队列的顺序存储结构

  • 分配一块连续的存储单元存放队列中的元素 并附设两个指针
  • 队头指针front指向队头元素,队尾指针指向队尾指针的下一个位置(具体问题具体分析)
  • 基本操作
    初始状态(队空条件):Q.front== Q.rear== 0
    进队操作:队不满时:向增值到队尾元素,再将队尾指针加一
    出队操作:队不空时:先取队头元素,再将队头指针加一
    假溢出:“在顺序队列操作中,假溢出的现象为:当元素被插入到数组中下标最大的位置上之后,队列的空间就用尽了,尽管此时数组的低端还有空闲空间。 解决:将存储队列逻辑上的数组头尾相接,形成循环队列。”
假溢出:

要明白假溢出 我们就需要知道两点 1、无论是进栈或者是出栈 rear 与front 都是增加的, 而队列判满的方式是:rear=size;尽管我们当rear=size时可以设置成一个动态来不停的扩展空间 ,但是空间的申请与数据的拷贝都是费时的 解决假溢出最常见的方式也就是头尾相连搞成一个环的形式
请添加图片描述
这也就引出了循环队列 但是若是简单的将头和尾相连 这样队满的条件就似乎是front==reat 这似乎与循环队列的初始化一样
请添加图片描述
于是就提出了下图的解决方式 牺牲一个空间用来 Q.rear+1=Q.front时队列为满
请添加图片描述

3.2、循环队列

这里我们就不再一个程序中写了 ,这一篇主要写(无逻辑上循环,也就是可能会假溢出的队列)顺序队列,下一篇写循环队列

二、结构体定义

ps:(个人愚见)其实写下来,你就发现其实把结构体定义出来之后 
程序好像就不是特别难写了

既然是顺序队列 那自然也是分为静态顺序与动态顺序之分 定义结构体如下(顺便推荐一篇博文关于结构体定义的粗解)

//静态顺序队列
typename struct{
	ElemType data[MAX];
	//int size;这里也是可以不需要的  因为判满用的的是rear==MAX; 
	int front;//因为限定了  只能从一端插入一端出  所以需要多定义两个指针,指向这些位置。
	int rear;// 
} Queue;

//动态顺序队列
typename struct{
	ElemType *data;
	//int size;这里也是可以不需要的  因为判满用的的是rear==MAX; 当rear==MAX 我们选择扩容就可以了 
	int capacity;
	int front;
	int rear;
} Queue;
// 可不可以向栈一样定义成一个指针类型的呢 我认为同样是问题不大的

typename struct{
	ElmeType *data;
	int capacity;
	ElemType *front;//其实和上面使用int 就取元素的时候是不一样的 
	ElemTyPe *rear;
} Queue;

第一种我们就不写了最常见 我们就不写了,第二中只需要在第一种的基础上添加扩容语句就可以了,并且带有一个容量Capacity之后这样就可以解决假溢出的问题,我们也不写了,我们使用方式三几乎没有人写方式, 其实都是大差不差的

三、代码分块

本代码基于上一个节中的栈中可执行代码的改造,强烈建议先自己尝试改造一下再来看,,改造出来 改造请看这一篇中的可执行部分

1、打印队列

只要注意是从front开始就可以了,在栈中是从base开始的

void PrintQueue(Queue &Q){
	cout<<"此时队列中的元素是(左边是队头)";
	for(ElemType* i=Q.front;i!=Q.rear;i++){//注意看这里定义的是指针类型的,指针类型也是可以加的。
		cout<<*i<<" ";
	} 
	cout<<endl;
}

2、初始化

bool InitQueue(Queue &Q){
	//当然这里也可以将MAX设置成变量,这里就不写了 要是想知道可以参考动态顺序表那一篇  至于为什么申请的是MAX+1 看上面第二个图中最后满的时候rear在申请区间的上方 也就是越界了
	Q.base=(ElemType*)malloc((MAX+1)*sizeof(ElemType));
	//使用了malloc就需要判断是否空间申请成功
	if(Q.base==NULL){
		//printf("空间申请失败\n");
		exit(1); 
	} 
	Q.front=Q.base;
	Q.rear=Q.base;
	Q.capacity=MAX;
}

3、判空

注意条件就可以了 使用的是队列中是front==rear 栈中使用的是base == top为空

bool QueueEmpty(Queue Q){
	if(Q.front==Q.rear) return true;
	else return false;
}

4、进队

注意是否为满 以及数据是先填入后加位置(当然这个可能不同的有不同的规定,具体问题具体分析)。

//还有进队列就是要判断此时是否队列满
bool EnQueue(Queue &Q,ElemType &x){
	int choice;
	 if(Q.rear-Q.base==Q.capacity){
	 	//此时队列已经满    
	 	printf("队列已满请问是否知道暗号?\n");
		cin>>choice;
		switch(choice){
		case 1:{
			return false;
		}
		case 2:{ 
			if(0==Q.capacity){
				cout<<"没有空间还想入队列?"<<endl; 
				break;
			}
			Q.base=(ElemType*)realloc(Q.base,Q.capacity*2+1);
			Q.capacity=Q.capacity*2; 
			break;
		}
		default:break;
		}
	 } 
	*(Q.rear++)=x;//这里是先用再加 
	cout<<"进行进队列操作之后";PrintQueue(Q);
	return true; 
}

5、出队

如果你把队列想成平放置 ,因为都是往后移动弹出也是++ 但是却是先取值再++ 在第二个图中也有体现

//DeQueue为弹出操作 ,弹出之前需要判断是否已经为空  弹出不需要释放空间 
bool DeQueue(Queue &Q,ElemType &x){
	 if(Q.front==Q.rear) return false;
	 else{
	 	x=*(Q.front++); //这里依然是先用再加 
	 }
	 cout<<"进行出队列之后的操作是";PrintQueue(Q); 
	 return true; 
}

6、获取队头的值

要判空 以及不能改变front的指向

bool GetHead(Queue Q,ElemType &x){
	if(Q.front==Q.rear) return false;
	 else{
	 	x=*(Q.front); //注意这里不是减减,不能改变top的位置指向 
	 }
	 return true; 
}

7、销毁

释放以及重新赋值 释放之后你再改地址的值还是和没有释放一样 ,但是加上NULL 再访问就会异常跳出

bool DestoryQueue(Queue &Q){
	for(ElemType* i=Q.front;i!=Q.rear;i++){//注意看这里定义的是指针类型的,指针类型也是可以加的。
		free(i);
	} 
	free(Q.base);
	Q.base=NULL;
	Q.rear=NULL;
	Q.front=NULL;
	return true;
}

可执行代码汇总

// InitQueue(&Q):初始化队列 构建一个空队列Q
// QueueEmpty(Q):判断队列是否为空若是队列为空 返回true  否则返回false
// EnQueue(&Q,x) 若是队列未满 则加入使之称为新的那个队尾
// DeQueue(&Q,&x) 出队 若是队列非空 删除队头元素,并用x 返回
// GetHead(Q,&x):读队头元素 若是未空,则将队头元素赋值给x
#include<bits/stdc++.h>
#define ElemType int
#define OK 1
#define ERROR 0 
#define MAX 3 
using namespace std;
typedef struct Queue{
	ElemType* base;
	int capacity;//定义一个最大容量 ,判满不需要capacity 但是扩容需要用到 
	ElemType* front;
	ElemType* rear; 
}Queue;
void PrintQueue(Queue &Q){
	cout<<"此时队列中的元素是(左边是队头)";
	for(ElemType* i=Q.front;i!=Q.rear;i++){
		cout<<*i<<" ";
	} 
	cout<<endl;
}
bool InitQueue(Queue &Q){
	//当然这里也可以将MAX设置成变量,这里就不写了 要是想知道可以参考动态顺序表那一篇 
	Q.base=(ElemType*)malloc((MAX+1)*sizeof(ElemType));
	//使用了malloc就需要判断是否空间申请成功
	if(Q.base==NULL){
		//printf("空间申请失败\n");
		exit(1); 
	} 
	Q.front=Q.base;
	Q.rear=Q.base;
	Q.capacity=MAX;
}
bool QueueEmpty(Queue Q){
	if(Q.front==Q.rear) return true;
	else return false;
}
//还有进队列就是要判断此时是否队列满 
bool EnQueue(Queue &Q,ElemType &x){
	int choice;
	 if(Q.rear-Q.base==Q.capacity){
	 	//此时队列已经满    
	 	printf("队列已满请问是否知道暗号?\n");
		cin>>choice;
		switch(choice){
		case 1:{
			return false;
		}
		case 2:{ 
			if(0==Q.capacity){
				cout<<"没有空间还想入队列?"<<endl; 
				break;
			}
			Q.base=(ElemType*)realloc(Q.base,Q.capacity*2+1);
			Q.capacity=Q.capacity*2; 
			break;
		}
		default:break;
		}
	 } 
	*(Q.rear++)=x;//这里是先用再加 
	cout<<"进行进队列操作之后";PrintQueue(Q);
	return true; 
}
//DeQueue为弹出操作 ,弹出之前需要判断是否已经为空  弹出不需要释放空间 
bool DeQueue(Queue &Q,ElemType &x){
	 if(Q.front==Q.rear) return false;
	 else{
	 	x=*(Q.front++); //这里依然是先用再加 
	 }
	 cout<<"进行出队列之后的操作是";PrintQueue(Q); 
	 return true; 
}
bool GetHead(Queue Q,ElemType &x){
	if(Q.front==Q.rear) return false;
	 else{
	 	x=*(Q.front); //注意这里不是减减,不能改变topd的位置指向 
	 }
	 return true; 
}
bool DestoryQueue(Queue &Q){
	for(ElemType* i=Q.front;i!=Q.rear;i++){//注意看这里定义的是指针类型的,指针类型也是可以加的。
		free(i);
	} 
	free(Q.base);
	Q.base=NULL;
	Q.rear=NULL;
	Q.front=NULL;
	return true;
}
void menu(){
	cout<<"请选择你要进行的操作"<<endl;
	cout<<"1、判断队列是否为空 2、进队列 3、出队列 "<<endl;
	cout<<"4、获得队列顶的值 5、销毁队列  6、展示队列中的内容 7、退出"<<endl;
} 
int main(){
	Queue Q;ElemType x;
	InitQueue(Q);
	int choice;
	while(1){
		menu();	
		cin>>choice;
		if(7==choice) break;
		switch(choice){
			case 1:{
				if(QueueEmpty(Q)){
					cout<<"队列为空"<<endl;
				}
				else{
					cout<<"队列不为空"<<endl;
				} 
				break;
			}
			case 2:{
				cout<<"请输入你要入队列的值"<<endl; 
				cin>>x; 
				EnQueue(Q,x);
				break;
			}
			case 3:{
				DeQueue(Q,x);
				break;
			}
			case 4:{
				GetHead(Q,x);
				cout<<"此时队列顶元素是"<<x;
				break;
			}
			case 5:{
				DestoryQueue(Q);
				break;
			}
			case 6:{
				PrintQueue(Q);
				break;
			} 
			default:break;
		}
	} 
	return 0;	
}

总结其实这个代码难度不大可以自己修改一下尝试一下再来通过修改我这篇对比 下一篇我们写循环队列

总结

若是文章对你的提升由哪怕一点帮助的话 请答应我 不要吝啬你的点赞评论 你的鼓励对作者是一种莫大的鼓励转载请告知哪一部分我检查一下

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值