八种基础排序算法(超全总结)

八种基础排序算法

/*
 * 排序
 * 包含了堆排序、快速排序、希尔排序、桶排序、归并排序、冒泡排序、选择排序、插入排序的数组实现
 * 以及相应的思路和效率
 * 作者:Univero
 * 时间:2024-4-20
 */

#include <iostream>
#include <algorithm>
using namespace std;

// 交换函数
void swap(int *arr, int i, int j)
{
    int tmp = arr[i];
    arr[i] = arr[j];
    arr[j] = tmp;
}

/*
 * 堆排序
 * 最大堆:父节点的值大于等于其子节点的值
 * 最小堆:父节点的值小于等于其子节点的值
 * 思路:
 * 1.将待排序的序列建构成一个最大堆(或最小堆)
 * 2.将堆顶元素和最后一个元素交换,并将堆的大小减一
 * 3.通过一次调整,使得堆重新满足最大堆(最小堆)性质
 * 4.重复上述步骤2、3直至堆的大小为1
 * 原理:
 * 构造最大堆(最小堆)后,第一个元素最大,交换第一个元素和最后一个元素,保证最后一个最大
 * 再将堆的大小减一,重复上述步骤确保倒数第二个最大(小)
 * 效率:n*log n
 * 不稳定
 */
// 调整堆,使得以root为根节点的子树满足最大堆性质(升序)
void heapify(int *num, int root, int size)
{
    int largest = root;       // 初始化根节点为最大值
    int left = root * 2 + 1;  // 计算左子节点的索引
    int right = root * 2 + 2; // 计算右子节点的索引

    // 找到左右节点中的最大值
    // 和size的比较是因为最后一层非叶子节点可能没有子节点
    if (left < size && num[left] > num[largest])
        largest = left;
    if (right < size && num[right] > num[largest])
        largest = right;

    // 如果最大值不是根节点,则交换根节点和最大值,并继续调整堆
    if (largest != root)
    {
        swap(num, largest, root);
        heapify(num, largest, size); // 交换后root处保证了最大堆的性质,但是被交换的largest节点不能保证,所以对largest递归调整
    }
}
// 堆排序函数
void heap_sort(int *num, int n)
{
    // 构建最大堆,从最后一个非叶子节点开始向上调整
    for (int i = n / 2 - 1; i >= 0; i--)
    {
        heapify(num, i, n);
    }

    // 交换堆顶元素和最后一个元素,并调整堆,直至堆的大小为1
    for (int i = n - 1; i >= 0; i--)
    {
        swap(num, 0, i);
        heapify(num, 0, i);
    }
}

/*
 * 快速排序
 * 思路:分治思想
 * 每次将一个基准元素放入正确的位置,保证左侧元素小于等于基准元素,右侧元素大于等于基准元素
 * 在对左右侧子序列进行递归操作直至序列中元素个数为1或0
 * 平均:O(n^log n)
 * 最坏:O(n^2)
 * 不稳定
 */
// 划分函数,返回基准元素位置
// 数组中左侧都小于pivot,右侧都大于pivot
int partition(int *arr, int left, int right)
{
    int pivot = arr[left];
    int i = left + 1;
    int j = right;
    // 寻找基准元素的正确位置,同时确保左右序列的大小关系
    while (true)
    {
        while (i <= j && arr[i] <= pivot)
            i++;
        while (i <= j && arr[j] >= pivot)
            j--;

        if (i > j) // 若i>j则说明从基准元素的下一个到都保证比基准元素小
            break;

        swap(arr, i, j); // 若i<=j则说明左侧子序列有元素大于基准序列需要换到右侧子序列
    }
    swap(arr, left, j); // 将基准元素放入正确的位置
    return j;           // 返回交换后的基准元素位置
}

void quick_sort(int *arr, int left, int right)
{
    if (left < right)
    {
        // 相当于把pivot放入正确的位置后再依次对左右子列排序
        int pivotIndex = partition(arr, left, right);
        quick_sort(arr, left, pivotIndex - 1);
        quick_sort(arr, pivotIndex + 1, right);
    }
    return;
}

/*
 * 桶排序
 * 适用于待排序数据均匀分布在一个区间范围内
 * 思路:
 * 将待排序的数据分到有限数量的桶中,每个桶分别排序,在按照桶的顺序依次取出
 * 步骤:
 * 1.确认桶的数量,可以更加数据范围、可以固定、可以动态分配
 * 2.将数据分到桶中,由映射函数决定分配到哪个桶中
 * 3.对每个桶进行排序,如快排
 * 4.合并桶中数据
 * ps:也可以开足够大的索引,直接按序取出(如果确定数据范围可以考虑)
 * 关键点:如果用数组实现,注意如何分配动态的二维数组
 *        自己写的快排要数组长度这一参数,用一个数组维护
 */
void bucket_sort(int *num, int n)
{
    int max_val = *max_element(num, num + n); // 获取最大元素
    int min_val = *min_element(num, num + n); // 获取最小元素
    int bucket_size = 10;                     // 固定桶的大小为10

    // 计算桶的数量
    int bucket_count = (max_val - min_val) / bucket_size + 1;
    int *bucket_num = new int[bucket_count]; // 用于统计桶中元素个数

    // 创建桶
    int **buckets = new int *[bucket_count];
    for (int i = 0; i < bucket_count; i++)
        buckets[i] = nullptr;

    for (int i = 0; i < n; i++)
    {
        int index = (num[i] - min_val) / bucket_size;
        if (buckets[index] == nullptr)
        {
            buckets[index] = new int[n];
            bucket_num[index] = 0;
        }
        buckets[index][bucket_num[index]++] = num[i]; // 在桶中相应位置插入元素并更新元素数量
    }

    int index = 0;
    for (int i = 0; i < bucket_count; i++)
    {
        if (buckets[i] != nullptr)
        {
            quick_sort(buckets[i], 0, bucket_num[i] - 1);
        }
        for (int j = 0; j < n && buckets[i][j]; j++)
        {
            num[index++] = buckets[i][j];
        }
        delete[] buckets[i];
    }
    delete[] buckets;
    delete[] bucket_num;
}

