C 语言贪心算法实战:解决经典活动选择问题

C 语言贪心算法实战:解决经典活动选择问题

活动选择问题是贪心算法的典型应用场景,核心目标是在一组互斥的活动中选择最多的互不冲突活动。本文通过完整的 C 语言代码实现,详解 "按结束时间排序 + 优先选结束最早活动" 的贪心策略,拆解排序、冲突判断、结果输出的全流程,帮助掌握贪心算法的核心思想与工程实现技巧。

一、活动选择问题核心规则

1. 问题描述

给定n个活动,每个活动有唯一的开始时间s[i]和结束时间f[i],活动在同一资源(如会议室)进行,要求选择最多数量的互不冲突活动(即任意两个选中活动的时间区间无重叠)。

2. 贪心策略(最优解核心)

  • 核心思想:优先选择结束时间最早的活动,为后续活动留出更多可安排时间;
  • 策略依据:结束时间越早,剩余可安排时间越长,能选择的活动数量越多(该策略满足 "贪心选择性质" 和 "最优子结构",可推导出全局最优解);
  • 关键前提:先将所有活动按结束时间从小到大排序。

3. 冲突判断规则

若当前活动的开始时间 ≥ 上一个选中活动的结束时间 → 两个活动无冲突,可选中。

二、完整代码实现与解析

c

运行

markdown 复制代码
/******************************
*文件名称:Activity_Arrangements.c
*作者:czy
*邮箱:caozhiyang_0613@163.com
*创建日期:2025/12/25
*修改日期:2025/12/26
			2025/12/28
*文件功能:贪心算法解决活动选择问题
*核心思路:
*  1. 排序:用冒泡排序将活动按结束时间从小到大排序(贪心前提);
*  2. 贪心选择:优先选结束最早的活动,最大化兼容活动数量;
*  3. 核心逻辑:选中的活动开始时间 ≥ 上一个活动结束时间 → 无冲突。
*****************************/

#include<stdio.h>

#define MAX 1000
/************************************************
*函数名称:swap
*函数功能: 交换两个整数的值(指针实现)
*输入参数: 
*   *a - 第一个整数的指针
*   *b - 第二个整数的指针
*返回参数: 无
*创建时间:2025/12/25
*修改时间:2025/12/26
*函数作者: czy
*注意事项:用于交换活动的开始/结束时间,保证排序时活动的时间对应
**************************************************/
void swap(int *a,int *b)
{
    int temp = *a; // 临时变量存储a的值,避免覆盖
    *a = *b;       // 将b的值赋给a
    *b = temp;     // 将临时变量的值赋给b
}

/************************************************
*函数名称:bubblesort
*函数功能: 冒泡排序 - 按活动结束时间从小到大排序
*输入参数: 
*   a[2][1000] - 二维数组:a[0][i]=第i个活动开始时间,a[1][i]=第i个活动结束时间
*   n          - 活动总数
*返回参数: 无
*创建时间:2025/12/25
*修改时间:1-----2025/12/26
		  2----2025/12/28
*函数作者: czy
*核心逻辑:比较相邻活动的结束时间,交换位置(同时交换开始时间,保证时间对应)
**************************************************/
void bubblesort(int a[2][MAX],int index[MAX],int n)
{
    // 外层循环:控制排序轮数(共n轮)
    for(int i=0; i<n; i++)
    {
        // 内层循环:每轮比较到n-i-1(后i个元素已排序)
        for(int j=0; j<n-i-1; j++)
        {
            // 若前一个活动结束时间 > 后一个 → 需要交换
            if(a[1][j] > a[1][j+1])
            {
                swap(&a[1][j], &a[1][j+1]); // 交换结束时间
                swap(&a[0][j], &a[0][j+1]); // 同步交换开始时间(关键!保证时间对应)
                swap(&index[j],&index[j+1]);// 同步交换活动编号(关键)				
            }
        }
    }
}

