算法分析与设计编程题 贪心算法

2023-09-14 22:16:04

活动安排问题

题目描述

在这里插入图片描述
在这里插入图片描述

解题代码

vector<bool> greedySelector(vector<vector<int>>& intervals) {
    int n = intervals.size();
    // 将活动区间按结束时间的从小到大排序
    auto cmp = [](vector<int>& interval1, vector<int>& interval2) {
        return interval1[1] < interval2[1];
    };
    sort(intervals.begin(), intervals.end(), cmp);
    vector<bool> res(n, false);
    // 结束时间最早的活动必定位于某个最优解之中
    int minStart = intervals[0][1];
    res[0] = true;
    for (int i = 1; i < n; ++i) {
        if (intervals[i][0] >= minStart) { // 将不重叠的活动加入最优解集
            res[i] = true;
            minStart = intervals[i][1];
        }
    }
    return res;
}

最优装载

题目描述

在这里插入图片描述

解题代码

vector<bool> optimisedLoading(vector<int>& weight, int c) {
	int n = weight.size();
	vector<bool> select(n, false);
    // 定义一个小顶优先队列,使得对于i,若其weight[i]最小,则排在队列的队头
	priority_queue<pair<int, int>, vector<pair<int, int>>, greater<pair<int, int>>> q;
	for (int i = 0; i < n; ++i) {
        // 构建二元组<重量,下标>并放入优先队列
		q.emplace(weight[i], i);
	}
	for (int i = 0; i < n; ++i) {
		auto [w, idx] = q.top(); // (C++17语法)取队头元素的w和对应下标
		q.pop();
		if (c < w) break; // 无法继续装载
		select[idx] = true; // 选择装载该货物
		c -= w; // 剩余载货量减少
	}
	return select;
}

哈夫曼编码

题目描述

在这里插入图片描述
在这里插入图片描述

解题代码

struct HuffmanNode {
	int left, right; // 左右结点
	int parent; // 父结点
	int weight; // 权重
	char data; // 数据
	HuffmanNode(int left = -1, int right = -1, int parent = -1, int weight = 0, char data = '*')
		: left(left), right(right), parent(parent), weight(weight), data(data) {}
};

vector<HuffmanNode> createHuffmanTree(vector<int>& weight, vector<char>& data) {
	int n = weight.size();
	vector<HuffmanNode> huffmanTree(2 * n);
    // 定义一个小顶优先队列,使得对于i,若其weight[i]最小,则排在队列的队头
	priority_queue<pair<int, int>, vector<pair<int, int>>, greater<pair<int, int>>> q;
	for (int i = 0; i < n; ++i) { // 初始化哈夫曼树和优先队列
		huffmanTree[i].data = data[i];
		huffmanTree[i].weight = weight[i];
		q.emplace(weight[i], i);
	}
	for (int i = 0; i < n - 1; ++i) {
		auto [weight1, idx1] = q.top(); // 取权值最小结点
		q.pop();
		auto [weight2, idx2] = q.top(); // 取权值第二小结点
		q.pop();
        // 创建两结点的父结点,其下标为n+i
		huffmanTree[idx1].parent = n + i;
		huffmanTree[idx2].parent = n + i;
		// 初始化该父结点的相关信息
		huffmanTree[n + i].left = idx1;
		huffmanTree[n + i].right = idx2;
		huffmanTree[n + i].weight = weight1 + weight2;
        // 将该父结点的<权值,下标>加入优先队列,以便进行贪心选择
		q.emplace(weight1 + weight2, n + i);
	}
	return huffmanTree;
}

