实验目的:
1、理解贪心算法与回溯法的概念;
2、掌握贪心算法与回溯法的基本要素;
3、掌握贪心算法与回溯法的解题步骤与算法柜架;
4、通过应用范例学习贪心算法与回溯法的设计技巧与策略;
实验原理
1、贪心算法
贪心算法(又称贪婪算法)是指,在对问题求解时,总是做出在当前看来是最好的选择。也就是说,不从整体最优上加以考虑,他所做出的仅是在某种意义上的局部最优解。贪心算法不是对所有问题都能得到整体最优解,但对范围相当广泛的许多问题他能产生整体最优解或者是整体最优解的近似解。
2、贪心算法的基本思想
- 建立数学模型来描述问题。
- 把求解的问题分成若干个子问题。
- 对每一子问题求解,得到子问题的局部最优解。
- 把子问题的解局部最优解合成原来解问题的一个解。
3、回溯法
回溯法是一个既带有系统性又带有跳跃性的的搜索算法。它在包含问题的所有解的解空间树中,按照深度优先的策略,从根结点出发搜索解空间树。算法搜索至解空间树的任一结点时,总是先判断该结点是否肯定不包含问题的解。如果肯定不包含,则跳过对以该结点为根的子树的系统搜索,逐层向其祖先结点回溯。否则,进入该子树,继续按深度优先的策略进行搜索。回溯法在用来求问题的所有解时,要回溯到根,且根结点的所有子树都已被搜索遍才结束。而回溯法在用来求问题的任一解时,只要搜索到问题的一个解就可以结束。这种以深度优先的方式系统地搜索问题的解的算法称为回溯法,它适用于解一些组合数较大的问题。
4、回溯法的基本思想
确定了解空间的组织结构后,回溯法就从开始结点(根结点)出发,以深度优先的方式搜索整个解空间。这个开始结点就成为一个活结点,同时也成为当前的扩展结点。在当前的扩展结点处,搜索向纵深方向移至一个新结点。这个新结点就成为一个新的活结点,并成为当前扩展结点。如果在当前的扩展结点处不能再向纵深方向移动,则当前扩展结点就成为死结点。换句话说,这个结点不再是一个活结点。此时,应往回移动(回溯)至最近的一个活结点处,并使这个活结点成为当前的扩展结点。回溯法即以这种工作方式递归地在解空间中搜索,直至找到所要求的解或解空间中已没有活结点时为止。
运用回溯法解题通常包含以下三个步骤:
- 针对所给问题,定义问题的解空间;
- 确定易于搜索的解空间结构;
- 以深度优先的方式搜索解空间,并且在搜索过程中用剪枝函数避免无效搜索;
实验内容:
1. 使用贪心算法解决最小生成树问题。
2. 使用回溯法解决0-1背包问题。
3. 通过上机实验进行贪心算法与回溯算法实现。
4. 保存和打印出程序的运行结果,并结合程序进行分析,上交实验报告。
实验代码:
#include<iostream>
#include<cstring>
using namespace std;
int n, m;
int map[101][101];
int pre[101];
int u[101], v[101], edge[101]; //u,v分别为两个点,edge为两个点之间的边
int find(int x)
{
int root = x;
while (pre[root] != root)
root = pre[root];
return root;
}
void prim() //最小生成树 prim算法
{
int count = 0;
cout << "prim下解为:" << endl;
int dist[101]; //lowcost[i]存树中点到i点的边的权值最小为多少
int closest[101]; //closest[i]存放树中哪个点到i点的边的权值最小是哪个
bool s[101];
for (int i = 1; i <= n; i++) //初始化lowcost和s
{
dist[i] = map[1][i];
s[i] = false;
closest[i] = 1;
}
s[1] = true;
for (int i = 1; i < n; i++) //最小生成树只有n-1条边
{
int min = 100000;
int k = 1;
for (int j = 2; j <= n; j++) //找最小边
{
if ((dist[j] < min) && (!s[j]))
{
min = dist[j];
k = j;
}
}
cout << k << "," << closest[k] << ":" << min << endl;
count += min;
s[k] = true;
for (int j = 1; j <= n; j++)
{
if ((map[k][j] < dist[j]) && (!s[j])) //如果新的结点到j的边比原来的结点到j的边小,就用新结点替换掉原结点
{
dist[j] = map[k][j];
closest[j] = k;
}
}
}
cout << "最小权重和为:" << count << endl;
}
void kruskal()
{
cout << "kruskal下解为:" << endl;
int minnum, fu, fv;
int count = 0;
int total = n - 1;
while (total > 0)
{
int min = 10000000;
for (int i = 1; i <= m; i++) //找最小值
{
if (u[i] == -1 || v[i] == -1)
continue;
if (edge[i] < min)
{
min = edge[i];
minnum = i;
}
}
fu = find(u[minnum]);
fv = find(v[minnum]);
if (fu != fv) //不连通,就连接两个点
{
cout << v[minnum] << "," << u[minnum] << ":" << edge[minnum] << endl;
count += edge[minnum];
pre[fu] = fv;
total--;
}
edge[minnum] = 100000000; //改变已经找到的最小值
u[minnum] = -1;
v[minnum] = -1;
}
cout << "最小权重和为:" << count << endl;
}
int main()
{
cout << "输入结点个数以及边条数:" << endl;
cin >> n >> m;
int i, a, b, tem;
memset(map, 0x3f, sizeof(map));
cout << "输入对应两结点序号以及两点间边的权重:" << endl;
for (i = 1; i <= n; i++) pre[i] = i;
for (i = 1; i <= m; i++)
{
cin >> a >> b;
cin >> tem;
map[a][b] = map[b][a] = tem;
u[i] = a;
v[i] = b;
edge[i] = tem;
}
prim();
kruskal();
return 0;
}
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
#define N 100010
int n;
double c;
double cw = 0.0;
double cp = 0.0;
double bestp = 0.0;
int put[100];
struct Edge {
double perp;
int order;
double v;
double w;
Edge() {};
Edge(double a, int b, double c, double d) {
perp = a;
order = b;
v = c;
w = d;
}
bool operator <(const Edge& S)const {
return perp > S.perp;
}
}e[N];
void knapsack()
{
int i, j;
for (i = 1; i <= n; i++)
e[i].perp = e[i].v / e[i].w;
sort(e + 1, e + n);
}
void backtrack(int i)
{
double bound(int i);
if (i > n)
{
bestp = cp;
return;
}
if (cw + e[i].w <= c)
{
cw += e[i].w;
cp += e[i].v;
put[i] = 1;
backtrack(i + 1);
cw -= e[i].w;
cp -= e[i].v;
}
if (bound(i + 1) > bestp)
backtrack(i + 1);
}
double bound(int i)
{
double leftw = c - cw;
double b = cp;
while (i <= n && e[i].w <= leftw)
{
leftw -= e[i].w;
b += e[i].v;
i++;
}
if (i <= n)
b += e[i].v / e[i].w * leftw;
return b;
}
int main()
{
int i;
cout << "请输入物品的数量和背包的容量:" << endl;
cin >> n >> c;
cout << "请依次输入" << n << "个物品的重量:" << endl;
for (i = 1; i <= n; i++) {
cin >> e[i].w;
e[i].order = i;
}
cout << "请依次输入" << n << "个物品的价值:" << endl;
for (i = 1; i <= n; i++) {
cin >> e[i].v;
}
knapsack();
backtrack(1);
printf("最优价值为:%.2lf\n", bestp);
printf("需要装入的物品编号是:");
for (i = 1; i <= n; i++)
{
if (put[i] == 1)
cout << e[i].order<<" ";
}
return 0;
}
实验结果: