的确,在实际项目开发过程中,很少用到算法,导致对算法不太感冒,总是一知半解。最近打算leetcode上刷算法题,但基础不好,感觉真的寸步难行,所以还得踏踏实实的从基础抓起。今天就彻底理解一下堆排序,没有谁比谁聪明,努力终究能见到回报。

一.堆

1.堆是什么?这里的堆, 是指二叉堆,可以理解成就是数组arr[n],如果数组满足arr[i] >= arr[2i+1] && arr[i] >= arr[2i+2],称为大顶堆;反之arr[i] <= arr[2i+1] && arr[i] <= arr[2i+2],称为小顶堆;如

长度为8的数组A[50,45,40,20,25,35,30,10,15]就可以叫做一个大顶堆;上图中用二叉树表示,不过为方便理解,本质上就是一个数组而已。

同理,数组B[10,20,15,25,50,30,40,35,45]是一个小顶堆

 

2.堆的特点,以长度为8的数组A大顶堆为例,下标0的值大于下标1和2的值,下标1的值大于下标3和下标4的值,下标2的值大于下标5和6的值.......数组中的第一个值总是最大的,或者说堆中的第一个值总是最大的,不知道大家有没有联想到定时任务?联想到优先队列?它们都是堆的应用场景

3.一个无序的数组怎样才能变成大顶堆?

我们以长度为15的数组C来说明,下图中的数字表示对应数组下标值

3.1)在下标为6、13、14这三个数的小结构中,要想实现大顶堆,怎么操作?很简单,下标13和14的值比较一下,找出大的,然后再和下标6的值比较一下,如果下标6的值小,交换一下,保证下标6所在的值大于下标13和14的值即可。同理下标5、11、12的小结构;4、9、10的小结构,3、7、8的小结构

3.2)依次进行比较交换,现在走到了2、5、6的这个小结构中,

经过上面的步骤,此时下标5中的数值是大于下标11和12的,下标6中的数值是大于下标13和14的,如果下标2中的数值特别小,下标2要发生交换动作,和下标5、下标6中较大的数值交换,然后2、5、6这三个小结构满足大顶堆。然后,2、5、6是不是就结束了,开始进行下面的1、3、4和0、1、2?看个具体的例子【X,X,31,X,X,X,0,X,X,X,X,X,X,29,30】

下标6的数值31被交换成了下标2的数值0以后,那下标13、14就要开始造反了,因为0没有29和30大呀,要重新比较下标6、13、14的这个小结构,小的值下沉,大的值上浮

......

就这样,比较下来,最终所有下标都要满足公式arr[i] >= arr[2i+1] && arr[i] >= arr[2i+2],时间复杂度为O(logN).

理解过程,代码其实很简单了,数组到堆代码部分

 1    public void arrayToHeap(int[] a) {
 2           int len = a.length - 1;//长度为15的数组,最后一个下标是14
 3           int beginIndex = (len - 1)/2;//从下标6开始,6、5、4、3、2、1、0
 4           for (int i = beginIndex; i >= 0; i--) {
 5               maxHeapify(a, i, len);
 6           }
 7       }
 8     
 9     /**
10      * 小值下沉,大值上浮
11      * @param a
12      * @param curNode
13      * @param len
14      */
15     private void maxHeapify(int[] a, int curNode, int len) {
16         int li = (curNode << 1) + 1; //下层左 6对应下层左13
17         if (li > len) return;//下层左 超出计算范围,直接返回
18         int ri = li + 1;//下层右 6对应下层右14
19         if (ri > len) return;//下层右 超出计算范围,直接返回
20         int cMax = li;
21         if (ri <= len && a[cMax] < a[ri]){//先判断左右,哪个较大
22             cMax = ri;
23         }
24         //如果下层左或右对应的值大于当前值,交换
25         if (a[cMax] > a[curNode]) {//大顶堆,若“<”表示小顶堆
26             swap(a, cMax, curNode);//对应数组值交换
27             maxHeapify(a, cMax, len);//交换后可能会导致下层的节点不符合大顶堆了,继续下层的可能交换动作
28         }
29     }
数组到堆

二.堆排序

1.数组C(0,n),满足大顶堆,数组中第一个数总是最大的,单独放好(交换到数组最后一个位置上);把C(0,n-1)再变成一个大顶堆,取出第一位最大值,放好(交换到数组倒数第二个位置上);把C(0,n-2)再变成一个大顶堆,取出第一位最大值,放好(交换到数组倒数第三个位置上)......最后数组从小到大排好顺序,时间复杂度O(NlogN),原地排序,空间复杂度O(1),堆排序不稳定(稳定性,是指在原序列中,arr[i]=arr[j],且arr[i]在arr[j]之前;排序后的序列中,arr[i]仍在arr[j]之前,则称这种排序算法是稳定的;否则称为不稳定的

堆排序代码部分

 1 public void heapSort(int[] a) {
 2         //1.完整数组转堆
 3         int len = a.length - 1;//长度为15的数组,最后一个下标是14
 4         int beginIndex = (len - 1)/2;//从下标6开始,6、5、4、3、2、1、0
 5         for (int i = beginIndex; i >= 0; i--) {
 6             maxHeapify(a, i, len);
 7         }
 8         //2.交换堆中第一个值,剩下的数组再转成堆
 9         for (int i = len; i > 0; i--) {
10             swap(a, 0, i);//每次取堆中第一位最大值交换堆中最后一位
11             maxHeapify(a,0, i - 1);//去掉第一位最大值后 再变成大顶堆
12         }
13     }
14     
15     /**
16      * 小值下沉,大值上浮
17      * @param a
18      * @param curNode
19      * @param len
20      */
21     private void maxHeapify(int[] a, int curNode, int len) {
22         int li = (curNode << 1) + 1; //下层左 6对应下层左13
23         if (li > len) return;//下层左 超出计算范围,直接返回
24         int ri = li + 1;//下层右 6对应下层右14
25         if (ri > len) return;//下层右 超出计算范围,直接返回
26         int cMax = li;
27         if (ri <= len && a[cMax] < a[ri]){//先判断左右,哪个较大
28             cMax = ri;
29         }
30         //如果下层左或右对应的值大于当前值,交换
31         if (a[cMax] > a[curNode]) {//大顶堆,若“<”表示小顶堆
32             swap(a, cMax, curNode);//对应数组值交换
33             maxHeapify(a, cMax, len);//交换后可能会导致下层的节点不符合大顶堆了,继续下层的可能交换动作
34         }
35     }
堆排序