一、实验目的
- 了解贪心算法思想
- 掌握贪心法典型问题,如背包问题、作业调度问题等。
二、实验内容
- 编写一个简单的程序,实现单源最短路径问题。
- 编写一段程序,实现找零。
【问题描述】当前有面值分别为2角5分,1角,5分,1分的硬币,请给出找n分钱的最佳方案(要求找出的硬币数目最少)。 - 编写程序实现多机调度问题
【问题描述】要求给出一种作业调度方案,使所给的n个作业在尽可能短的时间内由m台机器加工处理完成。约定,每个作业均可在任何一台机器上加工处理,但未完工前不允许中断处理。作业不能拆分成更小的子作业。
三、算法思想分析
- 单源最短路径
基本思想为设置一个顶点集合S(红点集),初始化时红点集中只有源点s,然后不断在蓝点集(V-S)中寻找特殊路径最短的点加到红点集中,并在加入之后更新蓝点集中的特殊路径数组值,直到蓝点集中只剩下最短距离为-1(即没有相连,无限大)或者所有蓝点进入红点集,这样从s到其余各个顶点的最短特殊路径就是单源最短路径的集合。
实现时需要设置红点集的数组,蓝点集的数组,存储当前红点集到蓝点集的最小权值的数组,循环对蓝点集的元素中最短特殊路径的那个进行处理,并将蓝点集对应的权值等信息更新,这样就完成了整个思路设计。 - 找零问题
基本思想为每次找可以割分的更大的面值进行找零,循环对剩余的钱进行贪心的找零直到剩余的钱为0,得到的结果就是最后的分配方案。
实现时我使用的是向量,这样可以避免由于找零的硬币数的不确定的动态问题,并设置一个数组存储各种面值并按降序排好,每次贪心找零时只需要从面值数组的开始到结束遍历找到可以找开的面值即可。 - 多机调度问题
这个问题是NP完全问题,即多项式复杂程度的非确定问题,这种题目贪心找到的是一种近似的最佳算法。由于想尽可能短的时间完成多机调度,因此需要每次将相对长的作业先分配完成,减少时间浪费。题目的基本思路是将作业依其所需处理时间从大到小排序,然后根据顺内需将作业分配到当前的空闲处理机,这样就可以完成多机调度。
四、实验过程分析
本次实验对贪心算法的理解进行了实践,在第二题与第三题的实现中,难的地方其实只有本身的算法思想,想到了就相对较好实现,但在第一题的单源最短路径中需要设置红点集,蓝点集,父结点集,红点集的最小权值集等,在实现时本身的算法思想理解就有一定难度,再加上数组与最小权值集的更新等操作,整道题做出来对我的难度还是挺大的,最开始是自己白手起家,当自己白手起家没起来(bug没调出来)后,寻求了ppt中代码的帮助,先理解后靠理解重新实现,才解决了第一题。
实验中对贪心算法的两个重要性质:贪心选择性质与最优子结构性质进行了实验运用,通过自己重新将题目的思路构建,我对贪心算法的理解更近了一层,这对我的算法本身的基础有十分重要的作用;同时做实验时也对我的代码能力,如向量的使用等,有了一定的锻炼。
五、算法源代码及用户屏幕
1.单源最短路径
①代码
#include <iostream>
using namespace std;
#define NUM 50
void minPace(double graph[][NUM], int r[], int b[], int d[], int p[], int max) {
while (true) {
int min = -1;
//获取第一个蓝点集中未被使用的点
for (int i = 0; i < max; i++) {
if (b[i] == 0)continue;
else {
min = i;
break;
}
}
//从这里向没有遍历到的点集中进行比较,找出最短的蓝点
for (int i = min; i < max; i++) {
if (b[i] == 1 && d[i] < d[min] && d[i] != -1) {
min = i;
}
}
if (min == -1)break;
//将该点加到红点集中
r[min] = 1;
b[min] = 0;
//设置最短路径数组和父节点数组
for (int i = 0; i < max; i++) {
//如果有路
if (graph[min][i] != -1 && min != i) {
if (graph[min][i] + d[min] < d[i] || d[i] == -1) {
d[i] = d[min] + graph[min][i];
p[i] = min;
}
}
}
}
}
int main() {
//设置图的权值
int maxPoint;
cout << "Please input the number of points: ";
cin >> maxPoint;
double graph[NUM][NUM];
cout << "Please input the number of weigh: " << endl;
for (int i = 0; i < maxPoint; i++) {
for (int j = 0; j < maxPoint; j++) {
cin >> graph[i][j];
}
}
//设置红,蓝点集数组
int r[NUM] = { 0 };
int b[NUM];
//设置存储当前已有红点到蓝点的最小权值的数组
int d[maxPoint] = { 0 };
//设置存储当前父节点的数组
int p[maxPoint];
//初始化
for (int i = 0; i < maxPoint; i++) {
b[i] = 1;
p[i] = -1;
d[i] = -1;
}
int source;
cout << "Please input the num of source point";
cin >> source;
while (true) {
if (source >= maxPoint) {
cout << "You have input a wrong source, please try again";
cin >> source;
}
else break;
}
r[source] = 1;
b[source] = 0;
d[source] = 0;
for (int i = 0; i < maxPoint; i++) {
if (graph[source][i] != -1 && source != i) {
d[i] = graph[source][i];
p[i] = source;
}
}
minPace(graph, r, b, d, p, maxPoint);
// cout << source << "";
for (int i = 0; i < maxPoint; i++) {
if (d[i] != -1) {
cout << "0 -> " << i << ": " << d[i] << endl;
}
}
system("pause");
return 0;
}
②用户界面
首先输入点的个数,其次输入各个边的权值(-1代表没有边相连,其中第0行第1列代表从0到1的有向边),再输入源点,回车即可显示源点到每个点的最短路径。
2.找零问题
①代码
#include <iostream>
#include <string.h>
#include <vector>
using namespace std;
vector<int>split(int splitMoney[], int totalMoney, int num) {
vector<int> result;
while (totalMoney != 0) {
for (int i = 0; i < num; i++) {
if (totalMoney % splitMoney[i] == 0) {
result.push_back(splitMoney[i]);
totalMoney -= splitMoney[i];
break;
}
}
}
return result;
}
void bubble(int num[], int n) {
bool jud = true;
for (int i = n; i > 0; i--) {
jud = true;
for (int j = 0; j < n - 1; j++) {
if (num[j] < num[j + 1]) {
int temp = num[j + 1];
num[j + 1] = num[j];
num[j] = temp;
jud = false;
}
}
if (jud)break;
}
}
int main() {
int totalMoney, num;
cout << "Please input the total money: ";
cin >> totalMoney;
cout << "Please input the num of split money";
cin >> num;
cout << "Please input the split money: ";
int splitMoney[4];
for (int i = 0; i < num; i++) {
cin >> splitMoney[i];
}
bubble(splitMoney, num);
vector<int> sum = split(splitMoney, totalMoney, num);
//使用迭代语句进行获取
cout << "the solution is: ";
for (vector<int>::iterator iter = sum.begin(); iter != sum.end(); iter++) {
cout << *iter << " ";
}
system("pause");
return 0;
}
②用户界面
首先输入总的钱数,然后输入硬币面值的种类数,再输入各个面值都有多少(可不按顺序),回车后显示贪心找零方案。
3.多机调度问题
①代码
#include <iostream>
using namespace std;
//冒泡排序
void bubble(int num[], int n) {
bool jud = true;
for (int i = n; i > 0; i--) {
jud = true;
for (int j = 0; j < n - 1; j++) {
if (num[j] < num[j + 1]) {
int temp = num[j + 1];
num[j + 1] = num[j];
num[j] = temp;
jud = false;
}
}
if (jud)break;
}
}
int scheduling(int job[], int machine[], int n, int m) {
bubble(job, n);
int num = 0;
//循环寻找当前最长作业分配到空闲作业
while (num != n) {
int temp = job[num];
bubble(machine, m);
machine[m - 1] += temp;
num++;
}
//找到机器使用时间中的最大值
bubble(machine, m);
return machine[0];
}
int main() {
//设有n个独立作业,m台机器
int m, n;
cout << "Please input the num of jobs and machines: ";
cin >> n >> m;
int machine[m] = { 0 };
int job[n];
cout << "Please input the time of each job: ";
for (int i = 0; i < n; i++) {
cin >> job[i];
}
int result = scheduling(job, machine, n, m);
cout << "the minimum of scheduling time is: " << result;
system("pause");
return 0;
}
②用户界面
首先输入作业数目与机器数目,然后输入每个作业的调度时间,回车输出调度的贪心最短时间与对应的作业调度方法。