“盘膝而坐,五心向天”
(钢炼截图)
关于堆排序的原理感觉不是甚难,可是在看网课老师代码的时候细节略迷糊,所以整理一下以供自己加深印象:
首先附上堆排序思路:
(1) 将无序序列构建成一个堆,根据升序降序需求选择大顶堆或小顶堆;
(2) 将堆顶元素与末尾元素交换,将最大元素"沉"到数组末端;
(3) 重新调整结构,使其满足堆定义,然后继续交换堆顶元素与当前末尾元素,反复执行调整+交换步骤,直到整个序列有序。
最大难点:构造大顶堆的过程
顺序是从下往上,从左往右依次考虑非叶子结点。对于每个非叶子结点,考虑其左右子树的较大值与其比较,如果子树里的较大值比该结点大,则将该结点和该较大子树进行交换,且交换后该结点要与原来子树的子树继续进行比较交换(这里也是弹幕里那么多人问为什么要写那个for循环的原因!)。
解释:由于是从下往上依次进行,所以下面的子树已经依次按从小到大的顺序排好了,但是上面的非叶子结点还没有参与过比较,有可能按大小顺序排列比其子树的子树还要小,即与子树以及子树的子树进行比较的本质就是要把该结点插入到下面按顺序排列好的树中。
为了解释后面的代码,这里先引入一个小例子,有一个数组arr如下,除了第0个元素12没有按顺序外,后面的元素已经按顺序排列好了,我现在要将12也放到按顺序排列的位置处(即13和10的中间):int arr[] = {12,20,19,15,13,10,9,8,5,1} ;
这里并不难,相邻元素两两比较然后交换顺序即可,但这里我想就交换顺序的代码做一点探讨:
对于像我一样经验不多的同学,可能第一时间会想到(a=b,b=c,c=a)这种方法,即按下面这样写,每次两两交换(注意因为每次比较后i的位置变成了k的位置,要加上i=k这一行):
int arr[] = {12,20,19,15,13,10,9,8,5,1} ;
//要求:把第一个元素放到合适的位置去
int i = 0;
//方法一:
for(int k = 1; k < arr.length;k++){
if(arr[k] > arr[i]){
int temp = arr[i];
arr[i] = arr[k];
i = k;
arr[i] = temp;
}
}
System.out.println("结果"+Arrays.toString(arr));
上面的处理是对的,只是对于这种多个元素交换位置的情况可以改进代码:
//方法二:
int temp = arr[i];
for(int k = 1; k< arr.length;k++){
if(arr[k] > temp){
arr[i] = arr[k];
i=k;
}else{
break;
}
}
arr[i] = temp;
二者区别在什么地方?看下面的图就明白了:
方法二展开就是:
注意方法二除了改进方法同时加了一个break(因为是按顺序排列的,有一个值比它小了,后面只会更小就不用比较了)
基于上面的讨论,就能解释老师的下面这部分代码了:
//将一个数组(二叉树),调整为一个大顶堆
/**
* 功能:完成将以i对应的非叶子结点的树调整为大顶堆
* 个人理解:就是以i为根节点的子树调整为大顶堆
* 举例:int arr[] = {4,6,8,5,9};
* @param arr 待调整的数组
* @param i 表示非叶子节点在数组中索引
* @param length 对多少个元素调整,在逐渐减少
*/
public static void adjustHeap(int arr[], int i, int length){
int temp = arr[i];//先取出当前元素的值,保存在临时变量
//开始调整
//说明:
//1.k = i*2+1 k是i结点的左子结点,k+1是右子结点,但是可能没有右子结点,得判断
for(int k = i*2+1;k<length;k = k*2+1){
if(k+1<length && arr[k] < arr[k+1]){//说明左子结点的值小于右子结点
k++;//!!! i 指向 k,继续循环比较(因为下面还有子结点,继续比)
}
if(arr[k] > temp){//如果子结点大于父节点
arr[i] = arr[k];//把较大值赋给当前结点
i=k;//i指向k
}else {
break;//因为下面的结点已经按顺序从小到大排好了,
// 如果子结点小,那子结点的子结点只会更小,就不用比较下去了
}
}
//当for循环结束后,我们已经将以i为父结点的树的最大值,放在了最顶上(局部)
arr[i] = temp;//将temp值放到调整后的位置
}
然后编写完整的堆排序方法(会调用上面调整堆程序):
//编写一个堆排序的方法
public static void heapSort(int arr[]){
int temp = 0;
System.out.println("堆排序!");
//1. 将无序序列构建成一个堆,根据升序降序需求选择大顶堆或小顶堆
for(int i = arr.length/2-1; i>=0; i--){
adjustHeap(arr,i,arr.length);
}
System.out.println("数组=" + Arrays.toString(arr));//9 6
/*
2).将堆顶元素与末尾元素交换,将最大元素"沉"到数组末端;
3).重新调整结构,使其满足堆定义,然后继续交换堆顶元素与当前末尾元素,反复执行调整+交换步骤,
直到整个序列有序。
*/
for(int j = arr.length-1; j>0;j--){
//交换
temp = arr[j];
arr[j] = arr[0];
arr[0] = temp;
adjustHeap(arr,0,j);
}
System.out.println("数组=" + Arrays.toString(arr));
}
解释步骤(1):
首先堆一定是完全二叉树,对于完全二叉树,从下往上第一个(即下标最大的)非叶子结点arr.length/2-1,且从0到该下标都是非叶子结点。
解释步骤(2)(3):
将堆顶元素与末尾元素交换后,为什么构造新的大顶堆不需要像步骤(1)一样把所有非叶子结点走一遍,而只是把0结点调整一下?
因为步骤(2)是把根节点和末尾结点对调,且步骤(3)又不需要用到尾结点,所以剩下的元素中除了根节点外,其他结点仍然是按顺序排好的,不再需要重新走一遍。
但是注意:执行一次adjustHeap,不只是将根节点和其挨着的子树比较,而是会一直和子树的子树比较,直到找到合适的位置(这就是adjustHeap里面那个for的作用),完成大顶堆的调整。
main函数内容如下:
public static void main(String[] args) {
//要求升序排序(则大顶堆)
int arr[] = {4,6,8,5,9};
heapSort(arr);
}
老师的代码很优质,只是细节讲的略快,自己悟吧~
~~~~~~~~~
变革:以后可能会偶尔分享简短读书笔记一则,或有趣人物、情节、对话,以及引用的诗文皆有可能。
分享1:《射雕》篇
宋代·欧阳修《定风波·把酒花前欲问公》
把酒花前欲问公。对花何事诉金钟。为问去年春甚处。虚度。莺声撩乱一场空。
今岁春来须爱惜。难得。须知花面不长红。待得酒醒君不见。千片。不随流水即随风。
《射雕》里黄药师曾对梅超风吟过“待得酒醒君不见,不随流水即随风”此词,果然梅超风后来随着陈玄风走了。 欧阳修1007年-1072年,郭靖生于1200年,比黄蓉大几岁,梅超风离开桃花岛的时候有提到大师兄30多岁,师父比他大十多岁,推测师父应该是40多岁生的黄蓉,故师父自己大约出生于1150-1160年,这个年代比欧阳修晚一个世纪左右,欧阳修的作品应该已经广泛流传开来,所以黄药师能随口吟欧阳修的诗文也挺合理。同时黄药师的徒弟都是以风命名:曲灵风、陈玄风、梅超风、武罡风(武眠风)、冯默风,我感觉多多少少和这句“不随流水即随风”的“风”有些关系。
兄弟们加油!
欢迎关注下走的个人号哈哈~