堆排序算法:
需要根据输入的元素,构建一个大根/小根堆,即完全二叉树(不存在没有左孩子或右孩子的非叶子结点,以数组保存所有结点为例,父子结点的关系为:left_child_idx = 2 * parent_idx + 1, right_child_idx = 2 * parent_idx + 2)
,其中每个非叶子结点的值均大于/小于其左右孩子,即根结点值为所有结点值中的最大/最小值。
构建大根堆的过程描述如下:
从最后一个非叶子结点开始遍历,直到根结点结束,如果当前非叶子结点为左右孩子中的最大值,则不做任何操作,跳过当前结点,开始调整前一个非叶子结点。
如果当前非叶子结点的左孩子的值比其父结点和兄弟结点大,则交换父结点和左孩子的值,
由于交换后的左孩子的值可能并不比其左右孩子大(此左孩子结点为非叶子结点),因此需要从当前结点从上往下调整左子树,直到叶子结点。
同理如果当前非叶子结点的右孩子的值比其父结点和兄弟结点大,则与其父结点交换,并调整右子树。
int main() {
GetLeastNumbers_Solution(vector<int>([5,7,3,2,10,2,9]), 4);
return 0;
}
// 堆排序,建立大根堆,找出前K个最大值
vector<int> GetLeastNumbers_Solution(vector<int> input, int k) {
if (input.size() < k) return vector<int>();
for (int i = 0; i < k; i++) {
buildMinHeap(input, input.size() - i);
}
return vector<int>(input.end() - k, input.end());
}
// 构建小根堆
void buildMinHeap(vector<int> &input, int len) {
// 最后一个非叶子结点的下标为len / 2 - 1
for (int i = len / 2 - 1; i >= 0; i--) {
// 尝试比较当前结点和其左右孩子的大小,并交换
// 左孩子下标 = 2 * i + 1
// 右孩子下标 = 2 * i + 2
adjustMinSubTree(input, i, len);
}
swap(input, 0, len - 1);
}
// 构建大概堆
void buildMaxHeap(vector<int> &input, int len) {
// 最后一个非叶子结点的下标为len / 2 - 1
for (int i = len / 2 - 1; i >= 0; i--) {
// 尝试比较当前结点和其左右孩子的大小,并交换
// 左孩子下标 = 2 * i + 1
// 右孩子下标 = 2 * i + 2
adjustMaxSubTree(input, i, len);
}
swap(input, 0, len - 1);
}
void adjustMinSubTree(vector<int> &input, int i, int len) {
int smaller = i;
if (2 * i + 1 < len && input[2*i+1] < input[smaller]) {
smaller = 2*i + 1;
}
if (2 * i + 2 < len && input[2*i+2] < input[smaller]) {
smaller = 2*i+2;
}
if (smaller != i) {
swap(input, i, smaller);
adjustMinSubTree(input, smaller, len);
}
}
void adjustMaxSubTree(vector<int> &input, int i, int len) {
int larger = i;
if (2 * i + 1 < len && input[larger] < input[2*i+1]) {
larger = 2*i + 1;
}
if (2 * i + 2 < len && input[larger] < input[2*i+2]) {
larger = 2*i+2;
}
if (larger != i) {
swap(input, i, larger);
adjustMinSubTree(input, larger, len);
}
}
void swap(vector<int> &input, int i, int j) {
int tmp = input[i];
input[i] = input[j];
input[j] = tmp;
}