清晰理解堆排序

堆的定义

  • 一个完全二叉树中,任意父结点总是大于或等于(小于或等于)任何一个子节点,则为大顶堆(小顶堆)。

堆的数组存储方式

完全二叉树适合采用顺序存储的方式,因此一个数组可以看成一个完全二叉树。

  • 节点编号:树根起,自上层到下层,每层从左至右,给所有结点顺序编号,能得到一个反映整个二叉树结构的线性序列。

clip_image001

  • 编号特点

从一个结点的编号就可推得其双亲,左、右孩子,兄弟等结点的编号。假设编号为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无右孩子。

注:ki(0≤i≤n)满足数组下标时,则可能的左右孩子分别为2i+1、2i+2。

堆排序的思想(以大顶堆为例)

利用堆顶记录的是最大关键字这一特性,每一轮取堆顶元素放入有序区,就类似选择排序每一轮选择一个最大值放入有序区,可以把堆排序看成是选择排序的改进

  1. 将初始待排序关键字序列(R0,R1,R2....Rn)构建成大顶堆,此堆为初始的无序区;
  2. 将堆顶元素R[0]与最后一个元素R[n]交换,此时得到新的无序区(R0,R1,R2,......Rn-1)和新的有序区(Rn);
  3. 由于交换后新的堆顶R[0]可能违反堆的性质,因此需要对当前无序区(R0,R1,R2,......Rn-1)调整为新堆。

不断重复此2、3步骤直到有序区的元素个数为n-1,则整个排序过程完成。

算法分析

image

筛选算法

//最难理解的地方

  • 目标:一个所有子树都为堆的完全二叉树。意思就是这个二叉树只差跟节点不满足堆的结构。//很重要,很重要,很重要

    如下图:

clip_image002

  • 方法:首先将root和它的左右子树的根结点进行比较,把最大的元素交换到root节点;然后顺着被破坏的路径一路调整下去,直至叶子结点,就得到新的堆。

clip_image003

  • 运用:1.在上文提到的堆排序思想,2-3步骤中将无序区调整为堆的时候用到。

          2.初始化堆

初始化堆

从最后一个非叶子节点i(i=n/2,n为节点个数)开始,将以i为根节点的二叉树通过筛选调整为堆。以第一张图为例,编号顺序为8、7、6...1。

从最后一个非叶子节就保证了筛选算法的正确性,因为筛选算法的目标是一个所有子树都为堆的完全二叉树

java实现堆排序

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
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.将无顺区调整为大顶堆,即选择出最大的元素。
         }
     }   
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值