heap堆结构以及堆排序

堆的定义

堆(heap)是计算机科学中一类特殊的数据结构的统称。堆通常是一个可以被看做一棵树的数组对象。堆总是满足下列性质:

  • 堆中某个结点的值总是不大于或不小于其父结点的值;

  • 堆总是一棵完全二叉树。

将根结点最大的堆叫做最大堆或大根堆,根结点最小的堆叫做最小堆或小根堆。常见的堆有二叉堆、斐波那契堆等。

堆是非线性数据结构,相当于一维数组,有两个直接后继。

堆使用数组保存

使用一个一维数组保存堆数据

堆顶位于index0位置,i位置的左孩子位于 2*i+1位置,右孩子位于2*i+2位置,父位置位于(i-1)/2位置

堆操作

pop:弹出堆顶元素并删除

push:加入一个元素

peek:查看堆顶元素

heapify:从index位置,往下看,不断的下沉,直到到达或者堆最后位置

heapInsert:新加进来的数,现在停在了index位置,依次往上移动直到适合位置结束

code

/**
 * title:Heap
 * description: 堆介绍及其相关操作
 * date: 2023/9/8
 * author: fooleryang
 * version: 1.0
 */

/**
 *  堆 也叫优先级队列,是一颗 完全二叉树,其次,堆分为大根堆和小根堆
 *      完全二叉树
 *
 *      大根堆:
 *          子树的根节点是子树的最大值
 *      小根堆:
 *          子树的根节点是子数的最小值
 *  使用一个数组来表示一个堆
 *
 *  堆操作:
 *      heapInsert:新加进来的数,现在停在了index位置,依次往上移动直到适合位置结束
 *      heapify:从index位置,往下看,不断的下沉,直到到达或者堆最后位置
 *      peek:查看堆顶元素
 *      pop:弹出堆顶元素并删除
 *      push:加入一个元素
 *  使用一个数组记录堆
 *  用heapSize控制堆大小,heapSize =0说明堆为空
 *
 */
public  class Heap {
    //大根堆
    //数组存放
   private int [] heap;
   //堆数据最大存储量
   private  final int limit;
   //堆大小
   private int heapSize;

   public Heap(int limit){
       this.limit  = limit;
       heap = new int[limit];
       heapSize = 0;
   }

