算法设计与实现--贪心篇

news2025/1/17 8:46:10

贪心算法

贪心算法是一种在每一步选择中都采取当前状态下最优决策的算法,以期望能够通过一系列局部最优的选择达到全局最优。贪心算法的关键是定义好局部最优的选择,并且不回退,即一旦做出了选择,就不能撤销。

一般来说,贪心算法适用于满足以下两个条件的问题:

  1. 最优子结构性质(Optimal Substructure): 问题的最优解包含了其子问题的最优解。这意味着可以通过子问题的最优解来构造原问题的最优解。
  2. 贪心选择性质(Greedy Choice Property): 当考虑做某个选择时,贪心算法总是选择当前看起来最优的解,而不考虑其他可能性。这个选择是局部最优的,希望通过这种选择能够达到全局最优。

关键的两步
提出贪心策略:观察问题特征,构造贪心选择
证明贪心正确:假设最优方案,通过替换证明

相关问题

1、部分背包问题

问题描述

部分背包问题是背包问题的一个变体,与 0-1 背包问题和完全背包问题不同。在部分背包问题中,每个物品可以选择一部分放入背包,而不是必须选择放入或不放入。

以下是部分背包问题的算法思想:

  1. 计算单位价值: 对每个物品计算单位价值,单位价值等于物品的价值/物品的重量。

    单位价值=物品的价值/物品的重量

  2. 按单位价值降序排序: 将所有物品按照单位价值降序排列,这样就可以优先选择单位价值较高的物品。

  3. 贪心选择: 从排好序的物品列表中按顺序选择物品放入背包。对于每个物品,可以选择一部分(即部分背包),而不必全部选择。

  4. 计算总价值: 根据所选物品计算放入背包的总价值

算法实现

#include <stdio.h>
#include <stdlib.h>

// 物品的结构
struct Item{
	int weight;  // 物品重量 
	int value;   // 物品价值 
}; 

// 1计算单位价值
double computeUnitValue(struct Item item){
	double result = item.value/item.weight;
	return result;
} 

// 2 按单位价值进行降序排序
// 在这个比较函数中,参数的类型为 const void*,
//这是因为这个函数是用于通用排序算法(例如 qsort)的,
//而通用排序算法不关心待排序元素的具体类型
int compare(const void* a,const void* b) {
	// *(struct Item*) 
	// 这是一种类型转换,将通用指针 const void* 转换为具体类型 struct Item*
	 double unitValueA = computeUnitValue(*(struct Item*)a);
	 double unitValueB = computeUnitValue(*(struct Item*)b);
	 
	 if(unitValueA < unitValueB){
	 	return 1;
	 }else if(unitValueA > unitValueB){
	 	return -1;
	 }else{
	 	return 0;
	 }
}

// 3 贪心算法
double fractionalKnapsack(struct Item items[],int n,int vtl) {
	// 跟据单位价值降序排列 
	qsort(items,n,sizeof(struct Item),compare);
	
	// 最大总价值 
	double maxValue = 0.0;  
	
	// 从排好序的物品列表中贪心选择,选择单位价值大的物品
	// 此时的items 是已经是跟据单位价值降序排序的,所以items[0] 是单位价值最大的物品 
	for(int i=0;i<n;i++){
		//  如果背包的容量>=物品的容量,则贪心策略,将整个物品放入背包 
		if(vtl>=items[i].weight){
			maxValue += items[i].value;  //  最大的价值更新 
			vtl -= items[i].weight;		// 背包容量更新 
		}else{ // 如果背包容量没法将整个物品放入,则计算他的单位价值,然后单位价值*剩余背包容量 
			maxValue += computeUnitValue(items[i])*vtl;
			break;
		}
	} 
	
	return maxValue;
}


// 主函数
int main() {
    struct Item items[] = {{10, 60}, {20, 100}, {30, 120}};
    int n = sizeof(items) / sizeof(items[0]);
    int vtl = 50; // 背包容量 

    double maxValue = fractionalKnapsack(items, n, capacity);

    printf("Maximum value that can be obtained = %.2f\n", maxValue);

    return 0;
}

