8.3.交换排序

1. 冒泡排序

核心思想

  1. 冒泡排序是重复遍历待排序序列,比较相邻元素的大小并交换逆序的数组元素(如:若为升序遍历,则将满足\(a[i] > a[i + 1]\)的两个元素交换)。

  2. 每次遍历,都会将未排序数组中的一个元素放到未排序数组的末尾,变成已排序数组中的首元素,最多经过n - 1次排序后,数组变得有序。

  3. 在遍历过程中增加一个标志位,当这次遍历未发生元素交换时,则说明数组元素已经有序,则直接跳出循环。

实现代码(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;
        }
    }
}

算法特性

  1. 数组已经有序时只需要进行一轮遍历,为最好时间复杂度为\(O(n)\),数组逆序时需要进行n - 1轮遍历,最坏时间复杂度为\(O(n^{2})\),平均时间复杂度为\(O(n_{2})\)

  2. 冒泡排序属于稳定排序,原地排序,空间复杂度为\(O(1)\)

  3. 冒泡排序每次会将一个元素放置到其最终的位置上。



2. 快速排序

核心思想

  1. 快排基于分治策略。

  2. 从数组中选择一个元素作为基准元素(pivot)。

  3. 将未排序数组中小于基准的元素放在基准的左边,大于基准的元素放在基准的右边,这个过程称为分区。

  4. 让基准左右两侧的子数组分别递归进行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);
    }
}

算法特性

  1. 最好时间复杂度为\(O(nlog_{2}n)\),最坏时间复杂度为\(O(n^{2})\),平均时间复杂度为\(O(nlog_{2}n)\)

  2. 快排的空间复杂度主要取决于递归调用栈的深度,平均空间复杂度为\(O(log_{2}n)\),最差空间复杂度为\(O(n)\)

  3. 快排是不稳定的算法,原地排序算法,快排的性能依赖于基准元素的选择,如果基准元素选择不当,可能会导致最坏情况的发生。

常见问题

  1. 快速排序和归并排序的比较

    1. 快速排序和归并排序都是不稳定的排序算法,但是通常,快速排序比归并排序更快,因为快速排序的常数因子较小。
  2. 快速排序如何优化?

    1. 对于小规模的子数组,插入排序的性能可能比快速排序更好,因此当子数组的长度小于某个阈值的时候,可以使用插入排序来处理。

    2. 对于包含大量重复元素的数组,可以将数组分为三部分:小于基准,等于基准和大于基准,从而减少不必要的比较和交换操作。

    3. 可以优化基准元素的选择,比如从序列的头尾和中间分别取出一个元素,使用他们的次大元素作为基准元素,或者随机从数组中选择一个元素作为基准元素。

  3. 快速排序的递归算法和非递归算法有什么区别?

    1. 递归算法代码简洁,易于理解,但是递归调用会消耗栈空间,可能导致栈溢出。

    2. 非递归实现,使用栈来模拟,避免了栈溢出的问题,但代码相对复杂。



posted @ 2025-03-28 21:15  薛定谔的AC  阅读(27)  评论(0)    收藏  举报