文章目录

  • 算法简介
  • Java 实现
  • 时间复杂度
  • 空间复杂度
  • 算法稳定性

 

算法简介

二叉堆

一棵完全二叉树,对于大顶堆来说,任何一个结点都要大于等于它左右孩子结点,对于小顶堆,任何一个结点都要小于等于它的左右孩子。

二叉堆与数组的联系

二叉堆一般存储在数组中,有这样的性质,如果我们把二叉堆按照从上到下,从左到右的顺序依次存进数组,如果 index 是某个结点的下标值,那么它左右孩子结点的下标值分别是2*index+1和2*index+2,不信的筒子们可以尝试写到数组中看看规律。我在数组中怎么知道这个下标的值是右结点还是左结点呢?我们减去 2 除以 2 除得尽就是右结点,否则是左结点,并且其父节点的下标也可以确定了。这个数组保存着这个二叉堆的所有信息

二叉堆的增删

二叉堆的新增和删除,我们会把一个结点新增到最下一层的最后面,然后与父节点比较,进而上浮或者不动;对于删除来讲,假如我们删除某一个中间结点,我们就需要用最尾部结点对删掉的节点位置进行补位,补上之后,再对这个补位的结点和最小(大)的直接子结点比较,进而选择下沉或不动

堆排序

我们利用二叉堆,上浮下沉的性质每次找到最大的结点上浮到顶端,然后我们再保存到最后面,这样就可以从小到大排序了!详细的说堆排序就是先创建大顶堆或小顶堆,然后把这个堆根和最尾部交换位置,将除了尾部的继续构成堆,这样不断循环就可以实现堆排序了

上浮还是下沉?

尾部添加结点时候是上浮操作,上浮比较简单,因为孩子结点只有一个双亲;删除结点是下沉操作,下沉麻烦一点,因为一个双亲结点有两个孩子结点,下沉要比较两个孩子

构建二叉堆

无序序列,非叶子结点开始往前遍历,每次遍历中,将该结点选择性下沉,这样遍历完就构建起了一个二叉堆

二叉堆的最后一个非叶子节点怎么求?

(arr.length-1-1)/2

Java 实现

思路

利用二叉堆的存入数组的性质很容易实现堆排序,我们一起来写一下吧

// 二叉堆尾部添加结点之后的自行调整
public static void upAdjust(int[] array) {
    int childIndex = array.length-1;
    int parentIndex = (childIndex-1)/2;
    // temp保存插入的叶子节点值,用于最后的赋值
    int temp = array[childIndex];
    while (childIndex > 0 && temp < array[parentIndex])
    {
        //无需真正交换,单向赋值即可
        array[childIndex] = array[parentIndex];
        childIndex = parentIndex;
        parentIndex = (parentIndex-1) / 2;
    }
    array[childIndex] = temp;
}

// 二叉堆删掉结点之后的自行调整
public static void downAdjust(int[] array, int parentIndex, int length) {
    // temp保存父节点值,用于最后的赋值
    int temp = array[parentIndex];
    int childIndex = 2 * parentIndex + 1;
    while (childIndex < length) {
        // 如果有右孩子,且右孩子小于左孩子的值,则定位到右孩子
        if (childIndex + 1 < length && array[childIndex + 1] < array[childIndex]) {
            childIndex++;
        }
        // 如果父节点小于任何一个孩子的值,直接跳出
        if (temp <= array[childIndex])
            break;
        //无需真正交换,单向赋值即可
        array[parentIndex] = array[childIndex];
        parentIndex = childIndex;
        childIndex = 2 * childIndex + 1;
    }
    array[parentIndex] = temp;
}

// 构造小顶堆
public static void getHeap(int[] array) {
    // 从最后一个非叶子节点开始,依次下沉调整
    for (int i = array.length / 2; i >= 0; i--) {
        downAdjust(array, i, array.length - 1);
    }
}

// 堆排序(从大到小)
public static void main(String[] args) {
    int[] array = new int[] {1,3,2,6,5,7,8,9,10,0};
    upAdjust(array);
    System.out.println(Arrays.toString(array));
    array = new int[] {7,1,3,10,5,2,8,9,6};
    buildHeap(array);
    System.out.println(Arrays.toString(array));
}
时间复杂度

时间复杂度为 O(n*logn)

空间复杂度

因为只用到有限的变量,并且是数组就地排序,空间复杂度为 O(1)

算法稳定性

堆排序一般来说是不稳定的排序算法