2、哈夫曼编码

哈夫曼编码(Huffman Coding)是一种基于字符出现频率的编码方式,它通过使用较短的比特序列来表示出现频率较高的字符,从而实现对数据的高效压缩。这种编码方式是由大卫·哈夫曼(David A. Huffman)于1952年提出的。

哈夫曼编码的基本思想:

  1. 构建哈夫曼树(Huffman Tree)
    • 对于需要编码的字符,根据其出现频率构建一个哈夫曼树。
    • 频率越高的字符在树中离根越近,频率越低的字符在树中离根越远。(首先选择最小的两个频)
  2. 分配编码
    • 遍历哈夫曼树的路径,给每个字符分配一个独一无二的二进制编码。
    • 一般来说,向左走表示添加一个0,向右走表示添加一个1。
  3. 生成哈夫曼编码表
    • 将每个字符与其对应的二进制编码建立映射关系,形成哈夫曼编码表。

算法实现

#include <stdio.h>
#include <stdlib.h>

// 哈夫曼树节点结构
struct HuffmanNode {
    char data;
    int frequency;
    struct HuffmanNode* left;
    struct HuffmanNode* right;
};

// 字符频率表结构
struct FrequencyTable {
    char data;
    int frequency;
};

// 优先队列中的元素
struct PriorityQueueElement {
    struct HuffmanNode* node;
    struct PriorityQueueElement* next;
};

// 优先队列结构
struct PriorityQueue {
    struct PriorityQueueElement* front;
};

// 初始化优先队列
void initPriorityQueue(struct PriorityQueue* pq) {
    pq->front = NULL;
}

// 插入元素到优先队列
void insertPriorityQueue(struct PriorityQueue* pq, struct HuffmanNode* node) {
    struct PriorityQueueElement* newElement = (struct PriorityQueueElement*)malloc(sizeof(struct PriorityQueueElement));
    newElement->node = node;
    newElement->next = NULL;

    if (pq->front == NULL || node->frequency < pq->front->node->frequency) {
        newElement->next = pq->front;
        pq->front = newElement;
    } else {
        struct PriorityQueueElement* current = pq->front;
        while (current->next != NULL && current->next->node->frequency <= node->frequency) {
            current = current->next;
        }
        newElement->next = current->next;
        current->next = newElement;
    }
}

// 从优先队列中取出最小元素
struct HuffmanNode* extractMinPriorityQueue(struct PriorityQueue* pq) {
    if (pq->front == NULL) {
        return NULL;
    }

    struct HuffmanNode* minNode = pq->front->node;
    struct PriorityQueueElement* temp = pq->front;
    pq->front = pq->front->next;
    free(temp);

    return minNode;
}

// 构建哈夫曼树
struct HuffmanNode* buildHuffmanTree(struct FrequencyTable frequencies[], int n) {
    struct PriorityQueue pq;
    initPriorityQueue(&pq);

    // 初始化优先队列,每个节点作为一个单独的树
    for (int i = 0; i < n; ++i) {
        struct HuffmanNode* newNode = (struct HuffmanNode*)malloc(sizeof(struct HuffmanNode));
        newNode->data = frequencies[i].data;
        newNode->frequency = frequencies[i].frequency;
        newNode->left = newNode->right = NULL;
        insertPriorityQueue(&pq, newNode);
    }

    // 重复合并节点,直到队列中只剩下一个节点,即哈夫曼树的根
    while (pq.front->next != NULL) {
        struct HuffmanNode* leftChild = extractMinPriorityQueue(&pq);
        struct HuffmanNode* rightChild = extractMinPriorityQueue(&pq);

        struct HuffmanNode* newNode = (struct HuffmanNode*)malloc(sizeof(struct HuffmanNode));
        newNode->data = '\0'; // 内部节点没有字符数据
        newNode->frequency = leftChild->frequency + rightChild->frequency;
        newNode->left = leftChild;
        newNode->right = rightChild;

        insertPriorityQueue(&pq, newNode);
    }

