栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > C/C++/C#

堆排序(heapsort)与比较器

C/C++/C# 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

堆排序(heapsort)与比较器

堆结构:

(1)逻辑概念上:完全二叉树(满二叉树或者是从左往右依次变满的二叉树)

(2)存储表示:数组从0出发的连续一段表示一颗完全二叉树,使用size描述该树有多少结点,在i位置上的结点,其左孩子下标为(2*i+1),右孩子下标为(2*i+2),父节点为(i-1)/2.

(3)分类:

大根堆:堆顶为整个堆最大的元素,同样的每个子树也满足子树的最大值是头结点的值

小根堆:堆顶为整个堆最小的元素,同样的每个子树也满足子树的最小值是头结点的值

两个重要过程: heapinsert:向上跟父节点比较调整的过程
 
    public static void heapinsert(int[] arr, int index){
        while(arr[index] > arr[(index - 1)/2]){//包含index在0位置的情况
            swap(arr, index, (index-1)/2);
            index = (index - 1) / 2;
        }
    }
heapify:向下跟子节点比较调整的过程
 
    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[index] >= arr[largest] ? index : largest;
            if(index == largest) break;//如果自己比孩子都大,则停止下沉
            swap(arr, index, largest);//否则交换,继续比较
            index = largest;
            left = index * 2 + 1;
        }
    }
堆排序(由小到大——大根堆)时复:O(N*logN),空复:O(1)

先形成堆,再从堆顶弹出元素,从后往前放入数组。

形成堆时:

法1:从数组的前面开始,依次向堆中添加元素建堆

法2:假设所有元素已经构成了堆形的树,然后从后往前下沉,先调整好局部小堆,在调整大堆。最后一层约有N/2个节点,每个结点处理1次(看一眼);倒数第二层约有N/4个节点,每个结点处理2次(看1眼,可能向下移动1步)....所有的相加为总的操作次数(式1)。将该式左右都乘以2(式2),将两式子错位相减,可以得到最后的时间复杂度为O(N)。

 //由小到大排序
    public static void heapSortMax(int[] arr){
        if(arr == null || arr.length < 2) return;
        int heapSize = 0;

        //让数组构成大根堆,法1:O(N*logN)
//        for(int i = 0; i < arr.length; i++){//构成一个大根堆  O(N)
//            heapinsert(arr, i); //O(logN)
//        }

        //让数组构成大根堆,法2:O(N)
        for(int i = arr.length - 1; i >= 0; i--){
            heapify(arr, i, arr.length);//这里的第三个参数是堆的大小,由于是倒序依次下沉的,所以取最大值
        }
        //大根堆堆顶元素依次弹出
        heapSize = arr.length;
        swap(arr, 0, --heapSize);//如果将先swap后heapify都写到循环中,就多执行了一次没有意义的heapify
        while (heapSize > 0 ){//O(N)
            heapify(arr, 0, heapSize);//需要 len-1 次heapify即可。O(logN)
            swap(arr, 0, --heapSize);//O(1)
        }
    }

 由大到小(小根堆),Java中系统自带的优先级队列默认是小根堆

 //小根堆
    public static void heapnsert_min(int[] arr, int index){
        while (arr[index] < arr[(index - 1) / 2]){
            swap(arr, index, (index - 1) / 2);
            index = (index - 1) / 2;
        }
    }

    public static void heapify_min(int[] arr, int index, int heapSize){
        int left = index * 2 + 1;
        while (left < heapSize){
            //注意这里只能是满足条件才能取右孩子,否则如果是else取右孩子,还存在右孩子不存在的情况,就会越界
            int less = (left+1 < heapSize) && arr[left + 1] < arr[left] ? left + 1 : left;
            less = arr[less] < arr[index] ? less : index;
            if(less == index) break;
            swap(arr, less, index);
            index = less;
            left = index * 2 + 1;
        }
    }
    //从大到小排序
    public static void heapSortMin(int[] arr){
        //建小根堆
        for(int i = arr.length - 1; i >= 0; i--){
            heapify_min(arr, i, arr.length);
        }
        //从堆顶依次弹出
        int heapSize = arr.length;
        swap(arr, 0, --heapSize);
        while (heapSize > 0){
            heapify_min(arr, 0, heapSize);
            swap(arr, 0, --heapSize);
        }
    }

例题: 

 

解决:使用默认的优先级队列 PriorityQueue将前(k+1)个元素建立一个小根堆,堆顶的元素一定放在arr[0]的位置,依次在向后添加新的元素构建新的小根堆,将弹出元素放入arr[1]的位置,直到整个数组的后(k+1)个元素都在堆中,然后从小到大依次弹出放入数组中。时间复杂度为O(N*logK)

如果考虑到堆的扩容问题,是成倍扩容,即扩容logN次,每次扩容完都要将原来的元素拷贝,即单次扩容代价是O(N),所以总的操作次数是O(N*logN),每个元素均摊的扩容代价为O[(N*logN)/N] = O(logN)

 public static void sortedArrDistanceLessK(int[] arr, int k){
        PriorityQueue heap = new PriorityQueue<>();
        int index = 0;
        for(; index < Math.min(k, arr.length); index++){//把前(k+1)个放入小根堆
            heap.add(arr[index]);
        }
        int i = 0;
        for(; index < arr.length; index++, i++){//弹出一个,添加一个,直到添加完
            arr[i] = heap.poll();//堆顶弹出放入k范围下的第一个位置
            heap.add(arr[index]);//k范围整体后移
        }
        while (!heap.isEmpty()){//堆中剩余元素按顺序弹出
            arr[i++] = heap.poll();
        }
    }
注意:

系统给的优先级队列(堆结构)与自己手写堆的区别:系统堆结构在初始建堆时结构元素大小已经固定,当如果想改变某一个数重新调整成堆时,可能需要重新扫描所有元素,heapify和heapinsert,有很高的代价,这时就需要手写堆,直接判断该变化的数是需要heapify还是heapinsert。如果只是简单的需要添加和弹出数,可以使用系统堆,涉及到更改堆元素,需要手写

比较器:

(1)实质:重载比较运算符

(2)可以很好的应用在比较特殊的排序规则上

(3)可以很好的应用在根据特殊标准排序的结构上

实现方法:自己编写一个比较器类实现Comparator<要比较的类>接口,重新compare

!!!规则:

 在Arrays.sort()、 PriorityQueue、TreeSet中都可以传入比较器对象,按照给定的要求建立。在堆中,比较器中排在前面的数应该放在上面,这样默认的小根堆也可以改成大根堆

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/928270.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号