队列:也是一种受到限制的线性表,一端进行入队,另一端进行出队,我们将入队的一端一般称作队尾,另一端称作队头。
特点:先进先出。
那么顺序表实现的队列为什么叫做循环队列?
关于循环队列队列的三个难点:
第一个难点:怎么让队列也可以实现入队和出队的时间复杂度都是O(1)?
最开始这是由顺序表实现的一个队列,里面的数据为1到5。
我们将6入队,会发现入队的时间复杂度为O(1),只需将6放在5号下标的格子位置。
如果将1出队,那么我们需要将0号下标位置里的1删除,还要将后边的数据依次向前移一个格子,这个时候时间复杂度为O(n),因为需要移动数据。
那应该怎么办呢?这个时候就需要一个队头指针和一个队尾指针,队尾指针指向的是下一个存放数据的位置。
解决办法:想象成环形,让数据不动,指针动。
比如将1出队,我们只需要将0号下标位置里的元素删除,然后再将队头指针指向1号下标的位置。
同时我们还要将它想象成一个环状,如果2到9号下标都有元素,0号和1号下标位置为空,此时,队头指针在2号下标位置,但该队列并没有满(因为0号和1号下标),我们把它想象成环状,这个时候就可以将队尾指针指向0号下标,又可以入队。
注意:这个循环只是一个概念,真是并不存在!
第二个难点:在第一个解决问题的基础上,会发现一个BUG,判空和判满条件一致(队头指针==队尾指针),怎么将其区分开?
队列为空时队头指针和队尾指针都指向同一个位置,队列满的时候也是相同情况(队头指针==队尾指针)
这里给出两种解决方案:
第一种:加上一个计数器cout(保存有效值个数)
队头指针==队尾指针&&cout==0(判空)
队头指针==队尾指针&&cout!=0(判满)
第二种:在队尾处:浪费一个空间不用,这个浪费的空间不在存储有效值,而是当做一个标记位
一般来说我们用第二种办法,这里的代码也是第二种办法。
第三个难点:怎么求有效长度?
这里记住公式:length=(rear-front+MAX_SIZE)%MAX_SIZE
rear(队尾指针),front(队头指针),MAX_SIZE(可以理解为格子数),此指针非彼指针。
循环队列结构体设计:
// 循环队列结构体设计:
typedef int ELEM_TYPE;
typedef struct Queue
{
ELEM_TYPE* data;//这个指针用来接收malloc从堆里申请的数组
int front;//队头指针(实际上保存的是数组下标,int)
int rear;//队尾指针 (实际上保存的是数组下标,int)
}Queue,*PQueue;
循环队列可执行函数声明:
//初始化
void Init_queue(PQueue pq);
//入队
bool Push(PQueue pq, ELEM_TYPE val);
//出队(还要获取出队的值,需要借助一个输出参数rtval)
bool Pop(PQueue pq, ELEM_TYPE* rtval);
//获取队头值
bool Top(PQueue pq, ELEM_TYPE* rtval);
//判空
bool IsEmpty(PQueue pq);
//判满
bool IsFull(PQueue pq);
//获取有效长度
int Get_length(PQueue pq);
//查找
int Search(PQueue pq, ELEM_TYPE val);
//清空
void Clear(PQueue pq);
//销毁
void Destroy(PQueue pq);
//打印
void Show(PQueue pq);
代码:
先设立一个宏
#define MAX_SIZE 100
初始化:
//初始化
void Init_queue(PQueue pq)
{
assert(pq!= NULL);
pq->data = (ELEM_TYPE*)malloc(sizeof(ELEM_TYPE) * MAX_SIZE);
assert(pq->data != NULL);
pq->front = pq->rear = 0;
}
入队:
bool Push(PQueue pq, ELEM_TYPE val)
{
assert(pq != NULL);
if (IsFull(pq))//判满
{
return false;
}
pq->data[pq->rear] = val;
//pq->rear++;//error
pq->rear = (pq->rear + 1) % MAX_SIZE;
return true;
}
注意,这里的队尾指针不可以通过++方式向后移,而是pq->rear = (pq->rear + 1) % MAX_SIZE;
出队:
//出队(还要获取出队的值,需要借助一个输出参数rtval)
bool Pop(PQueue pq, ELEM_TYPE* rtval)
{
assert(pq != NULL);
assert(rtval != NULL);
if (IsEmpty(pq))//判空,如果空,返回假
{
return false;
}
*rtval = pq->data[pq->front];
//pq->front++;//error
pq->front = (pq->front + 1) % MAX_SIZE;
return true;
}
获取队头值
//获取队头值
bool Top(PQueue pq, ELEM_TYPE* rtval)
{
assert(pq != NULL);
assert(rtval != NULL);
*rtval = pq->data[pq->front];
return true;
}
判空,判满,获取有效长度,查找
//判空
bool IsEmpty(PQueue pq)
{
assert(pq != NULL);
return pq->rear == pq->front;
}
//判满
bool IsFull(PQueue pq)
{
assert(pq != NULL);
return pq->front == (pq->rear + 1) % MAX_SIZE;
}
//获取有效长度
int Get_length(PQueue pq)
{
assert(pq != NULL);
return (pq->rear - pq->front + MAX_SIZE) % MAX_SIZE;
}
//查找
int Search(PQueue pq, ELEM_TYPE val)
{
assert(pq != NULL);
for (int i = pq->front; i != pq->rear; i = (i + 1) % MAX_SIZE)
{
if (pq->data[i] == val)
{
return i;
}
}
return -1;
}
清空,销毁,打印
//清空
void Clear(PQueue pq)
{
assert(pq != NULL);
pq->front = pq->rear = 0;
}
//销毁
void Destroy(PQueue pq)
{
assert(pq != NULL);
free(pq->data);
pq->data = NULL;
pq->front = pq->rear = 0;
}
//打印
void Show(PQueue pq)
{
assert(pq != NULL);
for (int i = pq->front; i != pq->rear; i=(i + 1) % MAX_SIZE)
{
printf("%d ", pq->data[i]);
}
printf("\n");
}
测试:
int main()
{
Queue head;//建立一个循环队列
Init_queue(&head);//初始化
for(int i=0; i<20; i++)//将1到20进队
{
Push(&head, i+1);
}
Show(&head);//打印
ELEM_TYPE tmp;
Pop(&head, &tmp);//出队
printf("pop = %d\n", tmp);//打印出队的值
Show(&head);//打印出队后的队列
printf("length = %d\n", Get_length(&head));//打印队列有效长度
ELEM_TYPE flag;
Top(&head, &flag);//获取队头元素
printf("top = %d\n", flag);//打印队头元素
Show(&head);//打印队列,没有出队
printf("length = %d\n", Get_length(&head));//打印有效长度
Clear(&head);//清空
Show(&head);//打印
printf("length = %d\n", Get_length(&head));//打印有效长度
Destroy(&head);//销毁
return 0;
}
结果:没有内存泄漏!