void printHuffmanCode(vector<HuffmanNode>& huffmanTree) {
	stack<int> s;
	for (int i = 0; i < huffmanTree.size() / 2; ++i) {
		int cur = i; // 当前结点下标
		int pre = huffmanTree[cur].parent; // 当前结点的父结点的下标
		while (pre != -1) {
			if (huffmanTree[pre].left == cur) {
				s.emplace(0); // 当前结点为其父结点的左孩子
			}
			else {
				s.emplace(1); // 当前结点为其父结点的右孩子
			}
            // 轮换下标
			cur = pre;
			pre = huffmanTree[cur].parent;
		}
        // 打印相应的哈夫曼编码
		cout << huffmanTree[i].data << " ";
		while (!s.empty()) {
			cout << s.top();
			s.pop();
		}
		cout << endl;
	}
}

单源最短路径

题目描述

在这里插入图片描述

解题代码

图的定义

struct MGraph {
	vector<char> vertices; // 顶点数组
	vector<vector<int>> edges; // 邻接矩阵
};

BellmanFord

此算法可适用于含有负权值边的图。

// G:图	start:源点	dist:最短路径
bool BellmanFord(MGraph& G, int start, vector<int>& dist) {
	int n = G.vertices.size();
	// 初始化最短路径
	dist.assign(n, INT32_MAX);
	for (int j = 0; j < n; ++j) {
		dist[j] = G.edges[start][j];
	}
	for (int t = 0; t < n - 1; ++t) { // 松弛次数t
		for (int i = 0; i < n; ++i) { // 边的起点i
			for (int j = 0; j < n; ++j) { // 边的终点j
				if (G.edges[i][j] != INT32_MAX && dist[i] != INT32_MAX
					&& dist[i] + G.edges[i][j] < dist[j]) {
					dist[j] = dist[i] + G.edges[i][j]; // 松弛操作
				}
			}
		}
	}
	for (int i = 0; i < n; ++i) {
		for (int j = 0; j < n; ++j) {
			// 若执行完算法后仍然存在非最短路径,则该图存在权值为负的环路,无最短路径
			if (G.edges[i][j] != INT32_MAX && dist[i] != INT32_MAX
				&& dist[i] + G.edges[i][j] < dist[j]) {
				return false;
			}
		}
	}
	return true;
}

Dijkstra

本算法仅适用于所有边的权值均为正的图。

// G:图	start:源点	dist:最短路径
void Dijkstra(MGraph& G, int start, vector<int>& dist) {
	int n = G.vertices.size();
	// 初始化最短路径
	dist.assign(n, INT32_MAX);
	vector<int> pre(n, -1); // 前驱数组
	vector<bool> visited(n, false); // 访问集,表示对应顶点最短路径是否已经找到
	visited[start] = true;
	// 小顶优先队列,元素为<dist[j],j>
	priority_queue<pair<int, int>, vector<pair<int, int>>, greater<pair<int, int>>> q;
	// 初始化最短路径
	for (int j = 0; j < n; ++j) {
		dist[j] = G.edges[start][j];
		q.emplace(dist[j], j);
	}
	while (!q.empty()) {
		int i = q.top().second; // 贪心选择最近结点i
		q.pop();
		visited[i] = true; // 结点i最短路径已得到
		for (int j = 0; j < n; ++j) { // 利用结点i进行松弛操作
			if (visited[j]) continue; // 结点j已得到最短路径,无需松弛
			if (G.edges[i][j] != INT32_MAX && dist[i] != INT32_MAX
				&& dist[i] + G.edges[i][j] < dist[j]) {
				dist[j] = dist[i] + G.edges[i][j]; // 松弛操作
				pre[j] = i; // 更新前驱结点
				q.emplace(dist[j], j); // 加入优先队列
			}
		}
	}
	// 打印源点到各结点的最短路径
	stack<int> s;
	for (int i = 0; i < n; ++i) {
		if (i == start) continue;
		if (dist[i] == INT32_MAX) {
			cout << "inf" << ": " << G.vertices[start] << "->" << G.vertices[i] << endl;
		}
		else {
			cout << dist[i] << ": " << G.vertices[start] << "->";
			int x = pre[i];
			while (x != -1) {
				s.emplace(x);
				x = pre[x];
			}
			while (!s.empty()) {
				cout << G.vertices[s.top()] << "->";
				s.pop();
			}
			cout << G.vertices[i] << endl;
		}
	}
}

