* 背景 |
近期温习算法,看到自己尘封许久的算法专栏,也应该增加新的成员了。今天给大家分享的是堆排序,选择排序的一种算法。
堆排序是利用对这种数据结构而设计的一种排序算法,他的最坏,最好,平均时间复杂度均为o(nlogn),同时也是不稳定的排序。
* 知识储备 |
堆是具有如下性质的完全二叉树:每个节点的内容都大于或等于其左右孩子节点的值,为大顶堆;或者每个节点的值都小于等于其左右孩子节点的值成为小顶堆。如下图:(图片来自社会)
对于堆的数据结构我们的存储方式可以采用线性存储,也可以采用链式存储,这里我们用线性存储的数组来存放他。
如果用数组的存储,我们来描述一下他的堆的定义:
大顶堆:arr[i]>=arr[2*i+1] && arr[i]>=arr[2*i+1]
小顶堆:arr[i]<=arr[2*i+1] && arr[i]<=arr[2*i+1]
* 堆排序的步骤 |
首先规定,我们排序的结果是升序,那我们就要构造大顶堆,这时根节点便是最大值,此时我们用最后一层的叶子节点与其交换,此时末尾的便是最大值,然后将剩余的n-1个节点重新构造成一个最大堆,然后根节点便成为n-1个节点的最大值,如此反复,便可以得到一个升序的序列了。
3.1 第一次建堆
这里我们说一下建堆的过程,假定无序序列为:4,6,8,5,9,转换为二叉树的结构为:
1. 我们从最后一层的叶子节点(从右往左)的父节点开始,使其满足最大堆的性质,那么我们先比较的是根节点为6的节点和其孩子节点是否满足性质。首先比较其左右孩子节点,5和9,发现9大,之后比较根节点6和右孩子9,发现9依然大,我们让9占到根节点的位置,那么6呢?因为9没有孩子节点了,所以6可以直接占到右孩子的位置。至此,这三个节点变满足了最大堆的性质了。构建过程如下:
2.最后一层的叶子节点的父节点我们已经排完了,继续上一层叶子节点(从右到左)的父节点为4,我们构建最大堆,这是比较父节点为4的孩子节点9,8发现9大,于是我们比较根节点4和9,依然是9大,这时9便坐到根节点的位置,那么原来的4呢?直接和左孩子交换吗?不是的,我们可以存在临时变量tmp里,这是我们让左孩子成为父节点,比较其孩子节点,发现6大,这是我们比较tmp和6,发现6比4大,于是原来右孩子6现在成为父节点,而4最后坐到了右孩子的位置,至此我们构建成大顶堆。构建过程如下:
3.2 循环建堆
将堆顶元素与末尾元素进行交换,使末尾元素最大。然后继续调整堆,再将堆顶元素与末尾元素交换,得到第二大元素。如此反复进行交换、重建、交换。具体重建堆的步骤不再赘述,请参考3.1
最后的调整结果为:
* 代码实现 |
如果这里没有代码,我认为我便是在耍流氓。下面是自己写的代码,当然之前自己也手写过,发现还是有必要执行一下,我们的思想和机器的思想有一些差别的。
/**
* 建堆过程(最小堆)-lyw--2018年8月25日23:40:52
*/
public void sfit(int[] arr,int k,int n){
int i=k;
int j=2*i+1;
int tmp=arr[k];
while (j<=n){
if (j<n && arr[j]>arr[j+1]){
j++;
}
if (tmp <arr[j]){
break;
}else {
arr[i]=arr[j];i=j;j=2*i+1;
}
}
arr[i]=tmp;
}
/**
* 交换
* @param
* @param
*/
public void swap(int[] arrTmp,int x,int y){
int tmp=arrTmp[x];
arrTmp[x]=arrTmp[y];
arrTmp[y]=tmp;
}
int[] arr={1,0,12,2,5,8,2,9,3};
/**
* 堆排序全过程--刘雅雯--2018年8月26日08:10:23
*/
@Test
public void heapSort(){
//构建一个最小堆
for (int i=arr.length/2;i>=0;i--){
sfit(arr,i,arr.length-1);
}
//循环一次构建一个最小堆
for (int i=arr.length-1;i>0;i--){
//swap(arr[0],arr[i]);
swap(arr,0,i);
sfit(arr,0,i-1);
}
//打印结果
System.out.print("堆排序的结果:");
for (int i=0;i<arr.length;i++){
System.out.print(arr[i]+", ");
}
}
* 结语 |
真的做起来也不是很困难,更多的是我们想不想话时间去做下去,我想这也是高级开发程序员必备的技能吧。