1.队列定义
队列时一种受限制的线性表,只能再一端进行插入,再另外一端进行删除。
就像现实中的排队一样在新来的人排在对尾,拍在队伍前面的人先进行服务后离开,期间不允许插队。
简单介绍:
- 假如有一个队列,将A,B,C,D插入队列中,对头元素是A,对尾元素是D。也就是说第一个被删除的元素是A,最后一个被删除的元素是D。由此可见,队列具有一个特征,就是先入先出,后入后出。
- 队列需要两个指针,一个对头指针(front),通常指向队列第一个进来的元素,一个对尾指针(rear),通常指向对尾的最后一个元素,也就是最新插入进来的元素。
- 通常插入元素时时在对尾插入(尾插),需要对尾指针发生变化,删除元素,在对头删除(头删),需要对头指针发生变化。
2.队列表现形式
顺序表(数组)
队列较为简单的方法时数组实现,一般情况是对头放数组下标小的位置,对尾放在数组下标大的位置,刚开始时对头指针/对尾指针初始化为-1,当插入元素数时,rear向右移动一格(加1),放入对尾元素。当删除一个元素时,front向右移动一格(加1),删除对头元素。
随着入队和出队的操作不断进行,会是得队列整体往后偏移,致使队列出现了第三种情况。此时对尾指针已经移动到了数组最后,已经不能再插入元素,
然而对头指针前面还有许多空间可以插入,这样的现象称为假溢出现象。会导致空间的严重浪费。
于是为了解决这种情况,衍生出另外一种形式循环队列:当对尾指针和对头指针到达数组最后时,能够折回到数组开始位置。可以想象为队列的对尾相连。
而实现这一操作可以用“rear(front)%数组长度”取余运算公式实现。
循环队列很好的解决假溢出的现象,但是再循环队列中,我们发现用front和rear表示队列为空时,rear的值与front的值相等,表示队列满时,rear与front的值也相等,于是,如何表示队列满和空的情况呢?
方法一:队列定义时在增加一变量size来记录队列的长度,这样判断size与数组容量capacity可以直接判断队列是否是空还是满。
方法二:少用一个元素空间。如图(3)所示,此时的情况是rear指针加1的值就与front的值相等了,公式为“(rear+1)%capacity=front”。
我们采用方法二,代码在下面。
链表
链表实现注意的是front指针必须指向链表的头结点,rear指针必须指向链表的尾结点,不能颠倒。因为front指向头结点方便删除操作,如果指向尾结点,上一个结点的位置无法得到,不方便删除操作。
3.队列主要操作
- 生成空队列。初始化。
- 判断队列是否满。数组:(rear+1)%capacity=front。链表不需要判断。
- 入队。将元素插入对尾,并且让rear指向它。
- 判断对空。数组:rear=front。链表:即链表尾空,front,rear=NULL。
- 出队。在对尾删除元素,让front指向下一个元素。
4.队列的代码实现
数组:
结构:
typedef int Elementtype;//方便维护
typedef struct Quelist{
Elementtype *a;
int front;//对头指针
int end;//对尾指针
int capacity;//最大容量
}Que;
初始化:
void Quequeinit(Que *list)
{
list->a = (Elementtype *)malloc(sizeof(Elementtype)* 12);//为数组申请空间
list->capacity = 12;//最大容量
list->end = 0;//对尾对头初始化下标尾0
list->front = 0;
}
入队和判断满:
void Quelistpush(Que *list, int x)
{
assert(list);
if ((list->end + 1) % list->capacity == list->front){//判断对满
printf("队列已满\n");
exit(-1);
}
//list->end++;做到循环队列,不能是简单加1
list->end = (list->end + 1) % list->capacity;//循环
list->a[list->end] = x;
}
出队和判断对空
Elementtype Quelistpop(Que *list)
{
assert(list);
if (list->end == list->front){//对尾和对头相等
printf("队列为空\n");
exit(-1);
}
Elementtype ass = 0;
list->front = (list->front + 1) % list->capacity;//循环
ass = list->a[list->front];//元素就是front下一个的元素
return ass;
}
打印:验证
void Quelistprint(Que *list)
{
assert(list);
if (list->end == list->front){
printf("队列为空\n");
exit(-1);
}
for (int i = list->front; i < list->end; i++){
printf("%d ", list->a[i]);
}
printf("\n");
}
链表
结构:
typedef int Elementtype;
struct Datalist{
Elementtype data;
struct Datalist *next;
};
typedef struct Datalist* Dlist;
typedef struct Quelist{
Dlist front;//头指针用来存放头节点地址
Dlist end;//尾指针存放尾节点地址
}Que;
初始化:
也可以在主函数中定义两个datalist的指针变量front和rear,直接对front,rear置空,这时传入参数就得传入这两个变量的地址。
void Quequeinit(Que *list)
{
assert(list);
list->front= NULL;//直接置空
list->end = NULL;
}
入队:
这里要考虑两种情况:
1.当没有结点的时候,front,rear两指针存放的都是第一个结点的地址,
2.当有结点的时候,front指针内容不变,rear存放新结点地址。
static Dlist Nodelist(int x){//创建节点
Dlist list = (Dlist)malloc(sizeof(struct Datalist));
list->data = x;
list->next = NULL;
return list;
}
//要考虑没有元素情况
void Quelistpush(Que *list, int x)
{
assert(list);
//assert(node);
Dlist ass = Nodelist(x);//创建结点
if (list->end == NULL&&list->front == NULL){//没节点
list->front = ass;//是等于,实际front与end存放ass地址
list->end = ass;//不是用next指向ass
}
else{//有节点
list->end->next = ass;
list->end = ass;
}
}
出队和判断是否为空:
注意这里一开始要判定是否为空。
不为空时,也有两种情况:
1.只有一个结点。删除结点后头尾指针都置空。
2.不止一个结点:只需要变化front指针。
//要考虑只有有一个元素的情况
Elementtype Quelistpop(Que *list)
{
assert(list);
Elementtype ass = 0;
if (list->end == NULL&&list->front == NULL){//判断是否尾空
printf("Empty\n");
exit(-1);
}
else{//不为空
Dlist temp;
if (list->front->next == NULL){//只有一个结点
ass = list->front->data;
list->end = NULL;
list->front = NULL;
}
else{
temp = list->front;
ass = list->front->data;
list->front = list->front->next;
free(temp);
}
}
return ass;
}
打印结点:验证
void Quelistprint(Que *list)
{
assert(list);
Dlist temp = list->front;//要用临时变量代替front,不是代替list
if (list->end == NULL&&list->front == NULL){
printf("Empty!\n");
exit(-1);
}
else{
while (temp){
printf("%d ", temp->data);
temp = temp->next;
}
printf("\n");
}
}