队列即为只允许在一端进行插入数据队列,在另一端进行删除数据操作的特殊线性表。但也有一些例外,如优先级队列。
队列具有先进先出的特性,即最先进入队列的元素将被最先出队列
有时也需要把进入队列中的元素分优先级(比如线程调度),出队列时首先选择优先级最高的元素出队列,对于优先级相同的元素则按照先进先出的原则出队列
顺序队列很容易实现,主要是控制好头和尾,通常用循环的方式实现,避免溢出的问题。基本操作同栈类似,具体注意的点我就在代码中细说了
- 结构体声明
typedef struct TreeNode* SeqQueueType;
//head作为队首,tail作为队尾
//插入数据时,tail向后移,从队列里删除数据时,head往后移
//数据出队列时其实并没有真实的被删除,而是随着head的后移,之前的队首元素不在有效范围内了
//tail到了数组末尾时,检查一下head是否表示的是数组(不是队列)头部,如果不是的话说明数组的头部还可以插入数据
//作为队列的尾部。直到size等于数组的最大容量时,才说明队列已经满了
typedef struct SeqQueue{
SeqQueueType data[SeqQueueMaxSize];
size_t head;
size_t tail;
size_t size;
}SeqQueue;
- 基本操作函数声明
//初始化
void SeqQueueInit(SeqQueue* seq);
//入队列
void SeqQueuePush(SeqQueue* seq,SeqQueueType value);
//出队列
void SeqQueuePop(SeqQueue* seq);
//取队首元素
int SeqQueueGetTop(SeqQueue* seq,SeqQueueType* value);
//销毁队列
void SeqQueueDestroy(SeqQueue* seq);
注意,虽然我们实现顺序队列没有在堆上开辟空间,但是我们依然需要一个销毁函数,不光是为了代码工整,更多的是一个提示作用,在一个较大的项目中,如果每个部分都有明确的销毁以及初始化体现,那么就会大大提高代码的可读性,不至于还要区分半天,这也是种编程习惯,即使在这种小工作中也尽量要求自己保持一个好的编程习惯,不要偷工减料
初始化和销毁(即使代码一模一样,也要区分开来写)
void SeqQueueInit(SeqQueue* seq)
{
if(seq==NULL)
{
//非法输入
return;
}
seq->head = 0;
seq->tail = 0;
seq->size = 0;
}
void SeqQueueDestroy(SeqQueue* seq)
{
if(seq==NULL)
{
//非法输入
return;
}
seq->head = 0;
seq->tail = 0;
seq->size = 0;
}
入队列出队列
void SeqQueuePush(SeqQueue* seq,SeqQueueType value)
{
if(seq==NULL)
{
//非法输入
return;
}
if(seq->size == SeqQueueMaxSize)
{
//队列已满
return;
}
if(seq->tail==SeqQueueMaxSize&&seq->size<SeqQueueMaxSize)
//此时tail已经到了数组末尾,而size还没有达到最大值,说明
//head前有空出的位置,所以将tail赋值0,直到size达到最大值,才说明队列已经满了
{
seq->tail = 0;
}
seq->data[seq->tail++] = value;
seq->size++;
}
void SeqQueuePop(SeqQueue* seq)
{
if(seq==NULL)
{
//非法输入
return;
}
if(seq->size == 0)
{
//空队列
return;
}
if(seq->head == SeqQueueMaxSize)
{
seq->head = 0;
}
else
{
seq->head++;
}
seq->size--;
}
取队首元素
int SeqQueueGetTop(SeqQueue* seq,SeqQueueType* value)
{
if(seq==NULL)
{
//非法输入
return 0;
}
if(seq->size==0)
{
//空队列
return 0;
}
*value = seq->data[seq->head];
return 1;
}
这里有链式栈队列实现入口->链式队列实现