必备关键代码

本文详细介绍了冒泡排序、快速排序、插入排序、选择排序、归并排序、希尔排序和堆排序的基本思想、实现方法。这些排序算法展示了分治策略和数据结构在排序问题中的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

排序算法

1.冒泡排序

冒泡排序基本思想是通过重复地交换相邻元素,将较大的元素逐渐“冒泡”到数组的末尾,而较小的元素则逐渐“浮”到数组的前面。冒泡排序的核心思想是比较相邻的元素,如果它们的顺序不符合排序要求,则交换它们的位置,重复这个过程直到整个数组排序完成。

// 比较a和b,如果a小于b,则返回1,否则返回0
int IsSmaller(int a, int b)
{
    if (a < b)
        return 1;
    else
        return 0;
}

// 比较a和b,如果a大于b,则返回1,否则返回0
int IsBiggr(int a, int b)
{
    if (a < b)
        return 0;
    else
        return 1;
}

// 冒泡排序函数,使用传入的比较规则Rule对整数数组a进行排序
void BubbleSort(int *a, int len, int (*Rule)(int, int))
{
    for (int i = 0; i < len - 1; i++)
    {
        int flag = 0; // 标志位,用于判断是否发生交换操作
        for (int j = 0; j < len - i - 1; j++)
        {
            // 使用传入的比较规则Rule比较a[j]和a[j+1],根据规则决定是否交换它们的位置
            if (Rule(a[j], a[j + 1]))
            {
                // 交换a[j]和a[j+1]的位置
                int temp = a[j];
                a[j] = a[j + 1];
                a[j + 1] = temp;
                flag = 1; // 设置标志位,表示发生了交换操作
            }
        }
        // 如果一轮遍历中没有发生交换操作(即flag仍然为0),说明数组已经有序,可以提前结束排序
        if (flag == 0)
        {
            break;
        }
    }
}

2.快速排序

快速排序的核心是分治策略,通过不断地划分子数组并排序,最终实现整个数组的排序。在每一轮划分中,选择基准元素,并根据基准元素将数组分为两部分。然后,递归地对左边部分和右边部分进行排序,直到子数组长度为1或0时停止递归。

// 快速排序函数,对整数数组a的子数组[start, end]进行排序
void Quicksort(int *a, int start, int end)
{
    // 如果子数组只有一个元素或为空,无需排序,直接返回
    if (start >= end)
    {
        return;
    }
    
    int temp = a[start]; // 选择第一个元素作为基准元素
    int left = start;    // 左指针
    int right = end;     // 右指针

    while (left < right)
    {
        // 从右边开始找到第一个小于基准元素的元素的位置
        while (left < right && a[right] >= temp)
        {
            right--;
        }
        if (left < right)
        {
            a[left] = a[right]; // 将找到的小于基准元素的元素移到左边
        }

        // 从左边开始找到第一个大于等于基准元素的元素的位置
        while (left < right && a[left] < temp)
        {
            left++;
        }
        if (left < right)
        {
            a[right] = a[left]; // 将找到的大于等于基准元素的元素移到右边
        }
    }

    a[left] = temp; // 将基准元素放到合适的位置,左边都小于它,右边都大于等于它

    // 递归地对基准元素左边和右边的子数组进行排序
    Quicksort(a, start, left - 1);
    Quicksort(a, left + 1, end);
}

3.插入排序

插入排序的基本思想是维护一个已排序的子数组,初始时只包含第一个元素。然后,逐个将未排序的元素插入到已排序的子数组中,确保每次插入后子数组仍然有序。这个过程重复进行,直到整个数组都排好序。

对于直接插入排序,它从未排序的部分依次选择元素,然后在已排序的部分中找到正确的插入位置,最后插入元素。
对于折半插入排序,它使用二分查找来找到插入位置,从而减少了比较的次数,但仍然需要向后移动元素。

// 直接插入排序
void DirectInsertionSort(int *a, int len)
{
    for (int i = 1; i < len; i++)
    {
        int current = a[i]; // 当前需要插入的元素
        int j = i - 1; // 已排序部分的最后一个元素的索引

        // 在已排序部分找到合适的插入位置
        while (j >= 0 && a[j] > current)
        {
            a[j + 1] = a[j]; // 向后移动大于当前元素的元素
            j--;
        }

        a[j + 1] = current; // 插入当前元素到正确的位置
    }
}

// 折半插入排序
void BinaryInsertionSort(int *a, int len)
{
    for (int i = 1; i < len; i++)
    {
        int current = a[i]; // 当前需要插入的元素
        int left = 0; // 已排序部分的左边界
        int right = i - 1; // 已排序部分的右边界

        // 使用二分查找找到插入位置
        while (left <= right)
        {
            int mid = left + (right - left) / 2; // 计算中间位置

            if (a[mid] > current)
                right = mid - 1; // 缩小右边界
            else
                left = mid + 1; // 缩小左边界
        }

        // 将元素插入到正确的位置
        for (int j = i - 1; j >= left; j--)
        {
            a[j + 1] = a[j]; // 向后移动大于当前元素的元素
        }

        a[left] = current; // 插入当前元素到正确的位置
    }
}

4.选择排序

选择排序的基本思想是维护一个已排序的子数组,初始时为空,然后在未排序的部分中找到最小(或最大)的元素,将其交换到已排序的子数组的末尾。这个过程不断重复,直到整个数组都排好序。

// 选择排序函数1,对整数数组a进行排序
void ChooseSort(int *a, int len)
{
    for (int i = 0; i < len; i++)
    {
        int min = i;     // 假设当前位置的元素是最小的
        int Minum = a[min];  // 记录最小元素的值

        // 在未排序的部分中找到最小的元素及其位置
        for (int j = i + 1; j < len; j++)
        {
            if (a[j] < Minum)
            {
                Minum = a[j];
                min = j;
            }
        }

        // 将最小元素与当前位置的元素交换
        int temp = a[i];
        a[i] = Minum;
        a[min] = temp;
    }
}

