• 堆排序


    什么是堆

    堆(heap)是计算机科学中的一种特别的树状数据结构。若是满足以下特性,即可称为堆:“给定堆中任意节点 P 和 C,若 P 是 C 的母节点,那么 P 的值会小于等于(或大于等于) C 的值”。

    若母节点的值恒小于等于子节点的值,此堆称为最小堆(min heap);

    若母节点的值恒大于等于子节点的值,此堆称为最大堆(max heap)。

    在堆中最顶端的那一个节点,称作根节点(root node),根节点本身没有母节点(parent node)。

    堆的实现通过构造二叉堆(binary heap),实为二叉树的一种;

    image

    堆的性质

    任意节点小于(或大于)它的所有后裔,

    最小元(或最大元)在堆的根上(堆序性)。

    堆总是一棵完全树。即除了最底层,其他层的节点都被元素填满,且最底层尽可能地从左到右填入。
    将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆

    常见的堆有二叉堆、斐波那契堆等。

    堆节点的访问

    通常堆是通过一维数组来实现的。在数组起始位置为0的情形中:

    父节点i的左子节点在位置:
    2i+1 2i+1

    父节点i的右子节点在位置:
    2i+2 2i+2

    子节点i的父节点在位置:
    floor(i1/2) floor((i-1)/2)

    若用A表示堆的一维数组,那么A具有两个属性:
    A.length: A.length:数组的元素个数

    A.heapsize: A.heap-size:表示有多少个堆元素存放于该数组中(堆的有效元素)

    0A.heapsizeA.length 0leq A.heap-sizeleq A.length
    若数组的起始位置为1,即树的根节点为A[1];

    给定一个节点的下标为i,则他的父节点,左孩子和右孩子 的下标为:

    父节点的下标:
    PRRENT[i]=i/2 PRRENT[i]=lfloor i/2 floor
    左孩子的下标:
    LEFT[i]=2i LEFT[i] = 2i
    右孩子的下标:
    RIGHT[i]=2i+1 RIGHT[i] = 2i+1
    image

    节点的高度:该节点到叶节点最长简单路径上的数目
    nθ(lg(n)) 如果一个堆有n个元素(可以看作一颗完全二叉树),那么堆的高度为 heta(lg(n))

    最大堆的性质:(除了根节点)
    A[PARENT[i]]A[i] A[PARENT[i]] geq A[i]
    最小堆的性质:(除了根节点)
    A[i]A[PARENT[i]] A[i]geq A[PARENT[i]]
    当用数组表示存储了n个元素的堆时,叶节点的下标分别是
    n/2+1,n/2+2,n/2+3,...,n lfloor n/2 floor + 1,lfloor n/2 floor + 2,lfloor n/2 floor + 3,...,n

    最大堆和最小堆的应用

    最大堆通常用于堆排序算法

    最小堆通常用于构造优先队列

    堆排序的概念

    堆排序(英语:Heapsort)是指利用堆这种数据结构所设计的一种排序算法。堆是一个近似完全二叉树的结构,并同时满足堆积的性质:即子节点的键值或索引总是小于(或者大于)它的父节点

    堆排序是对简单选择排序的改进

    简单选择排序是从n个记录中找出一个最小的记录,需要比较n-1次。但是这样的操作并没有把每一趟的比较结果保存下来,在后一趟的比较中,有许多比较在前一趟已经做过了,但由于前一趟排序时未保存这些比较结果,所以后一趟排序时又重复执行了这些比较操作,因而记录的比较次数较多。

    堆排序的原理

    维基百科:若以升序排序说明,把数组转换成最大堆积(Max-Heap Heap),这是一种满足最大堆积性质(Max-Heap Property)的二叉树:对于除了根之外的每个节点i, A[parent(i)] ≥ A[i]。

    重复从最大堆积取出数值最大的结点(把根结点和最后一个结点交换,把交换后的最后一个结点移出堆),并让残余的堆积维持最大堆积性质。

    将待排序的序列构造成一个大顶堆。此时,整个序列的最大值就是堆顶的根节点。将它移走(其实就是将其与堆数组的末尾元素交换,此时末尾元素就是最大值),然后将剩余的n-1个序列重新构造成一个堆,这样就会得到n个元素中的次最大值。如此反复执行,就能得到一个有序序列了。

    问题:

    1. 如何由一个无序序列构建成一个堆?
    2. 如何在输出堆顶元素后,调整剩余元素成为一个新的堆?

    堆排序的特点

    具有空间原址性:任何时候都只需要常数个额外空间存储临时数据

    维护堆的性质MAX-HEAPIFY(A,i)

    MAXHEAPIFY(A,i):A;i MAX-HEAPIFY(A,i):数组A ;i为下标

    MAX-HEAPIFY让A[i]在最大堆中**“逐级下降**”,从而避免了当LEFT[i]和RIGHT[i]都是最大堆,而A[i]小于其孩子的情况。

    伪代码

    思想:

    • 从每次调用MAX-HEAPIFY都选出 A[i], A[LEFT[i]], A[RIGHT[i]] 中的最大值
    • 如果最大值就是A[i]那么满足最大堆,否则交换A[i]和A[largest],交换后下标为largest的值为原来的A[i],以该节点为根节点的子树又可能违反最大堆的性质,所以需要对该子树进行递归调用MAX-HEAPIFY.
     MAX-HEAPIFY(A,i)
     	l = LEFT(i)
     	r = RIGHT(i)
     	if l ≤ A.heap-size and A[l] > A[i]
     		largest = l
     	else
     		largest = i
     	if r ≤ A.heap-size and A[r] > A[largest]
     		largest = r
     	if largest ≠ i
     		exchange A[i] with A[largest]
     		MAX-HEAPIFY(A,largest)
    

    image

    时间复杂度:
    O(lg(n)) O(lg(n))

    建堆BUIL-MAX-HEAP(A)

    我们已经知道对于一个堆数组大小为n的堆,该堆的叶子节点的子数组元素为
    A(n/2+1..n) A(lfloor n/2 floor + 1..n)

    那么我们采用自底向上的方法,利用前面维护堆的性质的函数MAX-HEAPIFY,将一个大小为n = A.length的数组A[1…n]转为最大堆。

    那么这里的自底向上的方法就是从最底层的叶节点开始建堆,

    首先每个叶节点可以看作只有一个元素的堆(肯定满足最大堆的性质),

    然后对除叶节点以外的其他每个节点都调用一次MAX-HEAPIFY(A,i),用于检查是否符合最大堆的性质*(这里的调用也是从下往上调用,i从floor(n/2)到1,最后到根节点)*

    伪代码
    BUILDMAXHEAP(A)A.heapsize=A.lengthfor i=A.length/2 downto 1MAXHEAPIFY(A,i) BUILD-MAX-HEAP(A)\ A.heap-size = A.length\ quad for i = lfloor A.length/2 floor downto 1 \ qquad qquad MAX-HEAPIFY(A,i)

    时间复杂度:

    每次调用MAX-HEAPIFY的时间复杂度为o(lgn)

    BUILD-MAX-HEAP需要o(n)次这样的调用

    因此总的时间复杂度为:
    O(lg(n))O(n)=O(nlg(n)) O(lg(n)) * O(n) = O(nlg(n))
    但是实际上还可以更加精确地计算得到时间复杂度为o(n),也就是说我们可以在线性时间内,将一个无序数组构造成为一个最大堆。

    image

    同理我们也可以在线性时间内构造一个最小堆

    堆排序算法

    HEAPSORT(A)
    HEAPSORT(A)BUILDMAXHEAP(A)for i=A.length downto 2exchange A[1] with A[i]A.heapsize=A.heapsize1MAXHEAPIFY(A,1) HEAPSORT(A)\ qquad qquadqquad qquad BUILD-MAX-HEAP(A)\ qquad qquadqquad for i = A.length downto 2\ qquad qquadqquad qquadqquad qquad exchange A[1] with A[i]\ qquad qquadqquad qquadqquad qquad qquad A.heapsize = A.heapsize - 1\ qquad qquadqquad qquadqquad qquad MAX-HEAPIFY(A,1)

    image

    image

    总的思想就是:

    • 首先先将一个无序数组构建成为一个最大堆BUILD-MAX-HEAP(A)
    • 然后从堆地最底层开始遍历,将根节点A[1]和树地最后一个A[i]交换,然后将堆的有效元素减一(相当于每次剔除当前堆中的最大的元素,也就是每次剔除当前堆的根节点)exchange A[1] with A[i]; A.heapsize = A.heapsize - 1
    • 然后将剩余的元素从新的根节点出发重新维护最大堆的性质MAX-HEAPIFY(A,1),
    • 那么最后得到的序列就是一个升序序列

    堆排序算法的实现

    import java.util.ArrayList;
    import java.util.Arrays;
    
    public class HeapSort1 {
    
        private static int heapSize = 0;
    
        /**
         * 左孩子的下标
         * @param i
         * @return
         */
        public int leftIndexSearch(int i){
            return 2*i+1;
        }
    
        /**
         * 右孩子的下标
         * @param i
         * @return
         */
        public int rightIndexSearch(int i){
            return 2*i+2;
        }
    
        /**
         * 父节点的下标
         * @param i
         * @return
         */
        public int parentIndexSearch(int i){
            return (int) Math.floor((i-1)/2);
        }
    
        /**
         * 元素交换
         * @param arr
         * @param i
         * @param j
         */
        public void swap(int[] arr,int i,int j){
            int temp = arr[i];
            arr[i] = arr[j];
            arr[j] = temp;
        }
    
        /**
         * 维护堆的性质
         * @param arr
         * @param i
         */
        public void maxHeapify(int[] arr,int i){
            System.out.println(Arrays.toString(arr));
            System.out.println("当前节点:"+i);
            int leftIndex = leftIndexSearch(i);
            System.out.println("左孩子:"+leftIndex);
            int rightIndex = rightIndexSearch(i);
            System.out.println("右孩子"+rightIndex);
    
            int heapSize = arr.length;
    
            int largetsIndex = 0;
            if(leftIndex <= heapSize -1 && arr[leftIndex] > arr[i]){
                System.out.println("leftIndex <= heapSize -1 && arr[leftIndex]:"+arr[leftIndex]+" > arr[i]:"+arr[i]);
                largetsIndex = leftIndex;
                System.out.println(" largetsIndex = leftIndex;largestIndex:"+largetsIndex);
            }else {
                System.out.println("不满足leftIndex <= heapSize -1 && arr[leftIndex]> arr[i]:");
                largetsIndex = i;
                System.out.println(" largetsIndex = i;largestIndex:"+largetsIndex);
            }
            if(rightIndex <= heapSize - 1 && arr[rightIndex] > arr[largetsIndex]){
                System.out.println("rightIndex <= heapSize -1 && arr[rightIndex]:"+arr[rightIndex]+" > arr[largetsIndex]:"+arr[largetsIndex]);
                largetsIndex = rightIndex;
                System.out.println(" largetsIndex = rightIndex;largestIndex:"+largetsIndex);
            }
            if(largetsIndex != i){
                System.out.println("largetsIndex != i 交换i:"+i+"和largestIndex:"+largetsIndex);
                swap(arr,i,largetsIndex);
                System.out.println(Arrays.toString(arr));
                System.out.println("递归调用maxHeapify");
                maxHeapify(arr,largetsIndex);
            }
        }
    
        /**
         * 建堆
         * @param arr
         */
        public void buildMaxHeap(int[] arr){
            for(int i = (arr.length-1)/2; i >= 0; i--){
                maxHeapify(arr,i);
            }
        }
    
        /**
         * 堆排序
         * @param arr
         */
        public ArrayList<Integer> heapSort(int[] arr){
            ArrayList<Integer> list = new ArrayList<>();
            int heapSize = arr.length;
    
            buildMaxHeap(arr);
            System.out.println("构建最大堆:");
            System.out.println(Arrays.toString(arr));
            for(int i = arr.length - 1; i >= 0; i--){
                System.out.println("
    "+"最后一个元素i:"+i+"和根节点arr[0]交换");
                swap(arr,0,i);
                list.add(arr[heapSize-1]);
                System.out.println(Arrays.toString(arr));
                heapSize = heapSize - 1;
                System.out.println("heapSize:"+heapSize);
    
                arr = Arrays.copyOfRange(arr,0,heapSize );
                System.out.println("新的arr"+Arrays.toString(arr));
                maxHeapify(arr,0);
                System.out.println(Arrays.toString(arr));
            }
            return list;
        }
    
    
        public static void main(String[] args) {
            HeapSort1 heapSort1 = new HeapSort1();
            int[] arr = { 4,1,3,2,16,9,10,14,8,7 };
            System.out.println("排序之前:");
            for (int i = 0; i < arr.length; i++) {
                System.out.print(arr[i] + " ");
            }
            System.out.println("
    ");
    
            // 堆排序
            ArrayList<Integer> list = heapSort1.heapSort(arr);
    
    
            System.out.println();
            System.out.println("排序之后:");
            for (int i = 0; i < list.size(); i++) {
                System.out.print(list.get(i) + " ");
            }
        }
    
    
    }
    
    
    排序之前:
    4 1 3 2 16 9 10 14 8 7 
    构建最大堆:
    [16, 14, 10, 8, 7, 9, 3, 2, 4, 1]
    排序之后:
    16 14 10 9 8 7 4 3 2 1 
    
    //简洁代码
    import java.util.ArrayList;
    import java.util.Arrays;
    
    public class HeapSort1 {
    
        private static int heapSize = 0;
    
        /**
         * 左孩子的下标
         * @param i
         * @return
         */
        public int leftIndexSearch(int i){
            return 2*i+1;
        }
    
        /**
         * 右孩子的下标
         * @param i
         * @return
         */
        public int rightIndexSearch(int i){
            return 2*i+2;
        }
    
        /**
         * 父节点的下标
         * @param i
         * @return
         */
        public int parentIndexSearch(int i){
            return (int) Math.floor((i-1)/2);
        }
    
        /**
         * 元素交换
         * @param arr
         * @param i
         * @param j
         */
        public void swap(int[] arr,int i,int j){
            int temp = arr[i];
            arr[i] = arr[j];
            arr[j] = temp;
        }
    
        /**
         * 维护堆的性质
         * @param arr
         * @param i
         */
        public void maxHeapify(int[] arr,int i){
            int leftIndex = leftIndexSearch(i);
            int rightIndex = rightIndexSearch(i);
    
            int heapSize = arr.length;
    
            int largetsIndex = 0;
            if(leftIndex <= heapSize -1 && arr[leftIndex] > arr[i]){
                largetsIndex = leftIndex;
            }else {
                largetsIndex = i;
            }
            if(rightIndex <= heapSize - 1 && arr[rightIndex] > arr[largetsIndex]){
                largetsIndex = rightIndex;
            }
            if(largetsIndex != i){
                swap(arr,i,largetsIndex);
                maxHeapify(arr,largetsIndex);
            }
        }
    
        /**
         * 建堆
         * @param arr
         */
        public void buildMaxHeap(int[] arr){
            for(int i = (arr.length-1)/2; i >= 0; i--){
                maxHeapify(arr,i);
            }
        }
    
        /**
         * 堆排序
         * @param arr
         */
        public ArrayList<Integer> heapSort(int[] arr){
            ArrayList<Integer> list = new ArrayList<>();
            int heapSize = arr.length;
    
            buildMaxHeap(arr);
            System.out.println("构建最大堆:");
            System.out.println(Arrays.toString(arr));
            for(int i = arr.length - 1; i >= 0; i--){
                swap(arr,0,i);
                list.add(arr[heapSize-1]);
                heapSize = heapSize - 1;
    
                arr = Arrays.copyOfRange(arr,0,heapSize );
                maxHeapify(arr,0);
            }
            return list;
        }
    
    
        public static void main(String[] args) {
            HeapSort1 heapSort1 = new HeapSort1();
            int[] arr = { 4,1,3,2,16,9,10,14,8,7 };
            System.out.println("排序之前:");
            for (int i = 0; i < arr.length; i++) {
                System.out.print(arr[i] + " ");
            }
            System.out.println("
    ");
    
            // 堆排序
            ArrayList<Integer> list = heapSort1.heapSort(arr);
    
    
            System.out.println();
            System.out.println("排序之后:");
            for (int i = 0; i < list.size(); i++) {
                System.out.print(list.get(i) + " ");
            }
        }
    
    
    }
    
    
    排序之前:
    4 1 3 2 16 9 10 14 8 7 
    
    构建最大堆:
    [16, 14, 10, 8, 7, 9, 3, 2, 4, 1]
    
    排序之后:
    16 14 10 9 8 7 4 3 2 1 
    

    优先队列

    优先队列:是一种用来维护一组元素构成的集合S的数据结构,其中每个元素都有一个相关的值,称为关键字,一个最大优先队列支持以下操作:

    INSERT(S,x):把元素x插入集合S中,等价于S = S

    MAXIMUM(S):返回S中具有最大键字的元素

    INCREASE-KEY(S,x, k):将元素x的关键字值增加到k

    假设A为最大堆

    //返回数组A中最大值(根节点)
    HEAP-MAXIMUM(A)
    	return A[1]
    
    //抽取并返回最大值,重新构建最大堆
    HEAP-EXTRACT-MAX(A)
      if(A.heap-size < 1)
      		error "heap underflow"
      max = A[1]
      //将堆中的最后一个元素赋值给A[1]
      A[1] = A[A.heap-size]
      //减小堆的长度
      A.heap-size = A.heap-size - 1
      //对新堆重新维护最大堆的性质
      MAX-HEAPIFY(A,1)
      return max
    
    //将元素i的关键字值增加到key(这里i为下标,key为具体的值)
    HEAP-INCREASE-KEY(A,i,key)
      if key < A[i]
      	error "new key is smaller than current key"
      A[i] = key
      //当i不为根节点并且A[i]的值比父节点A[PARENT(i)]的值还要大
      while i > 1 and A[PARENT(i)] < A[i]
        //那么就交换A[i]和父节点 A[PARENT(i)]
      	exchange A[i] with A[PARENT(i)]
      	//更新i的下标为父节点的下标
      	i = PARENT(i)
    

    image

    MAX-HEAP-INSERT(A,key)
    	//增加A的长度
    	A.heap-size = A.heap-size + 1
    	//将A的最后一个数设置为负无穷,因为负无穷肯定比key小
    	A[A.heap-size] = -∞
    	//调用增加方法将最后一个元素增加到Key
    	HEAP-INCREASE-KEY(A,A.heap-size,key)
    

    Java排序算法(五):堆排序

    [Heapsort 堆排序算法详解(Java实现)]

    《算法导论第三版》

  • 相关阅读:
    中国内地、台湾、香港、澳门和国外DNS服务器地址列表
    科学、道法、哲学
    Away 3d 基本属性
    away 3d的一些问题
    Adobe Flash CC 2014 下载及破解
    html5结合flash实现视频文件在所有主流浏览器兼容播放
    Html wmode 标签参数详解
    九宫格
    flash/flex 编译错误汇总
    Redis在windows下安装过程(转)
  • 原文地址:https://www.cnblogs.com/flyingcr/p/10493222.html
Copyright © 2020-2023  润新知