基本排序算法之堆排序

1、堆的概念

堆排序依赖的数据结构是完全二叉树,要想是完全二叉树,前提必须是二叉树(废话),二叉树就要求父亲结点至多有两个孩子,即可以有一个、两个或者没有孩子。

完全二叉树则是在二叉树的基础上多了一些限制条件,那就是:

  1、要么二叉树的每一层都是满的,即除叶子结点之外,其他结点都必须拥有左右孩子;

  2、要么最后一层可以不满,但是最后一层的叶子结点必须位于靠左边放置;

满足上述条件之一就是一棵标准的完全二叉树。然后在满足完全二叉树的基础上,这个二叉树的还要满足堆属性,才能称之为一个堆,这里我们以大顶堆作为例子,要求就是:父亲结点的值大于或者等于它的任一孩子。

2、堆的存储

我们存储一个堆,通过数组或者ArrayList进行存储,其中存储的时候,元素在数组中的位置也是有讲究的,要求:

  索引值为i的结点,它的左孩子位于数组的2i+1位置,右孩子位于数组的2i+2位置

  其父亲结点位于(i-1)/2位置,i>=1

  数组的首个元素,是堆的根结点,也是当前堆中最大的元素。

 

3、向堆添加一个新结点

给堆添加新结点,是构建堆和扩充堆的基础工作,基本的思路就是,先将新结点放到堆的最后位置,然后由底向上逐个进行判断是否符合大顶堆的属性,如果符合堆的属性则完成添加任务。

具体思想如下:

  新结点设置为当前结点

  while 当前结点大于其父亲结点

    交换当前结点和其父亲结点的值

    当前结点向上攀登了一层

4、删除根节点

删除根节点就是删除堆中的最大元素,删除完成之后,就需要现有的堆不一定符合大顶堆的属性,所以需要对删除根节点之后的堆进行调整,那我们具体如何完成呢,具体步骤如下:

  将堆的最后一个结点赋值给根结点,赋值完成之后,设置根节点为当前结点

  while 当前结点含有孩子结点 && 当前结点小于它的子结点

    将当前结点的值最大的孩子结点的值和当前结点进行交换

    当前结点向下掉了一层

 

将上面的添加和删除结点的算法思想封装成一个完整的类,实现的代码如下:

 

import java.util.ArrayList;

/*
    二叉堆满足两个条件:
        1.是一个完全二叉树,完全二叉树就是所有层都是满的,或者最后一层可以不满,但是所有叶子节点必须在左边
        2.父亲节点的值大于等于任意一个孩子节点的值
 */

public class Heap<E extends Comparable<E>>{
    private ArrayList<E> list = new ArrayList<>();

    public Heap(){

    }
    public Heap(E[] objects){
        for (E object : objects) {
            add(object);
        }
    }

    // 向堆增加一个新节点,首先添加到堆的末尾,然后自底向上根据堆的属性调整堆

    public void add(E newObject){
        list.add(newObject);
        // 数组列表中最后一个节点的索引,也即是刚插入的节点的索引
        int curIndex = list.size() - 1;

        while (curIndex > 0){
            // 求出当前节点的父亲节点的索引值
            int parentIndex = (curIndex - 1) / 2;
            // 将当前节点的值和其父亲节点的值进行比较,如果大于父亲节点的值则二者进行交换
            if (list.get(curIndex).compareTo(list.get(parentIndex)) > 0){
                E temp = list.get(curIndex);
                list.set(curIndex,list.get(parentIndex));
                list.set(parentIndex,temp);
            }else {
                break;
            }
            curIndex = parentIndex;
        }
    }

    // 删除根节点,首先将最后一个节点替换掉根节点,然后按照堆的属性调整重建二叉树

    public E remove(){
        if (list.size() == 0){
            return null;
        }
        E removeObject = list.get(0);
        // 将最后一个节点替换根节点
        list.set(0,list.get(list.size()-1));
        list.remove(list.size()-1);
        int curIndex = 0;
        while (curIndex < list.size()){
            int leftChildIndex = 2 * curIndex +1;
            int rightChildIndex = 2 * curIndex + 2;
            if (leftChildIndex >= list.size()){
                break;
            }
            int maxIndex = leftChildIndex;
            if (rightChildIndex < list.size()){
                if (list.get(maxIndex).compareTo(list.get(rightChildIndex)) < 0){
                    maxIndex = rightChildIndex;
                }
            }

            if (list.get(curIndex).compareTo(list.get(maxIndex)) < 0){
                E temp = list.get(maxIndex);
                list.set(maxIndex,list.get(curIndex));
                list.set(curIndex,temp);
                curIndex = maxIndex;
            }else {
                break;
            }
        }
        return removeObject;
    }

    public int getSize(){
        return list.size();
    }

}

 

5、堆排序(基于构建堆和删除根节点)

  堆排序的思想就是借助于大顶堆的特点,每次将堆的根节点删除即获得当前堆中的最大值,直接将堆中所有元素删除完,那么获得就是一个降序的元素列表。因此要实现堆排序,首先将给定的一组元素列表构建成堆,那么就按照添加新结点的方式进行,然后开始排序,即按照删除根节点的方式进行(获得的结果是降序的)。

最终的实现代码如下:

public class HeapSort {
    public <E extends Comparable<E>> void heapSort(E[] list){

        Heap<E> heap = new Heap<>();
        for (E e : list) {
            heap.add(e);
        }

        for (int i = list.length-1; i >=0 ; i--) {
            list[i] = heap.remove();
        }
    }

    public static void main(String[] args) {
        Integer[] list = {-44,-5,-3,3,3,1,-4,0,1,2,4,5,53};
        HeapSort heapSort = new HeapSort();
        heapSort.heapSort(list);
        for (Integer integer : list) {
            System.out.print(integer + " ");
        }
    }
}

 

 

参考自《Java语言程序设计》(进阶篇)

posted @ 2020-05-24 20:00  有心有梦  阅读(272)  评论(0编辑  收藏  举报