   public boolean isEmpty(){
       return heapSize == 0;
   }
   public boolean isFull(){
       return heapSize == limit;
   }
   private void swap(int [] arr,int l,int r){
       int temp = arr[l];
       arr[l] = arr[r];
       arr[r] = temp;
   }
   /**
    * 功能:
    *   堆插入时调整
    *   即: 在arr数组中,插入元素的位置为index,现需要进行调整
    * 参数:
    *   arr:待调整的堆
    *   index:待调整位置
    * 步骤:
    *   如果当前index位置和父位置 (index-1)/2 比较
    *   大于则交换,继续比较,直到小于等于或者到达堆顶位置即0位置
    *   小于等于则停止
    * 关于 (index -1) /2
    *   完全二叉树的求父节点位置应该是 index/2
    *   但是这是建立在index从1开始时
    *   如果index从0开始,那么就是 (index -1) /2
    *   1,2,3,4,5,6,7 => index=7的父节点index => 7/2 = 3
    *   0,1,2,3,4,5,6 => index=5的父节点index => (6-1)/2 = 2
    */
   private void heapInsert(int [] arr,int index){

       //结束条件包含:
       //arr[index] == 父节点value
       //index 达到0位置  arr[0] arr[(0-1)/2 = 0]
       while (arr[index] > arr[(index -1) / 2]){
           swap(arr,index,(index-1)/2);
           index = (index -1) /2;
       }
   }
   /**
    * @Description: 从index开始节点下沉,直到index位置的孩子都比index小,或者已经没有孩子
    * @Param arr:堆
    * @Param index:开始下沉节点
    * @Param heapSize:堆大小
    * 关于孩子节点:
    *   完全二叉树,左孩子 2*i +1,右孩子 2*i+2
    */
   private void heapify(int [] arr,int index,int heapSize){
       //左孩子index
       int leftChildIndex = 2 * index + 1;
       //遍历条件:index还有孩子
       while (leftChildIndex < heapSize){
           //比较index的左右孩子,记录得到最大左右孩子的index
           //右孩子存在 并且 右孩子大于左孩子,返回右孩子index,否则返回左孩子index
           //右孩子不存在 返回左孩子index
           int largestIndex = leftChildIndex+1

构建堆图示

1. 已经构建好的堆

heap堆结构以及堆排序_第1张图片

2. 新加入元素

heap堆结构以及堆排序_第2张图片

3. 弹出堆顶元素

heap堆结构以及堆排序_第3张图片

堆排序

时间复杂度 O(N*logN)

思路

待排序数组arr,将arr视作一个待排序的堆

先将arr构建成一个大根堆

再将大根堆arr调整为从小到大的顺序

构建大根堆过程
思路一

遍历arr数组

index = 0,arr 数组 0到index=0范围视为一个大根堆

index = 1,视为在0-0的大根堆加入一个元素,进行heapInsert操作

index = i,视为在0-(index-1)的大根堆加入元素

直到数组全部加入

for (int i = 0; i < arr.length; i++) {
    hearInsert(arr,i);
}
思路二

arr数组视为一个待排序堆,叶子结点分别构成的子树只有一个元素,天然为一个大根堆

依次往上构建的堆,则是需要调整,即新子树除了根节点其余子树都是大根堆,需要调整新子树的根节点,进行下沉调整

直到所有子树构建成一颗子树

如下图过程

1. 叶子结点分别为一个大根堆

heap堆结构以及堆排序_第4张图片

2. 加入上层节点,需要进行下沉调整

heap堆结构以及堆排序_第5张图片

3. 下沉调整

heap堆结构以及堆排序_第6张图片

4. 继续步骤二 ,直到所有节点进入堆中

将大根堆数组调整到大小顺序
思路

将堆顶元素和最后一个元素交换,此时最大元素到达数组最后位置

将最后一个元素剔除堆

此时堆顶元素为待调整的元素,进行下沉调整

反复直到堆元素为空

heap堆结构以及堆排序_第7张图片

int heapSize = arr.length;
do{
  swap(arr,0,--heapSize);// 等价与 swap(arr,0,heapSize-1);heapSize--;
  heapify(arr,0,heapSize);
}while (heapSize > 0);
code
    public static void heapSort(int [] arr){
        if(arr == null || arr.length < 2)return;

        //构建大根堆

        //从index = 0 开始
        //index = 0 ,则0-0为一个大根堆
        //inedx = 1,则将index=1插入到0-0堆中
        //index = i,则将index=i插入到0 到 (i-1)的堆中
        // O(N*logN)
//        for (int i = 0; i < arr.length; i++) {
//            hearInsert(arr,i);
//        }

        //或者这样调整,该步骤时间复杂度降低到O(N)
        //给定的数组是一个堆,只是顺序不对
        //从最低层即叶子节点开始下沉调整
        //最开始,叶子节点构成的子树为大根堆
        //上一层时,叶子节点构成的子树新加入一个节点并且位于子数的根节点,需要进行下沉调整
        //依次进行
        //O(N)
        for (int i = arr.length-1;i >=0;i--){
            heapify(arr,i,arr.length);
        }

        //将大根堆数组排序

        //将堆顶元素与数组最后一个元素交换
        //堆大小减一
        //交换完成后将堆顶元素下沉调整堆
//        int heapSize = arr.length;
//        swap(arr,0,heapSize-1);
//        heapSize--;
//        while (heapSize >0){
//            //调整
//            heapify(arr,0,heapSize);
//            swap(arr,0,heapSize-1);
//            heapSize--;
//        }
        int heapSize = arr.length;
        do{
            swap(arr,0,--heapSize);// 等价与 swap(arr,0,heapSize-1);heapSize--;
            heapify(arr,0,heapSize);
        }while (heapSize > 0);
    }
    private static void swap(int [] arr,int l,int r){
        int temp = arr[l];
        arr[l] = arr[r];
        arr[r] = temp;
    }
    //向下调整
    public static void heapify(int [] arr,int index,int heapSize){
        int left = index * 2 + 1;
        while (left < heapSize){
            int largest = left+1 < heapSize && arr[left+1] > arr[left]?left+1:left;
            if(arr[largest] < arr[index])break;
            swap(arr,index,largest);
            index = largest;
            left = index * 2 + 1;
        }
    }

    //插入一个新元素,向上调整
    public static void hearInsert(int [] arr,int index){
        int fatherIndex = (index - 1) / 2;
        while (arr[index] > arr[fatherIndex]){
            swap(arr,index,fatherIndex);
            index = fatherIndex;
            fatherIndex = (index - 1) / 2;
        }
    }

你可能感兴趣的:(算法,算法,数据结构,排序算法,java)