/************************************************
*函数名称:select_activity
*函数功能: 贪心算法选择最多的互不冲突活动
*输入参数: 
*   a[2][MAX] - 已按结束时间排序的活动数组(a[0]=开始,a[1]=结束)
*   n          - 活动总数
*返回参数: 最多能选择的兼容活动数量(n≤0时返回0)
*创建时间:2025/12/25
*修改时间:2025/12/26
2025/12/28
*函数作者: czy
*贪心策略:优先选结束最早的活动,为后续活动留出更多时间
**************************************************/
int select_activity(int a[2][MAX],int index[MAX],int n,int selected_idx[MAX])
{
    // 边界条件:活动数量≤0,提示错误并返回0
    if(n <= 0)
    {
        printf("错误:活动个数必须为正整数!\n");
        return 0;
    }
    
    int count = 1;                // 至少选1个活动(结束最早的第一个活动)
    int lastfinish = a[1][0];     // 记录上一个选中活动的结束时间(初始为第一个活动)
    selected_idx[0]=index[0];
    
    // 遍历剩余活动(从第2个开始)
    for(int i=1; i<n; i++)
    {
        // 核心判断:当前活动开始时间 ≥ 上一个活动结束时间 → 无冲突,可选
        if(a[0][i] >= lastfinish)
        {
        	selected_idx[count]=index[i];
            count++;                      // 选中活动,计数+1
            lastfinish = a[1][i];         // 更新上一个活动的结束时间
        }
    }
    return count; // 返回最多可选择的活动数
}

/*************************************************
*函数名称:main
*函数功能: 主函数 - 输入活动数据、调用排序+贪心函数、输出结果
*输入参数: 无
*返回参数: 
*   0 - 程序正常退出
*   1 - 程序异常退出(输入无效时)
*创建时间:2025/12/26
*修改时间:2025/12/26
*函数作者:czy
**************************************************/
int main()
{
    int n;                // 活动个数
    int a[2][MAX];       // 存储活动时间:a[0][i]=开始,a[1][i]=结束
    int index[MAX]; 	// 活动原始编号(1~n
    int selected_idx[MAX];//存储选中的活动原始编号
    
    // 提示用户输入活动个数
    printf("===== 活动选择问题(贪心算法)=====\n");
    printf("请输入活动个数:\n");
    scanf("%d", &n);
    
    // 输入校验:活动个数必须为正整数
    if(n <= 0 || n > MAX)
    {
        printf("错误:活动个数必须为1~%d之间的正整数!\n",MAX);
        return 1; // 非0返回值表示程序异常退出
    }
    
    // 提示用户输入每个活动的开始时间和结束时间
    printf("请依次输入每个活动的「开始时间 结束时间」(空格分隔):\n");
    for(int i=0; i<n; i++)
    {
    	index[i]=i+1;
        printf("活动%d:", i+1); // 提示当前输入的是第几个活动,更友好
        scanf("%d %d", &a[0][i], &a[1][i]);
    }
    
    // 步骤1:按结束时间排序(贪心算法的前提)
    bubblesort(a, index , n);
    
    // 步骤2:贪心选择最多的兼容活动
    int result = select_activity(a, index,n,selected_idx);
    
    // 输出结果(补充说明,更易理解)
    printf("\n====最终选择结果===\n");
    printf("\n最多能选择的互不冲突活动数量:%d\n", result);
	printf("选中的活动为:\n");
	// 遍历所有选中的活动(result是选中的活动总数)
	for(int i=0;i<result;i++)
	{
		int pos=0; // 初始化pos为0:用于存储"选中编号"在排序后index数组中的位置
		// 遍历排序后的index数组(找选中编号对应的位置)
		for(int j =0;j<n;j++)
		{
		 // 核心判断:找到"排序后index数组中 = 选中活动编号"的位置
			if(index[j] == selected_idx[i])
			{
				pos = j;// 记录该位置
				break; // 找到后立即退出循环,提升效率
			}
		}
		printf("活动%d:开始时间=%d , 结束时间=%d\n",selected_idx[i],a[0][pos],a[1][pos]);
	}

    return 0; // 程序正常退出
}

三、核心模块拆解

1. 工具函数:swap(指针交换)

c

运行

