选择排序 快速排序在内的排序算法|C++实现

排序算法综述

排序算法是计算机科学中非常重要的一类算法,用于将一组数据按照某种顺序进行排列。常见的排序算法有多种,每种算法在不同情况下有不同的性能表现。本文将介绍几种常见的排序算法,包括选择排序、快速排序、冒泡排序、插入排序、归并排序和堆排序,并对选择排序和快速排序进行更为详尽的介绍。

选择排序(Selection Sort)

思想

选择排序是一种简单直观的排序算法。其基本思想是:

  1. 从未排序部分中找到最小(或最大)的元素,将其放置在已排序部分的末尾。
  2. 重复上述过程,直到所有元素都被排序。

时间复杂度

选择排序的时间复杂度为 O(n^2),因为每次找到最小(或最大)元素需要 O(n) 的时间,一共需要进行 n 次选择。

示例代码

以下是选择排序的 C++ 实现,并结合具体例子帮助理解:

#include <iostream>
#include <vector>
#include <algorithm>

// 选择排序的实现函数
void selectionSort(std::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;
            }
        }
        std::swap(arr[minIndex], arr[i]);
    }
}

int main() {
    std::vector<int> arr = {64, 25, 12, 22, 11};
    selectionSort(arr);
    std::cout << "Sorted array: ";
    for (const auto& num : arr) {
        std::cout << num << " ";
    }
    std::cout << std::endl;
    return 0;
}

例子解析

假设我们有一个数组 arr = {64, 25, 12, 22, 11},选择排序的过程如下:

  1. 初始数组:{64, 25, 12, 22, 11}
  2. 第一次迭代:找到最小的元素 11,并将其与第一个元素 64 交换,结果为 {11, 25, 12, 22, 64}
  3. 第二次迭代:在剩余的元素中找到最小的 12,并将其与第二个元素 25 交换,结果为 {11, 12, 25, 22, 64}
  4. 第三次迭代:在剩余的元素中找到最小的 22,并将其与第三个元素 25 交换,结果为 {11, 12, 22, 25, 64}
  5. 第四次迭代:在剩余的元素中找到最小的 25,并将其与第四个元素 25 交换(无需交换),结果为 {11, 12, 22, 25, 64}
  6. 排序完成。

快速排序(Quick Sort)

思想

快速排序是一种基于分治法的排序算法。其基本思想是:

  1. 选择一个基准元素(pivot),通常选择数组的最后一个元素。
  2. 重新排列数组,使得所有比基准元素小的元素放在基准元素的左边,所有比基准元素大的元素放在基准元素的右边(称为分区操作)。
  3. 递归地对基准元素左边和右边的子数组进行快速排序。

时间复杂度

快速排序的平均时间复杂度为 O(n log n),但在最坏情况下(例如,每次选择的基准元素都是数组的最大或最小值),时间复杂度会退化为 O(n^2)。

示例代码

以下是快速排序的 C++ 实现,并结合具体例子帮助理解:

#include <iostream>
#include <vector>
#include <algorithm>

// 快速排序的分区函数
int partition(std::vector<int>& arr, int low, int high) {
    int pivot = arr[high];
    int i = low - 1;
    for (int j = low; j < high; ++j) {
        if (arr[j] < pivot) {
            ++i;
            std::swap(arr[i], arr[j]);
        }
    }
    std::swap(arr[i + 1], arr[high]);
    return i + 1;
}

// 快速排序的递归函数
void quickSort(std::vector<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);
    }
}

// 快速排序的主函数
void quickSort(std::vector<int>& arr) {
    quickSort(arr, 0, arr.size() - 1);
}

int main() {
    std::vector<int> arr = {10, 7, 8, 9, 1, 5};
    quickSort(arr);
    std::cout << "Sorted array: ";
    for (const auto& num : arr) {
        std::cout << num << " ";
    }
    std::cout << std::endl;
    return 0;
}

例子解析

