由于看到P1629 邮递员送信这题,就去学了优先队列.为学习Dijkstra算法做准备
什么是优先队列
优先队列:是一种特殊类型的队列,每个元素都有一个相关的优先级。在优先队列中,元素按照优先级的顺序进行排列,具有最高(或最低)优先级的元素会最先被删除或访问。
想实现优先队列可以先去学习堆排序,优先队列利用的就是堆排序这个原理,所以下面先介绍一下堆排序
什么是堆排序
堆排序是一种基于二叉堆数据结构的排序算法,它的主要思想是利用堆这种数据结构的特性来进行排序。
堆是一个完全二叉树,同时具有最大堆(左图)和最小堆(右图)两种形式。在最大堆中,每个父节点的值都比其子节点的值大;在最小堆中,每个父节点的值都比其子节点的值小。
堆排序的基本步骤如下:
- 构建最大堆(或最小堆):将待排序的数组看作是一个完全二叉树的结构,并通过从最后一个非叶子节点开始,逐个向前调整节点的位置,使得每个父节点都大于(或小于)其子节点。
- 交换堆顶元素和最后一个元素:将堆顶元素(即最大值或最小值)与数组的最后一个元素进行交换。
- 调整堆:将剩余的 n-1 个元素重新构建成一个堆,再次找出最大(或最小)值,然后与当前堆的最后一个元素交换位置。
- 重复步骤 2 和步骤 3,直到排序完成。 建议不会的朋友可以去看看b站up主"请叫我AXing".
堆排序的实现
#include <stdio.h>// 交换两个元素的值
void swap(int* a, int* b) {int temp = *a;*a = *b;*b = temp;
}// 调整堆 i表示要维护的结点 n表示数组的长度
void heapify(int arr[], int n, int i) {int largest = i; // 初始化最大元素为根节点int left = 2 * i + 1; // 左子节点int right = 2 * i + 2; // 右子节点// 如果左子节点比根节点大,则更新最大元素的索引if (left < n && arr[left] > arr[largest])largest = left;// 如果右子节点比当前最大元素大,则更新最大元素的索引if (right < n && arr[right] > arr[largest])largest = right;// 如果最大元素的索引不是根节点,交换根节点和最大元素if (largest != i) {swap(&arr[i], &arr[largest]);// 继续递归调整堆heapify(arr, n, largest); //继续对堆的调整}
}// 堆排序函数
void heapSort(int arr[], int n) {// 构建最大堆(初始时假设数组已经是一个完全二叉树)for (int i = n / 2 - 1; i >= 0; i--)heapify(arr, n, i);// 重复交换堆顶元素和最后一个元素,并调整堆for (int i = n - 1; i > 0; i--) {swap(&arr[0], &arr[i]);heapify(arr, i, 0);}
}// 打印数组元素
void printArray(int arr[], int n) {for (int i = 0; i < n; i++)printf("%d ", arr[i]);printf("\n");
}// 测试示例
int main() {int arr[] = {12, 11, 13, 5, 6, 7};int n = sizeof(arr) / sizeof(arr[0]);printf("原始数组:\n");printArray(arr, n);heapSort(arr, n);printf("排序后的数组:\n");printArray(arr, n);return 0;
}
回到优先队列
在学会堆排序后,再看优先队列的代码,你会发现就是基本的队列加上了堆排序
代码实现
#include <stdio.h>
#include <stdlib.h>#define MAX_SIZE 100typedef struct {int priority; //优先级int value;
} Element;typedef struct {Element elements[MAX_SIZE];int size;
} PriorityQueue;// 初始化优先队列
void initQueue(PriorityQueue* queue) {queue->size = 0;
}// 判断优先队列是否为空
int isEmpty(PriorityQueue* queue) {return (queue->size == 0);
}// 判断优先队列是否已满
int isFull(PriorityQueue* queue) {return (queue->size == MAX_SIZE);
}// 插入元素到优先队列
void enqueue(PriorityQueue* queue, int priority, int value) {if (isFull(queue)) { // 如果队列已满,则提示错误并返回printf("Priority Queue is full.\n");return;}Element newElement;newElement.priority = priority; // 赋值优先级newElement.value = value; // 赋值元素值// 将元素插入到队尾queue->elements[queue->size] = newElement;queue->size++;// 调整堆使得符合堆的性质int i = queue->size - 1; // 取新元素的下标while (i > 0 && queue->elements[i].priority < queue->elements[(i - 1) / 2].priority) {// 如果当前节点的优先级比父节点的优先级小,则交换当前节点和父节点的位置Element temp = queue->elements[i];queue->elements[i] = queue->elements[(i - 1) / 2];queue->elements[(i - 1) / 2] = temp;i = (i - 1) / 2; // 更新当前节点的下标 去到父结点}
}// 删除优先队列中具有最高优先级的元素
void dequeue(PriorityQueue* queue) {if (isEmpty(queue)) { // 如果队列为空,则提示错误并返回printf("Priority Queue is empty.\n");return;}// 将队尾元素移到队首queue->elements[0] = queue->elements[queue->size - 1];queue->size--;// 调整堆使得符合堆的性质int i = 0; // 从根节点开始调整while (1) {int leftChild = 2 * i + 1; // 左子节点的下标int rightChild = 2 * i + 2; // 右子节点的下标int smallest = i; // 最小值的下标,默认为当前节点的下标if (leftChild < queue->size && queue->elements[leftChild].priority < queue->elements[smallest].priority) {// 如果左子节点的优先级比当前节点的优先级小,则更新最小值的下标为左子节点的下标smallest = leftChild;}if (rightChild < queue->size && queue->elements[rightChild].priority < queue->elements[smallest].priority) {// 如果右子节点的优先级比当前节点的优先级小,则更新最小值的下标为右子节点的下标smallest = rightChild;}if (smallest != i) { // 如果最小值的下标不等于当前节点的下标,则交换当前节点和最小值的位置Element temp = queue->elements[i];queue->elements[i] = queue->elements[smallest];queue->elements[smallest] = temp;i = smallest; // 更新当前节点的下标}else { // 如果最小值的下标等于当前节点的下标,则调整完成,跳出循环break;}}
}int main() {PriorityQueue queue;initQueue(&queue); // 初始化队列// 插入测试数据enqueue(&queue, 2, 20);enqueue(&queue, 1, 10);enqueue(&queue, 3, 30);// 输出队首元素值printf("Front element: %d\n", queue.elements[0].value);// 删除队首元素dequeue(&queue);// 输出队首元素值printf("Front element: %d\n", queue.elements[0].value);return 0;
}