961全部内容链接
交换排序就是通过交换两个元素的位置,然后实现的排序。就是排序过程中,需要频繁交换两个元素的位置
这个是比较基本的排序算法,没学过也应该能自己想到。大致原理就像气泡往上冒。基本思想为:从最后一个元素开始,依次与前一个元素对比,若小于前一个元素,则两个元素交换位置,直到对比到第一个元素。然后进入下一轮,还从最后一个元素开始比较,这次比较到第二个元素,重复刚才的动作,依次类推。
public static void bubbleSort(Comparable[] array) {
if (array == null) return;
// 第一轮从最后一位对比到第0位,第二轮从最后一位对比到第1位,依次类推,当n轮过去后,即排序完毕
for (int i = 0; i < array.length - 1; i++) {
boolean flag = false; // 定义flag,标记是否发生了交换,若某轮没发生交换,说明已经有序,不需要再对比下去
// 从最后一个开始,不断向前对比,直到对比到i位置。
for (int j = array.length - 1; j > i; j--) {
if (array[j].compareTo(array[j - 1]) < 0) {
// 若后一位比前一位小,则交换两个位置,并标记发生了交换
Comparable temp = array[j - 1];
array[j - 1] = array[j];
array[j] = temp;
flag = true;
}
}
if (!flag) return; // 若该轮对比结束时没有发现有元素发生交换,说明已经有序,不需要再进行后续对比
}
}
易错点:
复杂度分析:
稳定性:稳定的。在两个元素交换时,若相等,则不发生交换
适用性:顺序存储和链式存储均可。
快速排序也是利用交换,基本思想为:每次将一个元素放到它最终的位置,即数组排好序后它应该在的位置。具体做法为:首先选定一个元素作为“枢纽(pivot)”(比如选择第一个元素),之后通过第一轮比较交换,将数组分为三个部分,“小于枢纽”,“枢纽”,“大于枢纽”,这个过程称为partition。然后再递归的对小于枢纽的部分和大于枢纽的部分进行再次进行同样的操作。最终完成快速排序。
举例,对于无序数列8975339012进行快速排序:
8 | 9 | 7 | 5 | 3 | 3 | 9 | 0 | 1 | 2 | |
---|---|---|---|---|---|---|---|---|---|---|
第一轮 | 7 | 5 | 3 | 3 | 0 | 1 | 2 | 8 (pivot) | 9 | 9 |
第二轮 | 5 | 3 | 3 | 0 | 1 | 2 | 7(pivot) | 8 | 9(pivot) | 9 |
第三轮 | 3 | 3 | 0 | 1 | 2 | 5(pivot) | 7 | 8 | 9 | 9 |
第四轮 | 0 | 1 | 2(pivot) | 3 | 3 | 5 | 7 | 8 | 9 | 9 |
在该例子中:
Java代码:
public static void quickSort(Comparable[] array) {
if (array == null) return;
quickSort(array, 0, array.length - 1); // 对整个数组进行快速排序
}
private static void quickSort(Comparable[] array, int left, int right) {
if (left >= right) return; // 当左节点与右节点重合时,说明该快速排序只有一个元素,直接返回。 这个不能漏,否则递归会永远递归下去。
// 对 [left,right]这个范围内的数据进行partition操作,将数组分为 小于等于枢纽,枢纽和大于枢纽,然后返回这个枢纽的下标
int position = partition(array, left, right);
quickSort(array, left, position - 1); // partition后,对其左边再次进行快速排序
quickSort(array, position + 1, right); // 对其右边再次进行快速排序
}
private static int partition(Comparable[] array, int left, int right) {
int pivotPosition = left; // 存储left的位置,要不然后面left被修改了,最开始位置就丢失了
Comparable pivot = array[left]; // 定义枢纽
left++; // left原先的位置变成枢纽了,所以+1
while (left <= right) {
// 当left超过right时跳出循环,注意,left和right重合时不能跳出循环,因为重合时那个节点还没有与枢纽进行比较。
if (array[left].compareTo(pivot) > 0) {
// 如果left元素大于枢纽,则与right交换位置,同时right往左移动一位,即-1
Comparable temp = array[left];
array[left] = array[right];
array[right] = temp;
right--;
} else {
// 如果left元素小于或等于枢纽,那么不发生交换,left直接右移即可。
left++;
}
}
// 当循环结束时,right及其左边的都是小于等于枢纽的,right右边的都是大于枢纽的。
// 所以让枢纽再跟right换一下位置。那么就变成了枢纽左边的都是小于等于枢纽的,枢纽右边的都是大于等于枢纽的。
// 这里的right = left - 1,所以right也可以换成left-1
Comparable temp = array[pivotPosition];
array[pivotPosition] = array[right];
array[right] = temp;
return right; // 返回枢纽的位置
}
易错点:
复杂度分析:
稳定性:不稳定。在partition的过程中,会导致两个相同的元素相对位置发生改变。
适用性:仅适用于顺序存储。
选择排序的思路就是每次从数组中选出一个最小的元素放在数组的前面。
直接利用选择排序的思想,没有什么其他心机。基本思想:从数组的第0位开始,从数组的中未排序的数组中选出一个最小的元素,与该位置进行交换。后面以此类推
Java代码:
public static void selectSort(Comparable[] array) {
for (int i=0; i <array.length - 1; i ++) {
int min = i;
for (int j=i+1; j<array.length; j++) {
if (array[min].compareTo(array[j]) > 0) min = j;
}
if (i != min) {
Comparable temp = array[i];
array[i] = array[min];
array[min] = temp;
}
}
}
易错点:
复杂度分析:
空间复杂度:O(1)
时间复杂度:最好,最坏,平均时间复杂度都是O(n^2)
稳定性:不稳定。假设数组为{2,2,1},简单排序之后会变成{1,2,2}。2的相对位置发生了变化
适用性:适用于顺序存储和链式存储。
堆排序是堆这种数据结构的其中一个应用。堆排序基本思想是:将数组构建成一个大根堆(也叫大顶堆,最大堆)。然后将大根堆的堆顶元素出队,然后再次将堆变成大根堆,然后再出队。
具体堆操作去复习前面章节的“优先队列与堆”
Java代码如下:
public static void heapSort(Comparable[] array) {
int size = array.length; // 堆的大小
// 叶子节点公式: i<=size/2-1为叶子节点,否则为分支节点(i>size/2-1)
// 首先构建大根堆:从最后一个分支节点开始,一直到堆顶,每个元素都进行下滤操作。
for (int i = size / 2 - 1; i >= 0; i--) {
percolateDown(array, i, size);
}
while (size > 0) {
// 将堆顶元素与最后一个元素交换,即将最大的元素放入堆的最后,然后将堆的大小-1
Comparable temp = array[size - 1];
array[size - 1] = array[0];
array[0] = temp;
size--;
percolateDown(array, 0, size); // 对堆顶元素进行下滤操作,调整大根堆
} // 当堆为空时,排序完成
}
private static void percolateDown(Comparable[] array, int i, int size) {
Comparable x = array[i]; // 暂存要调整的元素
while (i <= size / 2 - 1) {
int child = i * 2 + 1; // 访问节点的左孩子
if (child + 1 < size && array[child].compareTo(array[child + 1]) < 0) {
child++; // 如果节点有右孩子,且右孩子大于左孩子,则将child执行节点的右孩子
}
if (x.compareTo(array[child]) < 0) {
// 如果x比它更大的那个孩子要小,则交换位置
array[i] = array[child]; // 将节点的值修改为其更大的那个孩子的值
i = child; // i移动到其孩子的位置,这步别忘了
} else {
// 如果x比它更大的那个孩子要大,则下滤完成,跳出循环
break;
}
} // 若节点还是分支节点,就继续下滤,若为叶节点,则跳出循环
array[i] = x; // 下滤完成后,修改最终位置的值
}
易错点:
复杂度分析:
空间复杂度:没有使用额外空间。空间复杂度O(1)
时间复杂度:建堆所需时间O(n),之后向下调整了n-1次,每次调成的时间复杂度为O(h),h为树高。所以最好、最坏和平均情况下,堆排序的时间复杂度为O(n*log n)
稳定性:不稳定。因为要把第一个节点和堆底元素做交换。所以可能会发生相对位置变化。如{1,2,2},构建大根堆之后为 { 2,2,1},最后弄下来变成了 {1,2,2}。