假设我们有一个数组 arr = {10, 7, 8, 9, 1, 5},快速排序的过程如下:

  1. 初始数组:{10, 7, 8, 9, 1, 5}
  2. 选择最右边的元素 5 作为基准元素。重新排列数组,使得所有比 5 小的元素在左边,比 5 大的元素在右边,结果为 {1, 5, 8, 9, 10, 7}
  3. 对基准元素 5 左边的子数组 {1} 进行快速排序(无需操作)。
  4. 对基准元素 5 右边的子数组 {8, 9, 10, 7} 进行快速排序。
    • 选择最右边的元素 7 作为基准元素。重新排列数组,使得所有比 7 小的元素在左边,比 7 大的元素在右边,结果为 {1, 5, 7, 9, 10, 8}
    • 对基准元素 7 左边的子数组 {7} 进行快速排序(无需操作)。
    • 对基准元素 7 右边的子数组 {9, 10, 8} 进行快速排序。
      • 选择最右边的元素 8 作为基准元素。重新排列数组,使得所有比 8 小的元素在左边,比 8 大的元素在右边,结果为 {1, 5, 7, 8, 10, 9}
      • 对基准元素 8 左边的子数组 {8} 进行快速排序(无需操作)。
      • 对基准元素 8 右边的子数组 {10, 9} 进行快速排序。
        • 选择最右边的元素 9 作为基准元素。重新排列数组,使得所有比 9 小的元素在左边,比 9 大的元素在右边,结果为 {1, 5, 7, 8, 9, 10}
        • 对基准元素 9 左边的子数组 {9} 进行快速排序(无需操作)。
        • 对基准元素 9 右边的子数组 {10} 进行快速排序(无需操作)。
  5. 排序完成。

具体来说,partition段代码完成了以下步骤:

  1. 选择基准元素:在这段代码中,基准元素被选择为数组的最后一个元素,即 arr[high]

  2. 初始化指针:定义两个指针:

    • i:初始化为 low - 1,用来跟踪小于基准元素的区域的最后一个位置。
    • j:用来遍历数组的当前元素。
  3. 遍历和交换

    • 使用 for 循环遍历数组的元素(从 lowhigh - 1)。
    • 在每次迭代中,将当前元素 arr[j] 与基准元素 pivot 进行比较:
      • 如果 arr[j] 小于 pivot,则将 i 指针向右移动一位(++i),并交换 arr[i]arr[j] 的位置。这一步的目的是将小于基准元素的元素移到数组的左边。
  4. 最终交换基准元素

    • 循环结束后,将 i + 1 位置的元素与基准元素 arr[high] 交换,以确保基准元素位于正确的排序位置。
  5. 返回基准元素的位置

    • 返回 i + 1,即基准元素的位置。

具体例子

假设我们有一个数组 arr = {10, 7, 8, 9, 1, 5},以及 low = 0high = 5,执行分区函数的过程如下:

  1. 选择基准元素

    • pivot = arr[5] = 5
    • i = low - 1 = -1
  2. 遍历和交换

    • j = 0arr[0] = 1010 >= 5,不做交换。
    • j = 1arr[1] = 77 >= 5,不做交换。
    • j = 2arr[2] = 88 >= 5,不做交换。
    • j = 3arr[3] = 99 >= 5,不做交换。
    • j = 4arr[4] = 11 < 5++ii = 0),交换 arr[0]arr[4],结果为:{1, 7, 8, 9, 10, 5}
  3. 最终交换基准元素

    • 交换 arr[i + 1]arr[5],即交换 arr[1]arr[5],结果为:{1, 5, 8, 9, 10, 7}
  4. 返回基准元素的位置

    • 返回 i + 1 = 1

经过分区操作后,基准元素 5 位于正确的排序位置,其左边是所有小于 5 的元素,右边是所有大于 5 的元素。

分区函数代码

#include <iostream>
#include <vector>
#include <algorithm>

int partition(std::vector<int>& arr, int low, int high) {
    int pivot = arr[high];
    int i = low - 1;
    for (int j = low; j < high; ++j) {
        if (arr[j] < pivot) {
            ++i;
            std::swap(arr[i], arr[j]);
        }
    }
    std::swap(arr[i + 1], arr[high]);
    return i + 1;
}

int main() {
    std::vector<int> arr = {10, 7, 8, 9, 1, 5};
    int pi = partition(arr, 0, arr.size() - 1);
    std::cout << "Partition index: " << pi << std::endl;
    std::cout << "Array after partition: ";
    for (const auto& num : arr) {
        std::cout << num << " ";
    }
    std::cout << std::endl;
    return 0;
}

运行上述代码,你将看到分区后的数组以及基准元素的索引位置。

其他排序方法

冒泡排序(Bubble Sort)

思想

