嵌入式大厂面经排序算法常见考点(持续更新中!)
这是一个嵌入式大厂面试题专栏,每天更新高频面试题。专栏将包含题目描述、详细解析、相关知识点扩展以及实际代码示例。内容涵盖操作系统、驱动开发、通信协议等核心领域,并结合实际项目经验进行分析。每道题目都会附带面试官可能的追问方向,帮助大家更好地准备面试!
常用排序算法总结
1. 冒泡排序 (Bubble Sort)
基本思想
通过相邻元素的比较和交换,使较大的元素逐渐"浮"向数组末尾。
时间复杂度
- 最好情况:O(n),当数组已经有序时
- 最坏情况:O(n²)
- 平均情况:O(n²)
代码实现
void bubbleSort(int arr[], int n) {
int i, j;
bool swapped;
for (i = 0; i < n - 1; i++) {
swapped = false;
// 每次循环将最大的元素冒泡到末尾
for (j = 0; j < n - i - 1; j++) {
if (arr[j] > arr[j + 1]) {
// 交换arr[j]和arr[j+1]
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
swapped = true;
}
}
// 如果内层循环没有发生交换,说明数组已经有序
if (swapped == false)
break;
}
}
2. 选择排序 (Selection Sort)
基本思想
每次从未排序部分找出最小元素,放到已排序部分的末尾。
时间复杂度
- 最好情况:O(n²)
- 最坏情况:O(n²)
- 平均情况:O(n²)
代码实现
void selectionSort(int arr[], int n) {
int i, j, min_idx;
// 一个个确定位置
for (i = 0; i < n - 1; i++) {
// 找出未排序部分中的最小值
min_idx = i;
for (j = i + 1; j < n; j++) {
if (arr[j] < arr[min_idx]) {
min_idx = j;
}
}
// 将找到的最小值放到已排序序列的末尾
if (min_idx != i) {
int temp = arr[i];
arr[i] = arr[min_idx];
arr[min_idx] = temp;
}
}
}
3. 插入排序 (Insertion Sort)
基本思想
将未排序部分的元素逐个插入到已排序部分的适当位置。
时间复杂度
- 最好情况:O(n),当数组已经有序时
- 最坏情况:O(n²)
- 平均情况:O(n²)
代码实现
void insertionSort(int arr[], int n) {
int i, key, j;
for (i = 1; i < n; i++) {
key = arr[i]; // 当前要插入的元素
j = i - 1;
// 将比key大的元素都向后移动一位
while (j >= 0 && arr[j] > key) {
arr[j + 1] = arr[j];
j = j - 1;
}
arr[j + 1] = key; // 插入key
}
}
4. 快速排序 (Quick Sort)
基本思想
选择一个基准元素,将数组分为两部分,小于基准的放左边,大于基准的放右边,然后递归地对两部分进行排序。
时间复杂度
- 最好情况:O(n log n)
- 最坏情况:O(n²),当数组已经有序或逆序时
- 平均情况:O(n log n)
代码实现
// 分区函数,返回基准元素的最终位置
int partition(int arr[], int low, int high) {
int pivot = arr[high]; // 选择最后一个元素作为基准
int i = (low - 1); // 小于基准元素的区域边界
for (int j = low; j <= high - 1; j++) {
// 如果当前元素小于基准
if (arr[j] < pivot) {
i++; // 扩展小于基准的区域
// 交换arr[i]和arr[j]
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
// 将基准元素放到正确的位置
int temp = arr[i + 1];
arr[i + 1] = arr[high];
arr[high] = temp;
return (i + 1); // 返回基准元素的位置
}
// 快速排序主函数
void quickSort(int arr[], int low, int high) {
if (low < high) {
// 获取基准元素的位置
int pi = partition(arr, low, high);
// 递归排序基准元素左边的子数组
quickSort(arr, low, pi - 1);
// 递归排序基准元素右边的子数组
quickSort(arr, pi + 1, high);
}
}
5. 归并排序 (Merge Sort)
基本思想
将数组分成两半,递归地对两半进行排序,然后将排序好的两半合并成一个有序数组。
时间复杂度
- 最好情况:O(n log n)
- 最坏情况:O(n log n)
- 平均情况:O(n log n)
代码实现
// 合并两个有序子数组
void merge(int arr[], int l, int m, int r) {
int i, j, k;
int n1 = m - l + 1;
int n2 = r - m;
// 创建临时数组
int L[n1], R[n2];
// 复制数据到临时数组
for (i = 0; i < n1; i++)
L[i] = arr[l + i];
for (j = 0; j < n2; j++)
R[j] = arr[m + 1 + j];
// 合并临时数组回到arr[l..r]
i = 0;
j = 0;
k = l;
while (i < n1 && j < n2) {
if (L[i] <= R[j]) {
arr[k] = L[i];
i++;
} else {
arr[k] = R[j];
j++;
}
k++;
}
// 复制L[]的剩余元素
while (i < n1) {
arr[k] = L[i];
i++;
k++;
}
// 复制R[]的剩余元素
while (j < n2) {
arr[k] = R[j];
j++;
k++;
}
}
// 归并排序主函数
void mergeSort(int arr[], int l, int r) {
if (l < r) {
// 找到中点
int m = l + (r - l) / 2;
// 对左右两半分别排序
mergeSort(arr, l, m);
mergeSort(arr, m + 1, r);
// 合并排序好的两半
merge(arr, l, m, r);
}
}
6. 堆排序 (Heap Sort)
基本思想
将数组构建成一个最大堆,然后将堆顶元素(最大值)与末尾元素交换,调整堆结构,重复此过程。
时间复杂度
- 最好情况:O(n log n)
- 最坏情况:O(n log n)
- 平均情况:O(n log n)
代码实现
// 调整堆,使其满足堆的性质
void heapify(int arr[], int n, int i) {
int largest = i; // 初始化最大元素为根
int left = 2 * i + 1; // 左子节点
int right = 2 * i + 2; // 右子节点
// 如果左子节点大于根
if (left < n && arr[left] > arr[largest])
largest = left;
// 如果右子节点大于目前的最大值
if (right < n && arr[right] > arr[largest])
largest = right;
// 如果最大值不是根
if (largest != i) {
// 交换根与最大值
int temp = arr[i];
arr[i] = arr[largest];
arr[largest] = temp;
// 递归地调整受影响的子树
heapify(arr, n, largest);
}
}
// 堆排序主函数
void heapSort(int arr[], int n) {
// 构建大顶堆
for (int i = n / 2 - 1; i >= 0; i--)
heapify(arr, n, i);
// 一个个从堆顶取出元素
for (int i = n - 1; i > 0; i--) {
// 将当前堆顶(最大值)移到末尾
int temp = arr[0];
arr[0] = arr[i];
arr[i] = temp;
// 对剩余的堆进行调整
heapify(arr, i, 0);
}
}
7. 希尔排序 (Shell Sort)
基本思想
将整个数组按照一定间隔分组,对每组使用插入排序;随着间隔逐渐减小,数组变得更加有序,最后用间隔为1的插入排序完成排序。
时间复杂度
- 最好情况:O(n log n)
- 最坏情况:O(n²),取决于间隔序列
- 平均情况:O(n log n)
代码实现
void shellSort(int arr[], int n) {
// 开始时的间隔为n/2,每次减半
for (int gap = n / 2; gap > 0; gap /= 2) {
// 对每个子序列进行插入排序
for (int i = gap; i < n; i++) {
int temp = arr[i];
int j;
// 对子序列进行插入排序
for (j = i; j >= gap && arr[j - gap] > temp; j -= gap) {
arr[j] = arr[j - gap];
}
arr[j] = temp;
}
}
}
8. 计数排序 (Counting Sort)
基本思想
统计数组中每个值出现的次数,然后按顺序输出结果。适用于已知范围的整数排序。
时间复杂度
- 最好情况:O(n+k),k是数据范围
- 最坏情况:O(n+k)
- 平均情况:O(n+k)
代码实现
void countingSort(int arr[], int n) {
// 找出数组中的最大值
int max = arr[0];
for (int i = 1; i < n; i++) {
if (arr[i] > max) {
max = arr[i];
}
}
// 创建计数数组
int* count = (int*)malloc((max + 1) * sizeof(int));
if (count == NULL) {
printf("内存分配失败\n");
return;
}
// 初始化计数数组
for (int i = 0; i <= max; i++) {
count[i] = 0;
}
// 统计每个元素出现的次数
for (int i = 0; i < n; i++) {
count[arr[i]]++;
}
// 重建排序后的数组
int j = 0;
for (int i = 0; i <= max; i++) {
while (count[i] > 0) {
arr[j++] = i;
count[i]--;
}
}
free(count);
}
排序算法比较
冒泡排序 | O(n²) | O(n) | O(n²) | O(1) | 稳定 |
选择排序 | O(n²) | O(n²) | O(n²) | O(1) | 不稳定 |
插入排序 | O(n²) | O(n) | O(n²) | O(1) | 稳定 |
快速排序 | O(n log n) | O(n log n) | O(n²) | O(log n) | 不稳定 |
归并排序 | O(n log n) | O(n log n) | O(n log n) | O(n) | 稳定 |
堆排序 | O(n log n) | O(n log n) | O(n log n) | O(1) | 不稳定 |
希尔排序 | O(n log n) | O(n log n) | O(n²) | O(1) | 不稳定 |
计数排序 | O(n+k) | O(n+k) | O(n+k) | O(k) | 稳定 |
算法选择指南
- 数据量小:插入排序、冒泡排序
- 数据量大:快速排序、归并排序、堆排序
- 数据几乎有序:插入排序
- 对稳定性有要求:归并排序、插入排序、冒泡排序、计数排序
- 数据范围有限:计数排序
- 内存有限:堆排序(原地排序)
这是一个全面的嵌入式面试专栏。主要内容将包括:操作系统(进程管理、内存管理、文件系统等)、嵌入式系统(启动流程、驱动开发、中断管理等)、网络通信(TCP/IP协议栈、Socket编程等)、开发工具(交叉编译、调试工具等)以及实际项目经验分享。专栏将采用理论结合实践的方式,每个知识点都会附带相关的面试真题和答案解析。
查看17道真题和解析