彻底二叉树适合采用顺序存储的方式,所以一个数组能够当作一个彻底二叉树。java
从一个结点的编号就可推得其双亲,左、右孩子,兄弟等结点的编号。假设编号为i的结点是ki(1≤i≤n),则有:算法
①若i>1,则ki的双亲编号为i/2;若i=1,则Ki是根结点,无双亲。数组
②若2i≤n,则Ki的左孩子的编号是2i;不然,Ki无左孩子,即Ki一定是叶子。所以彻底二叉树中编号i>n/2的结点一定是叶结点。函数
③若2i+1≤n,则Ki的右孩子的编号是2i+1;不然,Ki无右孩子。ui
注:ki(0≤i≤n)知足数组下标时,则可能的左右孩子分别为2i+一、2i+2。spa
利用堆顶记录的是最大关键字这一特性,每一轮取堆顶元素放入有序区,就相似选择排序每一轮选择一个最大值放入有序区,能够把堆排序当作是选择排序的改进。3d
不断重复此二、3步骤直到有序区的元素个数为n-1,则整个排序过程完成。blog
//最难理解的地方排序
以下图:递归
2.初始化堆
从最后一个非叶子节点i(i=n/2,n为节点个数)开始,将以i为根节点的二叉树经过筛选调整为堆。以第一张图为例,编号顺序为八、七、6...1。
从最后一个非叶子节就保证了筛选算法的正确性,由于筛选算法的目标是一个全部子树都为堆的彻底二叉树。
package sort; import java.util.Arrays; import util.MathUtil; /** * 经过大顶堆实现堆排序,升序排序 * */ public class HeapSort { public static void main(String[] args) { int[] arr={9,6,12,32,23,11,2,100,85}; sort(arr); System.out.println(Arrays.toString(arr)); } //这里将i定义为彻底二叉树的根 //将彻底二叉树调整为大顶堆,前提是二叉树的根的子树已经为大顶堆。 public static void adjustHeap(int[]a ,int i,int size){ int lChild=2*i+1; //左孩子 int rChild=2*i+2; //又孩子 int max=i; //临时变量 if(i<size/2){ //若是i是叶子节点就结束 if(lChild<size&&a[max]<a[lChild]) max=lChild; if(rChild<size&&a[max]<a[rChild]) max=rChild; if(max!=i){ MathUtil.swap(a, max, i);//交换后破环了子树的堆结构 adjustHeap(a, max, size);//递归,调节子树为堆 } } } //创建堆,堆是从下往上创建的,由于adjustHeap函数是创建在子树已经为大顶堆。 public static void buildHeap(int[]a,int size){ for(int i=size/2;i>=0;i--){//从最后一个非叶子节点,才能构成adjustHeap操做的目标二叉树 adjustHeap(a, i, size); } } //将数组分为两部分,一部分为有序区,在数组末尾,另外一部分为无序区。堆属于无序区 public static void sort(int[] arr){ int size=arr.length; buildHeap(arr, size); for(int i=size-1;i>0;i--){//i为无序区的长度,通过以下两步,长度递减 //堆顶即下标为0的元素 MathUtil.swap(arr, i, 0);//1.每次将堆顶元素和无序区最后一个元素交换,即将无序区最大的元素放入有序区 adjustHeap(arr, 0, i); //2.将无顺区调整为大顶堆,即选择出最大的元素。 } } }