冒泡排序是一种简单的排序算法。其基本思想是:

  1. 重复遍历数组,每次比较相邻的两个元素,如果它们的顺序错误则交换它们。
  2. 经过一轮遍历后,最大的元素会被移到数组的末尾。
  3. 重复上述过程,对剩余的元素进行排序。
时间复杂度

冒泡排序的时间复杂度为 O(n^2)。

插入排序(Insertion Sort)

思想

插入排序是一种简单的排序算法。其基本思想是:

  1. 从第二个元素开始,依次将每个元素插入到已排序部分的适当位置。
  2. 重复上述过程,直到所有元素都被排序。
时间复杂度

插入排序的时间复杂度为 O(n^2)。

归并排序(Merge Sort)

思想

归并排序是一种基于分治法的排序算法。其基本思想是:

  1. 将数组分成两个子数组,对每个子数组进行归并排序。
  2. 合并两个已排序的子数组,得到排序后的数组。

堆排序(Heap Sort)

思想

堆排序是一种基于堆数据结构的排序算法。其基本思想是:

  1. 将数组构建成一个最大堆。
  2. 重复从堆中取出最大元素,并将其与堆的最后一个元素交换,缩小堆的大小,然后重新调整堆。
时间复杂度

堆排序的时间复杂度为 O(n log n)。

# 堆排序算法

## 概述

堆是一种特殊的树形数据结构,它满足以下性质:
1. 任何非叶子节点的值都大于或等于其左右子节点的值(大根堆),或者任何非叶子节点的值都小于或等于其左右子节点的值(小根堆)。
2. 堆通常被实现为一个数组,而堆的根节点通常被放在数组的第一个位置。

堆的基本操作包括:
- 插入操作:将一个新元素插入到堆中,并保持堆的性质。
- 删除操作:删除堆顶元素,并保持堆的性质。
- 建堆操作:将一个无序的数组转化为一个堆。

## 堆排序

堆排序是一种基于堆数据结构的排序算法。堆排序的基本思想是:
1. 将待排序的数组构建成一个大根堆。
2. 将堆顶元素(最大值)与堆的最后一个元素交换,然后将剩余的元素重新调整为大根堆。
3. 重复上述过程,直到堆中只剩下一个元素。

堆排序的时间复杂度为 O(n log n),空间复杂度为 O(1),是一种不稳定的排序算法。

## 堆排序与其他排序算法的优缺点

与快速排序相比:
- 优点:堆排序的最坏时间复杂度为 O(n log n),而快速排序的最坏时间复杂度为 O(n^2)。
- 缺点:堆排序的平均性能通常比快速排序差。

与归并排序相比:
- 优点:堆排序的空间复杂度为 O(1),而归并排序的空间复杂度为 O(n)。
- 缺点:堆排序是一种不稳定的排序算法,而归并排序是一种稳定的排序算法。

## C++ 实现

以下是一个使用堆排序来查找数组中第 k 个最大元素的 C++ 实现:
#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

// 调整大根堆
void heapify(vector<int>& nums, int n, int i) {
    int largest = i;
    int left = 2 * i + 1;
    int right = 2 * i + 2;

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

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

    if (largest != i) {
        swap(nums[i], nums[largest]);
        heapify(nums, n, largest);
    }
}

// 构建大根堆
void buildHeap(vector<int>& nums, int n) {
    for (int i = n / 2 - 1; i >= 0; i--) {
        heapify(nums, n, i);
    }
}

// 查找第 k 个最大元素
int findKthLargest(vector<int>& nums, int k) {
    int n = nums.size();
    buildHeap(nums, n);

    for (int i = n - 1; i >= n - k + 1; i--) {
        swap(nums[0], nums[i]);
        heapify(nums, i, 0);
    }

    return nums[0];
}

int main() {
    vector<int> nums = {3, 2, 1, 5, 6, 4};
    int k = 2;
    cout << "The " << k << "th largest element is " << findKthLargest(nums, k) << endl;
    return 0;
}
## 代码解释

1. `heapify` 函数:调整以索引 `i` 为根的子树,使其满足大根堆的性质。
2. `buildHeap` 函数:构建大根堆。
3. `findKthLargest` 函数:使用堆排序查找第 k 个最大元素。
4. `main` 函数:测试代码。

在 `findKthLargest` 函数中,我们首先构建大根堆,然后通过交换堆顶元素和堆的最后一个元素,并调整堆,最终获取第 k 个最大元素。

这个实现的时间复杂度为 O(n log n),满足题目要求。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值