C语言算法:排序算法入门

本文献给:

想要系统学习排序算法的C语言程序员。如果你对不同的排序方法感到困惑,或者想知道在什么情况下该用什么排序算法------本文将为你提供清晰的指导和实践。

你将学到:

  1. 理解排序算法的基本概念和分类
  2. 掌握三种基础排序算法的原理和实现
  3. 学会分析不同排序算法的性能特点
  4. 掌握排序算法的实际应用场景
  5. 建立选择合适排序算法的思维框架

让我们开始探索排序算法的精彩世界!


目录

第一部分:排序算法概述

1. 为什么要学习排序?

排序是计算机科学中最基础、最常用的问题之一。在实际开发中,我们经常需要对数据进行排序:

  • 学生成绩按分数排序
  • 商品按价格排序
  • 文件按时间排序
  • 搜索结果按相关性排序

排序的好处:

  • 提高查找效率(二分查找需要有序数据)
  • 便于数据分析(统计、分组)
  • 改善用户体验(有序显示)
  • 优化其他算法性能
c 复制代码
#include <stdio.h>

// 演示排序对查找的影响
void demonstrateSortImportance() {
    int unsorted[] = {42, 17, 89, 5, 23, 56, 71, 3};
    int sorted[] = {3, 5, 17, 23, 42, 56, 71, 89};
    int size = 8;
    int target = 23;
    
    printf("未排序数组: ");
    for (int i = 0; i < size; i++) printf("%d ", unsorted[i]);
    
    printf("\n已排序数组: ");
    for (int i = 0; i < size; i++) printf("%d ", sorted[i]);
    
    printf("\n\n查找目标: %d\n", target);
    
    // 在未排序数组中查找
    int unsortedComparisons = 0;
    for (int i = 0; i < size; i++) {
        unsortedComparisons++;
        if (unsorted[i] == target) break;
    }
    printf("未排序数组查找比较次数: %d\n", unsortedComparisons);
    
    // 在已排序数组中查找(二分查找)
    int sortedComparisons = 0;
    int left = 0, right = size - 1;
    while (left <= right) {
        sortedComparisons++;
        int mid = left + (right - left) / 2;
        if (sorted[mid] == target) break;
        else if (sorted[mid] < target) left = mid + 1;
        else right = mid - 1;
    }
    printf("已排序数组查找比较次数: %d\n", sortedComparisons);
}

int main() {
    demonstrateSortImportance();
    return 0;
}
复制代码
运行结果:
未排序数组: 42 17 89 5 23 56 71 3 
已排序数组: 3 5 17 23 42 56 71 89 

查找目标: 23
未排序数组查找比较次数: 4
已排序数组查找比较次数: 1

2. 排序算法分类

按时间复杂度分类:

  • O(n²):冒泡排序、选择排序、插入排序
  • O(n log n):快速排序、归并排序、堆排序
  • O(n):计数排序、桶排序、基数排序

按稳定性分类:

  • 稳定排序:冒泡排序、插入排序、归并排序
  • 不稳定排序:选择排序、快速排序、堆排序

按内存使用分类:

  • 原地排序:冒泡、选择、插入、快速、堆排序
  • 非原地排序:归并排序、计数排序、桶排序

第二部分:基础排序算法

1. 冒泡排序

算法思想:

重复遍历数组,比较相邻元素,如果顺序错误就交换,直到没有需要交换的元素。

c 复制代码
#include <stdio.h>

// 冒泡排序
void bubbleSort(int arr[], int n) {
    for (int i = 0; i < n - 1; i++) {
        // 每次遍历将最大的元素"冒泡"到末尾
        for (int j = 0; j < n - i - 1; j++) {
            if (arr[j] > arr[j + 1]) {
                // 交换相邻元素
                int temp = arr[j];
                arr[j] = arr[j + 1];
                arr[j + 1] = temp;
            }
        }
        
        // 打印每轮排序结果
        printf("第%d轮: ", i + 1);
        for (int k = 0; k < n; k++) {
            printf("%d ", arr[k]);
        }
        printf("\n");
    }
}