最小生成树

题目描述

在这里插入图片描述

解题代码

Kruskal

void Kruskal(MGraph& G) {
	int n = G.vertices.size();
    // 边集,元素为<权值weight,起点u,终点v>
	vector<tuple<int, int, int>> edges;
	for (int i = 0; i < n; ++i) {
		for (int j = i + 1; j < n; ++j) {
			if (G.edges[i][j] != INT32_MAX) {
                // 将边加入边集
				edges.emplace_back(G.edges[i][j], i, j);
			}
		}
	}
    // 对边集按权值大小进行升序排序
	sort(edges.begin(), edges.end());
    // 简单并查集,father[x]存放x的父结点
	vector<int> father(n);
    // 寻找x所在集合的父结点(所在连通分量编号)
	auto findFather = [&](int x) {
		int f = father[x];
		while (f != x) {
			x = f;
			f = father[x];
		}
		return f;
	};
	for (int i = 0; i < n; ++i) {
		father[i] = i; // 初始父结点为自身
	}
	int cnt = 0; // 已找到的边个数
	for (int i = 0; cnt <= n - 1 && i < edges.size(); ++i) {
		auto [weight, u, v] = edges[i];
		int fu = findFather(u);
		int fv = findFather(v);
		// 若u和v父结点相同(即u和v位于一个连通分量中),若选择加入边uv,则会导致回路
        if (fu != fv) { 
			cout << G.vertices[u] << " - " << G.vertices[v] << " : " << weight << endl;
			father[fu] = fv; // 两个连通分量合并为一个
			++cnt;
		}
	}
}

Prim

void Prim(MGraph& G) {
	int n = G.vertices.size();
    // minDist[i]表示结点i距离MST最近距离
	vector<int> minDist(n, INT32_MAX);
    // connected[i]表示在MST中与结点i相连的结点
	vector<int> connected(n, 0);
    // 表示结点i是否已加入MST
	vector<bool> visited(n, false);
	visited[0] = true;
	for (int j = 1; j < n; ++j) {
        // 初始化最近距离
		minDist[j] = G.edges[0][j];
	}
	for (int i = 1; i < n; ++i) {
        // 寻找距离MST的最近结点k
		int minVal = INT32_MAX, k = -1;
		for (int j = 1; j < n; ++j) {
			if (!visited[j] && minDist[j] < minVal) {
				minVal = minDist[j];
				k = j;
			}
		}
		if (k == -1) break;
        // 将结点k加入MST中
		visited[k] = true;
		cout << G.vertices[connected[k]] << " - " << G.vertices[k] << " : " << minVal << endl;
        // 更新minDist数组和connected数组
		for (int j = 1; j < n; ++j) {
			if (!visited[j] && G.edges[k][j] < minDist[j]) {
				minDist[j] = G.edges[k][j];
				connected[j] = k;
			}
		}
	}
}
更多推荐

软件测试进大厂,拿高薪,怎么做?看这里!

有些同学大学专业不对口,但有想进大厂想拿高薪心,只要你有想法,那就一定有实现的方法。俗话说:“世间无难事,只怕有心人”。仔细思索一下,哪家大厂能缺软件测试这一重要职位。相对大学所学专业而言,大厂录取测试人员,更多的是看重这个人的能力和相应的项目经验积累。所以不管是科班还是非科班出身的测试人员想敲开大厂的门,可以做到以下

大模型赛道如何实现华丽的弯道超车

🚀欢迎来到本文🚀🍉个人简介:陈童学哦,目前学习C/C++、算法、Python、Java等方向,一个正在慢慢前行的普通人。🏀系列专栏:陈童学的日记💡其他专栏:C++STL,感兴趣的小伙伴可以看看。🎁希望各位→点赞👍+收藏⭐️+留言📝​⛱️万物从心起,心动则万物动🏄‍♂️前言:Alluxio作为一款强大的

