当前位置: 首页 > news >正文

数据结构四大简单排序算法详解:直接插入排序、选择排序、基数排序和冒泡排序

一、引言

排序算法是计算机科学中最基础也是最重要的内容之一。在实际开发和面试中,四大简单排序算法(直接插入排序、选择排序、基数排序、冒泡排序)是必须掌握的基础知识。本文将全面详细地讲解这四种排序算法的原理、实现和适用场景。

二、直接插入排序

2.1 排序思路

直接插入排序的基本思想是:将待排序的元素插入到已经排好序的有序序列中,从而得到一个新的、记录数增加1的有序序列。

2.2 排序过程

  1. 从第一个元素开始,该元素可以认为已经被排序

  2. 取出下一个元素,在已经排序的元素序列中从后向前扫描

  3. 如果该元素(已排序)大于新元素,将该元素移到下一位置

  4. 重复步骤3,直到找到已排序的元素小于或者等于新元素的位置

  5. 将新元素插入到该位置后

  6. 重复步骤2~5

2.3 复杂度分析

  • 时间复杂度:最好情况:O(n) - 数组已经有序                    最坏情况:O(n²) - 数组逆序                                  平均情况:O(n²)

  • 空间复杂度:O(1) - 原地排序
  • 稳定性:稳定排序

2.4 排序过程图解

初始序列:[5, 2, 4, 6, 1, 3]
第1趟: [2, 5, 4, 6, 1, 3]
第2趟: [2, 4, 5, 6, 1, 3]
第3趟: [2, 4, 5, 6, 1, 3]
第4趟: [1, 2, 4, 5, 6, 3]
第5趟: [1, 2, 3, 4, 5, 6]

2.5 代码实现

#include <stdio.h>
void insertSort(int arr[], int n) {int i, j, key;for (i = 1; i < n; i++) {key = arr[i];j = i - 1;while (j >= 0 && arr[j] > key) {arr[j + 1] = arr[j];j = j - 1;}arr[j + 1] = key;}
}
void printArray(int arr[], int n) {for (int i = 0; i < n; i++) {printf("%d ", arr[i]);}printf("\n");
}
int main() {int arr[] = {5, 2, 4, 6, 1, 3};int n = sizeof(arr) / sizeof(arr[0]);insertSort(arr, n);printArray(arr, n);return 0;
}

三、选择排序

3.1 排序思路

选择排序的基本思想:首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然后再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。

3.2 排序过程

  1. 初始状态:无序区为R[0..n-1],有序区为空

  2. 第i趟排序(i=0,1,2,...,n-2)开始时,当前有序区和无序区分别为R[0..i-1]和R[i..n-1]

  3. 该趟排序从当前无序区中选出关键字最小的记录R[k]

  4. 将R[k]与无序区的第1个记录R[i]交换

  5. 重复n-1趟,排序完成

3.3 复杂度分析

  • 时间复杂度:最好情况:O(n²)                   最坏情况:O(n²)                平均情况:O(n²)

  • 空间复杂度:O(1) - 原地排序

  • 稳定性:不稳定排序

3.4 排序过程图解

初始序列:[64, 25, 12, 22, 11]
第1趟: [11, 25, 12, 22, 64]
第2趟: [11, 12, 25, 22, 64]
第3趟: [11, 12, 22, 25, 64]
第4趟: [11, 12, 22, 25, 64]

3.5 代码实现

#include <stdio.h>
void swap(int *a, int *b) {int temp = *a;*a = *b;*b = temp;
}
void selectSort(int arr[], int n) {int i, j, minIndex;for (i = 0; i < n - 1; i++) {minIndex = i;for (j = i + 1; j < n; j++) {if (arr[j] < arr[minIndex]) {minIndex = j;}}swap(&arr[minIndex], &arr[i]);}
}
void printArray(int arr[], int n) {for (int i = 0; i < n; i++) {printf("%d ", arr[i]);}printf("\n");
}
int main() {int arr[] = {64, 25, 12, 22, 11};int n = sizeof(arr) / sizeof(arr[0]);selectSort(arr, n);printArray(arr, n);return 0;
}

四、基数排序

4.1 排序思路

基数排序是一种非比较型整数排序算法,其原理是将整数按位数切割成不同的数字,然后按每个位数分别比较。

4.2 排序过程

  1. 取得数组中的最大数,并取得位数

  2. 从最低位开始,依次进行一次稳定的排序(如计数排序)

  3. 从最低位排序一直到最高位排序完成以后,数组就变成一个有序序列

4.3 复杂度分析

  • 时间复杂度:O(d*(n+k)) - d为位数,k为基数范围

  • 空间复杂度:O(n+k) - 需要额外的计数数组和输出数组

  • 稳定性:稳定排序

4.4 排序过程图解

初始序列:[170, 45, 75, 90, 2, 802, 24, 66]
按个位排序:[170, 90, 2, 802, 24, 45, 75, 66]
按十位排序:[2, 802, 24, 45, 66, 170, 75, 90]
按百位排序:[2, 24, 45, 66, 75, 90, 170, 802]