// 优化版本:如果某轮没有交换,提前结束
void optimizedBubbleSort(int arr[], int n) {
    for (int i = 0; i < n - 1; i++) {
        int swapped = 0;  // 标记是否发生交换
        
        for (int j = 0; j < n - i - 1; j++) {
            if (arr[j] > arr[j + 1]) {
                int temp = arr[j];
                arr[j] = arr[j + 1];
                arr[j + 1] = temp;
                swapped = 1;
            }
        }
        
        printf("第%d轮: ", i + 1);
        for (int k = 0; k < n; k++) printf("%d ", arr[k]);
        printf("%s\n", swapped ? "" : " (已有序,提前结束)");
        
        if (!swapped) break;  // 如果没有交换,说明已经有序
    }
}

int main() {
    int numbers[] = {64, 34, 25, 12, 22, 11, 90, 99};
    int n = sizeof(numbers) / sizeof(numbers[0]);
    
    printf("原始数组: ");
    for (int i = 0; i < n; i++) printf("%d ", numbers[i]);
    printf("\n\n");
    
    printf("=== 标准冒泡排序 ===\n");
    bubbleSort(numbers, n);
    
    printf("\n=== 优化冒泡排序 ===\n");
    int numbers2[] = {64, 34, 25, 12, 22, 11, 90, 99};
    optimizedBubbleSort(numbers2, n);
    
    return 0;
}
复制代码
运行结果:
原始数组: 64 34 25 12 22 11 90 99 

=== 标准冒泡排序 ===
第1轮: 34 25 12 22 11 64 90 99 
第2轮: 25 12 22 11 34 64 90 99 
第3轮: 12 22 11 25 34 64 90 99 
第4轮: 12 11 22 25 34 64 90 99 
第5轮: 11 12 22 25 34 64 90 99 
第6轮: 11 12 22 25 34 64 90 99 
第7轮: 11 12 22 25 34 64 90 99

=== 优化冒泡排序 ===
第1轮: 34 25 12 22 11 64 90 99
第2轮: 25 12 22 11 34 64 90 99
第3轮: 12 22 11 25 34 64 90 99
第4轮: 12 11 22 25 34 64 90 99
第5轮: 11 12 22 25 34 64 90 99
第6轮: 11 12 22 25 34 64 90 99  (已有序,提前结束)

冒泡排序特点:

  • 时间复杂度:O(n²)
  • 空间复杂度:O(1)
  • 稳定排序
  • 原地排序

2. 选择排序

算法思想:

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

c 复制代码
#include <stdio.h>

// 选择排序
void selectionSort(int arr[], int n) {
    for (int i = 0; i < n - 1; i++) {
        // 找到未排序部分的最小元素索引
        int minIndex = i;
        for (int j = i + 1; j < n; j++) {
            if (arr[j] < arr[minIndex]) {
                minIndex = j;
            }
        }
        
        // 将最小元素交换到当前位置
        if (minIndex != i) {
            int temp = arr[i];
            arr[i] = arr[minIndex];
            arr[minIndex] = temp;
        }
        
        // 打印每轮排序结果
        printf("第%d轮: ", i + 1);
        for (int k = 0; k < n; k++) {
            printf("%d ", arr[k]);
        }
        printf("(最小元素: %d)\n", arr[i]);
    }
}

int main() {
    int numbers[] = {64, 25, 12, 22, 11};
    int n = sizeof(numbers) / sizeof(numbers[0]);
    
    printf("原始数组: ");
    for (int i = 0; i < n; i++) printf("%d ", numbers[i]);
    printf("\n\n");
    
    printf("=== 选择排序过程 ===\n");
    selectionSort(numbers, n);
    
    printf("\n最终结果: ");
    for (int i = 0; i < n; i++) printf("%d ", numbers[i]);
    printf("\n");
    
    return 0;
}
复制代码
运行结果:
原始数组: 64 25 12 22 11 

=== 选择排序过程 ===
第1轮: 11 25 12 22 64 (最小元素: 11)
第2轮: 11 12 25 22 64 (最小元素: 12)
第3轮: 11 12 22 25 64 (最小元素: 22)
第4轮: 11 12 22 25 64 (最小元素: 25)

最终结果: 11 12 22 25 64 

选择排序特点:

  • 时间复杂度:O(n²)
  • 空间复杂度:O(1)
  • 不稳定排序
  • 原地排序

3. 插入排序

算法思想:

将数组分为已排序和未排序两部分,每次从未排序部分取一个元素,插入到已排序部分的正确位置。

