堆排序
1、定义
众所周知,有一种二叉树叫排序二叉树,他要求每一颗子树的左子树的节点值要小于(或大于)根节点,右子树节点值要大于(小于)根节点值,并且每一颗子树都是排序二叉树。 堆和排序二叉树有相似的地方,他要求每一颗子树的根节点都大于或者小于它的左右子树的节点值,其中每一颗子树的根节点值大于左右子树称之为大根堆,反之就是小根堆 因此堆排序就是将数组的值构建为大(小)根堆,然后取走堆顶元素,将堆底元素放置堆顶后重新调整堆结构让它成为大(小)根堆,继续重复操作直至完成排序的过程。
2、代码设计
用数组[4,5,7,3,2,1,6,8]构建大根堆来举例
2.1、构建堆结构
构建堆结构有两种,上浮和下沉
2.1.1、上浮
上浮是从堆顶开始,不断的循环判断自己和父节点的值,如果大于了父节点,那么父节点就不配做父节点,自己就得上浮替代父节点。
<<< 左右滑动见更多 >>>
public void buildMaxHeapSort(int[] nums){
for (int i = 0; i < nums.length; i++) {
ascend(i,nums);
}
}
private void ascend(int i, int[] nums) {
//从第二个节点开始
//索引从1开始
while (i>0 && nums[i] > nums[(i+1)/2-1]){
swap(nums,i,(i+1)/2-1);
//将自己的索引同样上浮
i = i / 2;
}
}
2.1.2、下沉
下沉是从堆底的父节点开始,不断判断自己和左右子树的节点值,如果小于就得下沉父节点作子节点。
<<< 左右滑动见更多 >>>
public void buildMaxHeap(int[] nums){
//
for (int i = (int)Math.floor(nums.length)-1 ; i >=0; i--) {
sink(nums,i,nums.length-1);
}
}
private int left(int x) {return x*2;}
private int right(int x) {return x*2+1;}
private void sink(int[] nums, int start, int end) {
//要交换的索引位置
//注意索引是从1开始
int maxIndex;
while (left(start+1)-1 <= end){
maxIndex = left(start+1) -1;
//(right(start+1) -1)<= end 右子树是否存在
//nums[right(start+1)-1] > nums[maxIndex]判断左右子树谁的值更大,大的那个当作maxIndex
if(right(start+1) -1 <= end && nums[right(start+1)-1] > nums[maxIndex]) maxIndex = right(start+1)-1;
//再去和根节点比较,满足条件进行交换
if(nums[start] < nums[maxIndex]){
swap(nums,start,maxIndex);
//同时将节点索引不断下沉比较
start = maxIndex;
}else {
break;
}
}
}
下沉和上浮区别在于一个是从堆底的父节点开始向前遍历,一个是从堆顶向后遍历,时间复杂度都是O(n)
2.2、堆排序
构建大根堆之后,堆顶是最大的元素,这时候我们只需要把堆顶和堆底元素进行交换,然后在n-1的范围内重新构建大根堆,再交换,重复多次直到达到排序。
<<< 左右滑动见更多 >>>
由图,往后不停交换即可得到排序效果(后图大家自行脑补,懒得画了~~)
for (int i = nums.length -1 ; i > 0; i--) {
swap(nums,0,i);
sink(nums,0,i - 1);
}
这里除了维护堆结构之外,范围也需要维护,拿掉一个元素时候范围缩小一个单位。
2.1.3、完整代码
public void headSort(int[] nums){
//构建大顶堆
buildMaxHeap(nums);
//交换排序
for (int i = nums.length -1 ; i > 0; i--) {
swap(nums,0,i);
sink(nums,0,i - 1);
}
}
private void buildMaxHeap(int[] nums) {
for (int i = (int)Math.floor(nums.length/2); i >= 0; i--) {
sink(nums, i, nums.length-1);
}
}
private int left(int x) {return x*2;}
private int right(int x) {return x*2+1;}
private void sink(int[] nums, int start, int end) {
int maxIndex;
while (left(start+1)-1 <= end){
maxIndex = left(start+1) -1;
if(right(start+1) -1 <= end && nums[right(start+1)-1] > nums[maxIndex]) maxIndex = right(start+1)-1;
if(nums[start] < nums[maxIndex]){
swap(nums,start,maxIndex);
start = maxIndex;
}else {
break;
}
}
}
觉得写的好可以关注下公众号,可以免费领大数据,java,资料~~