存储数据的方式:一般情况下为 FIFO 先进先出的结构
类似食堂排队打饭,排在前面的先打饭
容量
队头标记
队尾标记
数组队列(普通队列+循环队列)
链式队列
优先队列(类似 vip 服务)
用一个数组充当容量
队头标记 & 队尾标记就是数组下标,可以把它们的初始值等于 -1 (保持下标的一致性)或者全部等于 0,无强制性要求,只是在放数据的时候下标的操作不太一样而已
弊端:一旦做了出队操作,就会存在伪溢出问题:入完数据做出队操作,相对于以往来说容量改变了
原因:做了一个出队操作后(队头往后挪了),队头对应的下标不再是从 0 开始(队头来到下标[1]的位置),下标[0]对应的内存无法使用,假设刚开始创建队列时的最大容量是 6 ,出队一个元素后,最大容量变成了 5
一次性队列,可以把数据都存满,但是不能重复去操作
通过取余 % 让队尾 tail 回到前面去,不存在伪溢出问题(循环队列的方式)
假设 max == 10,一旦队头变化(假设队头到达对应数组下标为1的位置),就存不了10个元素,如果用 curSize 判断满的状态会引发中断
#include
#include
#include
//抽象结构--->以整型数据为例
typedef struct Queue
{
int* qMem; //队列内存
int front; //队头标记
int tail; //队尾标记
int curSize; //当前元素个数
int maxSize; //最大容量
}QUEUE,*LPQUEUE;
//创建队列
LPQUEUE createQueue(int max)
{
//描述结构的最初状态--->用结构体指针表示一个队列
LPQUEUE queue = (LPQUEUE)malloc(sizeof(QUEUE)); //动态内存申请
assert(queue);
//给队列属性做初始化
queue->maxSize = max;
queue->qMem = (int*)malloc(sizeof(int) * max); //数组的内存二次申请
assert(queue->qMem);
queue->curSize = 0;
queue->front = 0;
queue->tail = 0;
return queue;
}
//大小
int size(LPQUEUE queue)
{
return queue->curSize;
}
//判断是否为空
int empty(LPQUEUE queue)
{
return queue->curSize == 0;
}
//入队 把元素放到队列中队尾做变化 入的队 入的数据
void push(LPQUEUE queue, int data)
{
//判断是否为满的状态 用数组 放进去必须考虑满
if (queue->curSize == queue->maxSize) //一旦队头变化放不了这么多数据 存在伪溢出问题
{
printf("满了!\n");
return;
}
queue->qMem[queue->tail++] = data; //把数据存到队列后队尾++
queue->curSize++;
}
//获取队头元素
int front(LPQUEUE queue)
{
return queue->qMem[queue->front];
}
//出队 队头往后走
void pop(LPQUEUE queue)
{
//出队要考虑是否为空的状态
if (queue->curSize == 0)
{
printf("队列为空,无法出队\n");
return;
}
queue->front++; //如果能够出队 队头往后走即可
queue->curSize--;
}
int main()
{
LPQUEUE queue = createQueue(10); //创建队列 假设存放10个元素
for (int i = 0; i < 10; i++)
{
push(queue, i); //入队
}
/*做了一次出队操作后 队头此时移动到1的位置会存在问题*/
printf("%d\t", front(queue)); //0
pop(queue);
push(queue, 100); //入队实际上超过了数组的容量
push(queue, 200); //满了!
while (!empty(queue)) //队列不为空出队
{
printf("%d\t", front(queue)); //获取队头元素
pop(queue); //出队
}
return 0; //0 1 2 3 4 5 6 7 8 9
}
/*输出 一旦做了出队操作不能重复使用*/
0 满了!
1 2 3 4 5 6 7 8 9 100 //数组下标越界访问 vs编译器测试时没有引发中断
为了避免普通队列做完一次出队后想要重复使用,导致伪溢出问题而存在的
数组下标不变,只是改变队列的 front 和 tail 的值去做数组的遍历
队列永远都是先进先出,虽然数组中的顺序不是按照插入的顺序,但是出来仍然按照插入的顺序:不会影响打印结果,打印仍旧是有序的,按照插入的顺序先插入的先出去 FIFO 先进先出
判断队列满的状态的两种方式
当队尾 == 队头 & 队头 != 0 就是满的状态(也有可能是空的状态 队头 == 0)
if(queue->front==(queue->tail+1)%Max)也可以判断满的状态
建议用 curSize 判断,否则满的状态不太好判断
#include
#include
#include
typedef struct Queue
{
int* qMem;
int front;
int tail;
int curSize;
int maxSize;
}QUEUE,*LPQUEUE;
//创建
LPQUEUE createQueue(int maxSize)
{
LPQUEUE queue = (LPQUEUE)malloc(sizeof(QUEUE));
assert(queue);
queue->maxSize = maxSize;
queue->qMem = (int*)malloc(sizeof(int) * maxSize);
queue->front = 0;
queue->tail = 0;
queue->curSize = 0;
return queue;
}
//大小
int size(LPQUEUE queue)
{
return queue->curSize;
}
//判断队列是否为空
int empty(LPQUEUE queue)
{
return queue->curSize == 0;
}
//入队
void push(LPQUEUE queue, int data)
{
if (queue->curSize == queue->maxSize)
{
printf("队列满了,无法入队!\n");
return;
}
queue->qMem[queue->tail] = data; //队尾存数据
queue->tail = (queue->tail + 1) % queue->maxSize; //让它满的时候出队时回到前面去
queue->curSize++;
}
//获取队头元素
int front(LPQUEUE queue)
{
return queue->qMem[queue->front];
}
//出队
void pop(LPQUEUE queue)
{
if (queue->curSize == 0)
{
printf("队列为空无法出队!\n");
return;
}
queue->front = (queue->front + 1) % queue->maxSize;
queue->curSize--;
}
int main()
{
LPQUEUE queue = createQueue(10);
for (int i = 0; i < 10; i++)
{
push(queue, i);
}
pop(queue); //0
push(queue, 100); //1-9-100
while (!empty(queue))
{
printf("%d\t", front(queue));
pop(queue);
}
return 0;
}
/*输出 能重复使用*/
1 2 3 4 5 6 7 8 9 100 //只能放10个数据
一般用无头链表记录头和尾的方式,不需要找表尾,因为 tailNode 永远是指向链表的表尾
入队:链表的尾插法
出队:链表的头删法
空的状态:队列的最初状态,没有节点,两个结构体指针都指向空
只有一个节点的情况(既是队头也是队尾),队头和队尾都是指向同一个节点
两个节点的情况:把原来表尾(原来表尾指向1)的 next指针 指向新节点,再把表尾移到新节点的位置即可
链表使用的是零散内存,无大小限制,相比数组队列不需要传maxSize
#include
#include
#include
//节点
typedef struct Node
{
int data; //数据域
struct Node* next; //指针域
}NODE,*LPNODE,*LIST;
//创建节点--->把用户的数据变成一个节点才能插到队列中去
LPNODE createNode(int data)
{
LPNODE newNode = (LPNODE)malloc(sizeof(NODE));
assert(newNode);
newNode->data = data;
newNode->next = NULL;
return newNode;
}
//描述链表
typedef struct Queue
{
int curSize; //大小
LPNODE frontNode; //头节点
LPNODE tailNode; //尾节点
}QUEUE,*LPQUEUE;
//创建队列
LPQUEUE createQueue()
{
LPQUEUE queue = (LPQUEUE)malloc(sizeof(QUEUE)); //用指针表示一个队列
assert(queue);
queue->curSize = 0;
queue->frontNode = NULL; //队列刚开始都指向空
queue->tailNode = NULL;
return queue;
}
//万金油
int size(LPQUEUE queue)
{
return queue->curSize;
}
int empty(LPQUEUE queue)
{
return queue->curSize == 0;
}
//入队 入的队 入的数据
void push(LPQUEUE queue, int data)
{
LPNODE newNode = createNode(data); //入队前需要把用户的数据变成一个节点
//无头链表尾部插入
if (queue->curSize == 0) //第1次插入 插入的链表要成为队头和队尾
{
queue->frontNode = newNode;
//queue->tailNode = newNode;
//queue->curSize++;
}
else //链表有数据
{
queue->tailNode->next = newNode; //把tailNode->next指向新节点
//queue->tailNode = newNode; //把tailNode移到新节点的位置
//queue->curSize++;
}
queue->tailNode = newNode; //相同代码放在外面
queue->curSize++;
}
//获取队头元素
int front(LPQUEUE queue)
{
return queue->frontNode->data;
}
//出队
void pop(LPQUEUE queue)
{
//链表头删法
if (queue == NULL || queue->curSize == 0)
{
printf("队列为空,无法出队!\n");
return;
}
struct Node* nextNode = queue->frontNode->next; //记录下一个节点
free(queue->frontNode); //释放队头
queue->frontNode = nextNode; //把队头移到下一个节点的位置
queue->curSize--;
}
int main()
{
LPQUEUE queue = createQueue(10);
for (int i = 0; i < 10; i++)
{
push(queue, i);
}
while (!empty(queue))
{
printf("%d\t", front(queue));
pop(queue);
}
return 0;
}
/*测试代码*/
0 1 2 3 4 5 6 7 8 9
优先队列是按照优先权(自己设定的规则)出队的
可以用来描述操作系统的调度过程
可以用数组 | 链表实现
常把比较准则 + 数据本身构成一个数据,数据按照比较准则(优先权)做比较
优先队列出队所有数据都是有序的,从小到大出队(找最小的) | 从大到小出队(找最大的)
#include
#include
#include
#include
#define MAX 100
//数据--->不会存在固定的比较准则 需要为数据构建一个比较准则
typedef struct Data
{
int priority; //比较准则 按比较准则来出队
char name[20]; //数据本身
}DATA,*LPDATA,DARR[MAX]; //定义1个结构体数组
//描述优先队列
typedef struct priQueue
{
int curSize;
DARR qMem; //数组
}PRIO,*LPPRIO;
//创建优先队列
LPPRIO createPriorityQueue()
{
LPPRIO queue = (LPPRIO)malloc(sizeof(PRIO)); //用指针表示一个优先队列做动态内存申请
assert(queue);
queue->curSize = 0;
//数组的初始化--->用memset给一段内存做初始化
memset(queue->qMem, 0, sizeof(DATA) * MAX); //把所有的内存初始化为0
return queue;
}
//万金油
int size(LPPRIO queue)
{
return queue->curSize;
}
int empty(LPPRIO queue)
{
return queue->curSize == 0;
}
//入队 和普通队列入队一样
void push(LPPRIO queue,DATA data)
{
if (queue->curSize == MAX)
{
printf("队列满了,无法入队!\n");
}
else
{
queue->qMem[queue->curSize++] = data; //直接把数据放到数组中即可
}
}
//出队 优先队列按照优先权去出队 找最大|最小的看你想要实现怎样的优先队列 这里按最小的开始出队
void pop(LPPRIO queue, DATA* data)
{
if (queue->curSize == 0)
{
printf("队列为空,无法出队!\n");
}
else
{
//最小原则--->描述贪心算法(堆也算一种队列 只是组成形式研究方法不同 用二叉树的形式)
DATA minData = queue->qMem[0]; //假设最小元素是第1个元素
int minIndex = 0; //最小下标是0
//数组查找
for (int i = 1; i < queue->curSize; i++)
{
//如果最小值的元素>队列里的其他元素
if (minData.priority > queue->qMem[i].priority)
{
minData = queue->qMem[i]; //队列的元素成为最小元素
minIndex = i; //对应下标成为最小下标
}
}
//退出循环--->找到最小值 //queue->qMem[minIndex]
*data = minData; //把数据丢出去Data*
//删除-->数组的伪删除 把后面的往前挪 覆盖掉出队的元素
for (int i = minIndex; i < queue->curSize; i++)
{
queue->qMem[i] = queue->qMem[i + 1]; //数组的移位操作
}
queue->curSize--; //curSize--
}
}
int main()
{
LPPRIO queue = createPriorityQueue(); //创建队列
//放入元素
DATA array[5] =
{
1,"张三",
9,"李四",
4,"小芳",
3,"小丽",
5,"王五",
};
for (int i = 0; i < 5; i++) //入队
{
push(queue, array[i]); //入的队 入的元素是数组
}
while (!empty(queue)) //出队
{
DATA temp; //定义传出参数
pop(queue, &temp); //传出
printf("%d:%s\n", temp.priority, temp.name); //打印传出参数 优先权 数据
}
return 0;
}
/*输出*/
1 张三
3 小丽
4 小芳
5 王五
9 李四 //按照优先权的顺序出队