基于C语言的选择排序算法

一、选择排序算法的基本原理

        选择排序算法是一种简单直观的排序算法。其基本原理为:

        首先,将待排序的数组划分为已排序和未排序两部分。初始时,已排序部分为空,未排序部分为整个数组。

        在每一轮排序中,从未排序部分找出最小(或最大)的元素。然后,将这个最小(或最大)元素与未排序部分的起始位置元素交换,从而将其放入已排序部分的末尾。

        例如,对于数组 [88, 5, 15, 56, 32, 18, 69],按照从小到大的顺序进行排序。第一轮,在未排序部分 [88, 5, 15, 56, 32, 18, 69] 中找到最小的元素 5,将其与未排序部分的起始位置元素 88 交换,得到 [5, 88, 15, 56, 32, 18, 69]。第二轮,在未排序部分 [88, 15, 56, 32, 18, 69] 中找到最小的元素 15,与起始位置元素 88 交换,得到 [5, 15, 88, 56, 32, 18, 69]。依此类推,经过多轮比较和交换,最终使整个数组有序。

        选择排序的核心思想在于不断地选择未排序部分的最小(或最大)元素,并将其放置到已排序部分的正确位置。这种算法的优点是简单易懂,易于实现;缺点是效率相对较低,其时间复杂度为 O (n^2),在处理大规模数据时可能表现不佳。

二、选择排序算法的实现步骤

(一)普通实现

        普通选择排序的实现流程较为直观。首先,从待排序的数组中,假设第一个元素为最小元素。然后通过遍历剩余未排序的元素,逐一比较找出真正的最小元素。当找到最小元素后,将其与当前未排序部分的起始位置元素进行交换,这样就将最小元素放置在了已排序部分的末尾。随着每一轮的进行,已排序部分不断增加,未排序部分逐渐减少。

        例如,对于数组 [9, 7, 5, 3, 1],第一轮先假设 9 是最小元素,然后遍历后续元素,发现 1 是最小元素,将 1 与 9 交换,得到 [1, 7, 5, 3, 9]。第二轮从未排序部分 [7, 5, 3, 9] 开始,假设 7 是最小元素,遍历后发现 3 是最小元素,交换后数组变为 [1, 3, 5, 7, 9]。以此类推,直到整个数组有序。

(二)升级实现

        选择排序的升级版本通常是在每次循环中同时找到最小和最大元素。通过遍历未排序部分,记录下最小元素和最大元素的位置。然后将最小元素与未排序部分的起始位置元素交换,将最大元素与未排序部分的末尾位置元素交换,从而使得序列向中间收缩。

        例如,对于数组 [8, 6, 4, 2, 0],第一轮同时找到最小元素 0 和最大元素 8,交换位置后得到 [0, 6, 4, 2, 8]。第二轮在未排序部分 [6, 4, 2] 中找到最小元素 2 和最大元素 6,交换后数组变为 [0, 2, 4, 6, 8]。这样能够在一定程度上提高排序效率,减少排序的轮数。

三、选择排序算法的代码示例

#include <stdio.h>

// 选择排序函数
void selectionSort(int arr[], int n) {
    int i, j, minIndex, temp;
    
    for (i = 0; i < n - 1; i++) 
    // i 用于标记已排序部分的边界,从数组的第一个元素开始,直到数组中倒数第二个元素
    {
        
        minIndex = i;// 假设当前位置 i 为最小元素
        for (j = i + 1; j < n; j++) 
        // j 从 i 的下一个位置开始遍历到数组末尾,寻找真正的最小元素
        {
            if (arr[j] < arr[minIndex])
            // 如果当前遍历到的元素比当前标记的最小元素要小,则更新最小元素
            {
                minIndex = j;
            }
        }
        
        if (minIndex!= i) 
        // 如果标记的最小元素不是当前 i 的位置,说明找到了更小的元素,进行交换
        {
            temp = arr[i];
            arr[i] = arr[minIndex];
            arr[minIndex] = temp;
        }
    }
}

// 打印数组函数
void printArray(int arr[], int size) {
    for (int i = 0; i < size; i++)
        // 逐个打印数组中的元素,元素之间用空格分隔
        printf("%d ", arr[i]);
    // 打印完一行数组元素后换行
    printf("\n");
}

int main() {
    int arr[] = {64, 25, 12, 22, 11};
    int n = sizeof(arr) / sizeof(arr[0]);

    // 打印提示信息和未排序的数组
    printf("排序前的数组为:");
    printArray(arr, n);

    // 调用选择排序函数对数组进行排序
    selectionSort(arr, n);

    // 打印提示信息和排序后的数组
    printf("排序后的数组为:");
    printArray(arr, n);

    return 0;
}
#include <stdio.h>