c 复制代码
#include <stdio.h>

// 插入排序
void insertionSort(int arr[], int n) {
    for (int i = 1; i < n; i++) {
        int key = arr[i];  // 当前要插入的元素
        int j = i - 1;
        
        // 将比key大的元素向后移动
        while (j >= 0 && arr[j] > key) {
            arr[j + 1] = arr[j];
            j--;
        }
        arr[j + 1] = key;  // 插入key到正确位置
        
        // 打印每轮排序结果
        printf("第%d轮: ", i);
        for (int k = 0; k < n; k++) {
            printf("%d ", arr[k]);
        }
        printf("(插入元素: %d)\n", key);
    }
}

int main() {
    int numbers[] = {12, 11, 13, 5, 6};
    int n = sizeof(numbers) / sizeof(numbers[0]);
    
    printf("原始数组: ");
    for (int i = 0; i < n; i++) printf("%d ", numbers[i]);
    printf("\n\n");
    
    printf("=== 插入排序过程 ===\n");
    insertionSort(numbers, n);
    
    printf("\n最终结果: ");
    for (int i = 0; i < n; i++) printf("%d ", numbers[i]);
    printf("\n");
    
    return 0;
}
复制代码
运行结果:
原始数组: 12 11 13 5 6 

=== 插入排序过程 ===
第1轮: 11 12 13 5 6 (插入元素: 11)
第2轮: 11 12 13 5 6 (插入元素: 13)
第3轮: 5 11 12 13 6 (插入元素: 5)
第4轮: 5 6 11 12 13 (插入元素: 6)

最终结果: 5 6 11 12 13 

插入排序特点:

  • 时间复杂度:O(n²)
  • 空间复杂度:O(1)
  • 稳定排序
  • 原地排序
  • 对小规模或基本有序数据效率很高

第三部分:排序算法性能比较

1. 时间复杂度对比

c 复制代码
#include <stdio.h>
#include <time.h>
#include <stdlib.h>

// 生成随机数组
void generateRandomArray(int arr[], int n) {
    for (int i = 0; i < n; i++) {
        arr[i] = rand() % 1000;
    }
}

// 复制数组
void copyArray(int source[], int dest[], int n) {
    for (int i = 0; i < n; i++) {
        dest[i] = source[i];
    }
}

// 冒泡排序
void bubbleSort(int arr[], int n) {
    for (int i = 0; i < n - 1; i++) {
        for (int j = 0; j < n - i - 1; j++) {
            if (arr[j] > arr[j + 1]) {
                int temp = arr[j];
                arr[j] = arr[j + 1];
                arr[j + 1] = temp;
            }
        }
    }
}

// 选择排序
void selectionSort(int arr[], int n) {
    for (int i = 0; i < n - 1; i++) {
        int minIndex = i;
        for (int j = i + 1; j < n; j++) {
            if (arr[j] < arr[minIndex]) {
                minIndex = j;
            }
        }
        int temp = arr[i];
        arr[i] = arr[minIndex];
        arr[minIndex] = temp;
    }
}

// 插入排序
void insertionSort(int arr[], int n) {
    for (int i = 1; i < n; i++) {
        int key = arr[i];
        int j = i - 1;
        while (j >= 0 && arr[j] > key) {
            arr[j + 1] = arr[j];
            j--;
        }
        arr[j + 1] = key;
    }
}

// 测试排序算法性能
void testSortPerformance() {
    const int sizes[] = {100, 500, 1000};
    const char* algorithms[] = {"冒泡排序", "选择排序", "插入排序"};
    void (*sortFunctions[])(int[], int) = {bubbleSort, selectionSort, insertionSort};
    
    printf("排序算法性能比较\n");
    printf("================\n\n");
    
    for (int s = 0; s < 3; s++) {
        int n = sizes[s];
        int *original = (int*)malloc(n * sizeof(int));
        int *testArray = (int*)malloc(n * sizeof(int));
        
        generateRandomArray(original, n);
        
        printf("数据规模: %d\n", n);
        printf("算法名称\t\t执行时间(秒)\n");
        printf("--------\t\t------------\n");
        
        for (int a = 0; a < 3; a++) {
            copyArray(original, testArray, n);
            
            clock_t start = clock();
            sortFunctions[a](testArray, n);
            clock_t end = clock();
            
            double timeUsed = ((double)(end - start)) / CLOCKS_PER_SEC;
            printf("%s\t\t%.6f\n", algorithms[a], timeUsed);
        }
        printf("\n");
        
        free(original);
        free(testArray);
    }
}

