堆排序

什么是堆

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

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

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

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

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

image

堆的性质

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

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

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

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

堆节点的访问

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

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

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

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

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

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

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

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

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

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

最大堆的性质:(除了根节点)
A[PARENT[i]]≥A[i] A[PARENT[i]] \geq A[i] A[PARENT[i]]A[i]
最小堆的性质:(除了根节点)
A[i]≥A[PARENT[i]] A[i]\geq A[PARENT[i]] A[i]A[PARENT[i]]
当用数组表示存储了n个元素的堆时,叶节点的下标分别是
⌊n/2⌋+1,⌊n/2⌋+2,⌊n/2⌋+3,...,n \lfloor n/2 \rfloor + 1,\lfloor n/2 \rfloor + 2,\lfloor n/2 \rfloor + 3,...,n n/2+1,n/2+2,n/2+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)

MAX−HEAPIFY(A,i):数组A;i为下标 MAX-HEAPIFY(A,i):数组A ;i为下标 MAXHEAPIFY(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)) O(lg(n))

建堆BUIL-MAX-HEAP(A)

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

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

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

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

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

伪代码
BUILD−MAX−HEAP(A)A.heap−size=A.lengthfor i=⌊A.length/2⌋ downto 1MAX−HEAPIFY(A,i) BUILD-MAX-HEAP(A)\\ A.heap-size = A.length\\ \quad for \ i = \lfloor A.length/2 \rfloor \ downto \ 1 \\ \qquad \qquad MAX-HEAPIFY(A,i) BUILDMAXHEAP(A)A.heapsize=A.lengthfor i=A.length/2 downto 1MAXHEAPIFY(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(n\lg(n)) O(lg(n))O(n)=O(nlg(n))
但是实际上还可以更加精确地计算得到时间复杂度为o(n),也就是说我们可以在线性时间内,将一个无序数组构造成为一个最大堆。

image

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

堆排序算法

HEAPSORT(A)
HEAPSORT(A)BUILD−MAX−HEAP(A)for i=A.length downto 2exchange A[1] with A[i]A.heapsize=A.heapsize−1MAX−HEAPIFY(A,1) HEAPSORT(A)\\ \qquad \qquad\qquad \qquad BUILD-MAX-HEAP(A)\\ \qquad \qquad\qquad for\ i = A.length \ downto \ 2\\ \qquad \qquad\qquad \qquad\qquad \qquad exchange \ A[1]\ with \ A[i]\\ \qquad \qquad\qquad \qquad\qquad \qquad \qquad A.heapsize = A.heapsize - 1\\ \qquad \qquad\qquad \qquad\qquad \qquad MAX-HEAPIFY(A,1) HEAPSORT(A)BUILDMAXHEAP(A)for i=A.length downto 2exchange A[1] with A[i]A.heapsize=A.heapsize1MAXHEAPIFY(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("\n"+"最后一个元素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("\n");

        // 堆排序
        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("\n");

        // 堆排序
        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实现)]

《算法导论第三版》

转载于:https://www.cnblogs.com/flyingcr/p/10493222.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值