数据结构与算法之栈和队列基础——顺序队列、循环队列、链队列(C++)以及栈和队列的应用附解密QQ号

先进先出FIFO

这种先进先出(First In First Out, FIFO)的线性序列,称为“队列”。队列也是一种线性表,只不过它是操作受限的线性表,只能在两端操作:一端进,一端出。进的一端称为队尾(rear),出的一端称为队头(front)。队列可以用顺序存储,也可以用链式存储。

顺序队列的定义

队列的顺序存储采用一段连续的空间存储数据元素,并用两个整型变量记录队头和队尾元素的下标。
在这里插入图片描述

顺序队列的结构体定义(动态分配)

在这里插入图片描述
在这里插入图片描述

顺序队列的结构体定义(静态分配)

在这里插入图片描述
注意:队列只能在一端进、一端出,不允许在中间查找、取值、插入、删除等操作,先进先出是人为规定的,如果破坏此规则,就不是队列了。

算法图解

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
这时,虽然队列空间存满了,但是出现了一个大问题!当队满时,Q.front=Q.rear,这和队空的条件一模一样,无法区分到底是队空,还是队满。如何解决呢?

有两种办法:

一种办法是设置一个标志,标记队空和队满;

另一种办法是浪费一个空间,当队尾Q.rear的下一个位置Q.front时,就认为是队满
在这里插入图片描述
上述到达尾部又向前存储的队列称为循环队列,为了避免“假溢出”,顺序队列通常采用循环队列。

循环队列的定义

首先简述循环队列队空、队满的判定条件,以及入队、出队、队列元素个数计算等基本操作方法。

队空

无论队头和队尾在什么位置,只要Q.rear和Q.front指向同一个位置,就认为是队空。

循环队列队空的判定条件为:Q.front==Q.rear = 0;

队满

在此采用浪费一个空间的方法,当队尾Q.rear的下一个位置Q.front时,就认为是队满。但是Q.rear向后移动一个位置(Q.rear+1)后,很有可能超出了数组的最大下标,这时它的下一个位置应该为0
在这里插入图片描述
在图3-35中,队列的最大空间为Maxsize,当Q.rear=Maxsize-1时,Q.rear+1=Maxsize。而根据循环队列的规则,Q.rear的下一个位置为0才对,怎么才能变成0呢?

可以考虑取余运算,即(Q.rear+1)%Maxsize=0。而此时Q.front=0,即(Q.rear+1)%Maxsize=Q.front,此时为队满的临界状态。

因此,循环队列队满的判定条件为:(Q.rear+1)%Maxsize==Q.front。

入队

在这里插入图片描述
在这里插入图片描述

出队

在这里插入图片描述
在这里插入图片描述
注意:循环队列无论是入队还是出队,队尾、队头加1后都要取余运算,主要是为了处理临界状态。

队列元素个数计算

循环队列中到底存了多少个元素呢?循环队列中的内容实际上为Q.front~Q.rear-1这一区间的数据元素,但是不可以直接用两个下标相减得到。因为队列是循环的,所以存在两种情况。

1)Q.rear≥Q.front,如图3-39所示。该队列中元素个数为:Q.rear-Q.front=4-1=3。
在这里插入图片描述
2)Q.rear<Q.front
在这里插入图片描述
此时,Q.rear=4, Q.front=Maxsize-2, Q.rear-Q.front=6-Maxsize。但是我们可以看到循环队列中的元素实际上为6个,那怎么办呢?当两者之差为负数时,可以将差值加上Maxsize计算元素个数,即Q.rear-Q.front+Maxsize=6-Maxsize+Maxsize=6,元素个数为6。

因此,在计算元素个数时,可以分两种情况判断

  • 1)Q.rear≥Q.front:元素个数为Q.rear-Q.front。
  • 2)Q.rear<Q.front:元素个数为Q.rear-Q.front+Maxsize。

也可以采用取余的方法把两种情况巧妙地统一为一个语句。队列中元素个数为:(Q.rear-Q.front+Maxsize)%Maxsize。

小结

在这里插入图片描述
在这里插入图片描述

循环队列的基本操作

循环队列的基本操作包括初始化、入队、出队、取队头元素、求队列长度。

初始化

初始化循环队列时,首先分配一个大小为Maxsize的空间,然后令Q.front=Q.rear=0,即队头和队尾为0,队列为空。

bool InitQueue(SqQueue &Q)
{
    
    
	Q.base = new int[MAXSIZE];
	if(!Q.base)
	{
    
    
		return false;
	}
	Q.front = Q.rear = 0;
	return true;
}

入队

入队时,首先判断队列是否已满,如果已满,则入队失败;如果未满,则将新元素插入队尾,队尾后移一位。

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

出队

出队时,首先判断队列是否为空,如果队列为空,则出队失败;如果队列不空,则用变量保存队头元素,然后队头后移一位。

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

