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

C程序题案例分析

大纲要求

  1. 掌握C程序设计语言
  2. 掌握常用数据结构及其操作
  3. 掌握排序算法、查找算法
  4. 灵活应用算法设计策略

目录

一、算法设计的基础知识

1. 指针

2. 常用数据结构

1)线性表

2)队列

3)栈

4)堆

5)数组

6)树

7)Huffman树

8)图

二、常用算法设计技术

1)迭代法

2)穷举搜索法

3)递推法

4)递归法

5)回溯法

6)贪心法

7)分治法

8)动态规划法

三、例题 


一、算法设计的基础知识

1. 指针

  1. 本质与类型

    • 本质:存储内存地址的变量
    • 类型意义
      • 决定指针运算的步长(如int*步长为4字节)
      • 指定操作的内存单元大小(如char*操作1字节)
  2. 数组名与指针的关系

    • 数组名是常指针(首地址不可修改)
    • 示例:
      int arr[3] = {1, 2, 3};
      int *p = arr;  // arr等价于&arr[0]
      
  3. 指针作为函数参数

    • 值传递原则
      • 修改指针指向的内容:直接传递指针(如void func(int *p)
      • 修改指针本身:传递二级指针(如void alloc(char **p)
#include <stdio.h>   
int main() {int array3[6] = {100,101,102,103,110,111};printf("%x\n", array3); // 输出数组首地址(十六进制)int *p = array3;        // 数组名作为指针while (p <= &array3[5]) { printf("%8d", *(p++)); // 指针遍历数组}return 0;   
}
  • 数组名是指向首元素的常指针。
  • 指针自增操作 *(p++) 按类型大小移动(如 int 移动4字节)。

2. 常用数据结构

1)线性表

概念

  • 由相同数据类型的节点组成的有限序列,存在唯一首节点(无前驱)和尾节点(无后继)。
  • 基本操作:初始化(Initiate)、插入(Insert)、删除(Delete)、查找(Locate)等。

存储结构对比

类型优点缺点
顺序存储随机访问快(O(1))插入/删除需移动元素
链式存储动态扩展灵活访问需遍历(O(n))

链式变种

  • 单链表:节点含数据域 + 后继指针。
  • 循环链表:尾节点指向头节点,形成环。
  • 双向链表:节点含前驱 + 后继指针,支持双向遍历。
typedef struct Node {int data;struct Node *next;   
} Node;// 插入操作示例(未完整,需补充)   
void Insert(Node **head, int value) {Node *newNode = (Node*)malloc(sizeof(Node));newNode->data = value;newNode->next = *head;*head = newNode;
}
  •  链式存储:单链表
  • 链表的节点通过 malloc 动态分配内存。
  • 插入操作需处理指针指向的修改。

2)队列

特性

  • 先进先出(FIFO),队尾插入,队首删除。

实现方式

  • 顺序队列:数组实现,需处理假溢出(循环队列)。
  • 链队列:链表实现,无空间限制。
  • 优先级队列:按优先级出队,常用堆实现。
// 链队列节点结构   
typedef struct QNode {int data;struct QNode *next;   
} QNode;// 入队操作(需维护队尾指针rear)   
void EnQueue(QNode **rear, int value) {QNode *newNode = (QNode*)malloc(sizeof(QNode));newNode->data = value;newNode->next = NULL;(*rear)->next = newNode;*rear = newNode;   
}   

3)栈

特性

  • 后进先出(LIFO),仅在栈顶(表尾)插入/删除。

实现方式

  • 顺序栈:数组实现,需预设大小。
  • 链栈:链表实现,无容量限制。

应用场景

  • 函数调用栈、表达式求值、括号匹配。
// 链栈节点结构(文档中用于二叉树非递归遍历)   
typedef struct SNode {BiTree elem;  // 存储二叉树节点指针struct SNode *next;   
} SNode;// 压栈操作   
void Push(SNode **top, BiTree node) {SNode *newNode = (SNode*)malloc(sizeof(SNode));newNode->elem = node;newNode->next = *top;*top = newNode;    
}    

