zoukankan      html  css  js  c++  java
  • 数据结构(七):堆

     

    一、 堆的概述

      堆是计算机中一种重要的数据结构,它是完全二叉树的数组体现。

    二、 堆的特性

      2.1、堆是完全二叉树

      堆是完全二叉树的数据结构,除了树的最后一层结点不需要是满的,其他各层级从左到右都必须是满的,如果最后一层结点没有满,那么要求是左满右不满

       

      2.2、堆用数组来实现

      堆是用数组来存储上述完全二叉树的结点元素的,如根结点放在索引1位置处,根结点的左右子树放在2和3的位置,2和3的子结点又分别放在索引4,5和6,7的位置处,以此类推

      注:由于堆元素一般从数组的索引1处开始存储,所以0索引处不存放元素

      2.3、堆的父子结点在数组中有规律存在

      如一个节点的位置为索引k处,那么:

      父节点的位置:k/2

      左子树的位置:2*k

      右子树的位置:2*k+1

      所以在数组a[]中,我们可以通过a[k/2]来查找父节点,a[2*k]来查找左子结点,以此类推

       

      2.4、每个结点都大于两个子结点

      堆中每个结点元素都要大于两个子结点,两个子结点仅仅是都小于父节点,在左右子树中没有严格的要求

    三、 堆的实现思路

      1、 增加堆元素

      堆元素的增加其实是往数组中去添加元素,做法是在数组最后插入元素,然后通过树节点的上浮和父节点比较大小来调整位置,最终使得既插入元素又保证堆有序。

      如下图演示插入结点S的过程:

      3.1.1 初始堆数组

       

      3.1.2 往数组末端插入结点S,即H的右子结点

       

      3.1.3 添加的元素S破坏了堆的有序性和父节点必须大于两个子结点的特性,故对S做上浮处理,和H替换

       

      3.1.4 上述上浮操作后堆依旧无序,再对S和P进行上浮替换,经此步替换后堆重新有序,并且满足堆的特性,堆元素插入成功

       

      2、 删除堆元素

      假设删除堆顶最大的元素,则步骤如下:

      3.2.1 交换堆顶和堆末结点的元素,并删除堆末元素,即将数组最末位元素置空

       

      3.2.2 由于此时根结点为H,破坏了堆的有序性和特性,所以此时将H与子结点较大值进行替换,以此进行直到H比左右子树都大,则堆重新有序

       

       

      3、 修改元素

      同数组的元素修改操作相同。

      4、 查找元素

      由于堆的数据元素以数组来表示,故查找元素可通过遍历数组索引查找对应的值实现

    四、 堆的代码实现

      综上第三节描述,堆操作的思路,堆的实现代码如下:

    public class Heap<T extends Comparable<T>> {

          

           //初始化堆

           private T[] items;

          

           //初始化个数

           private int N;

           /**

            * 构造方法,传入堆的初始大小

            * @param size

            */

           public Heap(int size) {

                  items = (T[]) new Comparable[size + 1];

                  N = 0;

           }

          

           /**

            * 判断堆中索引i处的值是否小于j处的值

            * @param i

            * @param j

            * @return

            */

           private boolean bigger(int i,int j) {

                  return items[i].compareTo(items[j])>0;

           }

          

           /**

            * 元素位置的交换

            * @param col

            * @param i

            * @param j

            */

           private void switchPos(int i,int j) {

                  T temp = items[i];

                  items[i] = items[j];

                  items[j] = temp;

           }

          

           /**

            * 删除堆中最大的元素,并且返回这个元素

            * @return

            */

           public T delMaxValue() {

                  //获取根结点最大值

                  T maxValue = items[1];

                 

                  //交换根结点和尾结点

                  switchPos(1, N);

                 

                  //尾结点置空

                  items[N] = null;

                 

                  //堆数量减1

                  N--;

                 

                  //根结点下沉

                  sink(1);

                 

                  return maxValue;

           }

          

           /**

            * 往堆中插入一个元素t

            * @param t

            */

           public void insert(T t) {

                  items[++N] = t;

                  swim(N);

           }

          

           /**

            * 使用上浮算法,使堆中索引k处的值能够处于一个正确的位置

            * @param k

            */

           private void swim(int k) {

                  while(k>1) {

                         if(bigger(k/2, k)) {

                                switchPos(k/2, k);

                         }

                        

                         k = k/2;

                  }

           }

          

           /**

            * 使用下沉算法,使堆中索引k处的值能够处于一个正确的位置

            * @param k

            */

           private void sink(int k) {

                  while(2 * k <= N) {

                         int max;

                         //存在右子结点的情况

                         if(2*k+1 <= N) {

                                if(bigger(2*k,2*k+1)) {

                                       max = 2 * k;

                                }else {

                                       max = 2 * k + 1;

                                }

                         }else {

                                max = 2 * k;

                         }

                        

                         //当前结点不小,则退出

                         if(bigger(k, max)) {

                                break;

                         }

                        

                         switchPos(k, max);

                         k = max;

                  }

           }

    }

          

    五、 堆的构造

      由上述堆的特性和操作可知,对堆的下沉操作,必须发生在堆长度N/2的位置处,因为树节点的左右节点为空,无法再进行下沉处理。

      因此,在构建一个堆时,我们可以创建一个新数组,将原数组0~N-1的元素拷贝至新数组的1-N处,并对N/2处往前的元素进行下沉处理,当N/2到1处的元素下沉完毕时,即堆构造完成,此时堆有序

      构造示例如下:

       

       

       

       

       

       

    六、 堆排序及代码实现

      堆排序的实现思路:

      1、 如上所述构建好堆,使得初始堆有序和符合堆的特性

      2、 重复删除根结点的操作,即把根结点和堆末位结点交换,对根结点位置处的元素做下沉调整,下沉时分别比较左右节点,将较大值往上提

      3、 每次末位删除掉的根结点不再参与堆的删除和下沉操作

      4、 如上操作直到堆剩下一个元素为止

      如下所示为堆排序思路的演示图:

       

       

       

       

       

       

       

       

       

       

      堆排序的代码实现如下:

    import java.util.Arrays;

    public class HeapSort {

           /**

            * 堆排序

            * @param souce

            * @param heap

            */

           public static void sort(Comparable[] source) {

                  //创建一个比原数组大1的heap

                  Comparable[] heap = new Comparable[source.length+1];

                 

                  //构建heap

                  initHeap(source, heap);

                 

                  //初始化heap最大位置索引

                  int N = heap.length - 1;

                 

                  //排序操作,N=1时为排序完成(只有根结点)

                  while(N!=1) {

                         //首先交换目标结点和最末结点的位置

                         switchPos(heap, 1, N);

                        

                         //排序完数量-1

                         N--;

                        

                         //在N范围内完成下沉调整

                         sink(heap, 1, N);

                        

                  }

                 

                  //堆heap已有序,将有序的元素拷贝回原数组,即完成source的排序

                  System.arraycopy(heap, 1, source, 0, source.length);

           }

          

           /**

            * 比较堆中两个元素的大小

            * @param heap

            * @param i

            * @param j

            * @return

            */

           public static boolean bigger(Comparable[] heap,int i,int j) {

                  return heap[i].compareTo(heap[j]) > 0;

           }

          

           /**

            * 根据原数组,构建出初始堆

            * @param source

            * @param heap

            */

           public static void initHeap(Comparable[] source,Comparable[] heap) {

                  //原数组拷贝到heap中

                  System.arraycopy(source, 0, heap, 1, source.length);

                 

                  //对heap做初始化构造处理,从非叶子结点的N/2开始下沉,依次减1

                  int heapL = heap.length;

                  for(int i = heapL-1/2; i >= 1; i--) {

                         sink(heap, i, heapL-1);

                  }

           }

          

           /**

            * 堆排序的下沉调整

            * @param heap

            * @param i

            * @param j

            */

           public static void sink(Comparable[] heap,int target,int range) {

                  while(2 * target <= range) {

                         int max;

                         //存在右子结点的情况

                         if(2*target+1 <= range) {

                                if(bigger(heap,2*target,2*target+1)) {

                                       max = 2 * target;

                                }else {

                                       max = 2 * target + 1;

                                }

                         }else {

                                max = 2 * target;

                         }

                        

                         //当前结点不小,则退出

                         if(bigger(heap,target, max)) {

                                break;

                         }

                        

                         switchPos(heap,target, max);

                         target = max;

                  }

           }

          

           /**

            * 元素位置的交换

            * @param col

            * @param i

            * @param j

            */

           private static void switchPos(Comparable[] heap,int i,int j) {

                  Comparable temp = heap[i];

                  heap[i] = heap[j];

                  heap[j] = temp;

           }

          

           public static void main(String[] args) {

                  String[] arr = {"S", "O", "R", "T", "E", "X", "A", "M", "P", "L", "E"};

                  HeapSort.sort(arr);

                  System.out.println(Arrays.toString(arr));

           }

    }

  • 相关阅读:
    开始学习Angular Mobile UI
    .Net程序员飞扬有用的85个工具
    数据库查询效率,百万数据测试
    SQL Join的一些总结
    MODRD 指令 读取地址是哪儿来的
    如何在不同编程语言中获取现在的Unix时间戳(Unix timestamp)?
    easyui datagrid 的分页刷新按钮
    HttpRequest Get Post,WebClient Get
    GetWindowThreadProcessId用法(转)
    EASYUI DATAGRID 多列复选框CheckBox
  • 原文地址:https://www.cnblogs.com/jiyukai/p/14056574.html
Copyright © 2011-2022 走看看