取队头元素

取队头元素时,只是把队头元素数据复制一份即可,并未改变队头位置,因此队列中的内容没有改变

int GetHead(SqQueue Q)
{
    
    
	if(Q.front != Q.rear)
	{
    
    
		return Q.base[Q.front];
	}
	return -1;
}

求队列的长度

通过前面的分析,我们已经知道循环队列中元素个数为:(Q.rear-Q.front+Maxsize)% Maxsize,循环队列中元素个数即为循环队列的长度。

int QueueLength(SqQueue Q)
{
    
    
	return (Q.rear - Q.front + MAXSIZE) % MAXSIZE;
} 

敲个代码随便玩儿(循环队列)

#include<bits/stdc++.h>
using namespace std;
#define MAXSIZE 10 

typedef int ElemType;
typedef struct SqQueue{
    
    
	ElemType *base;
	int front, rear;
}SqQueue;

bool InitQueue(SqQueue &Q)
{
    
    
	Q.base = new int[MAXSIZE];
	if(!Q.base)
	{
    
    
		return false;
	}
	Q.front = Q.rear = 0;
	return true;
}

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

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

int GetHead(SqQueue Q)
{
    
    
	if(Q.front != Q.rear)
	{
    
    
		return Q.base[Q.front];
	}
	return -1;
}

int QueueLength(SqQueue Q)
{
    
    
	return (Q.rear - Q.front + MAXSIZE) % MAXSIZE;
} 

int main()
{
    
    
	SqQueue sq;
	InitQueue(sq);
	for(int i = 0; i < 6; i++)
	{
    
    
		EnQueue(sq, i);
	}
	int tmp;
	DeQueue(sq, tmp);
	cout << "删除的结点为:";
	cout << tmp << endl;
	cout << "对头结点为:";
	cout << GetHead(sq) << endl;
	
	return 0;
} 

运行结果:
在这里插入图片描述

解密QQ号

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

#include<stdio.h>

int main()
{
    
    
	int q[102] = {
    
    0, 6, 3, 1, 7, 5, 8, 9, 2, 4}, head, tail;
	int i;
	head = 1;
	tail = 10;
	while(head < tail)
	{
    
    
		printf("%d ", q[head]);
		head++;
		q[tail] = q[head];
		tail++;
		head++;
	}
	getchar();
	return 0;
}

在这里插入图片描述

队列小程序

#include<stdio.h>

typedef struct queue{
    
    
	int data[100];
	int head;
	int tail;
}queue; 

int main()
{
    
    
	queue q;
	int i;
	q.head = 1;
	q.tail = 1;
	for(i = 1; i <= 9; i++)
	{
    
    
		scanf("%d", &q.data[q.tail++]);
	}
	
	while(q.head < q.tail)
	{
    
    
		printf("%d ", q.data[q.head++]);
		
		q.data[q.tail] = q.data[q.head];
		q.tail++;
		q.head++; 
	}
	getchar();
	return 0;
}

样例运行结果:
在这里插入图片描述

链队列

队列除了用顺序存储,也可以用链式存储。顺序队列和链队列如图所示
在这里插入图片描述
在这里插入图片描述
顺序队列是分配一段连续的空间,用两个整型下标front和rear分别指向队头和队尾。而链队列类似一个单链表,需要两个指针front和rear分别指向队头和队尾。从队头出队,从队尾入队,为了出队时删除元素方便,可以增加一个头节点。

注意:链队列需要头节点。

因为链队列就是一个单链表的形式,因此可以借助单链表的定义。

注意:链队列需要头节点。因为链队列就是一个单链表的形式,因此可以借助单链表的定义
在这里插入图片描述
链队列的结构体定义
在这里插入图片描述
链队列的操作和单链表一样,只不过它只能队头删除,在队尾插入,是操作受限的单链表。下面讲解链队列的初始化、入队,出队,取队头元素等操作(元素以int类型为例)。

1.初始化

链队列的初始化,即创建一个头节点,头指针和尾指针指向头节点
在这里插入图片描述

void InitQueue(LinkQueue &Q)
{
    
    
	Q.front = Q.rear = new Qnode;
	Q.front->next = NULL;
} 

2.入队

先创建一个新节点,将元素e存入该节点的数值域
在这里插入图片描述
在这里插入图片描述
然后将新节点插入队尾,尾指针后移
在这里插入图片描述
赋值解释
① Q.rear->next=s:把s节点的地址赋值给队列尾节点的next域,即尾节点的next指针指向s。
② Q.rear=s:把s节点的地址赋值给尾指针,即尾指针指向s,尾指针永远指向队尾。

void EnQueue(LinkQueue &Q, int e)
{
    
    
	Qptr s;
	s = new Qnode;
	s->data = e;
	s->next = NULL;
	Q.rear->next = s;
	Q.rear = s;
}