4)堆

定义

  • 完全二叉树,满足堆性质:
    • 大顶堆:父节点 ≥ 子节点
    • 小顶堆:父节点 ≤ 子节点

典型应用

  • 堆排序(时间复杂度 O(nlogn))
  • 优先级队列
// 调整堆的代码未完整给出 
void HeapAdjust(int a[], int s, int m) {/* 将序列a[s..m]调整为一个大顶堆 */// 具体实现 
}// 初始化堆的步骤:   
for (int i = n/2; i > 0; i--) HeapAdjust(a, i, n);   

5)数组

类型

  • 静态数组:编译时分配固定大小(如 int a[10])。
  • 动态数组:运行时分配(如 malloc)。

特殊矩阵存储

  • 对称矩阵:仅存储上/下三角。
  • 稀疏矩阵:三元组(行、列、值)压缩存储。
// 动态数组分配(文档中指针部分示例)   
void GetMemory(char **p) {*p = (char*)malloc(100 * sizeof(char));   
}int main() {char *arr = NULL;GetMemory(&arr);  // 动态分配数组空间strcpy(arr, "Dynamic Array");free(arr);    
}    

6)树

二叉树性质

  • 第 i 层最多有 2^(i-1) 个节点。
  • 叶子节点数 n0 = n2 + 1n2 为度为2的节点数)。

遍历方式

  • 递归实现:代码简洁,可能栈溢出。
  • 非递归实现:借助栈(中序)或队列(层序)。

特殊二叉树

类型特点
满二叉树每一层节点数达到最大值。
完全二叉树除最后一层外节点数满,最后一层左对齐。
AVL树左右子树高度差 ≤ 1,平衡二叉查找树。
二叉查找树左子树值 < 根 < 右子树值。
// 二叉树节点结构    
typedef struct BiTNode {int data;struct BiTNode *lchild, *rchild;    
} BiTNode, *BiTree;// 中序遍历递归实现    
void InOrder(BiTree root) {if (root) {InOrder(root->lchild);printf("%d ", root->data);InOrder(root->rchild);}
}

7)Huffman树

定义

  • 带权路径长度最短的二叉树(最优二叉树)。

构造算法

  1. 选权值最小的两个节点合并为新树。
  2. 新树根节点权值为子节点权值和。
  3. 重复直到只剩一棵树。

应用

  • Huffman编码:高频字符用短编码,低频用长编码。
// 描述构造步骤(伪代码):
while (节点数 > 1) {1. 选择权值最小的两个节点;2. 合并为新节点(权值为两者之和);3. 将新节点加入集合;    
}    

8)图

表示方法

类型适用场景空间复杂度
邻接矩阵稠密图O(V²)
邻接表稀疏图O(V+E)

遍历算法

  • DFS(深度优先):递归/栈实现。(6.1)搜索-CSDN博客
  • BFS(广度优先):队列实现。


二、常用算法设计技术

1)迭代法

  • 核心思想:通过重复逼近求解数值近似解。
  • 步骤
    1. 确定迭代公式(如 x=g(x)​)。
    2. 选择初始近似值 x0​​。
    3. 循环计算 xn+1​=g(xn​)​。
    4. 终止条件:∣xn+1​−xn​∣<ϵ​(误差阈值)。
  • 适用场景:方程求根(如 x=cos(x)​)。
  • 注意事项
    • 需验证迭代公式的收敛性。
    • 可能陷入无限循环(需设置最大迭代次数)。

// 求方程x = cos(x)的近似解(示例)   
#include <math.h>   
void iteration() {float x0 = 0.5, x1 = cos(x0);  // 初始近似值while (fabs(x1 - x0) > 1e-6) { // 误差控制x0 = x1;x1 = cos(x0);              // 迭代公式}printf("近似解: %f\n", x1);   
}

