嵌入式大厂面经排序算法常见考点(持续更新中!)
这是一个嵌入式大厂面试题专栏,每天更新高频面试题。专栏将包含题目描述、详细解析、相关知识点扩展以及实际代码示例。内容涵盖操作系统、驱动开发、通信协议等核心领域,并结合实际项目经验进行分析。每道题目都会附带面试官可能的追问方向,帮助大家更好地准备面试!
常用排序算法总结
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编程等)、开发工具(交叉编译、调试工具等)以及实际项目经验分享。专栏将采用理论结合实践的方式,每个知识点都会附带相关的面试真题和答案解析。