【C++】基本的排序

1 三种基本排序

1.1 选择排序

基本思想: 每次选择整个序列中最小的值,放在前面。

vector<int> MySelectionSort(vector<int> arr) {
    for(int i = 0; i < arr.size()-1;i++){
        for (int j = i + 1; j < arr.size(); j++) {
            if (arr[j] < arr[i]) {
                swap(arr[j], arr[i]);
            }
        }
    }
    return arr;
}
  • 选择排序的时间复杂度为 O ( n 2 ) O(n^2) O(n2),最好情况和最坏情况均为 O ( n 2 ) O(n^2) O(n2)。但是注意到,上述代码中,针对每一对比较的元素都进行了交换,会导致运行量增大,改进如下。引入了minIndex,用来保存需要交换元素的下表,每轮循环,只需要进行一次元素交换。
vector<int> MySelectionSort(vector<int> arr) {
    int n = arr.size();
    for (int i = 0; i < n - 1; ++i) {
        int minIndex = i;
        for (int j = i + 1; j < n; ++j) {
            if (arr[j] < arr[minIndex]) {
                minIndex = j;
            }
        }
        if (minIndex != i) {
            swap(arr[i], arr[minIndex]);
        }
    }
    return arr;
}

1.2 冒泡排序

基本思想: 每一轮循环,选出一个最大值,放在最后。

vector<int> MyBubbleSort(vector<int> nums) {
    for (int i = 0; i < nums.size() - 1; i++) {
        bool flag = false;
        for (int j = 0; j < nums.size() - i - 1; j++) {
            // 增序
            if (nums[j + 1] < nums[j]) {
                swap(nums[j + 1], nums[j]);
                flag = true; //为True标记此轮有元素进行交换
            }
        }
        if (!flag) break;  // 如果没有发生交换,直接退出
    }
    return nums;
}
  • 冒泡排序的时间复杂度为 O ( n 2 ) O(n^2) O(n2),最好情况为 O ( n ) O(n) O(n)和最坏情况均为 O ( n 2 ) O(n^2) O(n2)

1.3 插入排序

基本思想: 将序列分为有序和无序两个序列,其中有序序列在前,每次从无序序列中取一个元素插入到有序序列中。

vector<int> MyInsertionSort(vector<int> arr) {
    for (int i = 1; i < arr.size(); i++) {
        int key = arr[i];
        int j = i - 1;
        // key值是一直不变的,因为需要比较
        while (j >= 0 && key < arr[j]) {
           arr[j + 1] = arr[j];
           j--;
        }
        // 跳出循环,此时j所指向的位置,arr[j] < key, 因此key值需插入到arr[j+1]位置
        arr[j + 1] = key;
    }
    return arr;
}
  • 插入排序的时间复杂度为 O ( n 2 ) O(n^2) O(n2),最好情况为 O ( n ) O(n) O(n)和最坏情况均为 O ( n 2 ) O(n^2) O(n2)

2 一种高级排序

2.1 快速排序

基本思想: 选择一个基准元素,将序列分为两个子序列,左子序列存放小于基准元素的值,右子序列存放大于基准元素的值。递归地对两个子序列进行排序,直到子序列的长度为1或0。

int partition(vector<int>& nums, int left, int right) {
    int pivot = nums[right];  // 选择最右边的元素作为基准值
    int i = left - 1;  // 定义一个指针,初始时指向左侧子数组的起始位置-1
    for (int j = left; j < right; j++) {  // 遍历整个数组
        if (nums[j] <= pivot) {  // 如果当前元素小于等于基准值
            i++;  // 将左侧子数组的末尾指针向右移动一位
            swap(nums[i], nums[j]);  // 交换左侧子数组的末尾元素和当前元素
        }
    }
    swap(nums[i + 1], nums[right]);  // 将基准值移到正确的位置上
    return i + 1;  // 返回基准值的索引
}

vector<int> MyQuickSort(vector<int> nums, int left, int right) {
    if (left < right) {
        int pivot_index = partition(nums, left, right);  // 将数组分成左右两部分,并返回基准值的索引
        MyQuickSort(nums, left, pivot_index - 1);  // 对左侧子数组进行递归排序
        MyQuickSort(nums, pivot_index + 1, right);  // 对右侧子数组进行递归排序
    }
    return nums;
}
  • 快速排序的时间复杂度为 O ( n l o g n ) O(nlogn) O(nlogn),最好情况为 O ( n l o g n ) O(nlogn) O(nlogn)和最坏情况均为 O ( n 2 ) O(n^2) O(n2)

2.2 堆排序