2)穷举搜索法

  • 核心思想:遍历所有可能的候选解,检查是否满足条件。
  • 适用场景
    • 解空间有限且无更优算法时(如百钱买百鸡问题)。
    • 密码破解、组合优化问题。
  • 特点
    • 保证找到解,但效率低(时间复杂度可能为指数级)。
    • 需合理剪枝以减少计算量。

  • 适用场景:解空间有限且无更优算法时
  • 特点
    • 按顺序检查所有候选解
    • 保证找到解但效率低(如百钱买百鸡问题)
// 百钱买百鸡问题(补充经典示例)   
void exhaustive() {for (int x = 0; x <= 20; x++)        // 公鸡数量for (int y = 0; y <= 33; y++) {  // 母鸡数量int z = 100 - x - y;         // 小鸡数量if (5*x + 3*y + z/3 == 100 && z%3 == 0)printf("公鸡%d, 母鸡%d, 小鸡%d\n", x, y, z);}
}

3)递推法

  • 核心思想:从已知小规模解推导大规模解(自底向上)。
  • 典型应用
    • Fibonacci数列:F(n)=F(n−1)+F(n−2)​。
    • 组合数学问题(如杨辉三角)。
  • 与递归的区别
    • 递推显式使用循环,无函数调用开销。
    • 递归通过函数自调用实现(可能栈溢出)。
// 递推实现   
int fibonacci(int n) {int f0 = 0, f1 = 1, f;for (int i = 2; i <= n; i++) {f = f0 + f1;  // 递推公式f0 = f1;      // 状态传递f1 = f;}return f;
}

4)递归法

  • 核心思想:将问题分解为同类子问题,直到达到最小规模(递归出口)。
  • 关键要素
    • 递归出口:最小问题的直接解(如 n=1​)。
    • 分解策略:如分治法(Hanoi塔)、回溯法(n皇后)。
  • 典型应用
    • Fibonacci数列背包问题树的遍历
  • 缺点
    • 重复计算(可通过记忆化优化)。
    • 栈空间消耗大(深度过大时溢出)。
// 背包问题递归框架   
void try(int i, int tw, int tv) {if (包含物品i是可接受的) {将物品i加入当前方案;if (i < n-1) try(i+1, tw + w[i], tv);else if (当前方案更优) 保存方案;回溯:移除物品i;}if (不包含物品i仍可能更优) {if (i < n-1) try(i+1, tw, tv - v[i]);else if (当前方案更优) 保存方案;}    
}    

5)回溯法

  • 核心思想:试探性搜索解空间,失败时回溯(DFS + 剪枝)。
  • 步骤
    1. 选择候选解并标记。
    2. 递归进入下一层。
    3. 若失败则撤销选择(回溯)。
  • 典型应用
    • n皇后问题:逐行放置皇后,检查冲突后回溯。
    • 数独求解全排列生成
  • 优化:剪枝(提前终止无效分支)。
// n皇后问题框架    
int is_valid(int *queens, int row, int col) {for (int i = 0; i < row; i++)if (queens[i] == col || abs(queens[i]-col) == abs(i-row))return 0;return 1;    
}void backtrack(int *queens, int row, int n) {if (row == n) { 打印解; return; }for (int col = 0; col < n; col++) {if (is_valid(queens, row, col)) {queens[row] = col;backtrack(queens, row+1, n); // 试探下一行}}        
}        

6)贪心法

(5.1)基本算法-CSDN博客

  • 核心思想:每一步选择局部最优解,不回溯。
  • 适用条件
    • 问题具有贪心选择性质(局部最优导致全局最优)。
    • Dijkstra算法(最短路径)、Huffman编码
  • 典型应用
    • 装箱问题:优先装体积大的物品。
    • 活动选择问题:选择最早结束的活动。
  • 局限性:不一定得到全局最优解(如部分背包问题)。
// 装箱问题伪代码        
void greedy_packing() {sort(items, items+n, descending);  // 按体积降序排序for (int i = 0; i < n; i++) {int j = find_first_available_box(); // 找到第一个能放的箱子if (j == -1) 启用新箱子;else 放入箱子j;}        
}        

7)分治法

  • 核心思想:将问题分解为独立子问题,合并子问题的解(自顶向下)。
  • 步骤
    1. 分解:将问题划分为多个子问题。
    2. 解决:递归求解子问题。
    3. 合并:将子问题的解组合为最终解。
  • 典型应用
    • 归并排序快速排序
    • Hanoi塔问题:移动n个盘子转化为移动n-1个盘子。
  • 效率分析:通常用Master定理计算时间复杂度(如归并排序为O(n \log n)​)。