    // 返回哈夫曼树的根节点
    return extractMinPriorityQueue(&pq);
}

// 生成哈夫曼编码
void generateHuffmanCodes(struct HuffmanNode* root, int code[], int top) {
    if (root->left != NULL) {
        code[top] = 0;
        generateHuffmanCodes(root->left, code, top + 1);
    }

    if (root->right != NULL) {
        code[top] = 1;
        generateHuffmanCodes(root->right, code, top + 1);
    }

    if (root->left == NULL && root->right == NULL) {
        printf("Character: %c, Code: ", root->data);
        for (int i = 0; i < top; ++i) {
            printf("%d", code[i]);
        }
        printf("\n");
    }
}

// 主函数
int main() {
    struct FrequencyTable frequencies[] = {{'A', 2}, {'B', 1}, {'C', 1}, {'D', 1},{'E',4}
	};
    int n = sizeof(frequencies) / sizeof(frequencies[0]);

    struct HuffmanNode* root = buildHuffmanTree(frequencies, n);

    int code[100];
    int top = 0;

    printf("Huffman Codes:\n");
    generateHuffmanCodes(root, code, top);

    return 0;
}

3、活动选择问题

活动选择问题(Activity Selection Problem)是一个经典的贪心算法问题,也称为区间调度问题。给定一组活动,每个活动都有一个开始时间和结束时间,目标是选择出最大可能的互不相交的活动子集。

以下是活动选择问题的算法思想:

  1. 将活动按照结束时间的先后顺序进行排序。
  2. 选择第一个活动作为初始活动,并将其加入最终选择的活动子集。
  3. 从第二个活动开始,依次判断每个活动是否与已选择的活动相容(即结束时间是否早于下一个活动的开始时间),如果相容,则将该活动加入最终选择的活动子集。
  4. 重复步骤3,直到遍历完所有活动。

通过贪心策略,每次选择结束时间最早的活动,可以确保选择的活动子集最大化。因为如果一个活动与已选择的活动相容,那么它一定是结束时间最早的活动,选择它不会影响后续活动的选择。
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

代码实现

该算法的核心就是 每次选择结束时间最早的活动

#include <stdio.h>
#include <stdlib.h>

// 活动结构
struct Activity {
    int start;
    int end;
};

// 比较函数,用于按结束时间升序排序
int compare(const void* a, const void* b) {
    return ((struct Activity*)a)->end - ((struct Activity*)b)->end;
}

// 活动选择算法
void activitySelection(struct Activity activities[], int n) {
    // 按结束时间升序排序
    qsort(activities, n, sizeof(struct Activity), compare);

    // 第一个活动总是被选择
    printf("Selected activity: (%d, %d)\n", activities[0].start, activities[0].end);

    // 从第二个活动开始选择
    int lastActivity = 0;
    for (int i = 1; i < n; ++i) {
        // 如果活动的开始时间晚于或等于上一个已选择活动的结束时间,选择该活动
        if (activities[i].start >= activities[lastActivity].end) {
            printf("Selected activity: (%d, %d)\n", activities[i].start, activities[i].end);
            lastActivity = i;
        }
    }
}