4.5 代码实现

#include <stdio.h>
#include <stdlib.h>
int getMax(int arr[], int n) {int max = arr[0];for (int i = 1; i < n; i++) {if (arr[i] > max) {max = arr[i];}}return max;
}
void countSort(int arr[], int n, int exp) {int output[n];int count[10] = {0};for (int i = 0; i < n; i++) {count[(arr[i] / exp) % 10]++;}for (int i = 1; i < 10; i++) {count[i] += count[i - 1];}for (int i = n - 1; i >= 0; i--) {output[count[(arr[i] / exp) % 10] - 1] = arr[i];count[(arr[i] / exp) % 10]--;}for (int i = 0; i < n; i++) {arr[i] = output[i];}
}
void radixSort(int arr[], int n) {int max = getMax(arr, n);for (int exp = 1; max / exp > 0; exp *= 10) {countSort(arr, n, exp);}
}
void printArray(int arr[], int n) {for (int i = 0; i < n; i++) {printf("%d ", arr[i]);}printf("\n");
}
int main() {int arr[] = {170, 45, 75, 90, 2, 802, 24, 66};int n = sizeof(arr) / sizeof(arr[0]);radixSort(arr, n);printArray(arr, n);return 0;
}

五、冒泡排序及其优化

5.1 基本冒泡排序思路

重复地走访过要排序的数列,一次比较两个元素,如果它们的顺序错误就把它们交换过来。

5.2 优化策略

  1. 设置标志位:如果某一趟排序中没有发生交换,说明已经有序

  2. 记录最后交换位置:下一趟排序只需比较到该位置

5.3 复杂度分析

  • 时间复杂度:最好情况:O(n) - 数组已经有序(优化后)             最坏情况:O(n²) - 数组逆序                        平均情况:O(n²)

  • 空间复杂度:O(1) - 原地排序

  • 稳定性:稳定排序

5.4 排序过程图解

初始序列:[5, 3, 8, 6, 4]
第1趟: [3, 5, 6, 4, 8]
第2趟: [3, 5, 4, 6, 8]
第3趟: [3, 4, 5, 6, 8]
第4趟: [3, 4, 5, 6, 8](无交换,提前结束)

5.5 代码实现

#include <stdio.h>
void swap(int *a, int *b) {int temp = *a;*a = *b;*b = temp;
}
void bubbleSort(int arr[], int n) {int i, j;int swapped;for (i = 0; i < n - 1; i++) {swapped = 0;for (j = 0; j < n - i - 1; j++) {if (arr[j] > arr[j + 1]) {swap(&arr[j], &arr[j + 1]);swapped = 1;}}if (swapped == 0) {break;}}
}
void printArray(int arr[], int n) {for (int i = 0; i < n; i++) {printf("%d ", arr[i]);}printf("\n");
}
int main() {int arr[] = {5, 3, 8, 6, 4};int n = sizeof(arr) / sizeof(arr[0]);bubbleSort(arr, n);printArray(arr, n);return 0;
}

六、四种排序算法的比较和选择

6.1 性能对比表

排序算法平均时间复杂度最好情况最坏情况空间复杂度稳定性
直接插入排序O(n²)O(n)O(n²)O(1)稳定
选择排序O(n²)O(n²)O(n²)O(1)不稳定
基数排序O(d*(n+k))O(d*(n+k))O(d*(n+k))O(n+k)稳定
冒泡排序O(n²)O(n)O(n²)O(1)稳定

6.2 适用场景分析

直接插入排序适用场景:

        数据量较小(n < 50)

        数据基本有序

        作为快速排序的补充(小区间优化)

选择排序适用场景:

        数据量较小

        对稳定性没有要求

        交换次数最少的需求

基数排序适用场景:

        数据为整数或字符串

        数据范围不大

        需要稳定排序且数据量较大

冒泡排序适用场景:

        数据量很小

        教学演示用途

        对稳定性有要求的小数据量排序

七、实际应用代码示例

7.1 学生成绩排序(直接插入排序)

#include <stdio.h>
typedef struct {int id;int score;
} Student;
void sortStudents(Student students[], int n) {int i, j;Student key;for (i = 1; i < n; i++) {key = students[i];j = i - 1;while (j >= 0 && students[j].score > key.score) {students[j + 1] = students[j];j = j - 1;}students[j + 1] = key;}
}
void printStudents(Student students[], int n) {for (int i = 0; i < n; i++) {printf("学号:%d 成绩:%d\n", students[i].id, students[i].score);}
}
int main() {Student students[] = {{1, 85}, {2, 92}, {3, 78}, {4, 90}, {5, 82}};int n = sizeof(students) / sizeof(students[0]);sortStudents(students, n);printStudents(students, n);return 0;
}

