二叉堆与排序

二叉堆

完全二叉树:
若设二叉树的深度为h,除第 h 层外,其它各层 (1~h-1) 的结点数都达到最大个数,第 h 层所有的结点都连续集中在最左边,这就是完全二叉树。
二叉堆本质上就是完全二叉树,分为最大堆和最小堆。
什么是最大堆呢?最大堆任何一个父节点的值,都大于等于它左右孩子节点的值。

什么是最小堆呢?最小堆任何一个父节点的值,都小于等于它左右孩子节点的值。
那我们如何构建一个堆呢?
基于堆的自我调整
对于二叉堆,如下有几种操作:

 - 插入节点
 - 删除节点
 - 构建二叉堆 

这几种操作都是基于堆的自我调整。
二叉堆虽然是一颗完全二叉树,但它的存储方式并不是链式存储,而是顺序存储。换句话说,二叉堆的所有节点都存储在数组当中。

我们思考一个这样的问题,我们如何把一个数字插入到二叉堆中,插入之后还是二叉堆呢?以小堆为例
插入—O(logn):二叉堆的节点插入,插入位置是完全二叉树的最后一个位置。如果我插入的数字比父节点还小,我就让这个数字向上,父节点向下。不用真正交换,因为它的父节点可能一路下移。

 public static void insertAdjust(int[] arr){
       int childIndex=arr.length-1;
       int parentIndex=(childIndex-1)/2;
       int insertVal=arr[childIndex];
       while(childIndex>0&&arr[parentIndex]>insertVal){
       //无需真正交换,单向赋值即可
           arr[childIndex]=arr[parentIndex];
           childIndex=parentIndex;
           parentIndex=(childIndex-1)/2;
       }
       arr[childIndex]=insertVal;
   } 

删除根节点用于堆排序。
注:无论插入还是删除,都是在已经是二叉堆的基础上,并要保证插入和删除完成还是二叉堆。

对于最大堆,删除根节点就是删除最大值;对于最小堆,是删除最小值。把堆存储的最后那个节点移到填在根节点处。再从上而下调整父节点与它的子节点。这样根节点就被删除了。这里的删除根节点和构建二叉堆是有关联的。

//删除根节点,就是对根节点做下沉
 public  static void deletAdjust(int[] arr){
       arr[0]=arr[arr.length-1];
        downAdjust(arr,0,arr.length-1);
   }
  /**
   * arr 待调整的堆
   * downIndex要下沉的节点索引值
   * validLength:数组的有效长度
   */
   public static void downAdjust(int[] arr,int downIndex,int validLength){
       //把要下沉的值保存在变量downVal
       int downVal=arr[downIndex];
       int childIndex=downIndex*2+1;
       while(childIndex<validLength){
           //找到子节点值最小的下标
            if(childIndex+1<validLength&&arr[childIndex+1]<arr[childIndex]){
                childIndex++;
            }
            //特别注意,不是判断arr[downIndex]<arr[childIndex],而是和那个特定的值比较
            if(downVal<arr[childIndex]){
                break;
            }
            arr[downIndex]=arr[childIndex];
            downIndex=childIndex;
            childIndex=downIndex*2+1;
       }
       arr[downIndex]=downVal;
   }

构建二叉堆—–O(n)

构建二叉堆,也就是把一个无序的完全二叉树调整为二叉堆,本质上就是让所有非叶子节点依次下沉。

  public  static void buildHeap(int[] arr){
        for(int i=(arr.length-2)/2;i>=0;i--){
            downAdjust(arr,i,arr.length);
        }
    }

特别说明:
如果仅从代码上直观观察,会得出构造二叉堆的时间复杂度为O(n㏒n)的结果,这个结果是错的,虽然该算法外层套一个n次循环,而内层套一个分治策略下的㏒n复杂度的循环,该思考方法犯了一个原则性错误,那就是构建二叉堆是自下而上的构建,每一层的最大纵深总是小于等于树的深度的,因此,该问题是叠加问题,而非递归问题。
这里写图片描述

参考:https://www.itcodemonkey.com/article/8660.html


二叉堆的使用—–堆排序

  1. 把无序数组构建成二叉堆。

  2. 堆顶元素和集合尾部交换后,删除堆顶元素。

public static void heapSort(int[] arr){
        //1
        buildHeap(arr);
        for(int i=arr.length-1;i>=0;i--){
            int temp=arr[i];
            arr[i]=arr[0];
            arr[0]=temp;
            //调用downAdjust以确保剩下的i个元素仍然是最小堆。
            downAdjust(arr,0,i);
        }
    }

小根堆每次把最小的元素交换到了最后面,用downAdjust对剩余的n-1个元素,生成一个小根堆。所以小根堆对应从大到小排序。
完整代码:

/**
 *
 * @author kaixin
 */
public class Heap {   
   /*
    * arr的最后一个元素就是插入的元素,我们向上悬浮调整 
    */
   public static void insertAdjust(int[] arr){
       int childIndex=arr.length-1;
       int parentIndex=(childIndex-1)/2;
       int insertVal=arr[childIndex];
       while(childIndex>0&&arr[parentIndex]>insertVal){
           arr[childIndex]=arr[parentIndex];
           childIndex=parentIndex;
           parentIndex=(childIndex-1)/2;
       }
       arr[childIndex]=insertVal;
   } 
  /**
   * arr 待调整的堆
   * downIndex要下沉的节点索引值
   * validLength:数组的有效长度
   */
   public static void downAdjust(int[] arr,int downIndex,int validLength){
       //把要下沉的值保存在变量downVal
       int downVal=arr[downIndex];
       int childIndex=downIndex*2+1;
       while(childIndex<validLength){
           //找到子节点值最小的下标
            if(childIndex+1<validLength&&arr[childIndex+1]<arr[childIndex]){
                childIndex++;
            }
            if(downVal<arr[childIndex]){
                break;
            }
            arr[downIndex]=arr[childIndex];
            downIndex=childIndex;
            childIndex=downIndex*2+1;
       }
       arr[downIndex]=downVal;
   }
   /**
    * 删除根节点元素的调整
    * @param arr 
    */
   public  static void deletAdjust(int[] arr){
       arr[0]=arr[arr.length-1];
        downAdjust(arr,0,arr.length-1);
   }
    public  static void buildHeap(int[] arr){
        for(int i=(arr.length-2)/2;i>=0;i--){
            downAdjust(arr,i,arr.length);
        }
    }
    public static void heapSort(int[] arr){
        buildHeap(arr);
        for(int i=arr.length-1;i>=0;i--){
            int temp=arr[i];
            arr[i]=arr[0];
            arr[0]=temp;
            downAdjust(arr,0,i);
        }
    }
   public static void main(String[] args) {
        int[] array = new int[] {1,3,2,6,5,7,8,9,10,0};
//        insertAdjust(array);
//        System.out.println(Arrays.toString(array));
//        array = new int[] {0, 1, 2, 6, 3, 7, 8, 9, 10, 5};
//        deletAdjust(array);
//        System.out.println(Arrays.toString(array));
//        array = new int[] {7,1,3,10,5,2,8,9,6};
//        buildHeap(array);
          array = new int[] {7,1,3,10,5,2,8,9,6};
          heapSort(array);
        System.out.println(Arrays.toString(array));
        }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值