基本概念
堆排序是指利用堆这种数据结构进行排序的排序算法,堆排序是一种选择排序,最好、最坏,平均时间复杂度均为O(nlogn)。
堆:堆是具有以下性质的完全二叉树:当其每个结点的值都大于或等于其左右孩子结点的值时,称为大顶堆;当其每个结点的值都小于或等于其左右孩子结点的值时,称为小顶堆。如下图所示
以大顶堆为例,对堆中的结点按层进行编号,从0开始,那么将这种结构按顺序写入数组中就是下图中的样子
则大顶堆及小顶堆满足的数学公式为
大顶堆:arr[i] >= arr[2i+1] && arr[i] >= arr[2i+2]
小顶堆:arr[i] <= arr[2i+1] && arr[i] <= arr[2i+2]
堆排序基本步骤
1.假定无序序列为
2.首先将其构造成一个大顶堆:从最后一个非叶结点开始,从左至右,从下至上挨个调整对应结点,使其满足大顶堆的要求,对于初始序列,最后一个非叶结点为len/2 - 1 = 1,也就是图中编号1的结点,调整过程如下图
3.1号结点调整完毕后,对0号结点判断是否需要调整,由于0号结点的左右孩子比他大,因此需要调整
4.上述过程结束后,1号结点所在分支的结构遭到破坏,再次对其进行调整
5.此时,将无序序列构造成了一个大顶堆,其根节点对应的元素值为最大值,将根节点的元素和序列最后的元素交换,那么第一个元素已经排好序了
6.接下来就是重复上述步骤,再次构造大顶堆,之后将根节点的元素值和倒数第二个元素进行交换,此时两个元素完成排序。
首先是0号结点进行调整
将根节点元素放到倒数第二的位置
后面的步骤就是反复重复上述过程,将序列调整为大顶堆的过程中需要传入待调整的长度,以使已经排好序的元素不再参与重新调整位置。
总结一下就是:
a.给定序列后根据需要首先构造一个大顶堆或小顶堆
b.将收尾元素进行交换
c.重新调整结构,继续交换首尾元素值
代码:
sortTestHelper.h
//
// Created by 开机烫手 on 2018/4/19.
#ifndef SORT_SORTTESTHELPER_H
#define SORT_SORTTESTHELPER_H
#include <iostream>
#include <ctime>
#include <cassert>
using namespace std;
namespace SortTestHelper {
int *generateRandomArray(int n, int RangeL, int RangeR) {
assert(RangeL <= RangeR);
int *arr = new int[n];
srand(time(NULL));
for (int i = 0; i < n; i++) {
arr[i] = rand() % (RangeR - RangeL + 1) + RangeL;
}
return arr;
}
template<typename T>
void printArray(T arr[], int n) {
for (int i = 0; i < n; i++) {
cout << arr[i] << ' ';
}
cout << endl;
}
template<typename T>
bool isSorted(T arr[], int n) {
for (int i = 0; i < n - 1; i++) {
if (arr[i] > arr[i + 1])
return false;
}
return true;
}
template<typename T>
void testSort(string name, void(*sort)(T [], int n), T arr[], int n) {
clock_t startTime = clock();
sort(arr, n);
clock_t endTime = clock();
assert(isSorted(arr, n));
cout << name << ": " << double(endTime - startTime) / CLOCKS_PER_SEC << " s" << endl;
}
int *copyIntArray(int a[], int n) {
int *arr = new int[n];
for (int i = 0; i < n; i++) {
arr[i] = a[i];
}
return arr;
}
};
#endif //SORT_SORTTESTHELPER_H
main.cpp
#include <iostream>
#include "sortTestHelper.h"
using namespace std;
const int MAX_SIZE = 70000;
template<typename T>
void InsertSort(T k[], int len) {
T temp, j;
for (int i = 1; i < len; i++) {
if (k[i] < k[i - 1]) {
temp = k[i];
for (j = i; j > 0 && k[j - 1] > temp; j--) {
k[j] = k[j - 1];
}
k[j] = temp;
}
}
}
template<typename T>
void ShellSort(T k[], int len) {
for (int gap = len / 2; gap > 0; gap /= 2) {
for (int i = gap; i < len; i++) {
int j = i;
T temp = k[j];
if (k[j] < k[j - gap]) {
while (j - gap >= 0 && temp < k[j - gap]) {
k[j] = k[j - gap];
j -= gap;
}
k[j] = temp;
}
}
}
}
template<typename T>
void HeapAdjust(T k[], int s, int len) {
int i;
T temp = k[s];
for (i = 2 * s + 1; i < len; i = i * 2 + 1) {
if (i + 1 < len && k[i] < k[i + 1])
i++;
if (temp >= k[i])
break;
k[s] = k[i];
s = i;
}
k[s] = temp;
}
template<typename T>
void HeapSort(T k[], int len) {
T temp;
for (int i = len / 2 - 1; i >= 0; i--) {
HeapAdjust(k, i, len);
}
for (int i = len - 1; i > 0; i--) {
temp = k[0];
k[0] = k[i];
k[i] = temp;
HeapAdjust(k, 0, i);
}
}
int main() {
int *arr = SortTestHelper::generateRandomArray(MAX_SIZE, 0, MAX_SIZE);
int *arr2 = SortTestHelper::copyIntArray(arr, MAX_SIZE);
int *arr3 = SortTestHelper::copyIntArray(arr, MAX_SIZE);
SortTestHelper::testSort("Insert Sort", InsertSort, arr, n);
SortTestHelper::testSort("Shell Sort", ShellSort, arr2, MAX_SIZE);
SortTestHelper::testSort("Heap Sort", HeapSort, arr3, MAX_SIZE);
delete[] arr;
delete[] arr2;
delete[] arr3;
return 0;
}
输出:
Insert Sort: 5.336 s
Shell Sort: 0.031 s
Heap Sort: 0.016 s
总的来看,堆排序是比较优秀的排序算法,即使最坏情况下依然到O(nlogn),或许唯一的不足是构建大顶堆时频繁的交换元素导致排序过程中交换次数“稍微”多了点。
还有就是建堆过程中是整个数组的各个位置都会访问到的,会让数据过于大距离的移动,缓存命中概率低一些,不利于缓存发挥作用,存取模型的局部性稍微差一些。(这句有些过于偏深入的理论了,自己也不懂具体原理,照搬网络了)
参考:
http://www.cnblogs.com/chengxiao/p/6129630.html