// Hanoi塔完整实现        
void hanoi(int n, char A, char B, char C) {if (n == 1) printf("Move disk 1 from %c to %c\n", A, C);else {hanoi(n-1, A, C, B);    // 分解为子问题printf("Move disk %d from %c to %c\n", n, A, C);hanoi(n-1, B, A, C);    // 合并子问题解}        
}        

8)动态规划法

  • 核心思想:存储子问题的解以避免重复计算(自底向上)。
  • 适用条件
    • 重叠子问题:子问题被多次重复计算。
    • 最优子结构:全局最优解包含局部最优解。
  • 典型应用
    • Fibonacci数列(记忆化)、背包问题
    • Floyd算法(多源最短路径)、编辑距离
  • 实现方式
    • 表格法(如填二维数组)。
    • 状态转移方程(如 dp[i]=dp[i−1]+dp[i−2]​)。
// 斐波那契数列的DP实现(对比递推法)
int dp_fib(int n) {int dp[n+1];dp[0] = 0; dp[1] = 1;for (int i = 2; i <= n; i++)dp[i] = dp[i-1] + dp[i-2];  // 复用子问题解return dp[n];
}
算法核心思想适用场景是否回溯时间复杂度
迭代法逐步逼近解数值计算(如方程求根)依赖收敛速度
穷举法遍历所有可能解小规模解空间指数级(O(2n)​)
递推法从已知解推导线性问题(如Fibonacci)​O(n)​
递归法分治 + 递归出口树形结构、分治问题可能指数级
回溯法试探 + 回溯 + 剪枝组合优化(如n皇后)指数级(剪枝优化)
贪心法局部最优选择最短路径、Huffman编码​O(nlogn)​
分治法分解→解决→合并排序、Hanoi塔​O(nlogn)​
动态规划存储子问题解最优化问题(如背包)多项式级(如O(n^2)​)


三、例题 

  1. 题目背景:设有m台完全相同的机器运行n个独立的任务,运行任务i所需时间为ti,需确定一个调度方案,使完成所有任务所需时间最短。
  2. 算法策略:假设任务已按运行时间从大到小排序,基于最长运行时间作业优先的策略,先把每个任务分配到一台机器上,然后将剩余任务依次放入最先空闲的机器。
  3. C代码相关说明
    • 常量和变量说明
      • m:机器数;
      • n:任务数;
      • t[]:输入数组,长度为n,每个元素表示任务的运行时间;
      • s[][]:二维数组,长度为m×n,元素s[i][j]表示机器i运行的任务j的编号;
      • d[]:数组,长度为m,元素d[i]表示机器i的运行时间;
      • count[]:数组,长度为m,元素count[i]表示机器i运行的任务数;
      • i、j、k:循环变量或临时变量;
      • max:完成所有任务的时间;
      • min:临时变量。
    • 函数schedule:函数用于实现上述任务调度算法,代码中先对机器相关信息(运行时间、任务编号等)进行初始化,后续有分配前m个任务等操作。
  4. 问题设置:要求阅读上述说明和C代码,回答问题1 - 问题3,并将解答写在答题纸的对应栏内。