手把手教你5种方法实现Java判断字符串是否为数字

方法一:用JAVA自带的函数publicstaticbooleanisNumeric(Stringstr){for(inti=str.length();--i>=0;){if(!Character.isDigit(str.charAt(i))){returnfalse;}}returntrue;}方法一通过遍历字符串的

js中哪些地方会用到window?

前言Window对象是JavaScript中的顶层对象,它代表了浏览器中打开的窗口或者标签页。浏览器中打开的每一个窗口/标签页都会有一个对应的Window对象。在浏览器中,全局作用域的this就是指向Window对象。正文在JavaScript中,window对象表示浏览器窗口(通常也称为浏览器窗口或浏览器窗口)。以下

Linux 中的make/makefile

一:背景make是一个命令工具,是一个解释makefifile中指令的命令工具,一般来说,大多数的IDE都有这个命令,比如:Delphi的make,VisualC++的nmake,Linux下GNU的make。可见,makefifile都成为了一种在工程方面的编译方法。一个工程中的源文件不计数,其按类型、功能、模块分别

Redis常用应用场景

Redis是一款开源的基于内存的键值存储系统,它提供了多种数据结构和丰富的功能,适用于各种不同的应用场景。以下是Redis常用的应用场景:1.缓存:Redis最常见的用途就是作为缓存。由于Redis存储在内存中,读取速度非常快,可以显著减轻数据库的负载。将频繁读取的数据存储在Redis中,可以大幅提高应用的响应速度。2

SpringBoot-RabbitMQ

RabbitMQ是一个开源的消息中间件,它实现了AMQP(AdvancedMessageQueuingProtocol)协议,并提供了可靠的消息传递机制。SpringBoot中使用RabbitMQ实现异步消息的发送和接收。使用SpringBoot提供的AmqpTemplate和@RabbitListener注解进行消息

数据分享|R语言逻辑回归、Naive Bayes贝叶斯、决策树、随机森林算法预测心脏病...

全文链接:http://tecdat.cn/?p=23061这个数据集(查看文末了解数据免费获取方式)可以追溯到1988年,由四个数据库组成。克利夫兰、匈牙利、瑞士和长滩。"目标"字段是指病人是否有心脏病。它的数值为整数,0=无病,1=有病(点击文末“阅读原文”获取完整代码数据)。数据集信息:目标:主要目的是预测给定的

难得有个冷静的程序员发言了:纯编码开发实施的项目,失败的案例也有很多

难得有个冷静的程序员发言了:纯编码开发实施的项目,失败的案例也有很多。假如用低代码实施,能达到不失败或提高成功率,对软件开发项目交付,会是重大的价值。我的观点:两者都可能失败,不同的是,传统编程开发的项目,失败都是发生在项目的中后期,前期靠PPT保证,在实施过程中,做着做着就发现做不下去了(需求风险,成本风险,技术风险

硬件故障诊断:快速定位问题

🌷🍁博主猫头虎(🐅🐾)带您GotoNewWorld✨🍁🦄博客首页——🐅🐾猫头虎的博客🎐🐳《面试题大全专栏》🦕文章图文并茂🦖生动形象🐅简单易学!欢迎大家来踩踩~🌺🌊《IDEA开发秘籍专栏》🐾学会IDEA常用操作,工作效率翻倍~💐🌊《100天精通Golang(基础入门篇)》🐅学会Gol

Golang数组和slice

Golang数组和Slice(切片)Go语言中数组长度固定不可变动,slice则可以增长缩短(使用较多)一、数组类型Go语言中数组长度固定,索引从0开始计数。需要注意数组的长度一开始必须是固定的,且不同长度的数组其表示不同的数据类型,相同的数组可以进行‘==’比较。数组作为函数参数是使用的是形参的方式,函数内不可改变其

热文推荐