排序专题(四) / 不稳定的内部排序 / 堆排序
2013-01-30 16:16
232 查看
堆的定义
前置定义
完全二叉树
完全二叉树是这样一棵树:设此树的高度为h,除第h层外,其它各层(1~h-1)的结点数都达到最大个数(2i-1),第h层所有的结点都连续集中在最左边。
满二叉树(满二叉树在国内国外的定义是不同的)
国内定义:设一个二叉树的高度为h,若其有2h-1个结点,则其为满二叉树。(显然,叶子结点只可能出现在第h层,且除叶子结点外的其它结点都有两个子节点)
美国及国际上所定义的满二叉树
full binary tree,美国NIST给出的定义为:A binary tree in which each node has exactly zero or two children. In other words, every node is either a leaf or has two children. For efficiency, any Huffman coding is a full binary tree.
即:满二叉树的任意结点,要么度为0,要么度为2,。换句话说:每个结点,要么为叶子结点,要么同时具有左右孩子。简洁明了地说,Huffman树就是一棵满二叉树。(这显然与国内的定义完全不同)
显然,堆应该是一个完全二叉树
堆一般都是用一个一维数组来存储的,完全二叉树的结构是隐含在这个一维数组中的,下所述假设数组的下标是从0开始的,i为该元素在存储数组中的索引:
父结点i的左孩子在存储数组中的索引为:2*i+1
父结点i的右孩子在存储数组中的索引为:2*i+2
子结点i的父结点在存储数组中的索引为:floor((i-1)/2) (floor:向下取整;ceil:向上取整;)
有了上面的铺垫大顶堆/小顶堆就很容易定义了
大顶堆:若一个结点有左右孩子,则该结点值大于其左右孩子结点的值
小顶堆:若一个结点有左右孩子,则该结点值小于其左右孩子结点的值
则显然:大顶堆的根元素一定是最大元素;小顶堆的根元素一定是最小元素
堆排序
时间复杂度:O(nlogn)
排序过程
初始构建堆(堆排序主要时间花费在这一步骤上,有两种方式:)
从空数组开始一个个数据添加,每添加一个元素根据堆的定义调整使得添加完后的数组符合堆的定义
从数组索引为array.length/2处开始调整,直至根结点,具体看下代码中注释
输出堆顶元素,用堆的最后一个元素替代输出堆顶元素所空出来的位置,调整剩余元素为新的堆结构,如此循环,直至输出所有元素,得到有序序列,具体调整过程如下:
用堆的最后一个元素替代输出堆顶元素所空出来的位置,从堆顶开始向下调整新的结构为堆
可以证明:调整的过程,堆顶元素走的路线一定是一条从根到某叶子结点的唯一路线
具体看下代码中注释
堆改进,增加删除方法,使其成为类似二叉排序树的一种存储结构
删除方法
情况1:如果删除的是最末元素,直接删除
情况2:如果删除的元素既没有左孩子又没有右孩子,则用最末元素与该元素交换,删除最末元素,从原最末元素现所在索引开始向上调整新结构为堆结构
情况3:如果删除的元素有左孩子或者有右孩子或者同时具有左右孩子,则:
大顶堆:交换该元素与其左右孩子中的较大者,一直到该元素被交换成为一个叶子结点,则就是情况2了
小顶堆:交换该元素与其左右孩子中的较小者,一直到该元素被交换成为一个叶子结点,则就是情况2了
堆排序代码
改进堆为类似二叉排序树的代码
PS:
Code还是去年工作半年闲暇时码的,目的是为了弥补自己本科期间没有动手写这些基础code的缺憾
总是工作时抽闲写一点,注释较少,望君手下留情
前置定义
完全二叉树
完全二叉树是这样一棵树:设此树的高度为h,除第h层外,其它各层(1~h-1)的结点数都达到最大个数(2i-1),第h层所有的结点都连续集中在最左边。
满二叉树(满二叉树在国内国外的定义是不同的)
国内定义:设一个二叉树的高度为h,若其有2h-1个结点,则其为满二叉树。(显然,叶子结点只可能出现在第h层,且除叶子结点外的其它结点都有两个子节点)
美国及国际上所定义的满二叉树
full binary tree,美国NIST给出的定义为:A binary tree in which each node has exactly zero or two children. In other words, every node is either a leaf or has two children. For efficiency, any Huffman coding is a full binary tree.
即:满二叉树的任意结点,要么度为0,要么度为2,。换句话说:每个结点,要么为叶子结点,要么同时具有左右孩子。简洁明了地说,Huffman树就是一棵满二叉树。(这显然与国内的定义完全不同)
显然,堆应该是一个完全二叉树
堆一般都是用一个一维数组来存储的,完全二叉树的结构是隐含在这个一维数组中的,下所述假设数组的下标是从0开始的,i为该元素在存储数组中的索引:
父结点i的左孩子在存储数组中的索引为:2*i+1
父结点i的右孩子在存储数组中的索引为:2*i+2
子结点i的父结点在存储数组中的索引为:floor((i-1)/2) (floor:向下取整;ceil:向上取整;)
有了上面的铺垫大顶堆/小顶堆就很容易定义了
大顶堆:若一个结点有左右孩子,则该结点值大于其左右孩子结点的值
小顶堆:若一个结点有左右孩子,则该结点值小于其左右孩子结点的值
则显然:大顶堆的根元素一定是最大元素;小顶堆的根元素一定是最小元素
堆排序
时间复杂度:O(nlogn)
排序过程
初始构建堆(堆排序主要时间花费在这一步骤上,有两种方式:)
从空数组开始一个个数据添加,每添加一个元素根据堆的定义调整使得添加完后的数组符合堆的定义
从数组索引为array.length/2处开始调整,直至根结点,具体看下代码中注释
输出堆顶元素,用堆的最后一个元素替代输出堆顶元素所空出来的位置,调整剩余元素为新的堆结构,如此循环,直至输出所有元素,得到有序序列,具体调整过程如下:
用堆的最后一个元素替代输出堆顶元素所空出来的位置,从堆顶开始向下调整新的结构为堆
可以证明:调整的过程,堆顶元素走的路线一定是一条从根到某叶子结点的唯一路线
具体看下代码中注释
堆改进,增加删除方法,使其成为类似二叉排序树的一种存储结构
删除方法
情况1:如果删除的是最末元素,直接删除
情况2:如果删除的元素既没有左孩子又没有右孩子,则用最末元素与该元素交换,删除最末元素,从原最末元素现所在索引开始向上调整新结构为堆结构
情况3:如果删除的元素有左孩子或者有右孩子或者同时具有左右孩子,则:
大顶堆:交换该元素与其左右孩子中的较大者,一直到该元素被交换成为一个叶子结点,则就是情况2了
小顶堆:交换该元素与其左右孩子中的较小者,一直到该元素被交换成为一个叶子结点,则就是情况2了
堆排序代码
public class HeapSort { /* * How to run the "adjuster" method on the given array just with one extra Integer space? * * Can you give another solution? */ public static void hahahaha(int[] array) { build(array); int max = array[0]; for (int j = array.length - 1; j > 0; j --) { adjuster(array, j); array[j] = array[0]; } array[0] = max; } private static void build(int[] array) { int index = array.length / 2; while (index > 0) { if (index * 2 + 1 < array.length) { if (index * 2 + 2 < array.length) { if (array[index * 2 + 1] > array[index * 2 + 2]) { if (array[index] < array[index * 2 + 1]) { Util.swap(array, index, index * 2 + 1); } } else { if (array[index] < array[index * 2 + 2]) { Util.swap(array, index, index * 2 + 2); } } } else { if (array[index] < array[index * 2 + 1]) { Util.swap(array, index, index * 2 + 1); } } } int innerIndex = index; while (innerIndex != 0) { if (array[innerIndex] > array[(innerIndex - 1) / 2]) { Util.swap(array, innerIndex, (innerIndex - 1) / 2); int temp = innerIndex; while (temp * 2 + 1 < array.length) { if (temp * 2 + 2 < array.length) { if (array[temp * 2 + 1] > array[temp * 2 + 2]) { if (array[temp] < array[temp * 2 + 1]) { Util.swap(array, temp, temp * 2 + 1); temp = temp * 2 + 1; } else { break; } } else { if (array[temp] < array[temp * 2 + 2]) { Util.swap(array, temp, temp * 2 + 2); temp = temp * 2 + 2; } else { break; } } } else { if (array[temp] < array[temp * 2 + 1]) { Util.swap(array, temp, temp * 2 + 1); } break; } } innerIndex = (innerIndex - 1) / 2; } else { break; } } index --; } } private static void adjuster(int[] array, int toIndex) { array[0] = array[toIndex]; int index = 0; while (index * 2 + 1 < toIndex) { if (index * 2 + 2 < toIndex) { if (array[index * 2 + 1] > array[index * 2 + 2]) { if (array[index] < array[index * 2 + 1]) { Util.swap(array, index, index * 2 + 1); index = index * 2 + 1; } else { break; } } else { if (array[index] < array[index * 2 + 2]) { Util.swap(array, index, index * 2 + 2); index = index * 2 + 2; } else { break; } } } else { if (array[index] < array[index * 2 + 1]) { Util.swap(array, index, index * 2 + 1); } break; } } } }
改进堆为类似二叉排序树的代码
public class MaxBinarySortHeap { private transient int size; private transient List<Integer> maxRootHeap; private transient Map<Integer, Integer> equivalientStatisticsMap; public MaxBinarySortHeap() { this.size = 0; this.maxRootHeap = new ArrayList<Integer>(); this.equivalientStatisticsMap = new HashMap<Integer, Integer>(); } public int size() { return size; } public int[] getHeapSequence() { int[] resultArray = new int[maxRootHeap.size()]; int index = 0; for (Integer element : maxRootHeap) { resultArray[index ++] = element; } return resultArray; } public void build(int[] array) { maxRootHeap.clear(); equivalientStatisticsMap.clear(); size = 0; for (int i = 0; i < array.length; i++) { add(array[i]); } } public void add(int element) { size ++; if (!(maxRootHeap != null && maxRootHeap.contains(element))) { maxRootHeap.add(element); int index = maxRootHeap.size() - 1; while (index > 0 && maxRootHeap.get(index) > maxRootHeap.get((index - 1) / 2)) { Util.swap(maxRootHeap, index, (index - 1) / 2); index = (index - 1) / 2; } } else { if (equivalientStatisticsMap.containsKey(element)) { equivalientStatisticsMap.put(element, equivalientStatisticsMap.get(element) + 1); } else { equivalientStatisticsMap.put(element, 1); } } } public boolean delete(int element) { if (!(maxRootHeap != null && maxRootHeap.contains(element))) { return false; } else { size --; if (equivalientStatisticsMap.containsKey(element)) { size -= equivalientStatisticsMap.get(element); equivalientStatisticsMap.remove(element); } int index = Util.getIndexByElement(maxRootHeap, element); if (index == maxRootHeap.size() - 1) { maxRootHeap.remove(index); return true; } else if (index * 2 + 1 >= maxRootHeap.size() && index * 2 + 2 >= maxRootHeap.size()) { Util.swap(maxRootHeap, index, maxRootHeap.size() - 1); maxRootHeap.remove(maxRootHeap.size() - 1); innerMaxRootHeapAdjuster(maxRootHeap, index); return true; } else { while (index * 2 + 1 < maxRootHeap.size()) { if (index * 2 + 2 < maxRootHeap.size()) { if (maxRootHeap.get(index * 2 + 1) > maxRootHeap.get(index * 2 + 2)) { Util.swap(maxRootHeap, index, index * 2 + 1); index = index * 2 + 1; } else { Util.swap(maxRootHeap, index, index * 2 + 2); index = index * 2 + 2; } } else { Util.swap(maxRootHeap, index, index * 2 + 1); maxRootHeap.remove(index * 2 + 1); return true; } } Util.swap(maxRootHeap, index, maxRootHeap.size() - 1); maxRootHeap.remove(maxRootHeap.size() - 1); innerMaxRootHeapAdjuster(maxRootHeap, index); return true; } } } public int[] sort() { int[] resultArray = new int[size]; if (maxRootHeap.size() <= 2) { int index = 0; for (Integer element : maxRootHeap) { resultArray[index ++] = element; if (equivalientStatisticsMap.containsKey(element)) { int temp = equivalientStatisticsMap.get(element); while (temp -- > 0) { resultArray[index ++] = element; } } } return resultArray; } else { List<Integer> tempList = new ArrayList<Integer>(maxRootHeap.size()); for (Integer element : maxRootHeap) { tempList.add(element); } int index = 0; for (int i = 0; i < maxRootHeap.size(); i ++) { maxRootHeapSortingAdjuster(maxRootHeap, i); resultArray[index ++] = maxRootHeap.get(i); if (equivalientStatisticsMap.containsKey(maxRootHeap.get(i))) { int temp = equivalientStatisticsMap.get(maxRootHeap.get(i)); while (temp -- > 0) { resultArray[index ++] = maxRootHeap.get(i); } } } maxRootHeap = tempList; return resultArray; } } /* * According to the given index of the element which destory the max root heap's structure, * adjust the sequence to a max root heap. * * Note : the given index must be of an element which is a leaf node of the complete binary tree. */ private void innerMaxRootHeapAdjuster(List<Integer> list, int index) { while (index != 0) { if (list.get(index) > list.get((index - 1) / 2)) { Util.swap(list, index, (index - 1) / 2); index = (index - 1) / 2; } else { break; } } } private void maxRootHeapSortingAdjuster(List<Integer> list, int fromIndex) { list.add(fromIndex, list.get(list.size() - 1)); list.remove(list.size() - 1); int index = 0; while (index * 2 + 1 + fromIndex < list.size()) { if (index * 2 + 2 + fromIndex < list.size()) { if (list.get(index * 2 + 1 + fromIndex) > list.get(index * 2 + 2 + fromIndex)) { if (list.get(index + fromIndex) < list.get(index * 2 + 1 + fromIndex)) { Util.swap(list, index + fromIndex, index * 2 + 1 + fromIndex); index = index * 2 + 1; } else { break; } } else { if (list.get(index + fromIndex) < list.get(index * 2 + 2 + fromIndex)) { Util.swap(list, index + fromIndex, index * 2 + 2 + fromIndex); index = index * 2 + 2; } else { break; } } } else { if (list.get(index + fromIndex) < list.get(index * 2 + 1 + fromIndex)) { Util.swap(list, index + fromIndex, index * 2 + 1 + fromIndex); index = index * 2 + 1; } else { break; } } } } }
PS:
Code还是去年工作半年闲暇时码的,目的是为了弥补自己本科期间没有动手写这些基础code的缺憾
总是工作时抽闲写一点,注释较少,望君手下留情
相关文章推荐
- 排序专题(八) / 不稳定的内部排序 / 希尔(shell)排序
- 排序专题(一) / 稳定的内部排序
- 排序专题(二) / 稳定的内部排序 / 二叉排序树
- 排序专题(三) / 稳定的内部排序 / 递归的2-路归并排序
- 排序专题(五) / 不稳定的内部排序 / 梳排序(Comb Sort)
- 排序专题(六) / 不稳定的内部排序 / 递归 | 非递归的快速排序
- 排序专题(七) / 不稳定的内部排序 / 选择排序
- 选择排序、快速排序、希尔排序、堆排序不是稳定的排序算法,而冒泡排序、插入排序、归并排序和基数排序
- 选择排序、快速排序、希尔排序、堆排序不是稳定的排序算法,而冒泡排序、插入排序、归并排序和基数排序是稳定的排序算法。
- java五种内部排序(直接插入排序、希尔排序、快速排序、堆排序、归并排序)
- 选择排序、快速排序、希尔排序、堆排序不是稳定的排序算法
- 选择排序、快速排序、希尔排序、堆排序不是稳定的排序算法,而冒泡排序、插入排序、归并排序和基数排序是稳定的排序算法。
- C++代码,数据结构-内部排序-选择排序-堆排序
- 十大基础排序 · 六 --- 堆排序(不稳定)
- 六、内部排序综合(九种)—插入类排序(直接插入、折半插入、希尔排序);交换类排序(冒泡、快速);选择类排序(简单选择、堆排序);二路归并排序;基数排序
- 数据结构_内部排序_希尔排序_快速排序_堆排序_归并排序_地址排序
- 选择排序、快速排序、希尔排序、堆排序不是稳定的排序算法, 冒泡排序、插入排序、归并排序和基数排序是稳定的排序算法。
- 不稳定排序之选择、快速、希尔以及堆排序
- 数据结构与算法专题之查找与排序——堆排序、桶排序
- 内部排序 稳定排序 不稳定排序