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

c回顾 01

1 C 语言本身的可移植性很强

 为什么说 C 语言可移植性强?

  1. 有统一的标准:C 语言有明确的官方标准,比如 ANSI C(1989 年)、C99、C11 等。只要代码严格遵循这些标准,不依赖平台专属特性,就能在不同系统上通用。
  2. 依赖编译器而非系统:C 语言代码需要通过编译器翻译成机器码才能运行。不同平台(Windows、Linux、macOS)都有对应的 C 编译器(如 GCC、Clang、MSVC),只需用目标平台的编译器重新编译代码,就能生成适配该平台的可执行文件,无需修改源代码。
  3. 底层无关性:标准 C 语言不直接操作特定硬件或系统内核,而是通过标准库(如stdio.hstdlib.h)提供通用功能,这些标准库会由编译器根据不同平台进行适配,保证代码的跨平台能力。

容易混淆的 “可移植性差” 场景

我们常说的 “C 语言代码可移植性差”,其实不是语言本身的问题,而是代码编写时的问题:

  • 使用了平台专属库或语法:比如 Windows 下的windows.h、Linux 下的pthread.h,这些库只在特定系统可用,会导致代码无法跨平台。
  • 依赖硬件底层操作:比如直接访问特定内存地址、操作硬件寄存器,这类代码只能在对应的硬件架构(如 x86、ARM)上运行。
  • 没有遵循C 语言标准:比如使用编译器的非标准扩展语法,换个编译器就可能报错。

2  C 语言中所有运算符的优先级(从高到低排序)

优先级运算符含义说明结合性
1() [] -> . ++ --(后缀)括号、数组、指针成员、结构体成员、后缀自增 / 减从左到右
2! ~ ++ --(前缀) +(正号) -(负号) (类型) *(指针) &(取地址) sizeof逻辑非、按位非、前缀自增 / 减、正负号、强制类型转换、指针解引用、取地址、求大小从右到左
3*(乘) /(除) %(取余)乘法、除法、取模从左到右
4+(加) -(减)加法、减法从左到右
5<<(左移) >>(右移)位左移、位右移从左到右
6< <= > >=小于、小于等于、大于、大于等于从左到右
7==(等于) !=(不等于)等于、不等于从左到右
8&(按位与)位与从左到右
9^(按位异或)位异或从左到右
10|(按位或)位或从左到右
11&&(逻辑与)逻辑与从左到右
12||(逻辑或)逻辑或从左到右
13?:(条件运算符)三目条件运算从右到左
14= += -= *= /= %= <<= >>= &= ^= |=赋值及复合赋值从右到左
15,(逗号运算符)逗号分隔表达式从左到右

关键说明:

  1. 优先级越高,越先执行:例如a + b * c中,*优先级高于+,先算乘法。
  2. 结合性决定同优先级运算顺序
    • 从左到右:如a + b + c等价于(a + b) + c
    • 从右到左:如a = b = c等价于a = (b = c)
  3. 避免过度依赖优先级:复杂表达式建议用()明确运算顺序,例如(a + b) * ca + b * c更清晰。

3 几个算法的时间复杂度

设顺序表的长度为n。下列算法中,最坏情况下比较次数小于n的是()
A 寻找最大项
B 堆排序
C 快速排序
D 顺序查找法正确答案:A

实现代码