7.2 手机号排序(基数排序)

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void phoneRadixSort(char phones[][12], int n) {char output[n][12];for (int digit = 10; digit >= 0; digit--) {int count[11] = {0};for (int i = 0; i < n; i++) {int index = (phones[i][digit] == '\0') ? 0 : (phones[i][digit] - '0' + 1);count[index]++;}for (int i = 1; i < 11; i++) {count[i] += count[i - 1];}for (int i = n - 1; i >= 0; i--) {int index = (phones[i][digit] == '\0') ? 0 : (phones[i][digit] - '0' + 1);strcpy(output[count[index] - 1], phones[i]);count[index]--;}for (int i = 0; i < n; i++) {strcpy(phones[i], output[i]);}}
}
void printPhones(char phones[][12], int n) {for (int i = 0; i < n; i++) {printf("%s\n", phones[i]);}
}
int main() {char phones[][12] = {"13812345678", "13987654321", "13711112222", "13655556666", "13599998888"};int n = sizeof(phones) / sizeof(phones[0]);phoneRadixSort(phones, n);printPhones(phones, n);return 0;
}

八、常见面试题

8.1 基础概念题

  1. 直接插入排序和冒泡排序在最好情况下的时间复杂度各是多少?

    直接插入排序最好情况O(n),冒泡排序最好情况O(n)
  2. 选择排序为什么是不稳定的?举例说明

    因为交换可能改变相同元素的相对位置,如[5, 5, 2]排序后第一个5可能到第二个5后面
  3. 基数排序适用于什么类型的数据?

    适用于整数、字符串等可以按位分割的数据类型
  4. 冒泡排序的优化方法有哪些?

    设置交换标志位、记录最后交换位置

8.2 代码实现题

  1. 手写直接插入排序算法

  2. 实现一个稳定的选择排序版本

  3. 用基数排序对字符串数组进行排序

  4. 优化冒泡排序,使其在最好情况下达到O(n)

8.3 综合应用题

  1. 如果数据基本有序,应该选择哪种排序算法?为什么?

    选择直接插入排序,因为其在数据基本有序时接近O(n)时间复杂度
  2. 如果需要排序的数据量很小(n<10),推荐使用哪种算法?

    推荐使用直接插入排序或冒泡排序
  3. 如何选择合适的排序算法?考虑哪些因素?

    数据量大小、数据分布情况、稳定性要求、空间限制等
  4. 解释为什么快速排序在实际应用中比这些简单排序更常用

    快速排序平均时间复杂度O(nlogn),对于大数据量更高效,且缓存友好

九、总结

四大简单排序算法是学习更复杂算法的基础,并且4种简单排序都是以双重for循环为核心,每种算法都有其独特的适用场景:

        直接插入排序:小数据量、基本有序数据

        选择排序:小数据量、交换次数少的场景

        基数排序:整数、字符串排序,稳定且高效

        冒泡排序:教学演示、小数据量稳定排序

掌握这些基础排序算法不仅有助于理解算法设计思想,也为大家学习更高级的排序算法打下坚实基础。在实际应用中,应根据具体需求选择合适的排序算法。

http://www.dtcms.com/a/507848.html

相关文章:

  • 官渡网站建设wordpress单页面制作
  • 企业电子商务网站开发数据库设计昆明seo博客
  • 东道 网站建设erp系统哪家做得好
  • 现代 Web 开发中检测用户离开页面的完整方案(附 Vue 实现)
  • [crackme]029-figugegl.1
  • 网站建站分辨率腾讯企点怎么注册
  • 第四章:L2CAP 的“数据语言”——揭秘蓝牙通信的报文格式
  • 【代码随想录算法训练营——Day43(Day42周日休息)】动态规划——300.最长递增子序列、674.最长连续递增序列、718.最长重复子数组
  • block的样式有哪些?如果copy的话分别会有啥样式
  • 如何做网络投票网站大数据开发工程师
  • 提示词 prompt 快速上手
  • 网站降权查询工具lnmp中安装wordpress
  • 一个空间放两个网站蓟门桥网站建设
  • DPC和DPC-KNN算法
  • git中tag标签远程管理
  • Babylon.js UtilityLayerRenderer 深度解析:创建3D工具与调试层的完整指南
  • 如何制造一个网站网站的图片怎么更换
  • 区块链安全评估:守护数字世界的“安全密码”
  • 多语言网站建设公司教你做企业网站
  • 第19节-非规范化数据类型-Drop-Type
  • docker desktop的容器间通信
  • 宝安做网站的公司企业文化经典句子
  • 学校二级网站建设百度关键词优化怎么做
  • 百度前端面试准备
  • 立创EDA学习(一、新建项目与自定义元件)
  • dify项目智能记账
  • 使用Jmeter进行接口测试:HTTP请求与响应报文结构详解
  • 前端6:CSS3 2D转换,CSS3动画,CSS3 3D转换
  • Python中使用SQLite
  • 简约个人网站欣赏wordpress pdf view