改进排序算法:堆排序(对简单选择排序的改进)

来源:互联网 发布:卫视直播软件哪个好 编辑:程序博客网 时间:2024/06/06 04:59

简单选择排序:在待排序的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。

从小到大排序,建立一个大顶堆;从大到小排序,建立一个小顶堆

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)的时间复杂度。

空间复杂度:

只有一个用来交换的暂存单元。


由于记录的比较和交换是跳跃式进行,堆排序也是一种不稳定的排序方法。

由于初始构建堆所需的比较次数较多,所以不适合待排序序列个数较少的情况。


原创粉丝点击