// 选择排序函数2,对整数数组a进行排序
void ChooseSort2(int *a, int len)
{
    int left = 0;        // 待排序部分的左边界
    int right = len - 1; // 待排序部分的右边界

    while (left < right)
    {
        int min = left; // 假设左边界的元素是最小的
        int max = right; // 假设右边界的元素是最大的

        // 在未排序的部分中找到最小和最大的元素及其位置
        for (int i = left; i <= right; i++)
        {
            if (a[i] < a[min])
                min = i;
            if (a[i] > a[max])
                max = i;
        }

        // 将最小元素与左边界的元素交换
        int temp = a[left];
        a[left] = a[min];
        a[min] = temp;

        // 如果最大元素的位置与右边界不相同,将最大元素与右边界的元素交换
        if (max != right)
        {
            temp = a[right];
            a[right] = a[max];
            a[max] = temp;
        }

        left++;  // 更新左边界
        right--; // 更新右边界
    }
}

5.归并排序

归并排序采用了分治策略。它的基本思想是将待排序的数组分成两个较小的子数组,然后递归地对这两个子数组进行排序,最后将它们合并成一个有序的数组。归并排序包括两个主要步骤:分割(将数组分成两个子数组)和合并(将两个有序的子数组合并成一个有序的数组)。

// 合并两个已排序的子数组
void Merge(int *a, int left, int mid, int right)
{
    int n1 = mid - left + 1; // 左子数组的长度
    int n2 = right - mid;    // 右子数组的长度
    int L[n1], R[n2];       // 临时数组用于存储左右子数组的元素

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

    int i = 0, j = 0, k = left;

    // 合并临时数组 L[] 和 R[] 到原数组 a[]
    while (i < n1 && j < n2)
    {
        if (L[i] <= R[j])
        {
            a[k] = L[i];
            i++;
        }
        else
        {
            a[k] = R[j];
            j++;
        }
        k++;
    }

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

// 归并排序递归函数
void MergeSort(int *a, int left, int right)
{
    if (left < right)
    {
        int mid = left + (right - left) / 2; // 找到中间索引
        // 递归地对左半部分和右半部分进行归并排序
        MergeSort(a, left, mid);
        MergeSort(a, mid + 1, right);
        // 合并两个子数组
        Merge(a, left, mid, right);
    }
}

6.希尔排序

希尔排序是一种改进的插入排序算法,其基本思想是通过分组排序的方式逐渐减小数组中元素之间的间隔(步长),从而将较小的元素尽早移动到合适的位置。希尔排序的关键思想是通过插入排序的方式,先排序间隔较远的元素,然后逐渐减小间隔,最终进行一次标准的插入排序。

// 对数组a的子数组进行插入排序,起始位置为pos,步长为step
void groupsort(int *a, int n, int pos, int step)
{
    int temp, i, j;
    
    // 从数组中的第pos+step个元素开始,每隔step个元素进行遍历
    for (i = pos + step; i < n; i += step)
    {
        temp = a[i]; // 存储当前需要插入的元素
        
        // 在已排序部分中,从当前位置向前寻找插入位置
        for (j = i - step; j >= 0; j -= step)
        {
            // 如果已排序部分的元素大于temp,就将元素向后移动
            if (a[j] <= temp)
            {
                break; // 找到了插入位置,退出循环
            }
            a[j + step] = temp; // 向后移动元素
        }
    }
}

// 希尔排序
void shellsort(int *a, int n)
{
    if (n < 2)
    {
        return; // 如果数组长度小于2,无需排序,直接返回
    }
    
    int i, step;
    
    // 初始步长为数组长度的一半,逐渐减小步长,直到步长为1
    for (step = n / 2; step > 0; step /= 2)
    {
        // 对每个子数组进行插入排序,子数组的起始位置从0到step-1,步长为step
        for (i = 0; i < step; i++)
        {
            groupsort(a, n, i, step);
        }
    }
}

7.堆排序

堆排序是一种基于二叉堆数据结构的排序算法。它的基本思想是首先将待排序的数组构建成一个最大堆(或最小堆),然后依次取出堆顶元素并重新调整堆,直到整个数组排序完成。

// 堆排序
void HeapSort(int *a, int n)
{
    // 构建最大堆(将数组堆化)
    for (int i = n / 2 - 1; i >= 0; i--)
    {
        Heapify(a, n, i);
    }

    // 依次取出堆顶元素并重新调整堆
    for (int i = n - 1; i > 0; i--)
    {
        // 交换堆顶元素和当前最后一个元素
        int temp = a[0];
        a[0] = a[i];
        a[i] = temp;

        // 重新调整堆,排除已排序的部分
        Heapify(a, i, 0);
    }
}

// 将以根节点为i的子树堆化成最大堆
void Heapify(int *a, int n, int i)
{
    int largest = i;       // 初始化最大值为根节点
    int left = 2 * i + 1;  // 左子节点索引
    int right = 2 * i + 2; // 右子节点索引

    // 如果左子节点比根节点大,更新最大值
    if (left < n && a[left] > a[largest])
    {
        largest = left;
    }

    // 如果右子节点比根节点大,更新最大值
    if (right < n && a[right] > a[largest])
    {
        largest = right;
    }

    // 如果最大值不是根节点,交换根节点和最大值
    if (largest != i)
    {
        int temp = a[i];
        a[i] = a[largest];
        a[largest] = temp;

        // 递归调整被交换的子树
        Heapify(a, n, largest);
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值