【杂乱算法】七种常见的排序

文章目录

        • [1、选择排序 O(n^2)](#1、选择排序 O(n^2))
        • [2、插入排序 O(n^2)](#2、插入排序 O(n^2))
        • [3、希尔排序 O(n log n)](#3、希尔排序 O(n log n))
        • [4、冒泡排序 O(n^2)](#4、冒泡排序 O(n^2))
        • [5、快速排序 O(n log n)](#5、快速排序 O(n log n))
        • [6、归并排序 O(n log n)](#6、归并排序 O(n log n))
        • [7、基数排序 O(d(n+k))](#7、基数排序 O(d(n+k)))
1、选择排序 O(n^2)

每次从未排序部分选择最小元素放到已排序部分的末尾。

cpp 复制代码
#include <iostream>
#include <vector>
#include <algorithm>

void selection_sort(std::vector<int>& arr, int l, int r) {
    for (int i = l; i < r - 1; i++) {
        int ind = i;
        for (int j = i + 1; j < r; j++) {
            if (arr[j] < arr[ind]) ind = j;
        }
        std::swap(arr[i], arr[ind]);
    }
}

// 打印向量的辅助函数
void printVector(const std::vector<int>& vec) {
    for (int num : vec) {
        std::cout << num << " ";
    }
    std::cout << std::endl;
}

int main() {
    std::vector<int> arr = {64, 25, 12, 22, 11, 35};
    
    std::cout << "原始数组: ";
    printVector(arr);
    
    selection_sort(arr, 0, arr.size());
    
    std::cout << "排序后数组: ";
    printVector(arr);
    
    return 0;
}

选择排序的特点:

  • 时间复杂度: O(n^2),无论输入如何都是这个复杂度
  • 空间复杂度: O(1),原地排序
  • 不稳定排序: 相等元素的相对顺序可能会改变
  • 交换次数少: 每次外循环最多交换一次
2、插入排序 O(n^2)

从第二个元素开始, 将每个元素插入到已排序的前面部分的合适位置, 通过不断交换相邻元素实现插入.(找到第一个小于它的数。)

cpp 复制代码
#include <iostream>
#include <vector>

void insert_sort(std::vector<int>& arr, int l, int r) {
    for (int i = l + 1; i < r; i++) {
        int j = i;
        while (j > l && arr[j] < arr[j - 1]) {
            swap(arr[j], arr[j - 1]);
            j -= 1;
        }
    }
}

// 打印数组的辅助函数
void printArray(const std::vector<int>& arr) {
    for (int num : arr) {
        std::cout << num << " ";
    }
    std::cout << std::endl;
}

int main() {
    std::vector<int> arr = {64, 34, 25, 12, 22, 11, 90};
    
    std::cout << "原始数组: ";
    printArray(arr);
    
    insert_sort(arr, 0, arr.size());
    
    std::cout << "排序后数组: ";
    printArray(arr);
    
    return 0;
}

插入排序的特点:

  • 时间复杂度:平均和最坏情况下为O(n^2),最好情况(已排序)为O(n)
  • 空间复杂度:O(1),原地排序
  • 稳定排序:相等元素的相对顺序不会改变
  • 对于小型数组或基本有序的数组,插入排序可能比其他更复杂的排序算法表现更好
3、希尔排序 O(n log n)
cpp 复制代码
#include <iostream>
#include <vector>

// 对子序列进行插入排序
void unguarded_insert_sort(vector<int>&arr, int l, int r, int step) {
    int ind = l;
    //确定最小值的边界
    for (int i = l + step; i < r; i += step) {
        if (arr[i] < arr[ind]) ind = i;
    }
    //挨个移动,确保稳定性
    while (ind > l) {
        swap(arr[ind], arr[ind - step]);
        ind -= step;
    }
    for (int i = l + 2 * step; i < r; i += step) {
        int j = i;
        while (arr[j] < arr[j - step]) {
            swap(arr[j], arr[j - step]);
            j -= step;
        }
    }
    return ;
}

// 希尔排序实现
void shell_sort(std::vector<int>& arr, int l, int r) {
    int k = 2, n = (r - l + 1), step;
    do {
        step = n / k == 0 ? 1 : n / k;
        for (int i = l, I = l + step; i < I; i++) {//大范围内分组进行插入排序
            unguarded_insert_sort(arr, i, r, step);
        }
        k *= 2;
    } while (step != 1);
}

// 打印数组
void printArray(const std::vector<int>& arr) {
    for (int num : arr) {
        std::cout << num << " ";
    }
    std::cout << std::endl;
}

int main() {
    std::vector<int> arr = {64, 34, 25, 12, 22, 11, 90};
    
    std::cout << "原始数组: ";
    printArray(arr);
    
    shell_sort(arr, 0, arr.size() - 1);
    
    std::cout << "排序后数组: ";
    printArray(arr);
    
    return 0;
}

希尔排序的优点:

  1. 性能优于简单插入排序,尤其是对于大规模的乱序数组。
  2. 不需要额外的存储空间,是原地排序算法。
  3. 对于部分有序的数组,效率较高。

缺点:

  1. 增量序列的选择对算法的性能影响较大,不同的增量序列可能导致不同的时间复杂度。
  2. 不稳定排序:相同键值的记录可能会在排序后改变其相对位置。

时间复杂度:

  • 最坏情况:O(n^2)
  • 平均情况:依赖于增量序列的选择,通常在O(n^1.3)到O(n^2)之间
  • 最好情况:O(n log n)

空间复杂度:O(1)

4、冒泡排序 O(n^2)
cpp 复制代码
/* 冒泡排序 */
void bubbleSort(vector<int> &nums) {
    // 外循环:未排序区间为 [0, i]
    for (int i = nums.size() - 1; i > 0; i--) {
        // 内循环:将未排序区间 [0, i] 中的最大元素交换至该区间的最右端
        for (int j = 0; j < i; j++) {
            if (nums[j] > nums[j + 1]) {
                // 交换 nums[j] 与 nums[j + 1]
                // 这里使用了 std::swap() 函数
                swap(nums[j], nums[j + 1]);
            }
        }
    }
}
  1. 性能特征:

    • 时间复杂度:最坏情况和平均情况都是 O(n^2),最好情况(已经排好序)是 O(n)
    • 空间复杂度:O(1),只需要一个临时变量来交换元素
    • 稳定性:稳定排序算法
  2. 优缺点:

    优点:

    • 实现简单,容易理解
    • 不需要额外的存储空间
    • 对于小规模数据或基本有序的数据效率较高

    缺点:

    • 对于大规模数据效率低下
    • 平均时间复杂度较高,为 O(n^2)
5、快速排序 O(n log n)

选择一个基准元素,将数组分为小于和大于基准的两个部分,递归地对这两个部分进行排序,从而实现整体有序。

cpp 复制代码
void quick_sort(int *arr, int l, int r) {
    if (r - l <= 2) {
        if (r - l <= 1) return ;
        if (arr[l] > arr[l + 1]) swap(arr[l], arr[l + 1]);
        return ;
    }
    // partition
    int x = l, y = r - 1, z = arr[l];
    while (x < y) {
        while (x < y && z <= arr[y]) --y;
        if (x < y) arr[x++] = arr[y];
        while (x < y && arr[x] <= z) ++x;
        if (x < y) arr[y--] = arr[x];
    }
    arr[x] = z;
    quick_sort(arr, l, x);
    quick_sort(arr, x + 1, r);
    return ;
}
  1. 时间复杂度:
    • 最佳情况:O(n log n)
    • 平均情况:O(n log n)
    • 最坏情况:O(n^2)(当数组已经排序或近乎排序时)
  2. 空间复杂度:
    • 平均情况:O(log n)
    • 最坏情况:O(n)(递归调用栈的深度)
  3. 优点:
    • 原地排序,不需要额外的存储空间
    • 平均情况下效率高
    • 缓存友好
  4. 缺点:
    • 不稳定排序(相等元素的相对位置可能改变)
    • 对于小数组,可能不如插入排序等简单算法效率高
    • 在最坏情况下性能较差
6、归并排序 O(n log n)
cpp 复制代码
std::vector<int> buff;

void merge_sort(std::vector<int>& arr, int l, int r) {
	if (r - l <= 1) return;
	int mid = (l + r) / 2;
	merge_sort(arr, l, mid);
	merge_sort(arr, mid, r);
	// merge
	int p1 = l, p2 = mid, k = 0;
	while (p1 < mid || p2 < r) {
		if (p2 == r || (p1 < mid && arr[p1] <= arr[p2])) {
			buff[k++] = arr[p1++];
		} else {
			buff[k++] = arr[p2++];
		}
	}
	for (int i = l; i < r; i++) arr[i] = buff[i - l];
}
  1. 基本思想:
    • 将待排序的数列递归地分成两半,分别对两半进行排序。
    • 然后将已排序的两半合并成一个有序序列。
  2. 工作流程: a. 分割:将数组递归地分成两半,直到每个子数组只包含一个元素。 b. 合并:将两个有序子数组合并成一个更大的有序数组。
  3. 时间复杂度:
    • 最佳情况:O(n log n)
    • 最坏情况:O(n log n)
    • 平均情况:O(n log n)
  4. 空间复杂度:
    • O(n),因为合并过程中需要额外的空间
  5. 稳定性:
    • 归并排序是稳定的排序算法,即相等元素的相对顺序在排序后不会改变。
  6. 优点:
    • 效率高且稳定
    • 适用于外部排序(当数据量很大,无法全部装入内存时)
  7. 缺点:
    • 需要额外的存储空间
    • 对于小规模数据,可能不如插入排序等简单算法效率高
7、基数排序 O(d(n+k))

基数排序是一种非比较型整数排序算法,通过从最低有效位到最高有效位的顺序,对整数的每一位进行分配和收集,最终实现对整数序列的排序。

cpp 复制代码
#include <iostream>
#include <vector>
#include <cmath>
#include <algorithm>

void radixSort(std::vector<int>& arr) {
    // 假定arr[0] 是最大数
    // 1. 通过遍历arr, 找到数组中真正最大值
    int max = arr[0];
    for (size_t i = 1; i < arr.size(); i++) {
        if (arr[i] > max) {
            max = arr[i];
        }
    }

    // 计算最大数字是几位数
    int maxLength = std::to_string(max).length();
    
    // 定义一个二维数组, 就是10个桶
    std::vector<std::vector<int>> bucket(10);
    
    // 根据最大长度的数决定比较的次数
    for (int i = 0, n = 1; i < maxLength; i++, n *= 10) {
        // 把每一个数字分别计算本轮循环的位数的值
        std::vector<int> bucketElementCounts(10, 0);
        
        for (size_t j = 0; j < arr.size(); j++) {
            // 计算
            int digitOfElement = (arr[j] / n) % 10;
            // 把当前遍历的数据放入指定的数组中
            bucket[digitOfElement].push_back(arr[j]);
            // 记录数量
            bucketElementCounts[digitOfElement]++;
        }
        
        // 记录取的元素需要放的位置
        size_t index = 0;
        // 把各个桶中(10个桶)存放的数字取出来, 放入到arr中
        for (size_t k = 0; k < bucketElementCounts.size(); k++) {
            // 如果这个桶中,有数据才取,没有数据就不取了
            if (bucketElementCounts[k] != 0) {
                // 循环取出元素
                for (size_t l = 0; l < bucket[k].size(); l++) {
                    // 取出元素
                    arr[index++] = bucket[k][l];
                }
                // 清空桶
                bucket[k].clear();
            }
        }
    }
}

int main() {
    std::vector<int> arr = {170, 45, 75, 90, 802, 24, 2, 66};
    radixSort(arr);
    
    // 输出排序结果
    for (int num : arr) {
        std::cout << num << " ";
    }
    return 0;
}

基数排序的主要特点:

  1. 时间复杂度:O(d(n+k)),其中d是位数,n是元素个数,k是每位的取值范围。
  2. 空间复杂度:O(n+k),需要额外的计数数组和临时数组。
  3. 稳定性:基数排序是稳定的排序算法。
  4. 适用范围:主要用于整数排序,特别是对于数字范围不是很大的情况。

[外链图片转存中...(img-rRxHo7Jy-1724056762931)]

相关推荐
simple_ssn18 分钟前
【C语言刷力扣】1502.判断能否形成等差数列
c语言·算法·leetcode
寂静山林27 分钟前
UVa 11855 Buzzwords
算法
Curry_Math31 分钟前
LeetCode 热题100之技巧关卡
算法·leetcode
ahadee39 分钟前
蓝桥杯每日真题 - 第10天
c语言·vscode·算法·蓝桥杯
军训猫猫头1 小时前
35.矩阵格式的一到一百数字 C语言
c语言·算法
Mr_Xuhhh2 小时前
递归搜索与回溯算法
c语言·开发语言·c++·算法·github
SoraLuna2 小时前
「Mac玩转仓颉内测版12」PTA刷题篇3 - L1-003 个位数统计
算法·macos·cangjie
爱吃生蚝的于勒4 小时前
C语言内存函数
c语言·开发语言·数据结构·c++·学习·算法
ChoSeitaku9 小时前
链表循环及差集相关算法题|判断循环双链表是否对称|两循环单链表合并成循环链表|使双向循环链表有序|单循环链表改双向循环链表|两链表的差集(C)
c语言·算法·链表
Fuxiao___10 小时前
不使用递归的决策树生成算法
算法