堆排序算法使用
堆排序原理及实现
关于最大堆
什么是最大堆和最小堆?最大(小)堆是指在树中,存在一个结点而且该结点有儿子结点,该结点的data域值都不小于(大于)其儿子结点的data域值,并且它是一个完全二叉树(不是满二叉树)。注意区分选择树,因为选择树(selection tree)概念和最小堆有些类似,他们都有一个特点是“树中的根结点都表示树中的最小元素结点”。同理最大堆的根结点是树中元素最大的。那么来看具体的看一下它长什么样?(最小堆这里省略)
最大堆ADT的创建
(1)创建一个最大堆
(2)最大堆的插入
(3)最大堆的删除
创建最大堆就需要考虑它的内存表达形式。在二叉树进行遍历的方法分为:先序遍历、中序遍历、后序遍历和层序遍历。我们可以通过层序遍历的方式将二叉树结点存储在数组中,由于最大堆是完全二叉树不会存在数组的空间浪费。
那么对于数组我们怎么操作父结点和左右子结点呢?对于完全二叉树采用顺序存储表示,那么对于任意一个下标为i(1 ≤ i ≤ n)的结点:
(1)、父结点为:i / 2(i ≠ 1),若i = 1,则i是根节点。
(2)、左子结点:2i(2i ≤ n), 若不满足则无左子结点。
(3)、右子结点:2i + 1(2i + 1 ≤ n),若不满足则无右子结点。
故选取数组作为最大堆的内存表达形式
基本定义:
struct Heap{
int size ;
int *array;
Heap(int n)
{
size = 0;
array = new int [n+1];
}
Heap()
{
delete array;
}
};
最大堆的插入
插入操作需要保证满足完全二叉树的标准,所以插入位置一定为末尾结点,那么就是数组中的array[++size]。并且要满足最大堆的父结点关键字值大于子结点,那么就需要移动相对位置。可以参考下图:
void insert (int value)//插入
{
array[++size] = value;
int index = size;
while (index > 1)
{
if (array[index] < array[index/2]) swap (array[index],array[index/2]);
index /= 2;
}
}
最大堆的删除
最大堆的删除操作,就是从根结点删除元素。同样删除后该树仍然是一颗完全二叉树,这里我们需要通过移动尾结点,让其满足完全二叉树的定义。如图在下面最大堆执行删除操作:
最大堆的创建
创建最大堆的思路就是先创建一个空的最大堆,然后将N个数值一个个插入最大堆中,便创建好了一个元素为N的最大堆。
而堆排序,便是将根结点一个个删除、弹出,便降序排序好了。(若是需要升序排序,便构建最小堆)
题目
好了,终于到了题目部分:已知一个几乎有序的数组,几乎有序是指,如果把数组排好顺序的话,每个元素移动的距离可以不超过k,并且k相对于数组来说比较小。请选择一个合适的排序算法针对这个数据进行排序。
排序算法 | 最差时间分析 | 平均时间复杂度 |
---|---|---|
冒泡排序 | O(N^2) | O(N^2) |
快速排序 | O(N^2) | O(N*log2N) |
插入排序 | O(N^2) | O(N^2) |
归并排序 | O(N*log2N) | O(N*log2N) |
堆排序 | O(N*log2N) | O(N*log2N) |
计数排序 | O(N) | O(N) |
基数排序 | O(N) | O(N) |
希尔排序 | O(N^2) | O(N*log2N) |
考虑到时间复杂度为O(N)的算法:计数排序与基数排序,因为不知道数字范围,所以不考虑。
其次是时间复杂度为O(N^2)的排序算法:冒泡与选择,但是冒泡与选择是与数组原始顺序没有关系的。
如果是插入排序,插入排序也是我看到这个题的第一个思路,因为插入的范围不和超过k。所以在这个题的时间复杂度应该是O(NK)。但应该还不是最好的。
那么接下来继续考虑时间复杂度为(Nlog2N)的排序算法:归并排序,也与数组原始顺序无关。
最终解决思路是,构建了一个大小为K的最小堆,先将前k个元素放入堆中,因为元素的移动距离不超过k,故而最小值必然在堆中,然后弹出根结点,将第k+1个元素插入堆中,弹出根结点。重复此操作,便排序完成。
时间复杂度为O(N*log2k)。
struct Heap{
int size ;
int *array;
Heap(int n){
size = 0;
array = new int [n+1];
}
~Heap()
{
delete array;
}
bool empty()//判断是否为空
{
if (size != 0) return false;
return true;
}
void insert (int value)//插入
{
array[++size] = value;
int index = size;
while (index > 1)
{
if (array[index] < array[index/2]) swap (array[index],array[index/2]);
index /= 2;
}
}
void del()//删除
{
if(empty()) return;
swap(array[1],array[size--]);
int index = 1;
while (2*index <= size)
{
int next = 2*index;
if (next < size && array[next+1] < array[next]) next++;
if (array[index]>array[next])
{
swap(array[index],array[next]);
index = next;
}
else break;
}
}
int max (){
if (empty()) return -1;
return array[1];
}
};
class ScaleSort {
public:
vector<int> sortElement(vector<int> A, int n, int k) {
Heap heap(k);
int a[n];
for (int i=0;i<k;i++)
{
heap.insert(A[i]);
}
for (int i=k;i<n;i++)
{
a[i-k]=heap.max();
heap.del();
heap.insert(A[i]);
}
for (int i=n-k;i<n;i++)
{
a[i]=heap.max();
heap.del();
}
for (int i=0;i<n;i++)
A[i]=a[i];
return A;
}
};
原理部分参考作者:凌云壮志几多愁
链接:https://www.jianshu.com/p/21bef3fc3030
来源:简书