0
点赞
收藏
分享

微信扫一扫

关于es6

拾杨梅记 2024-11-04 阅读 28

文章目录

1、冒泡排序

从左往右相邻的数两两比较,如果左边大于右边则交换,每一轮冒泡会选出一个未排序数字中的最大值放到最后,每一轮确定一个数字的最终位置,排序完n个数字需要n-1轮。

  • 外层循环通过 i 控制遍历次数,最大到 n-1。每轮比较中最大的元素都会被“冒泡”到数组的末尾,因此内层循环的范围是 n - i - 1

  • swapped 用于记录当前轮次是否有元素交换。如果某轮中没有发生交换,意味着数组已经有序,可以提前退出循环,避免不必要的比较。

  • 冒泡排序在最差情况下的时间复杂度是 O(n²),但在最佳情况下(数组已经有序时),通过 swapped 变量,时间复杂度可以优化为 O(n)。

void bubble_sort(int a[], int n) {
    for (int i = 0; i < n - 1; i++) {  // 每轮排好一个数字,排n-1轮
        bool swapped = false;  // 每次循环开始时重置标志位
        for (int j = 0; j < n - i - 1; j++) {  //排好的放在后面,未排序的数字减少一个
            if (a[j] > a[j + 1]) {  // 比较相邻元素
                swap(a[j], a[j + 1]);  // 交换元素
                swapped = true;  // 记录交换发生
            }
        }
        if (!swapped) break;  // 如果没有发生交换,数组已经有序,提前退出
    }
}

2、选择排序

从左往右扫描,每一轮选出一个当前未排序数字中的最小值,放到前面,每一轮确定一个数字的最终位置,排完n个数字需要n-1轮。

  • 外层循环通过 i 控制遍历次数,最大到 n-1,每次遍历选择数组中未排序部分的第一个元素。

  • 内层循环通过 j 遍历未排序的部分,从 i + 1 开始到 n。内层循环的目的是找到当前未排序部分的最小元素,并记录其索引 minIndex

  • 在每轮结束时,将当前元素 a[i] 与找到的最小元素 a[minIndex] 进行交换。

  • 即使数组已经有序,选择排序也仍然需要 O(n²) 的比较次数,因为每次都需要遍历未排序部分。

void select_sort(int a[], int n) {
    for (int i = 0; i < n - 1; i++) {  // 每轮排好一个数字,排n-1轮
        int minIndex = i;  // 假设当前元素为最小值
        for (int j = i + 1; j < n; j++)  // 排好的放在前面,未排序的数字减少一个
            if (a[j] < a[minIndex]) minIndex = j;  // 找到更小的元素,更新最小值索引
        swap(a[i], a[minIndex]);  // 交换最小值到当前排序位置
    }
}

3、插入排序

外层循环 (for (int i = 1; i < n; i++)):从第二个元素开始遍历,作为当前要插入的元素 x

x 变量:存储当前要插入的元素,以便在内层循环中进行比较和插入操作。

已排序部分的下标 jj 初始化为 i - 1,表示已排序部分的最后一个元素。

内层循环 (while (j >= 0 && x < a[j])):

  • 在已排序部分中查找 x 的插入位置。
  • 越界检查:首先判断 j >= 0,确保访问 a[j] 不会越界。
  • 如果 a[j] 大于 x,则将 a[j] 向右移动,腾出 j + 1 的位置来插入 x

插入操作 (a[j + 1] = x):当找到合适位置后,将 x 插入到 j + 1 的位置。

void insert_sort(int a[], int n) {
    for (int i = 1; i < n; i++) {  // 从第二个元素遍历要插入的元素
        int x = a[i];  // 当前要插入的元素
        int j = i - 1;  // 已排序的最后一个元素下标
        // 在已排序部分中找到 x 的插入位置
        while (j >= 0 && x < a[j]) {  // 先判断j>=0,防止越界错误
            a[j + 1] = a[j];  // 元素向右移动,为x空出位置
            j--;  // 移动到前一个元素
        }
        a[j + 1] = x;  // 将 x 插入到找到的位置
    }
}

4、桶排序

cnt[N] = {0}:定义一个大小为 N 的数组 cnt,用于统计每个元素的出现次数,初始化为 0。