void schedule() {int i, j, k, max = 0;for (i = 0; i < m; i++) {d[i] = 0;for (j = 0; j < n; j++) {s[i][j] = 0;}}for (i = 0; i < m; i++) {  //分配前m个任务s[i][0] = i;__ (1) __count[i] = 1;}for (__ (2) __; i < n; i++) {  //分配后n - m个任务int min = d[0];k = 0;for (j = 1; j < m; j++) {  //确定空闲机器if (min > d[j]) {min = d[j];k = j;  //机器k空闲}}__ (3) __;count[k] = count[k] + 1;d[k] = d[k] + t[i];}for (i = 0; i < m; i++) {  //确定完成所有任务所需要的时间if (__ (4) __) {max = d[i];}}   
}
  • 问题1(8分):依据给定说明和C代码,填充代码中的空(1)-(4)。
  • 问题2(2分):根据说明和C代码,判断该问题采用的算法设计策略,并给出时间复杂度(用O符号表示)。
  • 问题3(5分):针对实例m = 3(机器编号0 - 2),n = 7(任务编号0 - 6),各任务运行时间为{16, 14, 6, 5, 4, 3, 2},确定机器0、1和2上运行的任务(给出任务编号),以及从任务开始运行到完成所需要的时间。
  • 解析:本题考查贪心算法,该算法不追求最优解,以当前情况为基础做出最优选择,无需回溯。
  • 答案
    • 问题1:(1) d[i] = d[i] + t[i];(2) i = m;(3) s[k][0] = i;(4) Max < d[i]
    • 问题2:采用贪心算法策略,时间复杂度为O(2m×n + 2m)。
    • 问题3:机器0运行任务0;机器1运行任务1、5;机器2运行任务2、3、4、6;运行的最长时间为17。
http://www.xdnf.cn/news/4843.html

相关文章:

  • Nacos源码—6.Nacos升级gRPC分析一
  • 缓存(1):三级缓存
  • 企业如何借助国外动态IP抢占海外市场先机?
  • uniapp 微信小程序使用图表
  • 人工智能在网络安全中的重要性
  • kotlin JvmName注解的作用和用途
  • 【WebRTC-13】是在哪,什么时候,创建编解码器?
  • 驱动开发硬核特训 · Day 30(下篇): 深入解析 lm48100q I2C 音频编解码器驱动模型(基于 i.MX8MP)
  • 【MCP】为什么使用Streamable HTTP: 相比SSE的优势与实践指南
  • 初识Dockerfile之RUN和WORKDIR
  • 【MySQL】第二弹——MySQL表的增删改查(CURD))
  • [ctfshow web入门] web57
  • 2025 后端自学UNIAPP【项目实战:旅游项目】3、API接口请求封装,封装后的简单测试以及实际使用
  • springCloud/Alibaba常用中间件之GateWay网关
  • 大型语言模型在网络安全领域的应用综述
  • 【WEB3】区块链、隐私计算、AI和Web3.0——数据民主化(1)
  • Python爬虫(21)Python爬虫进阶:Selenium自动化处理动态页面实战解析
  • RabbitMQ--基础篇
  • Android Studio 模拟器配置方案
  • 跨平台移动开发框架React Native和Flutter性能对比
  • 每周靶点分享:Angptl3、IgE、ADAM9及文献分享:抗体的多样性和特异性以及结构的新见解
  • 从代码学习深度学习 - 单发多框检测(SSD)PyTorch版
  • Comfyui 与 SDwebui
  • C++内存管理与模板初阶详解:从原理到实践
  • Java详解LeetCode 热题 100(13):LeetCode 53:最大子数组和(Maximum Subarray)详解
  • Android学习总结之算法篇八(二叉树和数组)
  • 10. CSS通配符与选择器权重深度解析:以《悯农》古诗为例
  • 比较Facebook与其他社交平台的隐私保护策略
  • RSS 2025|斯坦福提出「统一视频行动模型UVA」:实现机器人高精度动作推理
  • 机器视觉的手机FPC油墨丝印应用