/*
 * 归并排序
 * 思路:
 * 二分序列,按顺序合并子序列实现排序
 * 效率:O(n*log n)
 * 稳定
 */

// 合并函数,将左右两个子序列合并
void merge(int *arr, int left, int mid, int right)
{
    int len = right - left + 1;
    int *tmp = new int[len];   // 分配临时数组,用于合并
    int k = 0;                 // 临时数组的索引
    int i = left, j = mid + 1; // 左右两个数组的起始位置

    // 合并已经排序过的两个子序列
    while (i <= mid && j <= right)
    {
        if (arr[i] <= arr[j])
            tmp[k++] = arr[i++];
        else
            tmp[k++] = arr[j++];
    }

    // 合并剩余序列
    while (i <= mid)
        tmp[k++] = arr[i++];

    while (j <= right)
        tmp[k++] = arr[j++];

    //将临时数组内容拷贝到原数组
    for (k = 0; k < len; k++)
        arr[k + left] = tmp[k];

    // 释放临时数组内存
    delete[] tmp;
}

void merge_sort(int *num, int left, int right)
{
    if (left >= right) // 待排序数组长度为1或0时直接返回
        return;

    int mid = (left + right) / 2; // 计算中间位置

    // 递归对左右子列排序
    merge_sort(num, left, mid);
    merge_sort(num, mid + 1, right);

    // 合并排序后的结果
    merge(num, left, mid, right);
}

/*
 * 希尔排序
 * 效率关键在于选择增量序列
 * 希尔增量序列:2^k (1,2,4,6...)
 * Hibbard增量序列 2^k-1 (1,3,5,7...)
 * 思路:
 * 由增量划分子序列,在每一个子序列中进行插入排序,直至增量为1
 * 效率:O(n^1.3)~O(n^2)由增量序列选择决定  
 * 稳定
 */
void shell_sort(int *num, int n)
{
    int increment = n / 2; // 定义一个增量(选用希尔增量,初值为n/2)

    while (increment >= 1)
    {
        // 对每个子序列插入排序(升序)
        for (int i = increment; i < n; i++)
        {
            int tmp = num[i];
            int j = i;

            while (j >= increment && num[j - increment] > tmp)
            {
                num[j] = num[j - increment];
                j = j - increment;
            }

            num[j] = tmp;
        }
        // 缩小增量
        increment /= 2;
    }
}

/*
 * 冒泡排序
 * 思路:
 * 依次交换相邻的两个元素使之有序
 * O(n^2)
 * 辅助空间:1
 * 稳定
 */
void bubble_sort(int *num, int n)
{
    // 升序
    for (int i = 0; i < n; i++)
    {
        for (int j = n - 1; j > i; j--)
        {
            if (num[j] < num[j - 1])
            {
                swap(num, j, j - 1);
            }
        }
    }
}

/*
 * 选择排序
 * 思路:
 * 从未排序部分选择最值,插入已排序部分末端
 * 没有好坏情况区分
 * 适用于移动慢,比较快
 * 辅助空间:1
 * 不稳定
 */
void selection_sort(int *num, int n)
{
    // 升序
    for (int i = 0; i < n; i++)
    {
        int m = i;

        // 寻找无序部分最小值
        for (int j = i + 1; j < n; j++)
        {
            if (num[j] < num[m])
                m = j; // 记录最小值索引
        }

        // 交换无序部分最小值至有序部分末端
        if (m != i)
        {
            swap(num, m, i);
        }
    }
}

/*
 * 插入排序
 * 思路:
 * 以第一个元素为有序表,依次往后遍历,插入有序表中的合适部分
 * 注意点:移动和插入可以同时进行,索引的位置要注意(最后一个插入位置)
 * 最坏:n^2
 * 平均:n^2/4
 * 辅助空间 1
 * 稳定
 */
void insertion_sort(int *num, int n)
{
    for (int i = 1; i < n; i++)
    {
        if (num[i] < num[i - 1])
        {
            int tmp = num[i];
            int index = i;

            // do-while循环实现(升序)
            do
            {
                num[index] = num[index - 1];
                index--;
            } while (index > 0 && num[index - 1] > tmp);

            num[index] = tmp;

            // fro循环实现(升序)

            // for (index = i - 1; index >= 0; index--)
            // {
            //     if (num[index] > tmp)
            //         num[index + 1] = num[index];
            //     else
            //         break;
            // }
            // num[index + 1] = tmp;
        }
    }
}

const int MAX_NUM = 1000;

int main()
{
    int num[MAX_NUM];
    int n;

    // 输入
    cin >> n;
    for (int i = 0; i < n; i++)
    {
        cin >> num[i];
    }

    // 主要逻辑
    heap_sort(num, n);         // 堆排序
    shell_sort(num, n);        // 希尔排序
    bubble_sort(num, n);       // 冒泡排序
    bucket_sort(num, n);       // 桶排序
    insertion_sort(num, n);    // 插入排序
    selection_sort(num, n);    // 选择排序
    merge_sort(num, 0, n - 1); // 归并排序
    quick_sort(num, 0, n - 1); // 快速排序
    // 输出
    for (int i = 0; i < n; i++)
        cout << num[i] << " ";
}
  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值