美文网首页程序员
具体把握堆排序(JAVA)

具体把握堆排序(JAVA)

作者: 林天涯 | 来源:发表于2018-01-03 22:33 被阅读0次

前言


  堆排序是一种动态排序,它基于堆这种数据结构。堆的实质是一棵二叉树,只不过使用的是连续存储。堆分为小根堆和大根堆。小根堆的特点是根结点最小,其每一个子堆也满足这个特点。相反,大根堆的特点是根结点最大,且其每一个子堆也满足这一个特点。

算法


  堆排序的实质是堆顶元素出堆,而后再维护堆的特点进行调整,继续出堆直至堆为空,排序完成。可按如下步骤进行:
  1. 初始建堆。根据序列构造完全二叉树,并进行调整,以满足堆的特点,由于是升序排序,所以建立小根堆。因为每一个子堆也必须满足小根堆的特点,所以需要对结点自底向上进行调整。叶子结点显然已满足,所以直接从最后一个叶子结点的父节点开始调整,也就是从序号为n/2的位置开始调整到根结点位置(位置为1)结束。
  对于每一个子堆的调整,其实质是可能存在孩子结点小于根结点,因而需要作向下调整。调整的方法是先保存根结点,再取其孩子结点(如果存在两个孩子结点则取其最小的一个),若这个结点值大于根结点值,则没有调整的必要,调整结束。否则,将这个结点值赋值给根结点,位置也赋值给根结点的位置,继续向下一层的孩子结点进行比较,直到调整到叶结点或者已经满足特性。
  2. 堆顶元素出堆。出堆的过程是将堆顶元素和堆底元素交换,并且堆长度减1。由于交换后,堆特性可能已经被破坏,因此需要从新调整根结点的位置,重新向下调整。出堆操作不断进行下去,直至堆为空,排序结束。输出出堆的元素,即为升序序列。

例子


仍然以序列4,3,1,2,5为例:
1.初始建堆,堆长为5

  初始二叉树为: 初始二叉树
  结点3向下调整。由于下层孩子结点2小于3,故2和3交换: 结点3调整

调整后序号2为根结点的子堆已满足小根堆特点。

  结点4向下调整。由于下层孩子结点,最小的是结点1,则将1和4交换: 结点4向下调整
可见此时已完全满足小根堆特点。
  1. 堆顶元素出堆。

      首先输出1,且将堆顶堆底交换,即1和5交换。堆长为4: 1出堆
    结点5向下调整后变为: 5向下调整
      再输出2,将2和5交换,堆长为3。再将结点5向下调整后为: 2出堆
      再输出3,将3和5交换,堆长为2。再将结点5向下调整后为: 3出堆
      再输出4,将4和5交换,堆长为1。无须调整: 4出堆
      再输出5,堆长为0,堆为空,排序结束: 5出堆

      排序后序列为1,2,3,4,5。排序成功。

Codes


package com.fairy.InnerSort;

import java.util.Scanner;
/**
 * 定义堆数据结构
 * @author Fairy2016
 *
 */
class Heap {
    int data[];//元素
    int length;//堆长
    int max = 100;//堆空间大小,如不够每次再加
    //初始建堆
    void InitBuildHeap(int a[], int n) {
        int i;
        data = new int[n+1];
        length = n;
        for(i = 1; i <= n; i++) {
            data[i] = a[i];
        }
        //从最后一个结点的父节点开始调整
        for(i = n/2; i > 0; i--) {
            AdjustDown(i);
        }
    }
    //向下调整,以保持堆的特性以及子堆的特性
    void AdjustDown(int k) {
        data[0] = data[k];
        for(int i = 2*k; i <= length; i*=2) {
            if(i < length && data[i] > data[i+1])
                i++;//由于一个结点的子节点可能有两个,还需比较大小
            if(data[0] < data[i]) {
                break;//如果其子节点都比它大,那么无须调整
            } else {
                data[k] = data[i];
                k = i;//向上赋值,更新结点位置
            }
        }
        data[k] = data[0];//赋值到调整后确定的位置
    }
    //向上调整,用于新入堆元素后保持堆的特性
    void AdjustUp(int k) {
        data[0] = data[k];
        for(int i = k/2; i > 0; i /= 2) {
            if(data[0] > data[i]) {
                break;//如果父结点都比它小,那么无须调整
            } else {
                data[k] = data[i];
                k = i;//向下赋值,更新结点位置
            }
        }
        data[k] = data[0];
    }
    //元素出堆
    int PopupHeap() {
        int e = data[1];
        //交换堆顶和堆底元素
        data[1] = data[length];
        data[length] = e;
        //堆长度减1
        length--;
        //需要调整以满足堆特性
        AdjustDown(1);
        return e;
    }
    //判断堆是否已空
    boolean IsEmpty() {
        if(length >= 1) {
            return false;
        }
        return true;
    }
    
