大顶堆小顶堆
public class A013_堆排序_大顶堆小顶堆 {
public static void main(String[] args) {
// int[] a = { 78, 56, 34, 43, 4, 1, 15, 2, 23 };
int[] a = { 5, 6, 3, 2, 8, 1 };
midOrder(a, 0);
System.out.println();
DoMinHeap(a);
midOrder(a, 0);
System.out.println();
DoMaxHeap(a);
midOrder(a, 0);
}
private static void DoMaxHeap(int[] a) {
for (int i = a.length / 2 - 1; i >= 0; i--)
MaxHeapFixDown(a, i);
}
public static void MaxHeapFixDown(int[] a, int parent) {
int left = parent * 2 + 1;
int right = parent * 2 + 2;
if (left >= a.length)
return;
int max = left;
if (right < a.length) {
if (a[max] <= a[right])
max = right;
}
if (a[parent] >= a[max]) {
return;
} else {
int t = a[parent];
a[parent] = a[max];
a[max] = t;
MaxHeapFixDown(a, max);
}
}
public static void DoMinHeap(int[] a) {
for (int i = a.length / 2 - 1; i >= 0; i--)
MinHeapFixDown(a, i);
}
public static void MinHeapFixDown(int[] a, int parent) {
int left = parent * 2 + 1;
int right = parent * 2 + 2;
if (left >= a.length)
return;
int min = left;
if (right < a.length) {
if (a[min] >= a[right])
min = right;
}
if (a[parent] <= a[min]) {
return;
} else {
int t = a[parent];
a[parent] = a[min];
a[min] = t;
MinHeapFixDown(a, min);
}
}
private static void midOrder(int[] arr, int i) {// 中
if (i >= arr.length)
return;
midOrder(arr, i * 2 + 1);
System.out.print(arr[i] + " ");
midOrder(arr, i * 2 + 2);
}
}
这是中序遍历生成的数组👆,第一行是初始的,第二行是小顶堆,第三行是大顶堆。
这里做了一个小顶堆,做了一个大顶堆,代码都差不多,可以通过max和min进行区分,这里就讲个小顶堆吧。
首先通过DoMinHeap方法,进行for循环,他是倒过来进行调整的,也就是非叶子结点的最后一个开始,进行调整,这里就是a.length / 2 - 1,数组长度除2,再-1就是了,然后把每一个数,都调用MinHeapFixDown方法:
进入方法后,先获取这个节点的左右节点坐标,如果左节点越界了,就说明是叶子节点直接return;这里用一个中间量min记录左节点的值,以方便接下来的代码,如果右节点的下标是正常的,那就说明有右节点,则进行判断,如果右节点,比左节点还要小,那么就把右节点的值,赋给min,就是min = right;
然后这个时候,就只有min这一个值了,然后开始将它与父节点进行判断,如果父节点是比min这个下标所在的节点还要小,那么就说明,这一个堆是个小顶堆,不用调整,else的话,就把min和parent两个下标所在的值进行交换,交换过后,还要进入递归,为什么呢?
因为交换过后,要判断,交换过后的,也就是min这个下标所在的堆,是不是小顶堆,如果不是的话,就递归再进行判断,如果越界的话,就直接在上面的出口,return了。
堆排序
import java.util.Arrays;
public class A013_堆排序_优化 {
public static void main(String[] args) {
int[] a = { 78, 56, 34, 43, 4, 1, 15, 2, 23 };
// int[] a = { 5, 6, 3, 2, 8, 1, 9 };
System.out.println(Arrays.toString(a));
sortMin(a);
System.out.println(Arrays.toString(a));
sortMax(a);
System.out.println(Arrays.toString(a));
}
private static void sortMin(int[] a) {
DoMinHeap(a);
for (int i = a.length - 1; i >= 0; i--) {
int t = a[0];
a[0] = a[i];
a[i] = t;
MinHeapFixDown(a, 0, i);
}
}
public static void DoMinHeap(int[] a) {
for (int i = a.length / 2; i >= 0; i--)
MinHeapFixDown(a, i, a.length);
}
public static void MinHeapFixDown(int[] a, int parent, int n) {
int left = parent * 2 + 1;
int right = parent * 2 + 2;
if (left >= n)
return;
int min = left;
if (right < n) {
if (a[min] >= a[right])
min = right;
}
if (a[parent] <= a[min]) {
return;
}
int t = a[parent];
a[parent] = a[min];
a[min] = t;
MinHeapFixDown(a, min, n);
}
private static void sortMax(int[] a) {
DoMaxHeap(a);
for (int i = a.length - 1; i >= 0; i--) {
int t = a[0];
a[0] = a[i];
a[i] = t;
MaxHeapFixDown(a, 0, i);
}
}
private static void DoMaxHeap(int[] a) {
for (int i = a.length / 2 - 1; i >= 0; i--)
MaxHeapFixDown(a, i, a.length - 1);
}
public static void MaxHeapFixDown(int[] a, int parent, int n) {
int left = parent * 2 + 1;
int right = parent * 2 + 2;
if (left >= n)
return;
int max = left;
if (right < n) {
if (a[max] <= a[right])
max = right;
}
if (a[parent] >= a[max]) {
return;
} else {
int t = a[parent];
a[parent] = a[max];
a[max] = t;
MaxHeapFixDown(a, max, n);
}
}
}
这里没用三种树的遍历方法,直接将数组从头到尾输出,第一行是初始的,第二行是小顶堆后的排序,第三行是大顶堆后的排序,可以看到小顶堆后的排序是降序,大顶堆反之。
上面讲了小顶对,我们这里讲大顶堆吧,首先调用的sortMax方法,进入该方法后,首先把乱序的数组通过调用DoMaxHeap,变成一个大顶堆,这和上面小顶堆的形成是一样的,就不多做赘述了,然后进入一个循环,由于现在数组是一个大顶堆,那么最大的那个数,就是在最头上,所以这时候,把数组的第一个元素,和数组的最后一个元素进行交换,然后将除了最后一个元素外的,前面的元素,用MaxHeapFixDown方法,再次进行大顶堆的生成,然后再回到for循环,还是用堆顶的元素,与i–,也就是数组的倒数第二个元素进行交换,以此类推,这样就会生成一个大元素在后,小元素在前的升序数组了。