堆排序--高级选择排序

堆排序介绍

堆排序是选择排序的一种,是简单选择排序的升级版,是一种高级的排序算法
需要先从二叉树_>完全二叉树->平衡树->大根堆这些开始补习。
因为堆排序应该不是直接由想排序而想到的,是先看到大顶堆和小顶堆才想到能应用于排序的。

堆:

heap
百度百科:
堆(英语:heap)是计算机科学中一类特殊的数据结构的统称。堆通常是一个可以被看做一棵树的数组对象。堆总是满足下列性质:
堆中某个节点的值总是不大于或不小于其父节点的值;
堆总是一棵完全二叉树。
堆一定要一定程度的有序,要么是arr[i] >= arr[2i+1] && arr[i] >= arr[2i+2] 要么是arr[i] <= arr[2i+1] && arr[i] <= arr[2i+2] ,也就是只有两种堆

大根堆/最大堆:
所有节点都比其左右子节点大,进而根节点是堆里所有关键节点中最大者
arr[i] >= arr[2i+1] && arr[i] >= arr[2i+2] ,小跟堆反之
小根堆/最小堆:
所有节点都比其左右子节点小,进而根节点是堆里所有节点最小者
arr[i] <= arr[2i+1] && arr[i] <= arr[2i+2]
一直有个疑惑,最大堆已经能直接排序了,其实并不是

只是所有节点有这个特性,树整体看起来并不是有序,的因为可能有N种排列组合达到这个目的,我们使用的递归查找也只不过是找到任意一个满足条件的解而已,最简单的,假设123三个数,构造成大根堆的时候3 2 1和3 1 2两种,都能满足大根堆的特性。所以先有最大堆,进而将堆顶元素,也就是最大值沉底,如此反复,得到有序的数据。这个找到最大值并沉底的操作是选择排序的套路,所以堆排序是高级的选择排序,只不过选择最大最小值的过程是通过构建大顶/小顶堆来实现的。

实现思路推演:

假设升序排序,逐个将最大值放到最后:
1、先将堆构造成一个大顶堆,这样序列的最大值就是堆顶的元素
2、将其与末尾元素交换,将最大值沉底。
3、然后将剩下的元素继续构造成大顶堆,如此反复
这一步有2个注意点:
1)边界,已经排序之后的元素,也就是经过一次次沉底的元素,不应该被继续拿来构建堆,注意这个逻辑的代码实现。
2)一旦下沉之后,其实是将一个较小值提到堆顶,这个时候要做的事情是从堆顶开始,逐步交换,将这个最小值交换到应该去的地方,这个只会牵动堆的某一条线,因为其它部分本身就是满足大顶堆的,所以只要某一条线满足大顶堆,整体一定满足大顶堆。
也就是第一步的构建大顶堆操作很耗时,后面的继续构建会快很多。

如何构造大顶堆:

1、从倒数第一个非叶子结点开始(因为每次的比较都是,节点和左右节点3个中找最小值并且互换的过程,没有必要考虑叶子节点,叶子节点会在父节点排序的时候考虑进来),最后一个非叶子节点的下标为(n-2)/2,因为最后一个节点为n-1,所以最后一个节点的父节点应该为(n - 1 - 1) /2。
1)假设某个节点下标为n,父节点是 (n-1)/2,这个结果如何得到?
我们先反过来看,假设父节点是n,那么左右节点分别是2*n+1和2*n+2,反过来父节点是(n-1)/2
2、第一步构建大顶堆的过程,需要一边向上构建,在上层节点发生交换之后,下面的节点可能因为交换下来的元素而不满足大顶堆特性,需要递归向下构建大顶堆。所以第一遍很耗时。

算法复杂度:

第一次构造大顶堆花费比较长的时间,但是之后每次交换之后重新构建大顶堆,只会行进树中的任一条路径,所以是O(logN)的算法,也就是找到最大值的时间复杂度为O(logN),要找n次,所以为O(nlogN)。这里和另外两种O(nlogN)的算法是有区别的,一趟排序是logn,一共需要n次。归并排序和快速排序是一趟排序n,一共需要logn次。
一趟排序的复杂度,利用堆特性能达到O(logn),是所有排序算法中最快的,其它的全部为O(n)
并且复杂度稳定在O(nlogn),不同乱序程度的数据,只是构建大根堆上有区别,构建大根堆之后,复杂度稳定在O(nlogn)