// 主函数
int main() {
    struct Activity activities[] = {{1, 4}, {3, 5}, {0, 6}, {5, 7}, {3, 9}, {5, 9}, {6, 10}, {8, 11}, {8, 12}, {2, 14}, {12, 16}};
    int n = sizeof(activities) / sizeof(activities[0]);

    printf("Activity schedule:\n");
    activitySelection(activities, n);

    return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1280827.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【栈和队列(2)】

文章目录 前言队列队列方法队列模拟实现循环队列练习1 队列实现栈 前言 队列和栈是相反的&#xff0c;栈是先进后出&#xff0c;队列是先进先出&#xff0c;相当于排队打饭&#xff0c;排第一的是最先打到饭出去的。 队列 队列&#xff1a;只允许在一端进行插入数据操作&…

C++基础从0到1入门编程(六)- 类继承、类多态

系统学习C&#xff0c;本章将记录类继承、类多态的相关概念 方便自己日后复习&#xff0c;错误的地方希望积极指正 往期文章&#xff1a; C基础从0到1入门编程&#xff08;一&#xff09; C基础从0到1入门编程&#xff08;二&#xff09; C基础从0到1入门编程&#xff08;三&am…

西瓜书-主要符号表

主要符号表 LaTeX符号说明How to read letter?\mathit{x}标量\boldsymbol{x}向量\mathrm{x}变量集\mathbf{A}矩阵\mathbf{I}单位阵\mathcal{X}样本空间或状态空间calligraphic X\mathcal{D}概率分布Ɗ calligraphic D\mathit{H}数据样本&#xff08;数据集)\mathcal{H}假设空…

智能优化算法应用:基于差分进化算法无线传感器网络(WSN)覆盖优化 - 附代码

智能优化算法应用&#xff1a;基于差分进化算法无线传感器网络(WSN)覆盖优化 - 附代码 文章目录 智能优化算法应用&#xff1a;基于差分进化算法无线传感器网络(WSN)覆盖优化 - 附代码1.无线传感网络节点模型2.覆盖数学模型及分析3.差分进化算法4.实验参数设定5.算法结果6.参考…

IM通信技术快速入门:短轮询、长轮询、SSE、WebSocket

文章目录 前言即时通讯常用技术 短轮询&#xff08;Short Polling&#xff09;实现原理优点缺点 长轮询(Long Polling)实现原理改进点基于iframe的长轮询实现原理总结 Server-Sent Events&#xff08;SSE&#xff09;实现原理浏览器对 SSE 的支持情况SSE vs WebSocket总结 WebS…

基于英特尔平台及OpenVINO2023工具套件优化文生图任务

当今&#xff0c;文生图技术在很多领域都得到了广泛的应用。这种技术可以将文本直接转换为逼真的图像&#xff0c;具有很高的实用性和应用前景。然而&#xff0c;由于文生成图任务通常需要大量的计算资源和时间&#xff0c;如何在英特尔平台上高效地完成这些计算是一个重要的挑…

基于Java SSM框架+Vue实现企业公寓后勤管理系统项目【项目源码+论文说明】

基于java的SSM框架Vue实现企业宿舍后勤管理网站演示 摘要 21世纪的今天&#xff0c;随着社会的不断发展与进步&#xff0c;人们对于信息科学化的认识&#xff0c;已由低层次向高层次发展&#xff0c;由原来的感性认识向理性认识提高&#xff0c;管理工作的重要性已逐渐被人们所…

RC低通滤波电路直接带载后会发生什么?

1、滤波的含义 滤波是频域范畴&#xff0c;它说的是不同频率的信号经过一个电路处理后&#xff0c;信号发生变化的问题&#xff0c;变化包含了原始信号幅值和相位的变化&#xff0c;滤波电路对信号的幅值做出的响应称为幅频响应&#xff0c;对信号相位做出的反应称为相频响应。…

【MySQL】视图 + 用户管理

视图 前言正式开始视图用户管理user表创建新用户修改用户密码权限管理给用户赋权剥夺权限 前言 本篇所讲的视图和我上一篇事务中所讲的读视图不是一个东西&#xff0c;二者没有任何关系&#xff0c;如果看过我前一篇博客的同学不要搞混了。 其实视图和用户管理本来是想着分开…

perl脚本批量处理代码中的中文注释乱码的问题

代码中统一使用utf-8编码是最好的&#xff0c;但是有一些多人合作的项目或者一些历史遗留代码&#xff0c;常见一些中文注释乱码的问题。这里以一个开源项目evpp为例子 evpp。以项目中的一个commit id为例&#xff1a; 477033f938fd47dfecde43c82257cd286d9fa38e &#xff0c; …

数据结构之堆排序以及Top-k问题详细解析

个人主页&#xff1a;点我进入主页 专栏分类&#xff1a;C语言初阶 C语言程序设计————KTV C语言小游戏 C语言进阶 C语言刷题 数据结构初阶 欢迎大家点赞&#xff0c;评论&#xff0c;收藏。 一起努力 目录 1.前言 2.堆排序 2.1降序排序 2.2时间复杂…

充电桩新老国标兼容性分析

1、背景介绍 1.1、充电桩相关标准发展历程 1.2、兼容性分析历史 1.3、兼容性分析的目的 1.4、兼容性分析的内容 2、B类协议兼容性分析 2.1、协议分层结构 2.2、链路层分析 2.3、版本协商与链路检测 ## 2.4、传输层分析 2.5、应用层 2.5.1、应用层数据 2.5.2、应用层数据…

谈谈MYSQL索引

基本介绍 索引是帮助MySQL高效获取数据的数据结构&#xff0c;主要是用来提高数据检索的效率&#xff0c;降低数据库的IO成本&#xff0c;同时通过索引列对数据进行排序&#xff0c;降低数据排序的成本&#xff0c;也能降低了CPU的消耗。 通俗来说, 索引就相当于一本书的目录,…

QML中常见布局方法

目录 引言常见方法锚定&#xff08;anchors&#xff09;定位器Row、ColumnGridFlow 布局管理器RowLayout、ColumnLayoutGridLayoutStackLayout 总结 引言 UI界面由诸多元素构成&#xff0c;如Label、Button、Input等等&#xff0c;各种元素需要按照一定规律进行排布才能提高界…

Java数据结构之《构造哈夫曼树》题目

一、前言&#xff1a; 这是怀化学院的&#xff1a;Java数据结构中的一道难度中等(偏难理解)的一道编程题(此方法为博主自己研究&#xff0c;问题基本解决&#xff0c;若有bug欢迎下方评论提出意见&#xff0c;我会第一时间改进代码&#xff0c;谢谢&#xff01;) 后面其他编程题…

kgma转换flac格式、酷狗下载转换车载模式能听。

帮朋友下载几首歌到U盘里、发现kgma格式不能识别出来&#xff0c;这是酷狗加密过的格式&#xff0c;汽车不识别&#xff0c;需要转换成mp3或者flac格式&#xff0c;网上的一些辣鸡软件各种收费、限制、广告&#xff0c;后来发现一个宝藏网站&#xff0c;可以在线免费转换成flac…

长度最小的子数组(Java详解)

目录 题目描述 题解 思路分析 暴力枚举代码 滑动窗口代码 题目描述 给定一个含有 n 个正整数的数组和一个正整数 target 。 找出该数组中满足其和 ≥ target 的长度最小的 连续子数组 [numsl, numsl1, ..., numsr-1, numsr] &#xff0c;并返回其长度。如果不存在符合条…

MyBatis自动生成代码(扩展)

可以利用Mybatis-Generator来帮我们自动生成文件 1、自动生成实体类 可以帮助我们针对数据库中的每张表自动生成实体类 2、自动生成SQL映射文件 可以帮助我们针对每张表自动生成SQL配置文件&#xff0c;配置文件里已经定义好对于该表的增删改查的SQL以及映射 3、自动生成接…

数据层融合、特征层融合和决策层融合是三种常见的数据融合方式!!

文章目录 一、数据融合的方式有什么二、数据层融合三、特征层融合&#xff1a;四、决策层融合&#xff1a; 一、数据融合的方式有什么 数据层融合、特征层融合和决策层融合是三种常见的数据融合方式。 二、数据层融合 定义&#xff1a;数据层融合也称像素级融合&#xff0c;…