zoukankan      html  css  js  c++  java
  • 堆和堆排序

    一、什么是优先队列?

    普通队列:先进先出,后进后出

    优先队列:出队顺序和入队顺序无关,和优先级相关。

    优先队列的实现:

      入队 出队
    普通数组 O(1) O(n)
    顺序数组 O(N) O(1)
    O(logN) O(logN)

    二、堆的基本实现

    二叉堆的特点:这很重要!!! 是核心

    • 任意节点小于其父节点
    • 除了最后一层叶子节点外,其他层的元素个数必须是最大值 ,叶子节点虽然可以不是最大值,但必须靠左排列(最大堆)
    • 堆是一棵完全二叉树

    用数组存储二叉堆

      

    这样用数组存储的堆中元素和数组下标有以下规律:  这很重要!!! 是核心

      • 父节点下标:parent (i) = ( i - 1) / 2
      • 左子节点: left child (i)  = (i + 1) * 2 
      • 右子节点:right child (i) = (i + 1) * 2 + 1 

    最大堆代码实现 (逐步的实现,下面只是简单的定义,各种操作的方法后续依次加入) :

     1 public class MaxHeap {
     2 
     3     // 存储元素
     4     private int[] data;
     5     // 记录堆中节点个数
     6     private int size;
     7     // 堆的容量
     8     private int capacity;
     9 
    10     public MaxHeap(int capacity) {
    11 
    12         this.capacity = capacity;
    13         data = new int[capacity]; // 堆的第一个元素索引为 0;
    14         this.size = 0;
    15     }
    16 
    17     public int size() {
    18         return size;
    19     }
    20 
    21     public boolean isEmpty() {
    22         return size == 0;
    23     }
    24 
    25 }

    往最大堆中添加元素(shiftUp)

                                        

    根据前面对最大堆的定义(任意子节点小于其父节点) 以及元素下标之间的关系,我们不断交换父子节点的位置,知道满足最大堆的原则,就完成了元素插入。下面是代码实现:

        /**
         * 往最大堆中加入一个元素
         * @param e 
         */
        public void insert(int e) {
    
            if (size - 1 < capacity) {
                data[size] = e;
                shiftUp(size);
                size++;
            }
        }
    
        /**
         * 根据堆的定义,交换父子节点的位置,直到满足最大堆的原则
         * @param k
         */
        private void shiftUp(int k) {
    
            while (k > 0 && data[k] > data[(k - 1) / 2]) {
                SortedHandler.swap(data, k, (k - 1) / 2);
                k = (k - 1) / 2;
            }
        }

    删除最大堆中的元素(shiftDown)

                

      根据优先队列的定义,元素的出顺序按照优先级,而在最大堆中,根节点的优先级就是最大的,因此我们删除的时候,总是从根节点开始。

      具体的思路是,首先交换根节点和最后一个节点的位置,然后删除掉交换后的根节点,也就是最大值,然后根据堆的定义交换节点位置维护最大堆的原则,最后返回删除了的最大值即可。代码实现如下:

        /**
         * 交换根节点和最后一个节点的位置,再将移除的根节点的值返回,并维护最大堆的原则
         * @return 原堆中的最大值
         */
        public int extractMax() {
    
            if (size > 0) {
                int res = data[0];
    
                // 交换第一个和最后一个的位置
                SortedHandler.swap(data, 0, size - 1);
                size--;
                shiftDown(0);
                return res;
            }
    
            return -1;
        }
    
        /**
         * 交换节点的位置  维护最大堆的定义
         * @param k 开始的节点位置
         */
        private void shiftDown(int k) {
    
            while (2 * k + 1 < size) {
                int j = 2 * k + 1; // 左子点的下标
                if (j + 1 < size && data[j + 1] > data[j]) {
                    j += 1;
                }
                if (data[k] < data[j]) {
                    SortedHandler.swap(data, k, j);
                    k = j;
                } else {
                    break;
                }
            }
    
        }

     三、基本的堆排序

      通过上面的努力,我们实现了一个基本操作的最大堆。如果前面的明白了的话,那么实现一个堆排序就是小问题了,因为我们的最大堆的输出顺序就是由大到小的,那么排序的问题不过是将数组的顺序反过来 .

        public static void heapSorted1(int arr[]) {
    
            int n = arr.length;
            MaxHeap heap = new MaxHeap(n);
    
            for (int i = 0; i < n; i++) {
                heap.insert(arr[i]);
            }
            for (int i = n - 1; i >= 0; i--) {
                arr[i] = heap.extractMax();
            }
        }

     最大堆的另外一种构造方法 —— Heapify

      在前面构造最大堆的实现中,我们都是首先构造一个初始化容量的数组,然后依次加入数组的每个元素。现在我们考虑一个情况,因为最大堆的存储本身就是数组实现的,那么当我们对数组需要排序的时候,是否可以直接将这个数组构造成为最大堆呢,而无需逐个的复制元素并shiftUp?答案是肯定的。

      具体的思路是:将待排序的数组本身看成是一棵二叉树,在这课二叉树中,所有不同的非叶子节点就是不用的最大堆。那么我们就从这棵二叉树的第一个非叶子节点开始执行shiftDown操作,直到整棵二叉树满足最大堆的原则。那么问题又来了?第一个非叶子是多少呢,这里又有一个规律:完全二叉树的排列中,第一个非叶子节点 i 等于数组的长度 (n - 1) / 2.代码实现如下:

        // heapify 的过程
        public MaxHeap(int arr[]) {
    
            int n = arr.length;
            data = new int[n];
            capacity = n;
            for (int i = 0; i < n; i++) {
                data[i] = arr[i];
            }
            size = n;
            // 第一个非叶子节点的下标 (n-1) / 2
            for (int i = (n - 1) / 2; i >= 0; i--) {
                shiftDown(i);
            }
    
        }
    
        public static void heapSorted2(int arr[]) {
    
            int n = arr.length;
            MaxHeap heap = new MaxHeap(arr);
    
            for (int i = n - 1; i >= 0; i--) {
                arr[i] = heap.extractMax();
            }
    
        }

    四、堆排序的优化——原地堆排序

        public static void heapSorted3(int arr[]) {
    
            int n = arr.length;
            
            // heapify ,将数组转换为堆
            for (int i = (n - 1) / 2; i >= 0; i--) {
                __shiftDown(arr, n, i);
            }
    
            // 
            for (int i = n - 1; i > 0; i--) {
                int tmp = arr[i];
                arr[i] = arr[0];
                arr[0] = tmp;
    
                __shiftDown(arr, i, 0);
            }
    
        }
    
        /**
         *  原地堆排序的shiftDown操作
         * @param arr
         * @param n
         * @param i
         */
        private static void __shiftDown(int[] arr, int n, int i) {
    
            while (2 * i + 1 < n) {
                int j = 2 * i + 1;
                if (j + 1 < n && arr[j] < arr[j + 1]) {
                    j += 1;
                }
                if (arr[j] > arr[i]) {
                    int tmp = arr[j];
                    arr[j] = arr[i];
                    arr[i] = tmp;
    
                    i = j;
                } else
                    break;
            }
    
        }
  • 相关阅读:
    详细聊聊k8s deployment的滚动更新(一)
    更新k8s镜像版本的三种方式
    深入理解docker信号机制以及dumb-init的使用
    10分钟教你理解反射
    nodejs的交互式解释器模式常用命令
    nrm的安装和使用
    复杂sql语句之单字段分类count计数和多字段count计数
    navicat连接mysql出现2059
    mongodb常规操作语句
    System.Web.NullPointerException
  • 原文地址:https://www.cnblogs.com/ytuan996/p/10597806.html
Copyright © 2011-2022 走看看