ini 复制代码
void swap(int *a,int *b)
{
    int temp = *a;
    *a = *b;
    *b = temp;
}
  • 作用:交换两个整数的值,用于排序时同步交换活动的开始时间、结束时间、原始编号;
  • 核心:通过指针操作直接修改原变量值,避免值传递导致的无效交换。

2. 排序模块:bubblesort(按结束时间排序)

c

运行

css 复制代码
void bubblesort(int a[2][MAX],int index[MAX],int n)
{
    for(int i=0; i<n; i++)
    {
        for(int j=0; j<n-i-1; j++)
        {
            if(a[1][j] > a[1][j+1])
            {
                swap(&a[1][j], &a[1][j+1]); // 交换结束时间
                swap(&a[0][j], &a[0][j+1]); // 同步交换开始时间
                swap(&index[j],&index[j+1]);// 同步交换活动编号
            }
        }
    }
}
  • 核心逻辑 :冒泡排序的核心是 "相邻比较、逆序交换",此处按结束时间升序排序(贪心算法的前提);
  • 关键细节:交换结束时间时,必须同步交换开始时间和活动原始编号,否则会出现 "时间与编号不匹配" 的错误。

3. 贪心核心:select_activity(选择最多兼容活动)

c

运行

ini 复制代码
int select_activity(int a[2][MAX],int index[MAX],int n,int selected_idx[MAX])
{
    if(n <= 0) { printf("错误:活动个数必须为正整数!\n"); return 0; }
    
    int count = 1;                // 初始选中第一个(结束最早的)活动
    int lastfinish = a[1][0];     // 记录上一个活动的结束时间
    selected_idx[0]=index[0];
    
    for(int i=1; i<n; i++)
    {
        if(a[0][i] >= lastfinish) // 无冲突判断
        {
        	selected_idx[count]=index[i];
            count++;
            lastfinish = a[1][i];
        }
    }
    return count;
}
  • 贪心策略落地

    1. 初始选中结束时间最早的活动(排序后的第一个);
    2. 遍历剩余活动,仅选择 "开始时间 ≥ 上一个活动结束时间" 的活动;
    3. 每选中一个活动,更新 "上一个结束时间",保证后续判断的准确性;
  • 返回值:最多可选择的互不冲突活动数量。

4. 主函数:流程整合与结果输出

(1)输入与校验

c

运行

perl 复制代码
scanf("%d", &n);
if(n <= 0 || n > MAX)
{
    printf("错误:活动个数必须为1~%d之间的正整数!\n",MAX);
    return 1;
}
  • 校验活动数量的合法性,避免数组越界或无效计算。
(2)数据输入

c

运行

css 复制代码
for(int i=0; i<n; i++)
{
    index[i]=i+1;
    printf("活动%d:", i+1);
    scanf("%d %d", &a[0][i], &a[1][i]);
}
  • 为每个活动分配原始编号(1~n),方便后续输出时识别活动;
  • 提示式输入提升用户体验。
(3)结果输出

c

运行

ini 复制代码
for(int i=0;i<result;i++)
{
    int pos=0;
    for(int j =0;j<n;j++)
    {
        if(index[j] == selected_idx[i])
        {
            pos = j;
            break;
        }
    }
    printf("活动%d:开始时间=%d , 结束时间=%d\n",selected_idx[i],a[0][pos],a[1][pos]);
}
  • 核心:通过原始编号匹配排序后的活动时间,保证输出的是活动的原始信息;
  • 细节:找到匹配位置后立即break,避免无效循环。

四、运行结果示例

输入示例

plaintext

diff 复制代码
===== 活动选择问题(贪心算法)=====
请输入活动个数:
5
请依次输入每个活动的「开始时间 结束时间」(空格分隔):
活动1:1 4
活动2:3 5
活动3:0 6
活动4:5 7
活动5:8 9

输出示例

plaintext

makefile 复制代码
====最终选择结果===

最多能选择的互不冲突活动数量:3
选中的活动为:
活动1:开始时间=1 , 结束时间=4
活动4:开始时间=5 , 结束时间=7
活动5:开始时间=8 , 结束时间=9

