zoukankan      html  css  js  c++  java
  • 排序算法(四):优先队列、二叉堆以及堆排序

    优先队列

    我们经常会碰到下面这种情况,并不需要将所有数据排序,只需要取出数据中最大(或最小)的几个元素,如排行榜。

    那么这种情况下就可以使用优先队列,优先队列是一个抽象数据类型,最重要的操作就是删除最大元素和插入元素,插入元素的时候就顺便将该元素排序(其实是堆有序,后面介绍)了。

    二叉堆

        二叉堆其实是优先队列的一种实现,下面主要讲的是用数组实现二叉堆。

    先上一个实例:

    如有一个数组A{9,7,8,3,0,6,5,1,2}

    用二叉树来表示数组更直观:

    从这张图我们可以总结一些规律:

    1. 当一个二叉树的每个结点都大于等于它的两个子节点时,称为堆有序
    2. 根节点是堆有序的二叉树中的最大结点
    3. 在数组中,位置为K的结点的父节点,位置为K/2,它的两个子节点位置分别为:2K和2K+1(下标从1开始,A[0]不使用)

    上面这三点应该非常好理解

    下面就引出一个问题,怎样让一个数组变成堆有序呢?

    首先,需要介绍两个操作:

    1. 由下至上的堆有序化(上浮)

    当插入一个结点,或改变一个结点的值时,上浮指的是交换它和它的父节点以达到堆有序

    在上面的堆有序的图中,如果我们把0换成10,那么上浮的操作具体为:

    (1)10比它的父节点7大,所以交换

    (2)交换后,10比它的父节点9还要打,交换

    之后得到的二叉树如下图:

     

    代码如下(需要注意,下标是从1开始,A[0]保留不用,以下所有代码相同):

    //index based on 1
    
        public void swim(Integer[] a,Integer key) {
    
            while(key > 1 && a[key/2] < a[key]) {
    
                change(a,key/2,key);
    
                key /= 2;
    
            }
    
        }

     2.  由上至下的堆有序化(下沉)

    由上浮可以很容易得出下沉的概念:

    当插入一个结点,或改变一个结点的值时,下沉指的是交换它和它的较大子节点以达到堆有序。

    在原来的二叉树中,如果将根节点9换成4,操作如下:

    (1)4与它的最大子节点8交换位置

    (2)4与它的最大子节点6交换位置

    交换后的二叉树如下图:

    代码如下:

        //index based on 1
    
        public void sink(Integer[] a,Integer key) {
    
            Integer max = key*2;
    
            while(key*2 < a.length - 1) {
    
                if(a[key*2] < a[key*2 + 1]) {
    
                    max = key*2 + 1;
    
                } else {
    
                    max = key*2;
    
                }
    
     
    
                if(a[key] > a[max])
    
                    break;
    
                
    
                change(a,key,max);
    
                key = max;
    
            }
    
        }

     

    那么将一个数组构造成有序堆,相应的也有两种方法:使用上浮以及使用下沉:

    初始数组如下:

            

    Integer[] a = {null,2,1,5,9,0,6,8,7,3};

    上浮构造有序堆:

        从数组左边到右边依次使用上浮,因为根节点A[1]没有父节点,所以从A[2]开始:

        public void buildBinaryHeapWithSwim(Integer[] a) {
    
            for(int k=2;k<a.length;k++) {
    
                swim(a,k);
    
            }
    
        }

        结果如下:

       

     a: [null ,9 ,7 ,8 ,5 ,0 ,2 ,6 ,1 ,3] 读者有兴趣可以自己画一下二叉树,看是否有序

        

        下沉构造有序堆:

    代码:
    
        public void buildBinaryHeapWithSink(Integer[] a) {
    
            //index based on 1
    
            for(int k=a.length/2;k>=1;k--) {
    
                sink(a,k);
    
            }
    
        }

    为什么使用下沉只需要遍历数组左半边呢?

    因为对于一个数组,每一个元素都已经是一个子堆的根节点了,sink()对于这些自对也适用。如果一个结点的两个子节点都已经是有序堆了,那么在该结点上调用sink(),可以让整个数组变成有序堆,这个过程会递归的建立起有序堆的秩序。我们只需要扫描数组中一半的元素,跳过叶子节点。

        a: [null ,9 ,7 ,8 ,3 ,0 ,6 ,5 ,1 ,2]

    可以看到使用下沉和上浮构造出来的有序堆并不相同,那么用哪一个更好呢?

    答案是使用下沉构造有序堆更好,构造一个有N个元素的有序堆,只需少于2N次比较以及少于N次交换。

    证明过程就略过了。

    堆排序

    前面说了那么多,终于要说到堆排序了,其实前面的优先队列和二叉堆都是为了堆排序做准备。

    现在我们知道如果将一个数组构造成有序堆的话,那么数组中最大的元素就是有序堆的根节点

    那么很容易想到一个排序的思路:

    第一种:将数组构造成有序堆,将根节点拿出来,即将A[1]拿出(因为A[0]不用,当然也可以使用,读者可以自己编程实现),对剩下的数组再构造有序堆……

    不过第一种思路只能降序排列,并且需要构造一个数组用来存放取出的最大元素,以及最大的弊端是取出最大元素后,数组剩下的其它所有元素需要左移。

    那么第二种办法就可以避免以上的问题:

    第二种:先看图:

    先来解释下这幅图:

    1. 一开始先将数组构造成一个有序二叉堆,如图1
    2. 因为有序二叉堆的最大元素就是根节点,将根节点和最后一个元素交换。
    3. 从index=1到index=a.lenth-1开始调用sink方法重新构造有序二叉堆。(即第二步交换过的最大元素不参与这次的构造)
    4. 经过第三步后,得到数组中第二大的元素即为根节点。
    5. 再次交换根节点和倒数第二个元素

      …….

     

        这样循环下去,即得到按升序排序的数组

     

    代码:

        public void heapSort(Integer[] a) {
    
            for(int k=a.length/2;k>=1;k--) {
    
                sink(a,k);
    
            }
    
            
    
            Integer n = a.length - 1;
    
            while(n > 0) {
    
                change(a,1,n--);
    
                //去除最后一个元素,即前一个有序堆的最大元素
    
                sink(a,1,n);
    
            }
    
        }

    注意在while循环中,sink()方法多了一个参数,这个参数的目的是去掉上一个有序堆的最大元素。

    全部代码如下:

    public class HeapSort extends SortBase {
    
     
    
        /* (non-Javadoc)
    
         * @see Sort.SortBase#sort(java.lang.Integer[])
    
         */
    
        @Override
    
        public Integer[] sort(Integer[] a) {
    
            // TODO Auto-generated method stub
    
            print("init",a);
    
            heapSort(a);
    
            print("result",a);
    
            return null;
    
        }
    
        
    
        public void buildBinaryHeapWithSink(Integer[] a) {
    
            //index based on 1
    
            for(int k=a.length/2;k>=1;k--) {
    
                sink(a,k);
    
            }
    
        }
    
        
    
        public void buildBinaryHeapWithSwim(Integer[] a) {
    
            for(int k=2;k<a.length;k++) {
    
                swim(a,k);
    
            }
    
        }
    
        
    
        public void heapSort(Integer[] a) {
    
            for(int k=a.length/2;k>=1;k--) {
    
                sink(a,k);
    
            }
    
            
    
            Integer n = a.length - 1;
    
            while(n > 0) {
    
                change(a,1,n--);
    
                //去除最后一个元素,即前一个有序堆的最大元素
    
                sink(a,1,n);
    
            }
    
        }
    
        
    
        //index based on 1
    
        public void swim(Integer[] a,Integer key) {
    
            while(key > 1 && a[key/2] < a[key]) {
    
                change(a,key/2,key);
    
                key /= 2;
    
            }
    
        }
    
        
    
        //index based on 1
    
        public void sink(Integer[] a,Integer key) {
    
            Integer max = key*2;
    
            while(key*2 < a.length - 1) {
    
                if(a[key*2] < a[key*2 + 1]) {
    
                    max = key*2 + 1;
    
                } else {
    
                    max = key*2;
    
                }
    
     
    
                if(a[key] > a[max])
    
                    break;
    
                
    
                change(a,key,max);
    
                key = max;
    
            }
    
        }
    
        
    
        public void sink(Integer[] a,Integer key,Integer n) {
    
            Integer max = key*2;
    
            while(key*2 < n) {
    
                if(a[key*2] < a[key*2 + 1]) {
    
                    max = key*2 + 1;
    
                } else {
    
                    max = key*2;
    
                }
    
     
    
                if(a[key] > a[max])
    
                    break;
    
                
    
                change(a,key,max);
    
                key = max;
    
            }
    
        }
    
        
    
        public static void main(String[] args) {
    
            Integer[] a = {null,2,1,5,9,0,6,8,7,3};
    
            //(new HeapSort()).sort(a);
    
            (new HeapSort()).buildBinaryHeapWithSink(a);
    
            print("a",a);
    
        }
    
        
    
    }

    堆排序的平均时间复杂度为NlogN

  • 相关阅读:
    spring源码-BeanFactoryPostProcessor-3.2
    web之前端获取上传图片并展示
    spring源码-增强容器xml解析-3.1
    spring源码-bean之增强初始化-3
    spring源码-bean之加载-2
    spring源码-bean之初始化-1
    spring源码-开篇
    java之扫描包里面的class文件
    vuex状态持久化
    Vue 使用 vuelidate 实现表单验证
  • 原文地址:https://www.cnblogs.com/edwinchen/p/4788541.html
Copyright © 2011-2022 走看看