三种基本排序(选择排序,冒泡排序,插入排序)

选择排序

选择排序(Selection Sort)是一种简单直观的排序算法。它的工作原理是:首先在未排序序列中找到最小(或最大)元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(或最大)元素,然后放到已排序序列的末尾。以此类推,直到所有元素均排序完毕。

以下是一个使用C语言实现的选择排序算法:

#include   
  
// 选择排序函数  
void selectionSort(int arr[], int n) 
{  
    int i, j, minIndex, temp;  
  
    // 遍历整个数组  
    for (i = 0; i < n-1; i++) 
        {  
        // 找到未排序部分的最小元素  
        minIndex = i;  
        for (j = i+1; j < n; j++) 
        {  
            if (arr[j] < arr[minIndex]) 
            {  
                minIndex = j;  
            }  
        }  
  
        // 将找到的最小元素与第一个未排序的位置交换  
        if (minIndex != i) 
        {  
            temp = arr[i];  
            arr[i] = arr[minIndex];  
            arr[minIndex] = temp;  
        }  
    }  
}  
  
// 打印数组  
void printArray(int arr[], int size) 
{  
    int i;  
    for (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("原始数组: \n");  
    printArray(arr, n);  
  
    selectionSort(arr, n);  
  
    printf("排序后的数组: \n");  
    printArray(arr, n);  
      
    return 0;  
}
  1. selectionSort 函数:这是选择排序的主要实现部分。它接受一个整数数组 arr 和一个整数 n(表示数组的大小)作为参数。

    • 在外层循环中,我们遍历整个数组,但注意我们只需要遍历到 n-1,因为最后一个元素在前面的迭代中已经被排序了。
    • 在内层循环中,我们从 i+1 开始,在未排序的部分中查找最小元素。如果找到一个比当前最小元素还小的元素,我们更新 minIndex
    • 在内层循环结束后,我们已经找到了未排序部分的最小元素。然后,我们将这个最小元素与第一个未排序的位置(即 i)交换。
  2. printArray 函数:这是一个辅助函数,用于打印数组的内容。

  3. main 函数:这是程序的入口点。

    • 我们定义了一个包含一些整数的数组 arr
    • 我们计算数组的大小,并将其存储在变量 n 中。
    • 我们打印原始数组。
    • 我们调用 selectionSort 函数对数组进行排序。
    • 我们打印排序后的数组。

这个选择排序的实现是升序排序,即从小到大。如果你想从大到小排序(降序),你只需要在内层循环中更改比较运算符即可(将 < 改为 >)。

冒泡排序 

冒泡排序(Bubble Sort)是一种简单的排序算法。它重复地遍历要排序的数列,一次比较两个元素,如果它们的顺序错误就把它们交换过来。遍历数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。

以下是一个使用C语言实现的冒泡排序算法:

​
#include   
  
// 冒泡排序函数  
void bubbleSort(int arr[], int n) 
{  
    int i, j, temp;  
    for (i = 0; i < n-1; i++) 
        {  
        // 标记位,用于优化,如果在某一趟排序中没有发生交换,说明序列已经有序,无需继续排序  
        int swapped = 0;  
        for (j = 0; j < n-i-1; j++) 
        {  
            // 如果当前元素大于下一个元素,则交换它们  
            if (arr[j] > arr[j+1]) 
            {  
                temp = arr[j];  
                arr[j] = arr[j+1];  
                arr[j+1] = temp;  
                // 标记发生了交换  
                swapped = 1;  
            }  
        }  
        // 如果在内层循环中没有交换过元素,说明数组已经是有序的  
        if (!swapped) 
        {  
            break;  
        }  
    }  
}  
  
// 打印数组  
void printArray(int arr[], int size) 
{  
    int i;  
    for (i = 0; i < size; i++) 
    {  
        printf("%d ", arr[i]);  
    }  
    printf("\n");  
}  
  
// 主函数  
int main() 
{  
    int arr[] = {64, 34, 25, 12, 22, 11, 90};  
    int n = sizeof(arr) / sizeof(arr[0]);  
      
    printf("原始数组:\n");  
    printArray(arr, n);  
      
    bubbleSort(arr, n);  
      
    printf("排序后的数组:\n");  
    printArray(arr, n);  
      
    return 0;  
}

​
  1. bubbleSort 函数:这是冒泡排序的主要实现部分。

    • 外层循环控制排序的趟数,最多需要遍历 n-1 趟(因为最后一个元素会在前面的迭代中自动到达正确的位置)。
    • 内层循环负责在每一趟中进行实际的比较和交换。j 从 0 遍历到 n-i-1,因为每趟排序后,最大的元素都会“冒泡”到序列的末尾,所以下一趟排序时就不必再考虑这些已经排好序的元素了。
    • 如果发生交换,swapped 被设置为 1,表示这一趟排序是有效的。如果在内层循环结束后 swapped 仍然是 0,说明没有发生交换,数列已经有序,可以提前结束排序。
  2. printArray 函数:这是一个辅助函数,用于打印数组的内容。

  3. main 函数:

    • 定义了一个待排序的数组 arr 和它的大小 n
    • 打印原始数组。
    • 调用 bubbleSort 函数对数组进行排序。
    • 打印排序后的数组。

冒泡排序的时间复杂度在最坏和平均情况下是 O(n^2),其中 n 是要排序的元素数量。在最好情况下(即数组已经有序),如果使用了上述代码中的优化,时间复杂度可以降低到 O(n)。空间复杂度是 O(1),因为排序是在原地进行的,不需要额外的存储空间。

插入排序

插入排序(Insertion Sort)是一种简单直观的排序算法,它的工作原理是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。插入排序在实现上通常使用in-place排序(即只需用到O(1)的额外空间)。

以下是一个使用C语言实现的插入排序算法:

#include   
  
// 插入排序函数  
void insertionSort(int arr[], int n) 
{  
    int i, key, j;  
    for (i = 1; i < n; i++) 
    {  
        // 将arr[i]保存到key中,它是要被插入到前面已排序序列中的元素  
        key = arr[i];  
        // 从i-1开始向前扫描已排序序列,找到key应该插入的位置  
        j = i - 1;  
        // 如果扫描到的元素比key大,就将该元素移到下一个位置  
        while (j >= 0 && arr[j] > key) 
        {  
            arr[j + 1] = arr[j];  
            j = j - 1;  
        }  
        // 找到key的位置后,将key插入  
        arr[j + 1] = key;  
    }  
}  
  
// 打印数组  
void printArray(int arr[], int size) 
{  
    int i;  
    for (i = 0; i < size; i++) 
    {  
        printf("%d ", arr[i]);  
    }  
    printf("\n");  
}  
  
// 主函数  
int main() 
{  
    int arr[] = {12, 11, 13, 5, 6};  
    int n = sizeof(arr) / sizeof(arr[0]);  
  
    printf("原始数组:\n");  
    printArray(arr, n);  
  
    insertionSort(arr, n);  
  
    printf("排序后的数组:\n");  
    printArray(arr, n);  
  
    return 0;  
}
  1. insertionSort 函数:
    • 外层循环从数组的第二个元素开始(索引为1),因为我们认为第一个元素(索引为0)已经被排序了。
    • 对于当前要插入的元素arr[i],我们将其值存储在key中。
    • 内层循环从i-1开始,向前扫描已排序的部分,直到找到一个不大于key的元素或到达数组的起始位置。
    • 如果扫描到的元素比key大,就将该元素向右移动一位,为key腾出插入的位置。
    • 当找到key的正确位置或到达数组起始位置时,退出内层循环,并将key插入到该位置。
  2. printArray 函数:
    • 用于打印数组内容,帮助验证排序是否正确。
  3. main 函数:
    • 定义了一个待排序的数组arr和数组的大小n
    • 打印原始数组。
    • 调用insertionSort函数对数组进行排序。
    • 打印排序后的数组。

插入排序的时间复杂度在最坏和平均情况下是O(n^2),其中n是要排序的元素数量。这是因为每次插入操作可能需要移动前面已排序序列中的所有元素。然而,对于部分已排序或小规模的数据集,插入排序可能会比其他O(n^2)算法(如冒泡排序)更快,因为它具有更少的交换操作。空间复杂度是O(1),因为排序是在原地进行的,不需要额外的存储空间。

总结

选择排序、冒泡排序和插入排序都是基础的排序算法,它们各有特点。以下是它们之间的比较:

  1. 工作原理
    • 选择排序:在未排序的序列中找到最小(或最大)元素,将其放到排序序列的起始位置。然后,再从剩余未排序的元素中继续寻找最小(或最大)元素,然后放到已排序序列的末尾。这个过程会一直重复,直到所有元素都排序完毕。
    • 冒泡排序:通过重复地遍历待排序序列,一次比较两个元素,如果它们的顺序错误就把它们交换过来。遍历序列的工作被重复进行直到没有更多的交换,也就是说序列已经排序完成。
    • 插入排序:将未排序的元素一个个插入到已排序序列的合适位置,从而达到排序的目的。
  2. 效率
    • 这三种排序算法的时间复杂度在平均和最坏的情况下都是O(n^2),其中n是列表的长度。这意味着随着列表长度的增加,排序所需的时间将按平方比例增加。
    • 在最好的情况下,冒泡排序和插入排序的时间复杂度可以达到O(n)(如果列表已经部分或完全排序),但这通常需要一些改进,比如添加一个标志来检查在某次遍历中是否进行了交换。
    • 选择排序在每次迭代中都需要找到剩余元素中的最小(或最大)值,这使得它即使在最好的情况下也保持O(n^2)的时间复杂度。
  3. 空间复杂度
    • 这三种排序算法都只需要常量级别的额外空间来存储临时变量,因此它们的空间复杂度是O(1)。
  4. 稳定性
    • 冒泡排序插入排序是稳定的排序算法,这意味着具有相同值的元素在排序后保持其原始顺序。
    • 选择排序是不稳定的,因为它可能会改变具有相同值的元素的相对顺序。
  5. 实际应用
    • 由于这三种排序算法在大型数据集上的效率相对较低,它们通常不用于实际的大规模排序任务。然而,它们在教学、小型数据集或特定情况下(如部分有序的列表)仍然是有用的。
    • 插入排序在实际应用中有时会比其他O(n^2)算法更受欢迎,因为它在处理小型和部分有序的数组时表现良好,并且它的常数因子较小。
  6. 简单性
    • 从实现的角度来看,这三种算法都非常直观和简单,是学习和理解排序算法的基础。

综上所述,选择排序、冒泡排序和插入排序在效率上相近,但在稳定性和简单性方面有所不同。在实际应用中,通常会选择更高效的排序算法,如快速排序、归并排序或堆排序。

你可能感兴趣的:(算法,排序算法,数据结构,c语言,c++)