#include <stdio.h>
#include <stdlib.h>
#include <time.h>// 记录比较次数
int compare_count = 0;// 重置比较计数器
void reset_count() {compare_count = 0;
}// A. 寻找最大项
int find_max(int arr[], int n) {reset_count();if (n <= 0) return -1; // 无效输入int max = arr[0];for (int i = 1; i < n; i++) {compare_count++; // 记录比较次数if (arr[i] > max) {max = arr[i];}}return max;
}// 交换两个元素
void swap(int* a, int* b) {int temp = *a;*a = *b;*b = temp;
}// 堆排序辅助函数 - 维护堆特性
void heapify(int arr[], int n, int i) {int largest = i;int left = 2*i + 1;int right = 2*i + 2;if (left < n) {compare_count++;if (arr[left] > arr[largest])largest = left;}if (right < n) {compare_count++;if (arr[right] > arr[largest])largest = right;}if (largest != i) {swap(&arr[i], &arr[largest]);heapify(arr, n, largest);}
}// B. 堆排序
void heap_sort(int arr[], int n) {reset_count();// 构建堆for (int i = n / 2 - 1; i >= 0; i--)heapify(arr, n, i);// 提取元素for (int i = n-1; i >= 0; i--) {swap(&arr[0], &arr[i]);heapify(arr, i, 0);}
}// C. 快速排序
int partition(int arr[], int low, int high) {int pivot = arr[high];int i = (low - 1);for (int j = low; j <= high-1; j++) {compare_count++;if (arr[j] <= pivot) {i++;swap(&arr[i], &arr[j]);}}swap(&arr[i+1], &arr[high]);return (i+1);
}void quick_sort(int arr[], int low, int high) {if (low < high) {int pi = partition(arr, low, high);quick_sort(arr, low, pi-1);quick_sort(arr, pi+1, high);}
}// D. 顺序查找法
int sequential_search(int arr[], int n, int target) {reset_count();for (int i = 0; i < n; i++) {compare_count++;if (arr[i] == target) {return i; // 找到目标,返回索引}}return -1; // 未找到
}// 打印数组
void print_array(int arr[], int size) {for (int i = 0; i < size; i++)printf("%d ", arr[i]);printf("\n");
}int main() {int n = 10;int arr[n];// 生成随机数组srand(time(0));for (int i = 0; i < n; i++) {arr[i] = rand() % 100;}printf("原始数组: ");print_array(arr, n);// 测试寻找最大项int max = find_max(arr, n);printf("A. 寻找最大项: 最大值=%d, 比较次数=%d\n", max, compare_count);// 测试堆排序int heap_arr[n];for (int i = 0; i < n; i++) heap_arr[i] = arr[i];heap_sort(heap_arr, n);printf("B. 堆排序: 比较次数=%d\n", compare_count);// 测试快速排序int quick_arr[n];for (int i = 0; i < n; i++) quick_arr[i] = arr[i];quick_sort(quick_arr, 0, n-1);printf("C. 快速排序: 比较次数=%d\n", compare_count);// 测试顺序查找(查找一个不存在的元素,最坏情况)sequential_search(arr, n, -1); // -1肯定不存在printf("D. 顺序查找: 比较次数=%d\n", compare_count);return 0;
}

题目分析

题目问的是 "最坏情况下比较次数小于 n" 的算法,其中 n 是顺序表的长度。

各选项分析:

  • A. 寻找最大项:只需要遍历一次数组,比较 n-1 次就能找到最大值,最坏情况也只需 n-1 次比较
  • B. 堆排序:最坏情况比较次数为 O (n log n),远大于 n
  • C. 快速排序:最坏情况(数组已排序)比较次数为 O (n²),远大于 n
  • D. 顺序查找法:最坏情况需要比较 n 次(要找的元素在最后或不存在)

所以正确答案是 A,因为只有寻找最大项的最坏情况比较次数是 n-1,小于 n。

算法解析

  1. 寻找最大项

    • 原理:遍历数组一次,记录遇到的最大值
    • 比较次数:n-1 次(无论数据如何分布)
    • 时间复杂度:O (n)
  2. 堆排序

    • 原理:构建最大堆,然后反复提取最大值并调整堆
    • 比较次数:约 n log n 次
    • 时间复杂度:O (n log n)(最好、最坏和平均情况相同)
  3. 快速排序

    • 原理:选择一个基准元素,将数组分为两部分,递归排序
    • 比较次数:平均情况 O (n log n),最坏情况 O (n²)(当数组已排序时)
    • 时间复杂度:平均 O (n log n),最坏 O (n²)
  4. 顺序查找法

    • 原理:从数组开头逐个查找目标元素
    • 比较次数:平均 n/2 次,最坏 n 次(目标在最后或不存在)
    • 时间复杂度:O (n)

通过运行代码,你会发现只有寻找最大项的比较次数在任何情况下都是 n-1,严格小于 n,这就是为什么正确答案是 A。

4 堆排序

堆排序是一种基于堆数据结构的高效排序算法,平均、最好、最坏时间复杂度均为 O(n log n),且是不稳定排序。

核心原理:先建堆,再排序

堆排序的过程分为两大步骤,核心是利用堆 “父节点值大于 / 小于子节点值” 的特性,反复提取最大值 / 最小值并调整堆结构。

  1. 构建初始堆:将无序数组转化为大根堆(升序排序用)或小根堆(降序排序用)。

    • 大根堆:每个父节点的值 大于等于 其左右子节点的值,堆顶是整个数组的最大值。
    • 小根堆:每个父节点的值 小于等于 其左右子节点的值,堆顶是整个数组的最小值。
  2. 逐步提取堆顶并调整

    • 第一步:将堆顶元素(最大值 / 最小值)与堆的最后一个元素交换,此时数组末尾已确定一个有序元素。
    • 第二步:将堆的规模缩小 1(排除已排序的末尾元素),对新堆顶执行 “堆调整” 操作,重新恢复堆的特性。
    • 第三步:重复前两步,直到堆的规模为 1,排序完成。

关键操作:堆调整(以大根堆为例)

堆调整(Heapify)是维护堆特性的核心步骤,当某个节点的值破坏堆结构时,通过 “向下渗透” 使其归位。

操作步骤

  1. 假设当前节点为 i,其左子节点为 2i+1,右子节点为 2i+2
  2. 找出 i、左子节点、右子节点中值最大的节点,记为 max_index
  3. 若 max_index 不等于 i(说明当前节点不是最大值),则交换 i 和 max_index 的值。
  4. 交换后,max_index 位置的新值可能破坏子堆结构,需递归对 max_index 执行堆调整,直到满足堆特性。

算法示例(升序排序)

以无序数组 [4, 6, 8, 5, 9] 为例,演示堆排序过程:

步骤操作数组状态(堆结构)
1构建初始大根堆[9, 6, 8, 5, 4]
2堆顶 9 与末尾 4 交换,缩小堆规模,调整堆[8, 6, 4, 5, 9](末尾 9 已排序)
3堆顶 8 与末尾 5 交换,缩小堆规模,调整堆[6, 5, 4, 8, 9](末尾 8、9 已排序)
4堆顶 6 与末尾 4 交换,缩小堆规模,调整堆[5, 4, 6, 8, 9](末尾 6、8、9 已排序)
5堆顶 5 与末尾 4 交换,堆规模为 1,排序完成[4, 5, 6, 8, 9](全部有序)

优缺点分析

优点缺点
时间复杂度稳定为 O (n log n),效率高不稳定排序(相等元素的相对位置可能变化)
空间复杂度低,仅需 O (1) 辅助空间(原地排序)对小规模数据,性能不如插入排序等简单算法
适合处理海量数据,无需额外内存存储实现逻辑较复杂,需理解堆结构和调整过程

5 快速排序

快速排序是一种高效的排序算法,采用分治法策略,平均时间复杂度为 O(n log n),最坏情况下为 O(n²)(可通过合理选择基准值优化),是不稳定排序。

核心原理:分而治之

快速排序的核心思想是通过分区操作将数组分为两部分,再递归处理子数组:

  1. 选择基准值(pivot):从数组中选择一个元素作为基准(通常选第一个、最后一个或中间元素)。
  2. 分区(partition):将数组重新排列,所有比基准值小的元素放基准左侧,比基准值大的放右侧(相等元素可放任意一侧)。
  3. 递归排序:对基准值左右两个子数组重复上述过程,直到子数组长度为 0 或 1(天然有序)。

关键操作:分区(以最后一个元素为基准)

分区是快速排序的核心步骤,目标是确定基准值的最终位置:

操作步骤

  1. 选数组最后一个元素为基准值 pivot
  2. 初始化一个指针 i(指向小于基准区域的边界,初始为 -1)。
  3. 遍历数组(0 到 n-2),对每个元素 arr[j]
    • 若 arr[j] < pivot,则 i++,交换 arr[i] 和 arr[j](扩展小于基准的区域)。
  4. 遍历结束后,交换 arr[i+1] 和 arr[n-1],将基准值放到最终位置 i+1
  5. 返回基准值位置,用于分割子数组。

算法示例

以数组 [8, 1, 5, 3, 9, 4, 7, 6, 2] 为例:

  1. 选最后一个元素 2 为基准,分区后数组变为 [1, 2, 5, 3, 9, 4, 7, 6, 8],基准 2 已就位。
  2. 递归处理左子数组 [1](已有序)和右子数组 [5, 3, 9, 4, 7, 6, 8]
  3. 对右子数组选 8 为基准,分区后为 [5, 3, 6, 4, 7, 8, 9],基准 8 就位。
  4. 继续递归处理剩余子数组,直至全部有序。

最终排序结果:[1, 2, 3, 4, 5, 6, 7, 8, 9]


优缺点分析

优点缺点
平均性能优秀,实际应用中通常比同为 O (n log n) 的归并排序快最坏情况时间复杂度为 O (n²)(如对已排序数组选择两端元素为基准)
原地排序,空间复杂度为 O (log n)(递归栈开销)不稳定排序(相等元素相对位置可能改变)
缓存友好,局部性好对小规模数据,性能不如插入排序

快速排序的c实现

#include <stdio.h>// 交换两个元素的值
void swap(int* a, int* b) {int temp = *a;*a = *b;*b = temp;
}/** 分区操作* 参数:*   arr[] - 待分区的数组*   low   - 分区的起始索引*   high  - 分区的结束索引(基准值初始位置)* 返回值:*   基准值的最终位置索引*/
int partition(int arr[], int low, int high) {// 选择最右侧元素作为基准值int pivot = arr[high];// i指向小于基准值区域的最后一个元素int i = (low - 1);// 遍历数组,将小于基准值的元素移到左侧for (int j = low; j <= high - 1; j++) {if (arr[j] <= pivot) {i++;  // 扩展小于基准值的区域swap(&arr[i], &arr[j]);}}// 将基准值放到最终位置(i+1)swap(&arr[i + 1], &arr[high]);return (i + 1);
}/** 快速排序主函数* 参数:*   arr[] - 待排序的数组*   low   - 当前排序区间的起始索引*   high  - 当前排序区间的结束索引*/
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);}
}// 打印数组元素
void printArray(int arr[], int size) {for (int i = 0; i < size; i++) {printf("%d ", arr[i]);}printf("\n");
}// 示例用法
int main() {int arr[] = {8, 1, 5, 3, 9, 4, 7, 6, 2};int n = sizeof(arr) / sizeof(arr[0]);printf("排序前: ");printArray(arr, n);quickSort(arr, 0, n - 1);printf("排序后: ");printArray(arr, n);return 0;
}

