基本排序算法之堆排序
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语言程序设计》(进阶篇)