改进排序算法:堆排序(对简单选择排序的改进)
2017-11-07 20:02
429 查看
简单选择排序:在待排序的n个记录中选择一个最小的记录需要比较n-1次。但是并没有把每一次的比较结果保存下来,在后一次的比较中,有许多比较在前一次已经做过了,但由于前一次排序时未保存这些比较结果,所以后一次排序时又重复执行了这些比较操作,因而比较次数较多。
堆排序:每次在选择到最小记录的同时,根据比较结果对其他记录做出相应的调整,使得排序的总体效率变高。
大顶堆:每个节点的值都大于或等于其左右孩子结点的值
小顶堆:每个节点的值都小于或等于其左右孩子结点的值
按照层序遍历的方式,给结点从1开始编号,则结点之间满足:
ki>=k2i,ki>=k2i+1或ki<=k2i,ki<=k2i+1,其中1<=i<=[n/2]
一颗完全二叉树,如果i=1,则结点i是二叉树的根,无双亲;如果i>1,其双亲是结点[i/2]。
堆排序:利用堆进行排序的方法(假设利用大顶堆)。
将待排序的序列构造成一个大顶堆。整个序列的最大值就是堆顶的根节点,将它移走(将其与堆数组的末尾元素交换,此时末尾元素是最大值),然后将剩余n-1个序列重新构造成一个堆,这样就会得到n个元素中的次大值。如此反复。
如何将待排序的序列构建成一个大顶堆:从下往上,从右往左,将每个非终端节点(非叶节点)当做根节点,将其和其子树调整成大顶堆。
在JAVA中,数组下标从0开始,若将一个完全二叉树按照层序遍历的方式存入数组中,节点i的左孩子为2i+1,右孩子为2i+2,有孩子的结点范围为0到arr.length/2 - 1。
从小到大排序,建立一个大顶堆;从大到小排序,建立一个小顶堆
堆排序复杂度分析:
时间复杂度:
运行时间主要消耗在初始构建堆和在重建堆的反复筛选上。
在构建堆的过程中,完全二叉树从最下层最右边的非终端节点开始构建的,将它与其孩子节点进行比较和若有必要的互换。对于每个非终端节点,最多进行两次比较和互换操作。构建堆的时间复杂度为O(n)。
在正式排序时,第i次取堆顶记录重建堆需要用O(logi)的时间(完全二叉树的某个节点到根节点的距离为[logi] + 1),并且需要取n-1次堆顶记录。重建堆的时间复杂度为O(nlogn)。
总体来说,堆排序的时间复杂度为O(nlogn)。
并且堆排序对原始记录的排序状态并不敏感,无论最好或最坏或平均时间复杂度均为O(nlogn)。远优于冒泡、简单选择和直接插入的O(n^2)的时间复杂度。
空间复杂度:
只有一个用来交换的暂存单元。
由于记录的比较和交换是跳跃式进行,堆排序也是一种不稳定的排序方法。
由于初始构建堆所需的比较次数较多,所以不适合待排序序列个数较少的情况。
堆排序:每次在选择到最小记录的同时,根据比较结果对其他记录做出相应的调整,使得排序的总体效率变高。
大顶堆:每个节点的值都大于或等于其左右孩子结点的值
小顶堆:每个节点的值都小于或等于其左右孩子结点的值
按照层序遍历的方式,给结点从1开始编号,则结点之间满足:
ki>=k2i,ki>=k2i+1或ki<=k2i,ki<=k2i+1,其中1<=i<=[n/2]
一颗完全二叉树,如果i=1,则结点i是二叉树的根,无双亲;如果i>1,其双亲是结点[i/2]。
堆排序:利用堆进行排序的方法(假设利用大顶堆)。
将待排序的序列构造成一个大顶堆。整个序列的最大值就是堆顶的根节点,将它移走(将其与堆数组的末尾元素交换,此时末尾元素是最大值),然后将剩余n-1个序列重新构造成一个堆,这样就会得到n个元素中的次大值。如此反复。
如何将待排序的序列构建成一个大顶堆:从下往上,从右往左,将每个非终端节点(非叶节点)当做根节点,将其和其子树调整成大顶堆。
在JAVA中,数组下标从0开始,若将一个完全二叉树按照层序遍历的方式存入数组中,节点i的左孩子为2i+1,右孩子为2i+2,有孩子的结点范围为0到arr.length/2 - 1。
从小到大排序,建立一个大顶堆;从大到小排序,建立一个小顶堆
import java.util.Arrays; public class Solution { public static void main(String[] args) { Solution s = new Solution(); int[] arr = {4,5,1,6,2,7,3,8,8,4}; s.HeapSort(arr); System.out.println(Arrays.toString(arr)); //输出:[1, 2, 3, 4, 4, 5, 6, 7, 8, 8] } private void HeapSort(int[] arr) { for (int i = (arr.length - 2) / 2; i >= 0; i--) //从0到(arr.length - 2) / 2都是有孩子的节点。第一个循环将待排序序列构建成一个大顶堆。 HeapAdjust(arr, i, arr.length); for (int i = arr.length - 1; i >= 0; i--) //第二个循环逐步将每个最大值的根节点与末尾节点交换,并且在调整其成为大顶堆。 { swap(arr, 0, i ); HeapAdjust(arr, 0, i); } } private void HeapAdjust(int[] arr, int s, int m) //m表示数组长度 { int temp = arr[s]; int j = 2 * s + 1; for (; j < m; j = 2 * j + 1) //数组下标从0开始,第i个节点的左孩子为2i+1,右孩子为2i+2 { if (j < m - 1 && arr[j] < arr[j + 1]) ++j; if (temp >= arr[j]) break; arr[s] = arr[j]; s = j; } arr[s] = temp; } private void swap(int[] arr, int i, int j) { int temp = arr[i]; arr[i] = arr[j]; arr[j] = temp; } }
堆排序复杂度分析:
时间复杂度:
运行时间主要消耗在初始构建堆和在重建堆的反复筛选上。
在构建堆的过程中,完全二叉树从最下层最右边的非终端节点开始构建的,将它与其孩子节点进行比较和若有必要的互换。对于每个非终端节点,最多进行两次比较和互换操作。构建堆的时间复杂度为O(n)。
在正式排序时,第i次取堆顶记录重建堆需要用O(logi)的时间(完全二叉树的某个节点到根节点的距离为[logi] + 1),并且需要取n-1次堆顶记录。重建堆的时间复杂度为O(nlogn)。
总体来说,堆排序的时间复杂度为O(nlogn)。
并且堆排序对原始记录的排序状态并不敏感,无论最好或最坏或平均时间复杂度均为O(nlogn)。远优于冒泡、简单选择和直接插入的O(n^2)的时间复杂度。
空间复杂度:
只有一个用来交换的暂存单元。
由于记录的比较和交换是跳跃式进行,堆排序也是一种不稳定的排序方法。
由于初始构建堆所需的比较次数较多,所以不适合待排序序列个数较少的情况。
相关文章推荐
- 常见的五类排序算法图解和实现(选择类:简单选择排序,锦标赛排序,树形选择排序,堆排序)
- 排序算法java版,速度排行:冒泡排序、简单选择排序、直接插入排序、折半插入排序、希尔排序、堆排序、归并排序、快速排序
- 选择排序(简单选择排序--改进的简单选择排序--堆排序)
- 常见的五类排序算法图解和实现(选择类:简单选择排序,锦标赛排序,树形选择排序,堆排序)
- 漫谈经典排序算法:一、从简单选择排序到堆排序的深度解析
- 排序算法总结(简单选择排序、堆排序)(python实现)
- 排序算法系列-堆排序-快速排序-基数排序-简单选择排序-归并排序
- 排序算法(四)、选择排序 —— 简单选择排序 和 堆排序
- 漫谈经典排序算法:一、从简单选择排序到堆排序的深度解析
- 排序算法合集(插入排序,折半插入排序,希尔排序,冒泡排序,快速排序,简单选择排序,堆排序,归并排序)
- 漫谈经典排序算法:一、从简单选择排序到堆排序的深度解析
- 排序算法java版,速度排行:冒泡排序、简单选择排序、直接插入排序、折半插入排序、希尔排序、堆排序、归并排序、快速排序
- 漫谈经典排序算法:一、从简单选择排序到堆排序的深度解析
- 6种排序算法及其比较 简单选择排序,堆排序,简单插入排序,希尔排序,冒泡排序,快速排序,归并排序
- 排序算法--选择排序(简单选择排序、堆排序)java实现
- 【Java常用排序算法】选择排序(简单选择排序、堆排序)
- 漫谈经典排序算法:一、从简单选择排序到堆排序的深度解析
- 排序算法(1):简单选择排序和堆排序
- C语言实现基本排序算法----排序(直接插入排序,SHELL排序,冒泡排序,快速排序,简单选择排序,堆排序)
- 排序算法(二)选择类排序:简单选择排序,堆排序,锦标赛排序