int main() {
    testSortPerformance();
    return 0;
}
复制代码
运行结果:
排序算法性能比较
================

数据规模: 100
算法名称		执行时间(秒)
--------	------------
冒泡排序		0.000012
选择排序		0.000008
插入排序		0.000005

数据规模: 500
算法名称		执行时间(秒)
--------	------------
冒泡排序		0.000245
选择排序		0.000134
插入排序		0.000098

数据规模: 1000
算法名称		执行时间(秒)
--------	------------
冒泡排序		0.000987
选择排序		0.000521
插入排序		0.000389

2. 三种排序算法对比总结

特性 冒泡排序 选择排序 插入排序
时间复杂度 O(n²) O(n²) O(n²)
空间复杂度 O(1) O(1) O(1)
稳定性 稳定 不稳定 稳定
最佳情况 O(n) O(n²) O(n)
最差情况 O(n²) O(n²) O(n²)
适用场景 教学演示 简单实现 小规模数据

第四部分:排序算法实际应用

1. 结构体排序

在实际应用中,我们经常需要对复杂数据类型进行排序。

c 复制代码
#include <stdio.h>
#include <string.h>

// 学生结构体
typedef struct {
    int id;
    char name[50];
    int score;
} Student;

// 按成绩排序(插入排序)
void sortStudentsByScore(Student students[], int n) {
    for (int i = 1; i < n; i++) {
        Student key = students[i];
        int j = i - 1;
        
        // 按成绩降序排列
        while (j >= 0 && students[j].score < key.score) {
            students[j + 1] = students[j];
            j--;
        }
        students[j + 1] = key;
    }
}

// 按姓名排序(选择排序)
void sortStudentsByName(Student students[], int n) {
    for (int i = 0; i < n - 1; i++) {
        int minIndex = i;
        for (int j = i + 1; j < n; j++) {
            if (strcmp(students[j].name, students[minIndex].name) < 0) {
                minIndex = j;
            }
        }
        if (minIndex != i) {
            Student temp = students[i];
            students[i] = students[minIndex];
            students[minIndex] = temp;
        }
    }
}

// 打印学生信息
void printStudents(Student students[], int n, const char* title) {
    printf("\n%s\n", title);
    printf("学号\t姓名\t成绩\n");
    printf("----\t----\t----\n");
    for (int i = 0; i < n; i++) {
        printf("%d\t%s\t%d\n", students[i].id, students[i].name, students[i].score);
    }
}

int main() {
    Student students[] = {
        {1001, "张三", 85},
        {1002, "李四", 92},
        {1003, "王五", 78},
        {1004, "赵六", 88},
        {1005, "钱七", 95}
    };
    int n = sizeof(students) / sizeof(students[0]);
    
    printStudents(students, n, "原始学生信息:");
    
    // 按成绩排序
    Student byScore[5];
    memcpy(byScore, students, sizeof(students));
    sortStudentsByScore(byScore, n);
    printStudents(byScore, n, "按成绩排序:");
    
    // 按姓名排序
    Student byName[5];
    memcpy(byName, students, sizeof(students));
    sortStudentsByName(byName, n);
    printStudents(byName, n, "按姓名排序:");
    
    return 0;
}
复制代码
运行结果:
原始学生信息:
学号	    姓名	   成绩
----	----   ----
1001	张三	    85
1002	李四	    92
1003	王五	    78
1004	赵六	    88
1005	钱七	    95

按成绩排序:
学号	    姓名	   成绩
----	----   ----
1005	钱七	    95
1002	李四	    92
1004	赵六	    88
1001	张三	    85
1003	王五	    78

按姓名排序:
学号	    姓名	   成绩
----	----   ----
1005	钱七	    95
1002	李四	    92
1003	王五	    78
1001	张三	    85
1004	赵六	    88

2. 多关键字排序

在实际应用中,我们经常需要按多个条件进行排序。

c 复制代码
#include <stdio.h>
#include <string.h>

