堆在优先队列中的应用
发布时间: 2024-05-02 06:11:59 阅读量: 66 订阅数: 29
![堆在优先队列中的应用](https://img-blog.csdnimg.cn/17071c530b3e4e62bed9d8f2f4b8d31e.png)
# 2.1 优先队列的基本概念和操作
优先队列是一种抽象数据类型,它支持以下操作:
- `insert(key, priority)`:插入一个键值对,其中 `key` 是要插入的元素,`priority` 是该元素的优先级。
- `delete_min()`:删除优先级最低的元素。
- `min()`:返回优先级最低的元素,但不删除它。
优先队列的优先级通常由一个比较函数决定,该函数返回两个元素的比较结果。比较函数通常定义为:
```python
def compare(a, b):
return a < b
```
如果 `a` 的优先级高于 `b`,则比较函数返回 `True`。如果 `a` 的优先级等于 `b`,则比较函数返回 `False`。如果 `a` 的优先级低于 `b`,则比较函数返回 `None`。
# 2. 优先队列的实现
### 2.1 优先队列的基本概念和操作
优先队列是一种抽象数据类型,它支持以下基本操作:
- **插入 (insert)**:将一个元素插入队列。
- **删除 (delete)**:从队列中删除优先级最高的元素。
- **查询 (find-min)**:返回队列中优先级最高的元素。
### 2.2 基于堆的优先队列实现
堆是一种完全二叉树,其中每个节点的值都大于或等于其子节点的值。这种结构允许我们高效地执行插入和删除操作。
#### 2.2.1 堆的插入和删除操作
**插入操作**:
1. 将新元素添加到堆的末尾。
2. 与其父节点比较,如果新元素的优先级更高,则交换两者。
3. 重复步骤 2,直到新元素到达其正确位置。
**删除操作**:
1. 将根节点(优先级最高的元素)与堆的最后一个元素交换。
2. 删除最后一个元素。
3. 将根节点向下调整,使其满足堆性质。
#### 2.2.2 优先队列的插入和删除操作
基于堆的优先队列的插入和删除操作与堆的插入和删除操作相同。由于堆的性质,优先级最高的元素始终存储在根节点中,因此查询操作只需返回根节点即可。
```python
class PriorityQueue:
def __init__(self):
self.heap = []
def insert(self, value):
self.heap.append(value)
self._heapify_up(len(self.heap) - 1)
def delete(self):
if not self.heap:
raise IndexError("Cannot delete from an empty priority queue")
self.heap[0], self.heap[-1] = self.heap[-1], self.heap[0]
value = self.heap.pop()
self._heapify_down(0)
return value
def find_min(self):
if not self.heap:
raise IndexError("Cannot find minimum in an empty priority queue")
return self.heap[0]
def _heapify_up(self, index):
while index > 0:
parent_index = (index - 1) // 2
if self.heap[index] < self.heap[parent_index]:
self.heap[index], self.heap[parent_index] = self.heap[parent_index], self.heap[index]
index = parent_index
def _heapify_down(self, index):
while True:
left_index = 2 * index + 1
right_index = 2 * index + 2
min_index = index
if left_index < len(self.heap) and self.heap[left_index] < self.heap[min_index]:
min_index = left_index
if right_index < len(self.heap) and self.heap[right_index] < self.heap[min_index]:
min_index = right_index
if min_index == index:
break
self.heap[index], self.heap[min_index] = self.heap[min_index], self.heap[index]
index = min_index
```
**代码逻辑分析**:
- `insert` 方法将新元素插入堆的末尾,并通过 `_heapify_up` 方法将其上浮到正确位置。
- `delete` 方法将根节点与堆的最后一个元素交换,然后通过 `_heapify_down` 方法调整根节点以满足堆性质。
- `find_min` 方法返回根节点,即优先级最高的元素。
- `_heapify_up` 方法将一个元素向上移动,直到其满足堆性质。
- `_heapify_down` 方法将一个元素向下移动,直到其满足堆性质。
**参数说明**:
- `value`:要插入优先队列的元素。
- `index`:要调整的元素在堆中的索引。
# 3. 堆在优先队列中的应用
### 3.1 堆排序算法
#### 3.1.1 堆排序的原理和步骤
堆排序是一种基于堆的数据结构进行排序的算法。其基本思想是将待排序的元素构建成一个最大堆(或最小堆),然后依次从堆顶取出元素,即可得到一个有序序列。
堆排序的步骤如下:
1. **构建堆:**将待排序的元素构建成一个最大堆(或最小堆)。
2. **取出堆顶元素:**从堆顶取出最大(或最小)元素,并将其与堆底元素交换。
3. **调整堆:**对剩余的堆进行调整,以保持堆的性质。
4. **重复步骤 2 和 3:**重复步骤 2 和 3,直到堆中只剩下一个元素。
#### 3.1.2 堆排序的复杂度分析
堆排序的时间复杂度为 O(n log n),其中 n 为待排序元素的数量。
* **构建堆:**构建堆的时间复杂度为 O(n),因为需要对每个元素进行一次调整。
* **取出堆顶元素和调整堆:**取出堆顶元素和调整堆的时间复杂度为 O(log n),因为每次调整只涉及堆的一部分。
因此,堆排序的总时间复杂度为 O(n log n)。
### 3.2 Dijkstra算法
#### 3.2.1 Dijkstra算法的原理和步骤
Dijkstra算法是一种求解加权有向图中单源最短路径的算法。其基本思想是使用优先队列来维护当前已知的最短路径,并不断更新和扩展最短路径,直到找到目标节点。
Dijkstra算法的步骤如下:
1. **初始化:**将源节点的距离设为 0,其他节点的距离设为无穷大。
2. **选择未访问的节点:**从未访问的节点中选择距离最小的节点。
3. **更新距离:**对于当前节点的每个相邻节点,计算经过当前节点到达该相邻节点的距离,并与该相邻节点的当前距离进行比较。如果经过当前节点的距离更短,则更新该相邻节点的距离。
4. **标记节点:**将当前节点标记为已访问。
5. **重复步骤 2-4:**重复步骤 2-4,直到所有节点都被访问。
#### 3.2.2 Dijkstra算法在优先队列中的应用
在 Dijkstra算法中,使用优先队列来维护当前已知的最短路径。优先队列中的元素为 (距离,节点),其中距离表示从源节点到该节点的当前已知最短路径,节点表示该节点。
每次选择未访问的节点时,从优先队列中取出距离最小的元素,即距离源节点最短的未访问节点。然后,更新该节点的相邻节点的距离,并将其加入优先队列。
通过这种方式,Dijkstra算法可以高效地找到从源节点到所有其他节点的最短路径。
# 4. 优先队列的扩展应用
### 4.1 带权并查集
#### 4.1.1 带权并查集的基本概念和操作
带权并查集是一种并查集的数据结构,其中每个元素都关联着一个权重值。与标准并查集类似,带权并查集支持以下操作:
* `find(x)`:查找元素 `x` 所属的集合的代表元素。
* `union(x, y)`:将元素 `x` 和 `y` 所属的集合合并为一个集合。
带权并查集与标准并查集的主要区别在于 `union` 操作。在带权并查集中,当合并两个集合时,权重较大的集合的代表元素成为合并后集合的代表元素。如果两个集合的权重相同,则任意一个集合的代表元素都可以成为合并后集合的代表元素。
#### 4.1.2 基于优先队列的带权并查集实现
基于优先队列的带权并查集实现利用优先队列来存储集合的代表元素。每个代表元素都存储在其集合的权重。当执行 `union` 操作时,算法将两个集合的代表元素从优先队列中弹出,并比较它们的权重。权重较大的代表元素成为合并后集合的代表元素,并将其权重更新为两个集合权重的和。
```python
class WeightedUnionFind:
def __init__(self, n):
self.parents = [i for i in range(n)]
self.weights = [1 for _ in range(n)]
self.pq = PriorityQueue()
def find(self, x):
if self.parents[x] != x:
self.parents[x] = self.find(self.parents[x])
return self.parents[x]
def union(self, x, y):
root_x = self.find(x)
root_y = self.find(y)
if root_x != root_y:
if self.weights[root_x] < self.weights[root_y]:
self.parents[root_x] = root_y
self.weights[root_y] += self.weights[root_x]
else:
self.parents[root_y] = root_x
self.weights[root_x] += self.weights[root_y]
```
### 4.2 Huffman编码
#### 4.2.1 Huffman编码的原理和步骤
Huffman编码是一种无损数据压缩算法,它通过为每个符号分配可变长度的代码来减少数据的长度。该算法的原理是:
1. 计算每个符号出现的频率。
2. 将频率最小的两个符号合并为一个新的符号,其频率为这两个符号频率之和。
3. 重复步骤 2,直到只剩下一个符号。
生成的树称为 Huffman 树,其中每个符号都由一条从根节点到叶节点的路径表示。路径上的每个节点代表一个符号,路径的长度就是该符号的编码长度。
#### 4.2.2 基于优先队列的Huffman编码实现
基于优先队列的 Huffman 编码实现利用优先队列来存储符号及其频率。算法从优先队列中弹出频率最小的两个符号,并将其合并为一个新的符号。新的符号的频率为这两个符号频率之和,并将其插入优先队列中。算法重复此过程,直到优先队列中只剩下一个符号。
```python
class HuffmanNode:
def __init__(self, char, freq):
self.char = char
self.freq = freq
self.left = None
self.right = None
def build_huffman_tree(data):
pq = PriorityQueue()
for char, freq in data.items():
pq.push(HuffmanNode(char, freq))
while pq.size() > 1:
node1 = pq.pop()
node2 = pq.pop()
new_node = HuffmanNode(None, node1.freq + node2.freq)
new_node.left = node1
new_node.right = node2
pq.push(new_node)
return pq.pop()
def generate_huffman_codes(root, code):
if root.left is None and root.right is None:
codes[root.char] = code
return
generate_huffman_codes(root.left, code + '0')
generate_huffman_codes(root.right, code + '1')
```
# 5.1 堆的优化策略
### 5.1.1 斐波那契堆
斐波那契堆是一种改进的堆数据结构,它通过引入额外的指针和合并操作来提高堆的性能。
**原理:**
斐波那契堆中的每个节点都有以下属性:
- `key`:节点的键值
- `degree`:节点的度,即以该节点为根的子树中节点的数量
- `parent`:节点的父节点
- `child`:节点的第一个子节点
- `left`:节点在兄弟节点中的左指针
- `right`:节点在兄弟节点中的右指针
斐波那契堆的合并操作通过将两个堆中根节点的度较小的一个作为另一个的子节点来进行。合并后的堆的根节点是度最大的节点。
**优势:**
- 斐波那契堆的插入和删除操作的时间复杂度为 `O(log n)`,比普通堆的 `O(log n)` 更优。
- 斐波那契堆的合并操作的时间复杂度为 `O(1)`,比普通堆的 `O(log n)` 更优。
**代码示例:**
```cpp
class FibonacciHeapNode {
public:
int key;
int degree;
FibonacciHeapNode *parent;
FibonacciHeapNode *child;
FibonacciHeapNode *left;
FibonacciHeapNode *right;
FibonacciHeapNode(int key) {
this->key = key;
this->degree = 0;
this->parent = nullptr;
this->child = nullptr;
this->left = this;
this->right = this;
}
};
class FibonacciHeap {
public:
FibonacciHeapNode *min;
int size;
FibonacciHeap() {
this->min = nullptr;
this->size = 0;
}
void insert(int key) {
FibonacciHeapNode *new_node = new FibonacciHeapNode(key);
insert(new_node);
}
void insert(FibonacciHeapNode *new_node) {
if (min == nullptr) {
min = new_node;
} else {
new_node->left = min;
new_node->right = min->right;
min->right = new_node;
new_node->right->left = new_node;
if (new_node->key < min->key) {
min = new_node;
}
}
size++;
}
FibonacciHeapNode *extractMin() {
if (min == nullptr) {
return nullptr;
}
FibonacciHeapNode *old_min = min;
if (min->child != nullptr) {
FibonacciHeapNode *child = min->child;
do {
child->parent = nullptr;
child = child->right;
} while (child != min->child);
child->left->right = min->right;
min->right->left = child->left;
min->child = nullptr;
}
min->left->right = min->right;
min->right->left = min->left;
if (min == min->right) {
min = nullptr;
} else {
min = min->right;
consolidate();
}
size--;
return old_min;
}
private:
void consolidate() {
int degree_count[size];
for (int i = 0; i < size; i++) {
degree_count[i] = 0;
}
FibonacciHeapNode *current = min;
do {
int degree = current->degree;
while (degree_count[degree] != 0) {
FibonacciHeapNode *other = degree_count[degree];
if (current->key > other->key) {
FibonacciHeapNode *temp = current;
current = other;
other = temp;
}
merge(current, other);
degree_count[degree] = 0;
degree++;
}
degree_count[degree] = current;
current = current->right;
} while (current != min);
min = nullptr;
for (int i = 0; i < size; i++) {
if (degree_count[i] != 0) {
if (min == nullptr) {
min = degree_count[i];
} else {
min->right = degree_count[i];
degree_count[i]->left = min;
min = min->right;
}
}
}
}
void merge(FibonacciHeapNode *a, FibonacciHeapNode *b) {
a->right->left = b;
b->left->right = a;
a->right = b;
b->left = a;
if (a->degree < b->degree) {
b->parent = a;
b->child = a->child;
if (a->child != nullptr) {
a->child->left->right = b;
a->child->left = b;
}
a->child = b;
a->degree++;
} else {
a->parent = b;
a->child = b->child;
if (b->child != nullptr) {
b->child->left->right = a;
b->child->left = a;
}
b->child = a;
b->degree++;
}
}
};
```
### 5.1.2 二项堆
二项堆是一种另一种改进的堆数据结构,它通过将堆组织成一组有序的二项树来提高性能。
**原理:**
二项堆中的每个二项树都有以下属性:
- `degree`:二项树的度,即树中节点的数量
- `root`:二项树的根节点
- `parent`:二项树的父二项树
二项堆的合并操作通过将两个度相同的二项树合并成一个新的二项树来进行。合并后的二项树的度比原先的两个二项树的度大 1。
**优势:**
- 二项堆的插入和删除操作的时间复杂度为 `O(log n)`,与普通堆相同。
- 二项堆的合并操作的时间复杂度为 `O(1)`,与斐波那契堆相同。
**代码示例:**
```cpp
class BinomialHeapNode {
public:
int key;
int degree;
BinomialHeapNode *parent;
BinomialHeapNode *child;
BinomialHeapNode *sibling;
BinomialHeapNode(int key) {
this->key = key;
this->degree = 0;
this->parent = nullptr;
this->child = nullptr;
this->sibling = nullptr;
}
};
class BinomialHeap {
public:
BinomialHeapNode *min;
int size;
BinomialHeap() {
this->min = nullptr;
this->size = 0;
}
void insert(int key) {
BinomialHeapNode *new_node = new BinomialHeapNode(key);
insert(new_node);
}
void insert(BinomialHeapNode *new_node) {
if (min == nullptr) {
min = new_node;
} else {
min = merge(min, new_node);
}
size++;
}
BinomialHeapNode *extractMin() {
if (min == nullptr) {
return nullptr;
}
BinomialHeapNode *old_min = min;
min = min->sibling;
if (old_min->child != nullptr) {
reverse(old_min->child);
old_min->child->sibling = min;
min = old_min->child;
}
size--;
return old_min;
}
private:
void reverse(BinomialHeapNode *node) {
if (node->sibling != nullptr) {
reverse(node->sibling);
node->sibling->sibling = node;
} else {
node->sibling = nullptr;
}
}
BinomialHeapNode *merge(BinomialHeapNode *h1, BinomialHeapNode *h2) {
if (h1 == nullptr) {
return h2;
} else if (h2 == nullptr) {
return h1;
} else if (h1->degree < h2->degree) {
h1->sibling = merge(h1->sibling, h2);
0
0