稳定性:

进行三个元素的比较,交换的时候,跨过了很多元素(从n到2*n+1和2*n+2),假设存在交换,后面的数会直接行进到前面位置,并不考虑中间有没有等于它的元素,所以不具有稳定性。

Java代码实现:

// 初始化大顶堆堆,把数组/列表数据,组装到成堆结构
private static int[] sort=new int[]{1,0,10,20,3,5,6,4,9,8,12,
        17,34,11};

public static void main(String[] args){
    heapify(sort);
    heapSort(sort);
    System.out.println(Arrays.toString(sort));
    //
}

private static void heapify(int[] data){
    // 构建堆
    // 获取初始节点,
    int startIndex = getParentIndex(sort.length - 1);
    // 从上到一半,构建大顶堆,只需要到一半就够了
    // 从底下往上构建,然后构建到上方,底下不满足的话,底部需要重新构建,这样最后会把最大值推到顶端,也就是为大顶堆
    // 这里只需要从倒数第二层开始,所以从1/2处循环
    // 为什么这里要循环,因为子节点并没有被找过,所以理论上应该都找一遍,从底下找到顶部
    // 因为不知道其它的是不是大顶堆,所以所有节点都要过一遍
    for(int i = startIndex; i >= 0;i--){
        maxHeapify(data, data.length, i);
    }
}

/**
 * 获取父节点index
 * 算法为length - 1 / 2
 * @return
 */
private static int getParentIndex(int current){
    // current - 1是最后下标
    return (current - 1 ) >> 1;
}

/**
 * 将index位置的节点和子节点(以及以下部分)构建成大顶堆
 * @param data
 * @param heapSize
 * @param index
 */
private static void maxHeapify(int[] data, int heapSize, int index){
    int left = getChildLeftIndex(index);
    int right = getChildRIghtIndex(index);

    int largest = index;
    // 判断左节点限定在一个范围内,因为超过这个范围的已经是经过了排序的沉底最大值,如果不加限制,这个最大值会再次参与排序,整个顺序就会乱掉。且左节点大于顶端,认为左节点是最大的
    if(left < heapSize && data[index] < data[left]){
        largest = left;
    }
    // 判断右节点限定在一个范围内,这个一方面限定不超过数组范围(初始化时,这个最后一个非叶子节点,可能只有左节点,另一方面限定排序范围,将已经排序后的不纳入排序范围),且右节点大于刚才的较大者,认为右节点最大
    // 也就是在顶、左、右三个元素中找最大值
    if(right < heapSize && data[largest] < data[right]){
        largest = right;
    }
    // 如果顶不是最大值,将顶和真正的最大值交换
    if(largest != index){
        int tmp = data[index];
        data[index] = data[largest];
        data[largest] = tmp;
        // 如果交换了,可能导致子节点不是最大堆了,需要继续将子节点构建为最大堆
        maxHeapify(data, heapSize, largest);
    }
}

private static int getChildLeftIndex(int current){
    return (current << 1) + 1;
}

private static int getChildRIghtIndex(int current){
    return (current << 1) + 2;
}

private static void heapSort(int[] data){
    // 构建成大顶堆之后,交换堆顶元素和末尾元素,然后使用余下的数据,继续构建大顶堆,然后继续交换,直到全部找完
    for(int i = data.length-1;i>=0;i--){
        int tmp = data[0];
        data[0] = data[i];
        data[i] = tmp;
        // i每一步都会变小,然后就自动将大于i的元素,也就是排过序的元素忽略掉,进行大顶堆构建
        // 将定点进行比较,重新构建大顶堆
        // 这里只需要从顶部往下,将有影响的修改即可,因为其它没被走到的,已经是大顶堆了
        maxHeapify(data, i, 0);
    }
}

猜你喜欢

转载自blog.csdn.net/u011531425/article/details/80530950