堆排序汇总

堆排序汇总

  • 堆结构就是用数组实现的完全二叉树结构,底层结构是数组
  • 大根堆:在完全二叉树中,每棵子树的最大值都在顶部
  • 小根堆:在完全二叉树中,每棵子树的最小值都在顶部
  • 优先队列就是堆结构

1、如何让数组变为堆结构?

  • 方式一:heapInsert,将元素与父节点进行比较,如果比父节点大,则交换,直到当前元素达到根节点,或比自己的父节点小为止
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;
	}
}

    for(int i = 0;i<arr.length;i++){//让数组堆化
        heapInsert(arr,i);//O(logn)
    }
  • 方式二:heapify,将元素与其孩子中较大的一个进行比较,若子孩子大,则交换,直到没有子孩子或当前元素比子孩子大
public static void heapify(int[] arr,int index,int heapSize){//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,largest,index);
		index = largest;
		left = index*2+1;
	}
}
        for(int i = arr.length-1;i>=0;i--){//用户一股脑把整个a[]给出来了,对应情景就是将数组改为大根堆,从最后一个节点开始heapify,这一步的复杂度为O(N),会比heapInsert更好
            heapify(arr,i,arr.length);
        }

两种方式的复杂度:

  • 前提:数组已经一次性给出(不是一次给一个元素的方式)
  • 当完全二叉树有N个节点时,高度为logN,因为heapify以及heapInsert每一个循环都是找元素的父节点或孩子节点,因此最多需要logN次即可完成一次调整
  • 对于heapInsert来说,从arr[0]开始调整堆,总时间复杂度为O(nlogn)
  • 对于heapify来说,从arr[arr.length-1]开始调整堆,完全二叉树最底层节点没有子孩子,且底层节点个数占总节点个数的近1/2,无需找孩子,复杂度1/2 * 1 * O(n),倒数第二层节点占1/4,找一次孩子,复杂度1/4 * 2 * O(n),依次类推,通过做差消项可得,总时间复杂度为O(n)
    2、堆排序
    思路:
    • 先让整个数组变成大根堆结构,建立堆
      • 通过heapInsert从arr[0]开始,时间复杂度O(nlogn)
      • 通过heapify从arr[arr.length-1]开始,时间复杂度O(n)
    • 把堆顶的最大值和堆末尾的值交换,然后减少堆的大小,继续重复堆顶堆尾交换操作,直到堆的大小减为0
import java.util.Arrays;

public class HeapSort {
    public void heapSort(int[] a){
        if(a == null || a.length <2) return;
        //for (int i = 0; i < a.length; i++) {//堆得元素一个一个给出来,从第一个节点开始heapInsert,复杂度O(nlogn)
        //    heapInsert(a,i);O(logn)
        // }
        for(int i = a.length-1;i>=0;i--){//用户一股脑把整个a[]给出来了,对应情景就是将数组改为大根堆,从最后一个节点开始heapify,这一步的复杂度为O(N),会更好
            heapify(a,i,a.length);
        }
        int heapSize = a.length;
        swap(a,0,--heapSize);//把最后一位的最大值和堆断掉联系
        while(heapSize>0){
            heapify(a,0,heapSize);
            swap(a,0,--heapSize);
        }
    }
    public void heapInsert(int[] a,int index){
        while(a[index]>a[(index-1)/2]){
            swap(a,index,(index-1)/2);
            index = (index-1)/2 ;
        }
    }
    public void heapify(int[] a,int index,int heapSize){//logN
        int left = 2*index+1;
        while(left < heapSize){
            int largest = left+1 < heapSize && a[left] >= a[left+1]?left:left+1;//相等的时候,取左孩子
            largest = a[largest] > a[index]?largest:index;
            if(largest == index) {
                break;
            }
            swap(a,index,largest);
            index = largest;
            left = 2*index+1;
        }
    }
    public void swap(int[] a, int i,int j){
        int temp = a[i];
        a[i] = a[j];
        a[j] = temp;
    }

    public static void main(String[] args) {
        int[] a = {1,3,5,32,2,4,2,3,3};
        HeapSort hs = new HeapSort();
        hs.heapSort(a);
        System.out.println(Arrays.toString(a));
    }
}

3、堆扩容
假设有n个数,每次扩容为当前容量的2倍,因此扩容次数为logn
堆的底层结构为数组,因此单次扩容时间复杂度O(n)
折合下来,n个数的扩容代价为O(nlogn)
单个数的扩容代价为logn

4、何时用系统自带堆,何时自己写堆
当需要改变堆内部某个节点的值,这时需要自己写,系统自带的堆会依次扫描,调整代价会很高。
自己写的堆,可以判断新值和旧值的大小,决定heapify还是heapInsert以减小代价,当新值>旧值,用heapInsert,否则用heapify
给堆一个数据,让堆弹出一个数据,可以用系统自带的堆
5、堆排序扩展题目
题目:已知一个几乎有序的数组,几乎有序是指,如果把数组排好序的话,每个元素移动的距离可以不超过K,并且K相对于数组来说比较小。选择合适排序算法对这个数据进行排序

public void sortedArrDistanceLessK(int[] arr,int k){
    PriorityQueue<Integer> heap = new PriorityQueue<>();
    int index;
    for(index = 0;index <= Math.min(arr.length,k);index++){//先将前K个数,放到小根堆里边,K个数之后的数 一定比第一个数值大,所以最小值必定在前K个数中
        heap.add(arr[index]);
    }
    int i;
    for(i = 0;index<arr.length;i++,index++){//加一个,弹一个
        heap.add(arr[index]);
        arr[i] = heap.poll();
    }
    while(!heap.isEmpty()){//最后将堆中的数都弹出
        arr[i++] = heap.poll();
    }
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值