嵌入式大厂面经排序算法常见考点(持续更新中!)

这是一个嵌入式大厂面试题专栏,每天更新高频面试题。专栏将包含题目描述、详细解析、相关知识点扩展以及实际代码示例。内容涵盖操作系统、驱动开发、通信协议等核心领域,并结合实际项目经验进行分析。每道题目都会附带面试官可能的追问方向,帮助大家更好地准备面试!

常用排序算法总结

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)

稳定

算法选择指南

  1. 数据量小:插入排序、冒泡排序
  2. 数据量大:快速排序、归并排序、堆排序
  3. 数据几乎有序:插入排序
  4. 对稳定性有要求:归并排序、插入排序、冒泡排序、计数排序
  5. 数据范围有限:计数排序
  6. 内存有限:堆排序(原地排序)
嵌入式面试八股文全集 文章被收录于专栏

这是一个全面的嵌入式面试专栏。主要内容将包括:操作系统(进程管理、内存管理、文件系统等)、嵌入式系统(启动流程、驱动开发、中断管理等)、网络通信(TCP/IP协议栈、Socket编程等)、开发工具(交叉编译、调试工具等)以及实际项目经验分享。专栏将采用理论结合实践的方式,每个知识点都会附带相关的面试真题和答案解析。

全部评论
😱😱😱😱😱😱😱😱
点赞 回复 分享
发布于 昨天 10:14 上海

相关推荐

评论
3
3
分享

创作者周榜

更多
牛客网
牛客企业服务