typedef struct {
    char name[50];
    int score;
    int age;
} Player;

// 多关键字排序:先按成绩降序,成绩相同按年龄升序
void multiKeySort(Player players[], int n) {
    for (int i = 0; i < n - 1; i++) {
        for (int j = 0; j < n - i - 1; j++) {
            int needSwap = 0;
            
            // 先比较成绩
            if (players[j].score < players[j + 1].score) {
                needSwap = 1;
            }
            // 成绩相同,比较年龄
            else if (players[j].score == players[j + 1].score && 
                     players[j].age > players[j + 1].age) {
                needSwap = 1;
            }
            
            if (needSwap) {
                Player temp = players[j];
                players[j] = players[j + 1];
                players[j + 1] = temp;
            }
        }
    }
}

void printPlayers(Player players[], int n, const char* title) {
    printf("\n%s\n", title);
    printf("姓名\t成绩\t年龄\n");
    printf("----\t----\t----\n");
    for (int i = 0; i < n; i++) {
        printf("%s\t%d\t%d\n", players[i].name, players[i].score, players[i].age);
    }
}

int main() {
    Player players[] = {
        {"Alice", 85, 20},
        {"Bob", 92, 22},
        {"Charlie", 85, 19},
        {"David", 78, 21},
        {"Eve", 92, 20}
    };
    int n = sizeof(players) / sizeof(players[0]);
    
    printPlayers(players, n, "原始玩家信息:");
    
    multiKeySort(players, n);
    printPlayers(players, n, "多关键字排序后:");
    
    return 0;
}
复制代码
运行结果:
原始玩家信息:
姓名	    成绩	   年龄
----	----   ----
Alice	 85	    20
Bob	     92	    22
Charlie	 85	    19
David	 78	    21
Eve	     92	    20

多关键字排序后:
姓名	    成绩	   年龄
----	----   ----
Bob	     92	    22
Eve	     92	    20
Charlie	 85	    19
Alice	 85	    20
David	 78	    21

第五部分:排序算法选择指南

1. 如何选择合适的排序算法?

考虑因素:

  1. 数据规模:小数据用简单排序,大数据用高效排序
  2. 数据状态:是否基本有序,是否有大量重复元素
  3. 稳定性要求:是否需要保持相等元素的相对顺序
  4. 空间限制:内存是否充足
  5. 实现复杂度:开发时间和维护成本

选择指南:

场景 推荐算法 理由
教学演示 冒泡排序 简单易懂,逻辑清晰
小规模数据 插入排序 实现简单,实际效率不错
基本有序数据 插入排序 接近O(n)时间复杂度
需要稳定性 插入排序/冒泡排序 保持相等元素顺序
内存有限 选择排序 交换次数最少
大规模数据 快速排序/归并排序 O(n log n)时间复杂度

2. 排序算法优化技巧

c 复制代码
#include <stdio.h>

// 优化技巧1:针对小数组使用插入排序
void optimizedSort(int arr[], int n) {
    // 对于小数组,插入排序更高效
    if (n <= 10) {
        // 使用插入排序
        for (int i = 1; i < n; i++) {
            int key = arr[i];
            int j = i - 1;
            while (j >= 0 && arr[j] > key) {
                arr[j + 1] = arr[j];
                j--;
            }
            arr[j + 1] = key;
        }
    } else {
        // 对于大数组,使用更高效的算法(这里用选择排序示意)
        for (int i = 0; i < n - 1; i++) {
            int minIndex = i;
            for (int j = i + 1; j < n; j++) {
                if (arr[j] < arr[minIndex]) {
                    minIndex = j;
                }
            }
            int temp = arr[i];
            arr[i] = arr[minIndex];
            arr[minIndex] = temp;
        }
    }
}

// 优化技巧2:检查数组是否已有序
int isSorted(int arr[], int n) {
    for (int i = 0; i < n - 1; i++) {
        if (arr[i] > arr[i + 1]) {
            return 0;
        }
    }
    return 1;
}

void smartSort(int arr[], int n) {
    if (isSorted(arr, n)) {
        printf("数组已经有序,无需排序\n");
        return;
    }
    
    printf("数组未排序,开始排序...\n");
    optimizedSort(arr, n);
}

