决定单独开一篇来说堆排序,因为觉得不好理解
堆排序的基本概念
所谓的堆排序就是利用二叉树的数据结构来对数据进行一定顺序的排列
比如,有一个数组
int[] list = {0, 1, 2, 3, 4, 5, 6};
那么我们可以用二叉树的形式把他排列成下图
这就是很直观的一个“堆”了
插一小段,为了方便查看,写了一段代码用堆的形式来输出一个数组
ArrayList<Integer> arrayList = new ArrayList<>(Arrays.asList(list));
public static void printAsHeap(ArrayList<Integer> arrayList) {
int base = 1;
while (base <= arrayList.size()) {
int gap = 2 * base - base;
int num = Math.min(gap, arrayList.size() - base + 1);
for (int i = 0; i < num; i++) {
System.out.print(arrayList.get(base + i - 1) + " ");
}
System.out.println();
base = base * 2;
}
}
好的大概知道了“堆”之后,我们还要引入大顶堆/小顶堆的概念。
大顶堆/小顶堆
顾名思义,大顶堆就是越往堆的顶部数据越大,小顶堆则是越小。
如上图就是一个小顶堆
父节点的数据小于子节点的数据,以上图为例
以第一层为父, {1} < {2, 3}
以第二层为父, {2} < {4, 5} {3} < {6}
只要保持父小于子,我们就可以说它是一个小顶堆,左子节点和右子节点之间不需要有固定的大小关系
开始排序
那么大概需要知道的概念就是上面的这些了
那么我们打乱一下原数据
int[] list = {3, 5, 1, 0, 6, 2, 4};
我们需要把他变成升序
int[] target = {0, 1, 2, 3, 4, 5, 6};
在堆排序中,做法是:
- 得到这个数组中的最大值
- 将这个最大值另外储存起来(可以放在数组最后
- 对剩余的值不断重复前两个步骤
这样看起来就很简单了,对于第一步的做法,只要结合前面的概念就能知道,将这个数组排列整理为大顶堆的形式,就能够得到这个数组的最大值,也就是这个大顶堆的根节点。
如何排列为大顶堆
找了半天的资料愣是没看懂,不知道是我太笨还是网上的太乱【。应该是蠢
然后看了下 《数据结构与算法分析:Java语言描述》里的代码,照着写了遍就懂啥意思了【。还是蠢
- 对于数组中的每一个节点,以这一节点为父节点,向下寻找子节点最大的分支
比如下图是乱序list的堆序列
那么我们从根节点 5 开始,找下一层的最大子节点分支, 也就是 5-6, 把大的节点往上顶,把小节点向下移,于是就变为 6-5 (6为父节点,5为子节点) - 从倒数第二层开始,向上对每一个节点进行步骤1操作
于是这样就能排列为一个大顶堆了
第一步的代码如下
/**
* 先把第i位的数字取出来
* 然后找到他的子节点最大的分支(分支内的所有节点都必须比第i位的数字大)
* 这一分支的所有子节点全部往上顶一位
* 最后把取出来的数放在分支最后一位
*/
private static void percDown(ArrayList<Integer> list, int i, int n) {
int child;
Integer temp;
for (temp = list.get(i); leftChild(i) < n; i = child) {
child = leftChild(i);
if (child != n - 1 && list.get(child).compareTo(list.get(child + 1)) < 0)
child++;
if (temp.compareTo(list.get(child)) < 0)
list.set(i, list.get(child));
else break;
// System.out.println("percDown from node " + i);
// printAsHeap(list);
}
list.set(i, temp);
}
在堆排序主函数中,我们先对数组进行一次排列,使之变成大顶堆
public static void heapSort(ArrayList<Integer> list) {
// 先进行一次初始的堆排序
for (int i = list.size() / 2 - 1; i >= 0; i--) {
percDown(list, i, list.size());
}
}
这一段很容易可以看到,是从二叉树的倒数第二层开始进行排序的,每一次都会从 某节点开始,向下找到该节点的有最大数据的子节点的路径,然后大的向上顶,小的向下排【。这段好绕
这一段的过程和结果如下图
存放最大值与重排大顶堆
那么之后剩下的问题就是要取出最大元素然后把剩下的数组排列了,
这个比较简单了
完整的代码如下
public static void heapSort(ArrayList<Integer> list) {
// 先进行一次初始的堆排序
for (int i = list.size() / 2 - 1; i >= 0; i--) {
percDown(list, i, list.size());
}
// System.out.println("***Intialization Result***");
// printAsHeap(list);
for (int i = list.size() - 1; i > 0; i--) {
swap(list, 0, i);
percDown(list, 0, i);
}
// printAsHeap(list);
}
好了然后就完成了,把代码放在class Sort里,直接调用
Sort.heapSort(arrayList);
arrayList 里就被排成升序了
最后多嘴一句,牢记一块代码一种功能的思想呀,要减少耦合呀,public static 是极好的呀
最后的最后感谢燕燕这么晚还不睡陪着我吧
最后的最后的最后,突然想到个问题
既然都已经排列成了大顶堆,那为啥不再对堆的定义再严格一点,使他的节点值恰好按照节点编号顺序递减呢,这样就能直接得到一个排列好的数组了
之后试试遍一下这样的代码