8.3.交换排序
1. 冒泡排序
核心思想:
-
冒泡排序是重复遍历待排序序列,比较相邻元素的大小并交换逆序的数组元素(如:若为升序遍历,则将满足\(a[i] > a[i + 1]\)的两个元素交换)。
-
每次遍历,都会将未排序数组中的一个元素放到未排序数组的末尾,变成已排序数组中的首元素,最多经过n - 1次排序后,数组变得有序。
-
在遍历过程中增加一个标志位,当这次遍历未发生元素交换时,则说明数组元素已经有序,则直接跳出循环。
实现代码(C语言):
void bubbleSort(int arr[], int n) {
int i, j, temp;
int swapped;
for (i = 0; i < n - 1; i++) {
swapped = 0;
for (j = 0; j < n - i - 1; j++) {
if (arr[j] > arr[j + 1]) {
// 交换 arr[j] 和 arr[j+1]
temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
swapped = 1;
}
}
// 如果没有发生交换,说明数组已经有序,提前结束排序
if (swapped == 0) {
break;
}
}
}
算法特性:
-
数组已经有序时只需要进行一轮遍历,为最好时间复杂度为\(O(n)\),数组逆序时需要进行n - 1轮遍历,最坏时间复杂度为\(O(n^{2})\),平均时间复杂度为\(O(n_{2})\)。
-
冒泡排序属于稳定排序,原地排序,空间复杂度为\(O(1)\)。
-
冒泡排序每次会将一个元素放置到其最终的位置上。
2. 快速排序
核心思想:
-
快排基于分治策略。
-
从数组中选择一个元素作为基准元素(pivot)。
-
将未排序数组中小于基准的元素放在基准的左边,大于基准的元素放在基准的右边,这个过程称为分区。
-
让基准左右两侧的子数组分别递归进行2和3,直到子数组的大小为0或1,此时数组已经有序。
实现代码(C语言):
void swap(int* a, int* b) {
int t = *a;
*a = *b;
*b = t;
}
// 分区函数,将数组分为两部分,左边部分小于基准,右边部分大于基准
int partition(int arr[], int low, int high) {
int pivot = arr[high];
int i = (low - 1);
for (int j = low; j <= high - 1; j++) {
if (arr[j] < pivot) {
i++;
swap(&arr[i], &arr[j]);
}
}
swap(&arr[i + 1], &arr[high]);
return (i + 1);
}
// 快速排序函数,递归地对数组进行排序
void quickSort(int arr[], int low, int high) {
if (low < high) {
int pi = partition(arr, low, high);
quickSort(arr, low, pi - 1);
quickSort(arr, pi + 1, high);
}
}
算法特性:
-
最好时间复杂度为\(O(nlog_{2}n)\),最坏时间复杂度为\(O(n^{2})\),平均时间复杂度为\(O(nlog_{2}n)\)。
-
快排的空间复杂度主要取决于递归调用栈的深度,平均空间复杂度为\(O(log_{2}n)\),最差空间复杂度为\(O(n)\)。
-
快排是不稳定的算法,原地排序算法,快排的性能依赖于基准元素的选择,如果基准元素选择不当,可能会导致最坏情况的发生。
常见问题:
-
快速排序和归并排序的比较
- 快速排序和归并排序都是不稳定的排序算法,但是通常,快速排序比归并排序更快,因为快速排序的常数因子较小。
-
快速排序如何优化?
-
对于小规模的子数组,插入排序的性能可能比快速排序更好,因此当子数组的长度小于某个阈值的时候,可以使用插入排序来处理。
-
对于包含大量重复元素的数组,可以将数组分为三部分:小于基准,等于基准和大于基准,从而减少不必要的比较和交换操作。
-
可以优化基准元素的选择,比如从序列的头尾和中间分别取出一个元素,使用他们的次大元素作为基准元素,或者随机从数组中选择一个元素作为基准元素。
-
-
快速排序的递归算法和非递归算法有什么区别?
-
递归算法代码简洁,易于理解,但是递归调用会消耗栈空间,可能导致栈溢出。
-
非递归实现,使用栈来模拟,避免了栈溢出的问题,但代码相对复杂。
-

浙公网安备 33010602011771号