    //元素入堆
    void PushHeap(int e) {
        //如果空间不够,需从新分配
        if(length == data.length-1) {
            int capa = max;
            while(capa <= length+1) {
                capa += max;
            }
            int help[] = new int[length];
            for(int i = 1; i <= length; i++) {
                 help[i-1] = data[i];
            }
            data = new int[capa];
            for(int i = 1; i <= length; i++) {
                 data[i] = help[i-1];
            }
            data[++length] = e;//新元素加入到堆底
        } else {
            data[++length] = e;//新元素加入到堆底
        }
        AdjustUp(length);
    }
}
/**
 * 堆排序
 * @author Fairy2016
 *
 */
public class HeapSort {
    
    public static void sort(int a[], int n) {
        Heap heap = new Heap();
        heap.InitBuildHeap(a, n);
        while(!heap.IsEmpty()) {
            System.out.print(heap.PopupHeap()+" ");
        }
    }
    
    public static void main(String args[]) {
        int n;
        int a[];
        Scanner scanner = new Scanner(System.in);
        while(scanner.hasNext()) {
            n = scanner.nextInt();
            if(n > 0) {
                a = new int[n+1];
                for(int i=1; i <= n; i++) {
                    a[i] = scanner.nextInt();
                }
                sort(a, n);
            }
        }
        scanner.close();
    }
}

后记


  堆排序的时间复杂度由出堆操作和调整维护堆特性操作嵌套决定,出堆操作o(n),调整操作o(log2(n))。所以时间复杂度为o(n*log2(n))。时间复杂度还算可以,但由于其用到了堆这个数据结构,空间复杂度为o(n),相对来说不如快速排序。
  堆的应用远远不止堆排序,而是作为一种重要的数据结构(优先队列)应用在实际开发中。本文虽然是简单的堆排序,但代码中对于堆这种数据结构还是进行了封装,包括其插入元素操作。其实无论是入堆还是出堆,元素操作之后还是要维护堆的特性不变,即大根堆仍然是大根堆,小根堆仍然是小根堆。更多关于堆的相关应用,在后面的贪心算法和分支限界算法文章会继续提到。

相关文章

  • 具体把握堆排序(JAVA)

    前言   堆排序是一种动态排序,它基于堆这种数据结构。堆的实质是一棵二叉树,只不过使用的是连续存储。堆分为小根堆和...

  • 堆排序(非递归版)

    时间复杂度是O(nlogN) 但是不常用堆排序,因为堆排序不稳定 Java实现

  • 排序

    八大排序算法 一、归并排序 递归及非递归的JAVA实现 二、快速排序 快排算法JAVA实现 三、堆排序 堆排序堆排...

  • 排序算法-堆排序

    参考: Java排序算法(五):堆排序 【算法与数据结构】图说堆排序 【数据结构】排序算法:希尔、归并、快速、堆排...

  • java堆排序

    什么是堆排序:图解堆排序堆排序:利用了堆这种数据结构堆数据结构:特殊的完全二叉树,因为具有以下的特点:1)每个结点...

  • 堆排序(java)

    将待排序序列构造成一个大顶堆,此时序列的最大值就是堆顶元素,将它和数组的末尾元素交换,再将剩余的n-1个元素构造成...

  • 堆排序(Java)

    堆排序:堆排序的思想比较难理解,首先将数据看成是一个二叉树,对数据进行二叉树的建立(建堆),这个过程也是排序的过程...

  • 堆排序问题

    堆排序问题 最近回顾算法,看了一下堆排序的原理,有点小疑问,求大神解答。 具体问题如下:以升序为例,我们需要构建一...

  • 堆排序

    一、堆排序 堆排序是我们所掌握的重要的排序算法之一,理解并使用,可以让我们对它的了解更加深刻: 话不多说上代码 java

  • 堆排序java实现

    //堆排序://基本思想://首先要了解堆这种数据结构://堆是实质上是一个满足如下条件的完全二叉树:树中任意非叶...

网友评论

    本文标题:具体把握堆排序(JAVA)

    本文链接:https://www.haomeiwen.com/subject/msmknxtx.html