堆及堆排序应用

发布于:2023-01-17 ⋅ 阅读:(457) ⋅ 点赞:(0)


前言

堆结构、堆排序及其应用


一、堆结构

堆结构是一个数组对象,就是一颗特殊的完全二叉树或是一颗特殊的满二叉树。
堆分为大根堆小根堆

满二叉树:

在这里插入图片描述

完全二叉树:()

在这里插入图片描述


某个节点i

  • 左孩子节点下标索引:i*2+1
  • 右孩子节点下标索引:i*2+2
  • 父节点下标索引:(i-1)/2

1、大根堆

每一棵子树的最大值为头节点的值,就为大根堆

如下

在这里插入图片描述

(1)向上调整(heapInsert)

调整某个位置能否往上移动

代码如下:

// 某个数在index位置能否往上移动
    public static void heapInsert(int[]arr,int index){
        while(arr[index]>arr[(index-1)/2]){
            swap(arr,index,(index-1)/2);
            index=(index-1)/2;
        }
    }

(2)向下调整(heapify)

某个位置能否往下移动。
返回最大值并移除:最大值就是第一个值,用最后边那个数代替头节点,整体堆长度减小一,此时需要对这个头节点进行调整,在子孩子中找到最大的再与头节点比较,如果头节点小于子孩子就交换位置,以此类推当子孩子不大于头结点或者没有子孩子时,停止,此时为大根堆。

调整代价为:O(logN)

代码如下:

    // 某个数在Index位置,能否往下移动
    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;
            //比较父和子哪个值大,值大的记录
            largest=arr[largest]>arr[index]?largest:index;
            if (largest==index){ // 符合跳出循环
                break;
            }
            swap(arr,index,largest);
            index=largest;
            left=index*2+1;
        }
    }

(3)合并应用

当改变其中某个值,可以分别使用向下调整和向上调整,调整过后得到大根堆。

调整代价为:O(logN)

2、小根堆

每一棵子树的最小值为头节点的值,就为小根堆,和大根堆刚好相反。

如下

在这里插入图片描述

二、堆排序

1.过程

不断的从0~1,0 ~2…做到大根堆或小根堆,heapSize为堆长度,每一次新进来个数heapSize++,最终得到堆,然后将最大的与最后一个位置交换,heapSize–,就将最大位置排在了最后并剔除了堆,利用向下调整得到堆,再重复上述步骤,最终heapSize为0时,这个数组就有序了。

时间复杂度为:O(NlogN)
额外空间复杂度为:O(1)

代码如下:

public class bigRootPile {

    public static void main(String[] args) {
        int []arr={2,4,5,1,8,2,9,4,3,6};
        heapSort(arr);
        System.out.println(Arrays.toString(arr));
    }
    public static void heapSort(int[]arr){
        if (arr==null||arr.length<2){
            return;
        }
        // O(NlogN)
        // 可优化
        for (int i=0;i<arr.length;i++){
            heapInsert(arr,i);
        }
        int heapSize=arr.length;
        swap(arr,0,--heapSize);
        while (heapSize>0){
            heapify(arr,0,heapSize);// O(logN)
            swap(arr,0,--heapSize);//O(1)
        }
    }
    // 某个数在index位置能否往上移动
    public static void heapInsert(int[]arr,int index){
        while(arr[index]>arr[(index-1)/2]){
            swap(arr,index,(index-1)/2);
            index=(index-1)/2;
        }
    }
    // 某个数在Index位置,能否往下移动
    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;
            //比较父和子哪个值大,值大的记录
            largest=arr[largest]>arr[index]?largest:index;
            if (largest==index){ // 符合跳出循环
                break;
            }
            swap(arr,index,largest);
            index=largest;
            left=index*2+1;
        }
    }
    // 交换
    public static void swap(int[]arr,int index,int fIndex){
        int temp=arr[index];
        arr[index]=arr[fIndex];
        arr[fIndex]=temp;
    }
}

结果如下:
在这里插入图片描述

只让一个不为大根堆的变为大根堆可以将以下代码优化

 for (int i=0;i<arr.length;i++){
            heapInsert(arr,i);
        }

优化后:

  for (int i=arr.length-1;i>=0;i--){
            heapify(arr,i,arr.length);
        }

在最后一个数往下调整,做到依次局部大根堆,最终得到大根堆。

三、堆排序扩展

如果做到高效的进行堆结构的调整,就必须自己手写,而不用系统自带的堆结构,因为系统自带的堆结构在调整时,是从头开始扫描调整

JAVA中的堆结构:(默认为小根堆)
PraiorityQyueueheap=new PriorityQueue<>();

(1)几乎有序数组排序

已知一个几乎有序数组,几乎有序是指,如果把数组排好顺序的话,每个元素移动的距离可以不超过k,并且k相对于数组来说比较小。请选择一个合适的排序算法针对则个数据进行排序。

时间复杂度:O(NlogK)
代码如下:

public class almostOrdinal {
    public static void main(String[] args) {
        int k=2;
        int []arr={2,8,6,4,14,12,16,16,18};
        System.out.println("排序前:"+Arrays.toString(arr));
        almostOrderedSort(arr,k);
        System.out.println("排序后:"+Arrays.toString(arr));
    }
    public static void almostOrderedSort(int[]arr,int k){
        PriorityQueue<Integer>heap=new PriorityQueue<>();
        int index=0;
        for (;index<=Math.min(arr.length,k);index++){// 把前k+1个数放入小根堆(如果k大于数组长度则把整个数组放入)
            heap.add(arr[index]);
        }
        int i=0;
        for (;index<arr.length;index++){
            heap.add(arr[index]);// 新加后一位数放到小根堆中
            arr[i++]= heap.poll();//小根堆弹出最小的数放到i位置
        }
        while (!heap.isEmpty()){// 如果没有可加的新数字 依次弹出就行
            arr[i++]= heap.poll();
        }
    }
}

结果如下:
在这里插入图片描述

四、比较器

自己定义比较策略的类,实质就是重载比较运算符。

总结

以上文章主要讲了堆(大根堆、小根堆)和堆排序的思想,以及扩展,和比较器。

本文含有隐藏内容,请 开通VIP 后查看