输入读取和计数:使用 for 循环读取 n 个整数输入,检查 x 是否在合法范围内 [0, N),并通过 cnt[x]++ 对每个 x 的出现次数进行统计。

输出:第二个 for 循环遍历 cnt 数组,通过嵌套循环输出每个数 i 的值,输出的次数取决于 cnt[i] 的值。

const int N = 10000;
int cnt[N] = {0}, n;
cin >> n;  // 读取元素个数
// 读取每个元素并进行计数
for (int i = 0; i < n; i++) {
    int x;
    cin >> x;
    if (x >= 0 && x < N) cnt[x]++;
}
for (int i = 0; i < N; i++)
    for (int j = 0; j < cnt[i]; ++j)
        cout << i << " ";  // 按照计数输出数字 i

5、快速排序

通过分治法,每次递归时选取一个基准值,然后通过双指针将数组划分成两部分,使得左边部分的值小于等于基准值,右边部分的值大于等于基准值。然后递归地对两部分进行排序,直到数组被完全排序。

  • a[] 是要排序的数组。
  • l 是当前处理的数组区间的左边界(闭区间)。
  • r 是当前处理的数组区间的右边界(闭区间)。

递归终止的条件是 l >= r,也就是说当前区间长度为 0 或 1 时,不需要继续排序。

基准值x = a[(l + r) >> 1] 通过 (l + r) >> 1 计算出中间元素的下标,并将它作为基准值 x

双指针初始化

  • i = l - 1,指向比基准值小的区域。
  • j = r + 1,指向比基准值大的区域。

主循环条件while (i < j),保证左右指针在数组内并且没有交错。