// 优化的选择排序函数
void selectionSortOptimized(int arr[], int n) {
    int left = 0; // 指向待排序区间的左端
    int right = n - 1; // 指向待排序区间的右端
    int minIndex, maxIndex, temp;

    // 当左端小于右端时进行排序,确保至少有两个元素待排序
    while (left < right) {
        minIndex = left; // 初始假设左端的元素为最小元素的索引
        maxIndex = left; // 初始假设左端的元素为最大元素的索引

        // 遍历当前待排序区间,寻找最小和最大元素的索引
        for (int i = left; i <= right; i++) {
            if (arr[i] < arr[minIndex]) {
                minIndex = i; // 更新最小元素索引
            }
            if (arr[i] > arr[maxIndex]) {
                maxIndex = i; // 更新最大元素索引
            }
        }

        // 如果最小元素不在左端,则交换左端元素和最小元素
        if (minIndex!= left) {
            temp = arr[minIndex];
            arr[minIndex] = arr[left];
            arr[left] = temp;
        }

        // 如果最大元素此时在左端(因为交换了最小元素到左端)
        if (maxIndex == left) {
            maxIndex = minIndex; // 更新最大元素索引为之前的最小元素索引
        }

        // 如果最大元素不在右端,则交换右端元素和最大元素
        if (maxIndex!= right) {
            temp = arr[maxIndex];
            arr[maxIndex] = arr[right];
            arr[right] = temp;
        }

        // 缩小待排序区间
        left++;
        right--;
    }
}

// 打印数组函数
void printArray(int arr[], int size) {
    for (int i = 0; i < size; i++)
        printf("%d ", arr[i]);
    printf("\n");
}

int main() {
    int arr[] = {98, 56, 23, 77, 11};
    int n = sizeof(arr) / sizeof(arr[0]);

    printf("排序前的数组为:");
    printArray(arr, n);

    selectionSortOptimized(arr, n);

    printf("排序后的数组为:");
    printArray(arr, n);

    return 0;
}

        在普通选择排序代码中,通过两层循环找出每一轮的最小元素,并与当前位置交换。而优化版本则同时考虑最小和最大元素,在一次循环中进行两次交换,使序列向中间收缩,提高了一定的效率。

四、选择排序算法的复杂度、稳定性分析

(一)时间复杂度

        选择排序无论在最好、最坏还是平均情况下,时间复杂度均为 O (n^2)。其原因在于,对于长度为 n 的数组,每次选择最小(或最大)元素都需要遍历未排序部分的所有元素。外层循环执行 n - 1 次,内层循环在每次外层循环时的执行次数逐渐减少,但总体比较和交换的操作次数约为 (n - 1) + (n - 2) +... + 2 + 1,通过求和公式可计算得出,其时间复杂度接近 O (n^2)。

(二)空间复杂度

        选择排序在排序过程中,仅使用了几个固定的额外变量来存储临时值,如用于记录最小元素索引的变量、用于交换元素的临时变量等,不需要额外的数组或其他大量的存储空间。因此,其空间复杂度为 O (1),是一种原地排序算法。

(三)稳定性

        选择排序是不稳定的排序算法。例如,对于数组 [5, 8, 5, 2, 9],第一次选择最小元素 2 与第一个 5 交换位置,导致两个 5 的相对顺序发生了改变。原本在前面的 5 移动到了后面,破坏了相等元素的原有顺序。所以,选择排序无法保证相同元素在排序前后的相对位置不变。

五、选择排序算法的优化策略

(一)减少交换次数

        在普通的选择排序中,每次发现新的最小元素就立即与当前位置进行交换。然而,这种频繁的交换操作会增加算法的开销。一种优化策略是先遍历找出最小元素的位置,在一轮遍历结束后,再统一进行交换。例如,对于数组 [7, 5, 9, 3, 1],先依次比较记录最小元素的位置为 4,然后再将位置 0 的 7 与位置 4 的 1 进行交换。这样可以减少不必要的交换操作,提高算法的效率。

(二)同时确定最大和最小值

        另一种有效的优化方法是在每次遍历中同时确定最大和最小值。通过一次遍历,记录最大和最小元素的位置,然后将它们分别与未排序部分的起始位置和末尾位置的元素进行交换。以数组 [8, 6, 4, 2, 0] 为例,第一轮同时找到最小元素 0 和最大元素 8,然后交换它们的位置,得到 [0, 6, 4, 2, 8]。这种方式能够在相同的循环次数内完成更多的排序工作,从而减少总的排序轮数,提升算法性能。同时,在实现过程中需要注意处理最大和最小元素位置重合等特殊情况,以保证算法的正确性。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值