3.出队

出队相当于删除第一个数据元素,即将第一个数据元素节点跳过去。首先用p指针指向第一个数据节点,然后跳过该节点,即Q.front->next=p->next
在这里插入图片描述
若队列中只有一个元素,删除后需要修改队尾指针
在这里插入图片描述

bool DeQueue(LinkQueue &Q, int &e)
{
    
    
	Qptr p;
	if(Q.front == Q.rear)		//队空 
	{
    
    
		return false;
	}
	p = Q.front->next;
	e = p->data;
	Q.front->next = p->next;
	if(Q.rear == p)
	{
    
    
		Q.rear = Q.front;
	}
	delete p;
	return true;
}

4.取队头元素

队头实际上是Q.front->next指向的节点,即第一个数据节点,队头元素就是将该节点的数据域存储的元素
在这里插入图片描述

int GetHead(LinkQueue Q) 
{
    
    
	if(Q.front != Q.rear)
	{
    
    
		return Q.front->next->data;
	}
	return -1;
}

例子自己想去吧,我累了。。。

栈和队列的应用

数制的转换

题目:将一个十进制数n转换为二进制数。

解题思路

十进制数转换为二进制,可以采用辗转相除、取余数的方法得到。例如十进制数11转二进制。先求余数11%2=1,求商11/2=5,然后用商5再求余数,求商,直到商为0,结束。

11%2=1 11/2=5
5%2=1 5/2=2
2%2=0 2/2=1
1%2=1 1/2=0

先求出的余数是二进制数的低位,后求出的余数是二进制数的高位,将得到的余数逆序输出就是所要的二进制数,即11的二进制数为1011。如何将余数逆序输出呢?逆序输出正好符合栈的先入后出性质,因此可以借助栈来实现。

算法步骤

  • 1)初始化一个栈S。
  • 2)如果n! =0,将n%2入栈S,更新n=n/2。
  • 3)重复运行第2步,直到n=0为止。
  • 4)如果栈不空,弹出栈顶元素e,输出e,直到栈空。

算法图解

十进制数11转二进制的计算步骤如下:

  • 1)初始时,n=11;
  • 2)n%2=1,1入栈,更新n=11/2=5;
  • 3)n %2 =1,1入栈,更新n=5/2=2;
  • 4)n %2 =0,0入栈,更新n=2/2=1;
  • 5)n %2 =1,1入栈,更新n=1/2=0;
  • 6)n=0时,算法停止。

在这里插入图片描述
如果栈不空,则一直出栈
在这里插入图片描述
出栈结果正好是十进制数11转换的二进制数1011

#include<bits/stdc++.h>
using namespace std;

#define Maxsize 10

typedef int ElemType;
typedef struct SqStack{
    
    
	ElemType *base;
	ElemType *top;
//	int stacksize;
}SqStack;

bool InitStack(SqStack &S)
{
    
    
	S.base = new int[Maxsize];
	if(!S.base)
	{
    
    
		return false;
	}
	S.top = S.base;
//	S.stacksize = Maxsize;
	return true;
}

bool Push(SqStack &S, int e)
{
    
    
	if(S.top - S.base == Maxsize)
	{
    
    
		return false;
	}
	*S.top++ = e;		//等价于*S.top = e;S.top++; 
	return true;
}

bool Pop(SqStack &S, int &e)
{
    
    
	if(S.base == S.top)
	{
    
    
		return false;
	}
	e = *--S.top;
	return true; 
} 
 
int GetTop(SqStack S)
{
    
    
	if(S.top != S.base)
	{
    
    
		return *(S.top - 1);
	}
	else
	{
    
    
		return -1;
	}
} 


void Binaryconversion(int n)
{
    
    
	SqStack S;
	int e;
	InitStack(S);
	while(n)
	{
    
    
		Push(S, n % 2);
		n /= 2;
	} 
	while(S.top != S.base)
	{
    
    
		Pop(S, e);
		cout << e << '\t';
	}
} 
 
int main()
{
    
    
	SqStack sq;
	InitStack(sq);
//	for(int i = 0; i < 6; i++)
//	{
    
    
//		Push(sq, i);
//	}
//	int tmp;
//	Pop(sq, tmp);
//	cout << tmp << endl;
//	cout << GetTop(sq) << endl; 

	int n;
	cin >> n;
	Binaryconversion(n);
	return 0;
} 

在这里插入图片描述

之后我会持续更新,如果喜欢我的文章,请记得一键三连哦,点赞关注收藏,你的每一个赞每一份关注每一次收藏都将是我前进路上的无限动力 !!!↖(▔▽▔)↗感谢支持!

猜你喜欢

转载自blog.csdn.net/qq_44631615/article/details/117471368