内循环(左指针 ido i++; while (a[i] < x); 从左向右找到第一个大于等于基准值的元素。

内循环(右指针 jdo j--; while (a[j] > x); 从右向左找到第一个小于等于基准值的元素。

元素交换:当 i < j 时,交换 a[i]a[j],确保基准值左边的元素都小于等于它,右边的元素都大于等于它。

左半部分递归:对 lj 的部分继续执行快速排序。此时 j 是划分完成后,左半部分的最大值位置。

右半部分递归:对 j + 1r 的部分继续执行快速排序。

void quick_sort(int a[], int l, int r) {
    // 递归结束条件:当区间元素只有1个元素或者没有元素时
    if (l >= r) return;  
    
	// 初始化双指针i和j,
    int i = l - 1, j = r + 1;  //后面会先++或--,所以指向边界的前一个或后一个元素
    int pivot = a[(l + r) >> 1]; // 选择中间位置的值作为基准值
    
    // 双指针划分过程
    while (i < j) {
        // 左指针向右移动,指针左侧都是小于基准值的,找到第一个大于等于基准值的元素
        do i++; while (a[i] < pivot);
        // 右指针向左移动,指针右侧都是大于基准值的,找到第一个小于等于基准值的元素
        do j--; while (a[j] > pivot);
        // 如果i和j两个指针还没有相遇,交换a[i]和a[j]
        if (i < j) swap(a[i], a[j]);
    }

    quick_sort(a, l, j);  // 递归调用,对左半部分(l到j)进行快速排序
    quick_sort(a, j + 1, r);  // 递归调用,对右半部分(j+1到r)进行快速排序
}

6、归并排序

通过递归将数组分成两半,分别排序,递归到最后,实际是把单个元素看作一个有序序列,开始两两归并,形成一个两个元素的有序序列,再两两归并,形成一个四个元素的有序序列,不断合并两个有序的子数组来达到排序的效果。

在这里插入图片描述

  • a[] 是要排序的数组。
  • l 是当前处理的数组区间的左边界(闭区间)。
  • r 是当前处理的数组区间的右边界(闭区间)。

终止条件:如果 l >= r(即数组只剩一个或没有元素时),直接返回。

  • mid = (l + r) >> 1:通过取中间位置 mid 将当前区间 [l, r] 分为两个子区间 [l, mid][mid + 1, r]
  • 递归调用 merge_sort(a, l, mid) 对左半部分排序,merge_sort(a, mid + 1, r) 对右半部分排序。

合并两个有序部分

  • 使用两个指针 ij 分别指向左半部分 [l, mid] 和右半部分 [mid + 1, r] 的起始位置。通过比较 a[i]a[j] 的值,将较小的值放入临时数组 tmp[]
  • 如果左半部分未遍历完,将剩余部分加入 tmp[]
  • 如果右半部分未遍历完,也将其加入 tmp[]
  • 最后,将临时数组 tmp[] 中的元素复制回原数组 a[],完成本次合并。
void merge_sort(int a[], int l, int r) {
    // 递归终止条件:如果子序列中只有1个元素或0个元素,返回
    if (l >= r) return;

    int mid = l + r >> 1; // 计算中间索引,将数组一分为二
    
    merge_sort(a, l, mid); // 递归排序左半部分
    merge_sort(a, mid + 1, r); // 递归排序右半部分

    // 合并两个已排序的部分
	int k = 0; // 临时数组的索引
	int i = l, j = mid + 1; // 左半部分和右半部分的指针
     
    // 合并过程:将较小的元素放入临时数组
    while (i <= mid && j <= r) {
        // 如果左半部分当前元素小于右半部分,加入临时数组
        if (a[i] < a[j]) tmp[k++] = a[i++]; 
        // 否则,加入右半部分的当前元素
        else tmp[k++] = a[j++]; 
    }
    
    // 将左半部分的剩余元素加入临时数组
    while (i <= mid) tmp[k++] = a[i++]; 
    // 将右半部分的剩余元素加入临时数组
    while (j <= r) tmp[k++] = a[j++]; 

    // 将临时数组的内容复制回原数组
    for (i = l, k = 0; i <= r; i++, k++) a[i] = tmp[k]; 
}

7、sort()排序

7.1 概述

sort()函数是一个比较灵活的函数。很多解释是:sort()函数是类似于快速排序的方法,时间复杂度为n*log2(n),执行效率较高。

7.2 sort()的使用方法

在C++中使用sort()函数需要使用#include <algorithm>头文件,algorithm意为"算法",是C++的标准模版库(STL)中最重要的头文件之一,提供了大量基于迭代器的非成员模版函数。

sort(begin, end, cmp);

sort()函数可以对给定区间的元素进行排序,它有三个参数:

  • 其中begin为待排序数组的起始地址
  • end为指向待排序数组结束地址下一个位置的指针
  • cmp参数为排序准则,如果不写,默认从小到大进行排序。如果我们想从大到小排序可以将cmp参数写为greater<int>()<>中表示排序数组的类型,C++11中可以透明比较器greater<>()。如果需要按照其他的排序准则,那么需要我们自己定义一个bool类型的函数来传入。

使用sort()不仅仅可以从大到小或者从小到大排,还可以按照一定的准则进行排序,编写cmp函数传入sort()函数。比如按照个位从小到大比较:

bool cmp(int a, int b) {
    return a % 10 > b % 10;
}

结构体进行排序,比如定义一个结构体包含学生的姓名和年龄,按照年龄从小到大排序:

struct Student {
    string name;
    int age;
};

bool cmp(Student a, Student b) {
    return a.age < b.age;
}

在以上的代码示例中使用了值传递,每次调用函数都会创建Student对象的副本,增加额外的开销,降低排序的效率,使用引用传递可以避免拷贝开销,更加高效。值传递会创建参数的副本,对于大型对象或复杂数据结构,可能涉及大量的内存分配和数据复制,引用传递避免了这些操作,因为它直接操作原始对象。

特性值传递引用传递
数据传递方式副本传递,函数操作的是实参的副本引用传递,函数操作的是实参的原数据
内存开销需要创建副本,开销较大,尤其对于大型对象无需创建副本,内存开销小
是否修改实参函数内部的修改不会影响实参函数内部的修改会直接影响实参
安全性相对更安全,因为函数无法修改外部数据可能产生副作用,修改不应修改的数据
适用场景当不希望修改外部数据,或数据结构比较简单时使用当需要修改实参,或数据结构较大时使用
bool cmp(const Student& a,const Student& b) {
    return a.age < b.age;
}

引用参数const Student& aconst Student& b 表示对 Student 类型的常量引用,这样函数内部无法修改 Student 的内容,同时避免了复制带来的性能损失。

const 关键字:使用 const 关键字表明函数不会修改传入的对象,这也是一个良好的编码习惯。

举报

相关推荐

0 条评论