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]作为数组下标,忽略排序后下标已变化的问题。
相关推荐
+VX:Fegn08959 小时前
计算机毕业设计|基于springboot + vue物流配送中心信息化管理系统(源码+数据库+文档)
数据库·vue.js·spring boot·后端·小程序·课程设计
qq_12498707539 小时前
基于微信小程序的宠物交易平台的设计与实现(源码+论文+部署+安装)
java·spring boot·后端·微信小程序·小程序·毕业设计·计算机毕业设计
禹曦a9 小时前
Java实战:Spring Boot 构建电商订单管理系统RESTful API
java·开发语言·spring boot·后端·restful
superman超哥9 小时前
精确大小迭代器(ExactSizeIterator):Rust性能优化的隐藏利器
开发语言·后端·rust·编程语言·rust性能优化·精确大小迭代器
guchen669 小时前
WPF拖拽功能问题分析与解决方案
后端
Smoothzjc10 小时前
别再只把AI当聊天机器人了!揭秘大模型进化的终极形态,看完颠覆你的认知!
后端·langchain·ai编程
superman超哥10 小时前
惰性求值(Lazy Evaluation)机制:Rust 中的优雅与高效
开发语言·后端·rust·编程语言·lazy evaluation·rust惰性求值
9号达人10 小时前
AI最大的改变可能不是写代码而是搜索
java·人工智能·后端