堆
当用数组表示存储 n n n个元素的堆时,叶结点下标分别是 [ n / 2 ] + 1 [n/2]+1 [n/2]+1, [ n / 2 ] + 2 [n/2]+2 [n/2]+2,…, n n n。
表示堆的数组 A A A包含两个属性: A . l e n g t h A.length A.length表示数组元素的个数, A . h e a p − s i z e A.heap-size A.heap−size表示有多少个堆元素存储在该数组中。
- 给定结点的下标 i i i,计算它的父结点、左孩子和右孩子的下标:
PARENT(i)
return [i/2]
LEFT(i)
return 2i
RIGHT(i)
return 2i+1
维护堆的性质
MAX-HEAPIFY
过程:维护最大堆性质的重要过程,其时间复杂度为 O ( l g n ) O(lgn) O(lgn),它的输入是一个数组 A A A和一个下标 i i i
在调用MAX-HEAPIFY
的时候,假定根结点为LEFT
(
i
i
i)和RIGHT
(
i
i
i)的二叉树都是最大堆,这时
A
[
i
]
A[i]
A[i]有可能小于其孩子,这样就违背了最大堆的性质。MAX-HEAPIFY
通过让
A
[
i
]
A[i]
A[i]的值在最大堆中“逐渐下降”,从而使得以下标
i
i
i为根结点的子树重新遵循最大堆的性质
MAX-HEAPITY(A, i)
l = LEFT(i)
r = RIGHT(i)
if l <= A.heap-size and A[l] > A[i]
largest = l
else largest = i
if r <= A.heap-size and A[r] > A[largest]
largest = r
if largest ≠ i
exchange A[i] with A[largest]
MAX-HEAPIFY(A, largest)
在程序的每一步中,从
A
[
i
]
A[i]
A[i]、
A
[
L
E
F
T
(
i
)
]
A[LEFT(i)]
A[LEFT(i)]和
A
[
R
I
G
H
T
(
i
)
]
A[RIGHT(i)]
A[RIGHT(i)]中选出最大的,并将其下标存储在
l
a
r
g
e
s
t
largest
largest中,如果
A
[
i
]
A[i]
A[i]是最大的,那么以
i
i
i为根结点的子树已经是最大堆,程序结束,否则,最大元素是
i
i
i的某个孩子结点,则交换
A
[
i
]
A[i]
A[i]和
A
[
l
a
r
g
e
s
t
]
A[largest]
A[largest]的值,从而使
i
i
i及其孩子都满足最大堆的性质,在交换后,下标为
l
a
r
g
e
s
t
largest
largest的结点的值是原来的
A
[
i
]
A[i]
A[i],于是以该结点为根的子树又有可能会违反最大堆的性质。因此,需要对该子树递归调用MAX-HEAPIFY
建堆
BUILD-MAX-HEAP
过程:具有线性时间复杂度,其时间复杂度为 O ( n l g n ) O(nlgn) O(nlgn),功能是从无序的输入数据数组中构造一个最大堆
可以用自底向上的方法利用过程MAX-HEAPIFY
把一个大小为
n
=
A
.
l
e
n
g
t
h
n=A.length
n=A.length的数组
A
[
1..
n
]
A[1..n]
A[1..n]转换为最大堆。子数组
A
(
[
n
/
2
]
+
1..
n
)
A([n/2]+1..n)
A([n/2]+1..n)中的元素都是树的叶结点,BUILD-MAX-HEAP
过程就是对树中的其他结点都调用一次MAX-HEAPIFY
BUILD-MAX-HEAP(A)
A.heap-size = A.length
for i = [A.length/2] downto 1
MAX-HEAPIFY(A, i)
每次调用MAX-HEAPIFY
的时间复杂度是
O
(
l
g
n
)
O(lgn)
O(lgn),BUILD-MAX-HEAP
需要
O
(
n
)
O(n)
O(n)次这样的调用,所以总的时间复杂度是
O
(
n
l
g
n
)
O(nlgn)
O(nlgn)
堆排序算法
HEAPSORT
过程:事件复杂度为 O ( n l g n ) O(nlgn) O(nlgn),功能是对一个数组进行原址排序
初始时候,堆排序算法利用BUILD-MAX-HEAP
将输入数组
A
[
1..
n
]
A[1..n]
A[1..n]建成最大堆,其中
n
=
A
.
l
e
n
g
t
h
n=A.length
n=A.length。因为数组中的最大元素总在根结点
A
[
1
]
A[1]
A[1]中,通过把它与
A
[
n
]
A[n]
A[n]进行互换,然后从堆中去掉结点
n
n
n(这一操作可以通过减少
A
.
h
e
a
p
−
s
i
z
e
A.heap-size
A.heap−size的值来实现),对于新的根结点调用MAX-HEAPIFY
进行最大堆的维护,从而在
A
[
1..
n
−
1
]
A[1..n-1]
A[1..n−1]上构造一个新的最大堆,堆排序算法会不断重复这一过程,直到堆的大小从
n
−
1
n-1
n−1降到2。
HEAPSORT(A)
BUILD-MAX-HEAP(A)
for i = A.length downto 2
exchange A[1] with A[i]
A.heap-size = A.heap-size - 1
MAX-HEAPIFY(A, 1)
C++ SLT中的priority_queue
- priority_queue基本用法
头文件<queue>
priority_queue< type, container, function>
- type:类型
- container:实现优先队列的底层容器(缺省)
- function:元素之间的比较方式(缺省)
对于container,要求必须是数组形式实现的容器,例如vector、deque,而不能使list。在STL中,默认情况下(不加后面两个参数)是以vector为容器,以 operator< 为比较方式,所以在只使用第一个参数时,优先队列默认是一个最大堆,每次输出的堆顶元素是此时堆中的最大元素。
- 大顶堆
priority_queue<int,vector<int>,less<int> > Q;
- 小顶堆
priority_queue<int,vector<int>,greater<int> > Q;
- 操作
(1)取堆顶操作:Q.top()
(2)判断堆空操作:Q.empty()
(3)添加元素入堆:Q.push(int x)
(4)元素弹出堆:Q.pop()
(5)求堆中元素个数:Q.size()
应用:求数组中的第k大元素
按上方的理论进行的代码编写
class Solution {
public:
void maxHeapify(vector<int>& a, int i, int heapSize) {
int l = i * 2 + 1, r = i * 2 + 2, largest = i;
if (l < heapSize && a[l] > a[largest]) {
largest = l;
}
if (r < heapSize && a[r] > a[largest]) {
largest = r;
}
if (largest != i) {
swap(a[i], a[largest]);
maxHeapify(a, largest, heapSize);
}
}
void buildMaxHeap(vector<int>& a, int heapSize) {
for (int i = heapSize / 2; i >= 0; --i) {
maxHeapify(a, i, heapSize);
}
}
int findKthLargest(vector<int>& nums, int k) {
int heapSize = nums.size();
buildMaxHeap(nums, heapSize);
for (int i = nums.size() - 1; i >= nums.size() - k + 1; --i) {
swap(nums[0], nums[i]);
--heapSize;
maxHeapify(nums, 0, heapSize);
}
return nums[0];
}
};
使用C++ STL库进行求解
class Solution {
public:
int findKthLargest(vector<int>& nums, int k) {
// 构造一个空的优先队列,此优先队列是一个小顶堆
priority_queue<int,vector<int>,greater<int> > Q;
for (int i = 0; i < nums.size(); i++){
if (Q.size() < k){
Q.push(nums[i]);
} else if (nums[i] > Q.top()) {
Q.pop();
Q.push(nums[i]);
}
}
return Q.top();
}
};
应用:求数组中的第k小元素
class Solution {
public:
void minHeapify(vector<int>& a, int i, int heapSize) {
int l = 2 * i + 1, r = 2 * i + 2, min = i;
if (l < heapSize && a[l] < a[min]) {
min = l;
}
if (r < heapSize && a[r] < a[min]) { // 这里不能写a[i],只能写a[min]
min = r;
}
if (min != i) {
swap(a[i], a[min]);
minHeapify(a, min, heapSize);
}
}
void buildMinHeap(vector<int>& a, int heapSize) {
for (int i=(int)heapSize/2; i >= 0; i--) {
minHeapify(a, i, heapSize);
}
}
int findKthMinimum(vector<int>& nums, int k) {
int heapSize = nums.size();
buildMinHeap(nums, heapSize);
for (int i = nums.size() - 1; i >= nums.size() - k + 1; i--) {
swap(nums[0], nums[i]);
heapSize--;
minHeapify(nums, 0, heapSize);
}
return nums[0];
}
};
class Solution {
public:
int findKthLargest(vector<int>& nums, int k) {
priority_queue<int, vector<int>, less<int> > Q;
for (int i = 0; i < nums.size(); i++) {
if (Q.size() < k) {
Q.push(nums[i]);
} else if (nums[i] < Q.top()) {
Q.pop();
Q.push(nums[i]);
}
}
return Q.top();
}
};