队列是只允许在一端进行插入操作,而在另一端进行删除操作的线性表。它是一种先进先出的线性表,简称FIFO。允许插入的一端称为队尾,允许删除称为队头。
循环队列的提出主要是用于解决顺序队列的假溢出问题。解决假溢出的办法就是后面满了,再从头开始,头尾相接的循环。我们把队列的这种头尾相接的顺序存储结构称为循环队列。本文将从循环队列的顺序存储和链队列出发,讲解常用的出队与入队操作。下面先看下循环队列。
一、循环队列
1.顺序存储结构
private T[] datas;
private static int MAXSIZE = 10;
private int front; //头指针
private int rear; //尾指针,若队列不为空,指向队列尾元素的下一个位置
2.队空与队满判断
队空条件:front == rear;
队满条件:(rear+1)%MAXSIZE = front;
队列长度计算:(rear + MAXSIZE - front) % MAXSIZE;
3.入队操作
//初始化队列
private void InitQueue()
{
datas = new T[MAXSIZE];
front = 0;
rear = 0;
}
//入队操作
public void EnQueue(T value)
{
if (datas == null)
InitQueue();
//队列满判断
if ((rear + 1) % MAXSIZE == front)
return;
datas[rear] = value;
//rear指针后移一位置,若到最后则转到数组头部
rear = (rear + 1) % MAXSIZE;
}
4.出队操作
//出队操作
public T DeQueue()
{
T value = default(T);
//队空判断
if (front == rear)
return value;
value = datas[front];
//front指针向后移一位置,若到最后则转到数组头部
front = (front + 1) % MAXSIZE;
return value;
}
5.求队列长度
//求队列长度
public int QueueLength()
{
return (rear + MAXSIZE - front) % MAXSIZE;
}
6.测试
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace 循环队列_顺序存储
{
class Program
{
static void Main(string[] args)
{
int[] datas = new int[] { 10, 20, 30, 40, 50, 60, 70, 80 };
foreach (int temp in datas)
{
//入队
SqQueueTest<int>.Instance.EnQueue(temp);
}
//队列长度
int length = SqQueueTest<int>.Instance.QueueLength(),i=0;
while (i < length)
{
Console.Write(SqQueueTest<int>.Instance.DeQueue()+" ");
i++;
}
Console.WriteLine();
Console.ReadKey();
}
}
}
实验截图:
二、链队列
链队列,其本质是带头结点的单链表尾插法。
1.链式存储结构
结点类QNode.cs:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace 循环队列_链式存储
{
class QNode<T>
{
public T data;
public QNode<T> next;
public QNode()
{ }
public QNode(T value,QNode<T> nextNode)
{
this.data = value;
this.next = nextNode;
}
}
}
2.出队与入队操作示意图
2.1入队
由上图可知,入队分为两步,第一步,把新节点s赋值给原队尾结点的后继,即rear.next = s;第二步,把当前s设置为队尾结点,即rear = s;
2.2出队
此时,出队有两种情形。第一种,出队后,队尾不指向头结点;第二种,出队后,队尾指向头结点,此时删除后,需要将rear指向头结点如图中第三步。基本出队操作也分为两步:1.将要删除的对头结点暂存给临时结点,即tempNode = front.next,如图中第一步;2.将原队头结点后继front.next赋值给头结点的后继结点,即front.next = tempNode.next,如图中第二步;
3.链式队列实现类
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace 循环队列_链式存储
{
class LinkQueueTest<T>
{
private QNode<T> front; //指向头结点
private QNode<T> rear; //队尾指针
public int COUNT = 0;//队列中的元素个数
//单例模式
private static LinkQueueTest<T> _instance = null;
public static LinkQueueTest<T> Instance {
get {
if (_instance == null)
_instance = new LinkQueueTest<T>();
return _instance;
}
}
//初始化队列
public QNode<T> InitInitQueue()
{
if (front == null)
front = new QNode<T>();
rear = front;
front.next = rear;
return front;
}
//入队操作 与单链表尾插入类似
public void EnQueue(T value)
{
//队列不存在
if (front == null)
{
front = InitInitQueue();
}
//根据元素,实例化新的结点参数
QNode<T> newNode = new QNode<T>(value,null);
//将新结点插入到队列中
rear.next = newNode;
rear = newNode;
COUNT++;
}
//出队操作
public T DeQueue()
{
T value = default(T);
QNode<T> tempNode = new QNode<T>();
//队空判断
if (front == rear)
return value;
tempNode = front.next;
value = tempNode.data;
front.next = tempNode.next;
//若队头是队尾,则删除后将rear指向头结点
if (rear == tempNode)
rear = front;
COUNT--;
return value;
}
}
}
4.测试
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace 循环队列_链式存储
{
class Program
{
static void Main(string[] args)
{
int[] datas = new int[] { 10, 20, 30, 40, 50, 60, 70, 80 };
foreach (int temp in datas)
{
//入队
LinkQueueTest<int>.Instance.EnQueue(temp);
}
int count = LinkQueueTest<int>.Instance.COUNT,i = 0;
Console.WriteLine("队列中的元素的个数:"+ count);
//输出队列
while (i < count)
{
//出队
Console.Write(LinkQueueTest<int>.Instance.DeQueue()+" ");
i++;
}
Console.WriteLine();
Console.ReadKey();
}
}
}
实验截图:
三、循环队列与链队列的比较
从时间上,二者的基本操作都是O(1)。但循环队列是事先申请好空间,使用期间不会被GC机制回收,而链队列,每次申请和GC机制回收结点会存在一些时间开销,如果入队出队频繁,二者还是有细微差异。
从空间上,循环队列必须要有一个固定的长度,即MAXSIZE,所以就会存在存储元素个数和空间浪费的问题。而链队列不存在这个问题,尽管它需要一个指针域,会产生一些空间上的开销,但影响不大,因此,空间上,链队列更加灵活。
四、栈和队列的比较
它们均可以用线性表的顺序存储结构来实现,但都存在着顺序存储的一些弊端,但都利用各自的技巧来解决这个问题。
对于栈来说,如果是两个相同数据类型的栈,则可以用数组的两端作栈底的方法来让栈共享数据,这就可以最大化利用数组的空间。对于队列来说,为了避免数组插入和删除时需要移动数据,于是就引入了循环队列,使得对头和队尾可以在数组中循环变化。解决了移动数据的时间损耗,使得本来插入和删除是O(n)的时间复杂度变成了O(1)。它们也都可以通过链式存储结构来实现,实现原则上与线性表基本相同。
以上就是今天队列的介绍,如有疑问,欢迎私信我!!!