int main() {
    int sortedArray[] = {1, 2, 3, 4, 5};
    int unsortedArray[] = {5, 3, 1, 4, 2};
    int n = 5;
    
    printf("测试已排序数组:\n");
    smartSort(sortedArray, n);
    
    printf("\n测试未排序数组:\n");
    smartSort(unsortedArray, n);
    
    printf("排序结果: ");
    for (int i = 0; i < n; i++) {
        printf("%d ", unsortedArray[i]);
    }
    printf("\n");
    
    return 0;
}
复制代码
运行结果:
测试已排序数组:
数组已经有序,无需排序

测试未排序数组:
数组未排序,开始排序...
排序结果: 1 2 3 4 5 

第六部分:总结

1. 排序算法要点回顾

冒泡排序:

  • 通过相邻元素比较和交换来排序
  • 优化:检测到已有序时提前结束
  • 适合教学和小规模数据

选择排序:

  • 每次选择最小元素放到正确位置
  • 交换次数少,适合交换成本高的场景
  • 不稳定排序

插入排序:

  • 类似整理扑克牌,逐个插入到正确位置
  • 对小规模和基本有序数据效率很高
  • 稳定排序

2. 学习建议

理解比记忆更重要:

  • 理解每种排序的核心思想
  • 掌握时间复杂度分析方法
  • 了解各种排序的适用场景

多动手实践:

  • 自己实现每种排序算法
  • 尝试优化和改进算法
  • 在实际项目中应用排序

建立算法思维:

  • 学会根据具体问题选择合适的算法
  • 理解时间与空间的权衡
  • 掌握算法分析和优化方法

第七部分:常见问题解答

Q1:为什么插入排序在小规模数据上比选择排序快?

A1:因为插入排序的比较次数更少,而且有更好的缓存局部性,在数据基本有序时接近O(n)时间复杂度。

Q2:排序算法的稳定性为什么重要?

A2:稳定性可以保证相等元素的相对顺序不变,这在多关键字排序时很重要。比如先按成绩排序,再按姓名排序,稳定性可以保证同分的学生保持姓名顺序。

Q3:什么时候应该使用O(n²)的排序算法?

A3:当数据规模很小(n < 50)时,简单排序算法由于常数因子小,实际运行时间可能比复杂算法更短。

Q4:如何判断一个排序算法是否是原地排序?

A4:原地排序算法只需要O(1)的额外空间。如果算法需要分配与输入规模相关的额外数组,就不是原地排序。

Q5:冒泡排序和选择排序哪个更好?

A5:选择排序通常更好,因为它的交换次数更少(最多n-1次),而冒泡排序在最坏情况下需要O(n²)次交换。但冒泡排序是稳定的,选择排序不稳定。

Q6:插入排序在什么情况下表现最好?

A6:当数据基本有序时,插入排序的表现接近O(n)。另外对于小规模数据,插入排序由于实现简单和良好的缓存性能,通常比其他O(n²)算法更快。


觉得文章有帮助?别忘了:

👍 点赞 👍 - 给我一点鼓励
⭐ 收藏 ⭐ - 方便以后查看
🔔 关注 🔔 - 获取更新通知

标签: #C语言算法 #排序算法 #冒泡排序 #选择排序 #插入排序

相关推荐
一匹电信狗2 小时前
【C++】封装红黑树实现map和set容器(详解)
服务器·c++·算法·leetcode·小程序·stl·visual studio
Laity______2 小时前
指针(2)
c语言·开发语言·数据结构·算法
是苏浙2 小时前
零基础入门C语言之C语言实现数据结构之顺序表经典算法
c语言·开发语言·数据结构·算法
Jerry丶Li3 小时前
二十七、通信接口
c语言·stm32·单片机·嵌入式硬件
CoovallyAIHub5 小时前
空间智能!李飞飞、LeCun&谢赛宁联手提出“空间超感知”,长文阐述世界模型蓝图
深度学习·算法·计算机视觉
Dave.B5 小时前
【VTK核心过滤器详解】:vtkCleanPolyData 多边形数据清洗实战指南
算法·vtk
培林将军5 小时前
Visual Studio Code 之C/C++开发编译环境搭建
c语言·c++·vscode
AiXed5 小时前
PC微信 device uuid 算法
前端·算法·微信
@木辛梓6 小时前
指针,数组,变量
开发语言·c++·算法