排序问题之堆排序
作者:互联网
排序问题
算法问题的基础问题之一,便是排序问题:
输入:n个数的一个序列,<a1, a2,..., an>。
输出:一个排列<a1',a2', ... , an'>,满足a1' ≤ a2' ≤... ≤ an' 。(输出亦可为降序,左边给出的例子为升序)
一.算法描述
(1)堆
二叉堆:是一个数组,能近似的看做完全二叉树。除了最底层,树是完全充满的,而且从左至右填充。
最大堆:除了根节点外,所有的节点i都满足A[parent(i)] ≥A[i]的堆。
最小堆:除了根节点外,所有的节点i都满足A[parent(i)] ≤A[i]的堆。
例如下图中,如果我们将数组记作A[1~6],那么将数组中的六个元素依次由上至下由左至右得添加到完全二叉树中,就能得到下面的堆,而且恰好是一个最大堆。
使用这种方法可以总结出如下规律:
i的父节点 parent(i) = ⌊i / 2⌋
i的左子节点 left(i) = 2i
i的右子节点 right(i) = 2i + 1
(这里由于书本上是用起始下标为1来讲解的,本文的讲解和代码都基于arr[1]来存放序列的第一个元素,且主要说明最大堆的排序过程)
(2)维护堆的性质Max-Heapify
对每一个非叶子节点i,假设以其左子节点和右子节点为根的两个堆都是最大堆,如果要保持以i为根的堆也为最大堆,就要保证A[i]不小于A[left(i)]和A[right(i)],如果A[i]不是三者最大,就要将A[i]与largest = max(A[left(i)], A[right(i)])交换,但是交换后可能破坏A[largest]为根的堆是最大堆的性质,所以还需要对交换后的节点调用Max-Heapify。由于最多将根节点交换到最底层的叶子节点,时间复杂度为O(H),H为树的高度。
(3)建堆
自底向上地对所有的非叶子节点使用Max-Heapify,最终就能得到一个最大堆。
(4)堆排序
由于最大堆的根节点是堆中的最大值,所以我们每次将根取出后,补上一个叶子节点,然后使用Max-Heapify让剩余元素的最大值继续交换到根节点,循环下去取出的值就是降序排列的。
二.代码实现
下面是插入排序的C++实现:
#include<iostream> #include<vector> using namespace std; //三个内联函数,用来求每个节点的父、左子、右子节点 inline int left(int parent) { return parent << 1; } inline int right(int parent) { return 1 + (parent << 1); } inline int parent(int child) { return child >> 1; } //维护最大堆的性质 void Max_Heapify(vector<int> &v, int i) { int size = v.size() - 1; int largest; int l = left(i); int r = right(i); //左子大于父节点,large = l if(l <= size && v[l] > v[i]) largest = l; else largest = i; //large取右子和左子、父中的较大值节点 if(r <= size && v[r] > v[largest]){ largest = r; } if(largest != i){ swap(v[i],v[largest]); Max_Heapify(v,largest); } } //从最大parent节点往上维护最大堆性质,就能形成最大堆 void Build_Max_Heap(vector<int> &v) { //最后一个子节点v.size()/2向下取整是号码最大的parent节点,所以直接从这里count down for(int i = v.size()/2; i > 0; i--) { Max_Heapify(v,i); } } //堆排序 void Heapsort(vector<int> &v) { vector<int> ret; Build_Max_Heap(v); for(int i = v.size()-1; i > 1; i--) { swap(v[i], v[1]); ret.push_back(v[i]); v.pop_back(); Max_Heapify(v,1); } ret.push_back(v[1]); v = ret; } int main() { //arr为要排序的数组 int arr[] = {5,2,4,7,10,9,8,1,6,3}; //把数组放入向量中,首部添0模拟图示中的序号 vector<int> v(arr, arr + sizeof(arr)/sizeof(int)); v.insert(v.begin(),0); //对向量使用堆排序 Heapsort(v); //按顺序打印排序后向量中的所有元素 copy (v.begin(), v.end(), ostream_iterator<int> (cout, " ")); cout << endl; }View Code
三.算法分析
(1)时间复杂度
由于建堆时对所有的非叶子节点都要进行一次Max-Heapify,复杂度为O(h)
Best-case:每次划分出的子序列长度都不大于划分前的一半。有递归式T(n) = 2T(n/2) + θ(n),推出T(n) = θ(nlgn),即时间复杂度为o(nlgn)。
Worst-case:每次划分都不平衡,得到的子序列长度分别为0和n-1。有递归式T(n) = T(n-1) + θ(n)。这样的快排近似于插入排序,时间复杂度为o(n2)。
Average-case:划分后两子序列的比例为常数,例如2:8。经过画递归树得到这种平衡划分的情况与1:1划分的情况时间复杂度相同,为o(nlgn)。
(2)稳定性
快速排序的稳定性取决于划分算法,第二节代码实现中给出的划分算法是稳定的。而在1962年Hoare在《QuickSort》论文中首次提出这个算法时,给出的Hoare_Partition划分是非稳定的。
(3)适合范围
被认为是最佳的比较排序算法,平均时间最短,对于随机分布的序列来说一般都不会是worst-case。
(4)算法改进
标签:parent,int,Max,堆排序,Heapify,问题,largest,排序,节点 来源: https://www.cnblogs.com/lovelybunny/p/10906789.html