优化建议

  1. 基准值选择:可采用 “三数取中” 法(首、尾、中间元素的中值)避免最坏情况。
  2. 小规模数组优化:当子数组长度小于一定阈值(如 10)时,改用插入排序。
  3. 尾递归优化:对较大的子数组采用循环处理,减少递归栈深度。
http://www.dtcms.com/a/398012.html

相关文章:

  • 【LeetCode 每日一题】3484. 设计电子表格——(解法一)二维数组
  • python+django/flask+springboot实践性教学系统 实训任务发布 学生作业提交 教师评阅管理系统
  • 洞悉未来,智驭不确定性:蒙特卡洛模拟决策模型实践
  • 长宁哪里有做网站优化比较好利润在100万到300万之间税率2021
  • 沈阳网站设计外包广西建设网官网桂建云
  • vscode 插件怎么实现编辑器行号处添加图标标记
  • Git 从零到一:以 Gitee 为例的实战与可视化指南
  • React 标准 SPA 项目 入门学习记录
  • HAProxy 完整指南:简介、负载均衡原理与安装配置
  • 领码课堂 | React 核心组件与高级性能优化全实战指南
  • 涡轮丝杆升降机的丝杆材质有哪些?
  • 前端笔记:vue中 Map、Set之间的使用和区别
  • 中美关系最新消息视频重庆seo优化公司
  • 【Cesium 开发实战教程】第六篇:三维模型高级交互:点击查询、材质修改与动画控制
  • 英雄联盟视频网站源码做产品设计之前怎么查资料国外网站
  • Vue3-接入飞书H5应用
  • 四川省建设厅网站川北医学院广告网站怎么建设
  • 七彩喜智慧养老:科技向善,让晚年生活绽放“喜”悦之光
  • 模型驱动的 AI Agent架构:亚马逊云科技的Strands框架技术深度解析
  • 【数据结构】——外部排序(K路归并)
  • 【观成科技】活跃黑产团伙“黑猫”攻击武器加密通信分析
  • 高斯过程(Gaussian Process)回归:一种贝叶斯非参数方法
  • 微算法科技(NASDAQ MLGO)创新基于账户加权图与后量子密码学的区块链
  • 中国银行信息科技岗位笔试
  • WXML 编译错误修复总结
  • 怎么给网站wordpress游戏网站策划书
  • Halcon学习--(3)图像阈值处理
  • 知识导航新体验:Perplexica+cpolar 24小时智能服务
  • 全面解析Redis分布式锁
  • 自由学习记录(103)