堆排序包含建立堆和调整堆两个操作。元素少的时候在建立堆和调整堆的过程中会产生大开销,因此不适用。

// 最大堆化函数,用于维护最大堆的性质
void maxHeapify(vector<int>& vec, int n, int i) {
    int largest = i; // 假设当前节点 i 的值最大
    int left = 2 * i + 1; // 左子节点索引
    int right = 2 * i + 2; // 右子节点索引

    if (left < n && vec[left] > vec[largest])
        largest = left;

    if (right < n && vec[right] > vec[largest])
        largest = right;

    if (largest != i) {
        swap(vec[i], vec[largest]);
        maxHeapify(vec, n, largest); // 递归地最大堆化子树
    }
}

// 堆排序函数
void heapSort(vector<int>& vec) {
    int n = vec.size();

    // 构建最大堆
    for (int i = n / 2 - 1; i >= 0; i--)
        maxHeapify(vec, n, i);

    // 从最大堆中依次取出元素,进行排序
    for (int i = n - 1; i > 0; i--) {
        swap(vec[0], vec[i]); // 将当前最大值放到数组末尾
        maxHeapify(vec, i, 0); // 对新的根节点进行最大堆化
    }
}
  • 快速排序的时间复杂度为 O ( n l o g n ) O(nlogn) O(nlogn),最好情况为 O ( n l o g n ) O(nlogn) O(nlogn)和最坏情况均为 O ( n l o g n ) O(nlogn) O(nlogn)
  • 堆排序算法适合元素较多的情况。

2.3 归并排序

归并排序将数组递归的氛围两个子数组,然后对子数组进行排序,最终将两个有序子数组合并为一个有序数组。

// 合并两个有序数组
void merge(std::vector<int>& arr, int left, int mid, int right) {
    int n1 = mid - left + 1;
    int n2 = right - mid;

    // 创建临时数组来存储左右两个子数组
    vector<int> L(n1), R(n2);

    // 将数据复制到临时数组中
    for (int i = 0; i < n1; ++i)
        L[i] = arr[left + i];
    for (int j = 0; j < n2; ++j)
        R[j] = arr[mid + 1 + j];

    // 合并临时数组,按照顺序填充到原始数组中
    int i = 0, j = 0, k = left;
    while (i < n1 && j < n2) {
        if (L[i] <= R[j]) {
            arr[k] = L[i];
            ++i;
        }
        else {
            arr[k] = R[j];
            ++j;
        }
        ++k;
    }

    // 处理剩余的元素
    while (i < n1) {
        arr[k] = L[i];
        ++i;
        ++k;
    }
    while (j < n2) {
        arr[k] = R[j];
        ++j;
        ++k;
    }
}

// 归并排序
void mergeSort(std::vector<int>& arr, int left, int right) {
    if (left < right) {
        int mid = left + (right - left) / 2;

        // 分割数组为两个子数组
        mergeSort(arr, left, mid);
        mergeSort(arr, mid + 1, right);

        // 合并两个有序子数组
        merge(arr, left, mid, right);
    }
}

  • 无论是最好情况、最坏情况还是平均情况下,其时间复杂度都为 O ( n l o g n ) O(n log n) O(nlogn)。由于归并排序需要额外的空间来存储中间结果,因此其空间复杂度为 O ( n ) O(n) O(n)

比较

平均时间复杂度最坏时间复杂度最好时间复杂度空间复杂度
选择排序 O ( n 2 ) O(n^2) O(n2) O ( n 2 ) O(n^2) O(n2) O ( n 2 ) O(n^2) O(n2) O ( 1 ) O(1) O(1)
冒泡排序 O ( n 2 ) O(n^2) O(n2) O ( n 2 ) O(n^2) O(n2) O ( n ) O(n) O(n) O ( 1 ) O(1) O(1)
插入排序 O ( n 2 ) O(n^2) O(n2) O ( n 2 ) O(n^2) O(n2) O ( n ) O(n) O(n) O ( 1 ) O(1) O(1)
快速排序 O ( n l o g n ) O(nlogn) O(nlogn) O ( n 2 ) O(n^2) O(n2) O ( n l o g n ) O(nlogn) O(nlogn) O ( l o g n ) O(logn) O(logn)
堆排序 O ( n l o g n ) O(nlogn) O(nlogn) O ( n l o g n ) O(nlogn) O(nlogn) O ( n l o g n ) O(nlogn) O(nlogn) O ( 1 ) O(1) O(1)
堆排序 O ( n l o g n ) O(nlogn) O(nlogn) O ( n l o g n ) O(nlogn) O(nlogn) O ( n l o g n ) O(nlogn) O(nlogn) O ( n ) O(n) O(n)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值