结果验证

  • 排序后活动按结束时间为:活动 1(1,4)、活动 2(3,5)、活动 3(0,6)、活动 4(5,7)、活动 5(8,9);

  • 贪心选择:

    1. 选活动 1(结束 4);
    2. 跳过活动 2(开始 3<4)、活动 3(开始 0<4);
    3. 选活动 4(开始 5≥4);
    4. 选活动 5(开始 8≥7);
  • 总计 3 个,符合最优解。

五、贪心算法的核心要点与扩展

1. 活动选择问题的贪心正确性

  • 贪心选择性质:选择结束最早的活动,剩余的时间区间最大,能容纳更多活动;
  • 最优子结构:若 A 是最优解,那么 A 中第一个活动一定是结束最早的活动,剩余部分也是子问题的最优解。

2. 扩展优化思路

(1)输入合法性校验(时间维度)

c

运行

css 复制代码
scanf("%d %d", &a[0][i], &a[1][i]);
if(a[0][i] < 0 || a[1][i] <= a[0][i])
{
    printf("错误:活动%d的开始时间需≥0,且结束时间>开始时间!\n", i+1);
    i--; // 重新输入当前活动
    continue;
}
  • 避免输入 "开始时间为负""结束时间≤开始时间" 的无效活动。
(2)替换更高效的排序算法

冒泡排序的时间复杂度为 O (n²),可替换为快速排序(O (nlogn)),提升大数量活动的排序效率:

c

运行

css 复制代码
// 快速排序核心(按结束时间排序)
void quicksort(int a[2][MAX], int index[MAX], int left, int right)
{
    if(left >= right) return;
    int pivot = a[1][right]; // 基准为最右侧结束时间
    int i = left - 1;
    for(int j=left; j<right; j++)
    {
        if(a[1][j] <= pivot)
        {
            i++;
            swap(&a[1][i], &a[1][j]);
            swap(&a[0][i], &a[0][j]);
            swap(&index[i], &index[j]);
        }
    }
    i++;
    swap(&a[1][i], &a[1][right]);
    swap(&a[0][i], &a[0][right]);
    swap(&index[i], &index[right]);
    quicksort(a, index, left, i-1);
    quicksort(a, index, i+1, right);
}

六、新手避坑指南

  1. 排序时未同步交换数据:仅交换结束时间,导致开始时间 / 编号与结束时间不匹配,输出结果混乱;
  2. 忽略活动原始编号:排序后直接输出数组下标,用户无法识别原始活动;
  3. 边界条件缺失 :未校验n≤0n>MAX,导致数组越界或程序崩溃;
  4. 冲突判断逻辑错误 :误写为a[0][i] > lastfinish,漏掉 "等于" 的情况(如活动开始时间等于上一个结束时间,实际无冲突);
  5. 输出时未匹配位置 :直接用selected_idx[i]作为数组下标,忽略排序后下标已变化的问题。
相关推荐
止语Lab7 小时前
好的 DX 不等于少写代码——三种语言的摩擦力设计课
后端
吃饱了得干活7 小时前
别再手动解析 LLM 输出了!LangChain 四种结构化输出方案对比
后端·python·langchain
程序员天天困7 小时前
Arthas trace 命令怎么用?一行定位最慢那行代码
jvm·后端
Huiturn7 小时前
GPT 5.6 连续编码 10 小时,纯 Python 啃下 Word 二进制格式——doc2docx 实现拆解
后端
用户298698530147 小时前
Python 实现 Excel 与 Markdown 互转的实用指南
后端·python·excel
用户77283104908407 小时前
krono-job:零侵入、单二进制交付的分布式任务调度平台(开源)
后端
Conan在掘金7 小时前
鸿蒙报错速查:Function lacks ending return statement,返回路径漏 return 就炸,根因 + 真解法
后端
人间凡尔赛8 小时前
AI-Native 云原生架构:2026 年从容器编排到智能体编排的范式革命
后端·云原生·架构
IT_陈寒8 小时前
Vue的响应式让我加班到凌晨3点,原来问题出在这
前端·人工智能·后端
卷无止境8 小时前
提升 Python 代码健壮性的方法大盘点
后端·python