堆排序
堆的定义
堆实际也是一个二叉树,也是一个完全二叉树,当不是满二叉树时,从第一个叶子结点开始将元素放在已存在的叶子结点下面。
特点:
- 完全二叉树
- 堆中的元素小于等于父亲节点
堆排序的方案
以最大堆为例
- 先将数组整理成一个最大堆,依次从堆中移除最大元素,移除后放在一个数组中存储起来,每移除一个元素,则将堆重新整理成一个最大堆
- 先将数组整理成一个最大堆,将数组中最大值的元素和数组末尾的元素交换,记录未排序的数组边界,重新将为排序的数组整理成最大堆,再交换数组中的第一个元素和最后一个元素,重复直到数组元素都排序为止。
第一种方式相对简单一些,但是需要额外开辟空间,空间复杂度是O(n),第二种方式相对复杂一些,但是不会额外开辟空间,空间复杂度时O(1)。
示例图如下:
最大堆的实现代码如下:
#include <stdio.h>
#include <vector>
#include <iostream>
using namespace std;
class MaxHeapExe {
private:
vector<int> data;
public:
int getSize() {
return (int)data.size();
}
bool isEmpty() {
return data.size() == 0;
}
/// 0
/// 1 2
/// 3 4
/// 上面的图形中,1位置的孩子位置是3、4
int parent(int k) {
return (k-1)/2;
}
int leftChild(int k) {
return k * 2 + 1;
}
int rightChild(int k) {
return k * 2 + 2;
}
/// 添加元素到数组末尾,让新添加的元素进行上浮
void add(int v) {
data.push_back(v);
siftUp((int)data.size()-1);
}
int findMax() {
if (data.size() == 0) {
throw "data is empty";
}
return data[0];
}
int extractMax() {
int ret = findMax();
data[0] = data[data.size()-1];
data.pop_back();
siftDown(0);
return ret;
}
/// 如果元素大于父亲节点,则和父亲节点交换
void siftUp(int k) {
while (k > 0) {
int parentIndex = parent(k);
if (data[k] > data[parentIndex]) {
swap(k, parentIndex);
k = parentIndex;
} else {
break;
}
}
}
/// 如果元素小于孩子,那么和左右孩子中的最大孩子交换
void siftDown(int k) {
cout << "start siftDown \n";
/// 如果没有左孩子了,直接结束
while (true) {
int j = leftChild(k);
if (j >= data.size()) {
break;
}
/// 判断到底是左孩子大还是右孩子大
if (j+1<data.size() && data[j+1] > data[j]) {
j = j + 1;
}
if (data[j] > data[k]) {
swap(j, k);
k = j;
} else {
/// 说明孩子都没有父亲节点大,直接结束。这里容易忽略!!!
break;
}
}
}
void swap(int i, int j) {
int temp = data[i];
data[i] = data[j];
data[j] = temp;
}
};
原地堆排序代码如下:
将最大堆改造,可支持传入数组进行操作
#include <stdio.h>
#include <vector>
using namespace std;
class MaxHeapExe {
public:
/// 0
/// 1 2
/// 3 4
/// 上面的图形中,1位置的孩子位置是3、4
int parent(int k) {
return (k-1)/2;
}
int leftChild(int k) {
return k * 2 + 1;
}
int rightChild(int k) {
return k * 2 + 2;
}
/// 如果元素小于孩子,那么和左右孩子中的最大孩子交换
void siftDown(vector<int>& arr, int k, int n) {
/// 如果没有左孩子了,直接结束
while (true) {
int j = leftChild(k);
if (j > n) {
break;
}
/// 判断到底是左孩子大还是右孩子大
if (j+1<=n && arr[j+1] > arr[j]) {
j = j + 1;
}
if (arr[j] > arr[k]) {
swap(arr, j, k);
k = j;
} else {
/// 说明孩子都没有父亲节点大,直接结束。这里容易忽略!!!
break;
}
}
}
void swap(vector<int>& arr, int i, int j) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
};
将数组进行 heapify,然后循环将最大元素和未排序的元素交换
#include <stdio.h>
#include <vector>
#include "MaxHeapExe.hpp"
using namespace std;
class MaxHeapSort {
public:
void sort(vector<int>& arr) {
MaxHeapExe maxHeapExe;
int size = (int)arr.size();
/// 先执行 heapify,将数组整理成最大堆
for (int i=(size-2)/2; i>=0; i--) {
maxHeapExe.siftDown(arr, i, size-1);
}
/// 不停的将最大元素和未排序的元素交换,然后执行 siftDown 的操作
for (int i=size-1; i>0; i--) {
maxHeapExe.swap(arr, 0, i);
maxHeapExe.siftDown(arr, 0, i-1); /// 注意,这里 siftDown 的边界是未排序的数组的最后一个元素的下标
}
}
};
测试代码如下:
vector<int> arr = {62,41,30,28,16,22,13,19,17,15,32,99,11};
MaxHeapSort maxHeapSort;
maxHeapSort.sort(arr);
输出结果如下: