一、堆排序
5.1 堆的定义
- 用数组保存效率高,第
i
个节点保存在下标为i
的位置,也就是arry[0]
空着。 - 第
i
个元素的左节点在2 * i
,右节点在2 * i + 1
。 - 一个节点的父节点在
i / 2
位置。 - 以一个
size
变量记录堆的大小。
5.2 堆的基本操作
5.2.1 提取最大元素
return array[1]
5.2.2 删除最大元素
(1)将 根
和最后一个元素互换,删除最后一个元素
array[1] = array[size];
size--;
这个时候 根
的两个子树是最大堆,但整体不是。
(2)下调算法 (percolate down)
与子节点中较大的那个节点互换位置。如果父节点不是比子节点都大的话,继续这个下调操作。
时间复杂度O(Log2N)
。
/**
* array: 数组
* i: 子堆的根
* size: 堆的大小
*/
public static void percolateDown(int[] array, int i, int size) {
int childIndex;
while ((childIndex = leftChild(i)) < size) {
if (childIndex + 1 < size && // 如果存在右节点
array[childIndex] < array[childIndex + 1]) {
childIndex += 1; // 左右子节点中,较大的一个是右节点
}
// 如果子节点比父节点大,则需要互换
if (array[i] < array[childIndex]) {
swap(array, i, childIndex);
i = childIndex;
} else {
return;
}
}
}
private static int leftChild(int i) {
return 2 * i + 1;
}
5.2.3 插入一个元素
(1)将元素添加到数组的末尾
array[++size] = item;
(2)上调算法(percolate up)
是下调算法的逆过程。如果父节点比自己小,则交换位置。
时间复杂度O(Log2N)
。
5.2.4 建堆
方法一:不断重复插入过程,利用上调操作建堆。
方法二:把一个数组看作是一个完全树,从最后一个非叶子节点开始,应用下调操作将其转化为堆,接着移到前一个节点。(效率更高)下面只介绍方法二。
示例:
int size = array.length;
for (int i = (size - 1) / 2; i >= 0; i--) {
percolateDown(array, i, size);
}
5.3 堆排序步骤
对array进行排序(位置0空闲),size = array.length - 1:
(1)建堆:在array上建堆,建堆之后array[1]是最大的元素。
(2)”删除”最大元素:也就是将 array[1]与array[size]互换,然后从根开始进行下调操作。
(3)循环
时间复杂度:O( N * Log2 N )
。
public static void heapSort(int[] array) {
// 建堆
int size = array.length;
for (int i = (size - 1) / 2; i >= 0; i--) {
percolateDown(array, i, size);
}
// 删除最大元素
for (int i = size - 1; i > 0; i--) {
swap(array, 0, i);
percolateDown(array, 0, i);
}
}
/**
* array: 数组
* i: 子堆的根
* size: 堆的大小
*/
public static void percolateDown(int[] array, int i, int size) {
int childIndex;
while ((childIndex = leftChild(i)) < size) {
if (childIndex + 1 < size && // 如果存在右节点
array[childIndex] < array[childIndex + 1]) {
childIndex += 1; // 左右子节点中,较大的一个是右节点
}
// 如果子节点比父节点大,则需要互换
if (array[i] < array[childIndex]) {
swap(array, i, childIndex);
i = childIndex;
} else {
return;
}
}
}
private static int leftChild(int i) {
return 2 * i + 1;
}
二、快速排序
6.1 算法
6.2 改进
6.2.1 基准的选择
6.2.2 短子列表
6.3 代码
public static final int CUT_OFF = 2;
// 选基准的优化,返回左右中三个数中的中位数,过程类似与插入排序
public static int median(int[] array, int left, int right) {
int center = (left + right) / 2;
if (array[center] < array[left]) {
swap(array, left, center);
}
if (array[right] < array[left]) {
swap(array, left, right);
}
if (array[right] < array[center]) {
swap(array, center, right);
}
// 现在的情况是: left < center(pivot) < right
// 把pivot放到 right-1的位置,这样left和right-1之间都是没有和pivot比较过的数
swap(array, center, right - 1);
return array[right - 1];
}
public static void quickSort(int[] array) {
quickSort(array, 0, array.length - 1);
}
private static void quickSort(int[] array, int left, int right) {
// 小数组就不用快速排序了,直接使用插入排序
if (left + CUT_OFF >= right) {
insertionSort(array);
return;
}
int pivot = median(array, left, right);
// 与基准比较并进行交换
int l = left;
int r = right - 1;
while (true) {
while (array[l] < pivot) {
l++;
}
while (array[r] >= pivot) {
r--;
}
if (l < r) {
swap(array, l, r);
} else {
break;
}
}
// 将基准移到中间来,左边的元素都比基准小,右边的元素都比基准大
swap(array, l, right - 1);
// 递归
quickSort(array, left, l - 1);
quickSort(array, l + 1, right);
}