蛮力法
文章目录
1. 蛮力法的原理及特点
蛮力法是一种简单直接地解决问题的方法,通常直接基于问题的描述和所涉及的概念定义,找出所有可能的解。然后选择其中的一种或多种解,若该解不可行则试探下一种可能的解。
蛮力法通常用于:
- 搜索所有解空间:问题的解存在于规模不大的解空间中。
- 搜索所有的路径:这类问题中不同的路径对应不同的解。
- 直接计算:按照基于问题的描述和所涉及的概念定义,直接进行计算。往往是一些简单的题,不需要算法技巧的。
- 模拟和仿真:按照求解问题的要求直接模拟或仿真即可。
2. 蛮力法的基本应用示例,掌握幂级空间和排列空间的构造
【基本应用示例】
1. 字符串匹配问题
2. 最大子序列和
【幂级空间】
1. 求解幂级问题
【问题描述】
对于给定的正整数n(n≥1),求1~n构成的集合的所有子集(幂集)。
【问题求解】
解法1:直接蛮力法
将 1~n 存放在数组b中,求解问题变为构造集合b的所有子集。
对于含有n(n≥1)个元素的集合a,求幂集的过程如下:
for (i=0;i<2n;i++) //穷举a的所有子集并输出
{
将i转换为二进制数b;
输出b中为1的位对应的a元素构成一个子集;
}
【代码】
//求解幂级问题
//对于给定的正整数n(n≥1),求1~n构成的集合的所有子集(幂集)。
//1. 采用直接蛮力法求解
#include<iostream>
#include<cmath>
using namespace std;
#define MAXN 20
void Bin(int a[],int n) //将a表示的二进制数增1
{
for (int i = 0; i < n; i++)
{
if (a[i]) //将元素1改为0
a[i] = 0;
else //将元素0改为1并退出for循环
{
a[i] = 1;
break;
}
}
}
void Solve(int a[], int b[], int n)
{
int pw = pow(2, n); //求2^n
cout << "幂集为:";
for (int i = 0; i < pw; i++) //执行2^n次
{
cout << "{";
for(int j=0;j<n;j++) //执行n次
if (a[j])
{
cout << b[j] << " ";
}
cout << "}";
Bin(a, n);
}
cout << endl;
}
int main()
{
int a[MAXN], b[MAXN];
int n;
cout << "请输入n:";
cin >> n;
cout << "请依次输入数字:";
for (int i = 0; i < n; i++)
cin >> b[i];
Solve(a, b, n);
system("pause");
return 0;
}
【算法分析】
显然该算法的时间复杂度为O(n×2n),属于指数级的算法。
解法2:增量蛮力法
【问题求解】
即穷举1~n的所有子集。先建立一个空子集,对于i(1≤i≤n),每次都是在前面已建立的子集上添加元素i而构成若干个子集。
void f(int n) //求1~n的幂集ps
{
置ps={{}}; //在ps中加入一个空子集元素
for (i=1;i<=n;i++)
在ps的每个元素中添加i而构成一个新子集;
}
【代码】
#include<iostream>
#include<vector>
using namespace std;
vector<vector<int> > ps; //存放幂集
void PSet(int n) //求1~n的幂集ps
{
vector<vector<int> > ps1; //子幂集
vector<vector<int> >::iterator it;//幂集迭代器
vector<int> s;
vector<int>::iterator sit;
ps.push_back(s); //添加{}空集合元素
for (int i = 1; i <= n; i++) //循环添加1~n
{
ps1 = ps; //ps1存放上一步得到的幂集
for (it = ps1.begin(); it != ps1.end(); ++it)
(*it).push_back(i); //在ps1的每个集合元素末尾添加i
for (it = ps1.begin(); it != ps1.end(); ++it)
ps.push_back(*it); //将ps1的每个集合元素添加到ps中
}
for (it = ps.begin(); it != ps.end(); it++) {
cout << "{";
for (sit = (*it).begin(); sit != (*it).end(); sit++)
cout << " " << *sit << " ";
cout << "}";
}
cout << endl;
}
int main()
{
int n;
cout << "请输入n:";
cin >> n;
cout << "1~" << n << "的幂集为:";
PSet(n);
system("pause");
return 0;
}
【算法详解】
对于给定的n,每一个集合元素都要处理,有2n个,所以上述算法的时间复杂度为O(2n)。
2. 求解0/1背包问题
【问题描述】
有n个重量分别为{w1,w2,…,wn}的物品,它们的价值分别为{v1,v2,…,vn},给定一个容量为W的背包。设计从这些物品中选取一部分物品放入该背包的方案,每个物品要么选中要么不选中,要求选中的物品不仅能够放到背包中,而且具有最大的价值。并对下表所示的4个物品求出W=6时的所有解和最佳解。
物品编号 | 重量 | 价值 |
---|---|---|
1 | 5 | 4 |
2 | 3 | 4 |
3 | 2 | 3 |
4 | 1 | 1 |
【问题求解】
对于n个物品、容量为W的背包问题,采用前面求幂集的方法求出所有的物品组合。
对于每一种组合,计算其总重量sumw和总价值sumv,当sumw小于等于W时,该组合是一种解,并通过比较将最佳方案保存在maxsumw和maxsumv中,最后输出所有的解和最佳解。
【代码】
#include<iostream>
#include<vector>
using namespace std;
vector<vector<int> > ps; //存放幂集
void pSet(int n)
{
vector<vector<int> >ps1; //存放子幂集
vector<vector<int> >::iterator it; //子幂集迭代器
vector<int> s;
ps.push_back(s); //添加{ }空集合元素
for (int i = 1; i <= n; i++)
{
ps1 = ps; //ps1存放上一步存放的幂集
for (it = ps1.begin(); it != ps1.end(); ++it) //在ps1上的每个集合元素末尾添加i
{
(*it).push_back(i);
}
for (it = ps1.begin(); it != ps1.end(); ++it) //将ps1的每个集合元素添加到ps中
{
ps.push_back(*it);
}
}
}
void Knap(int w[], int v[], int W) //求所有方案和最佳方案
{
int count = 0; //方案编号
int sumw, sumv; //当前方案的总重量和总价值
int maxi, maxsumw = 0, maxsumv = 0; //最优方案的编号,总重量和总价值
vector<vector<int> >::iterator it; //幂集迭代器
vector<int>::iterator sit; //幂集集合元素迭代器
cout << " 序号\t选中物品\t总重量\t总价值\t能否装入" << endl;
for (it = ps.begin(); it != ps.end(); ++it)
{
cout << count + 1 << "\t";
sumw = sumv = 0;
cout << "{";
for (sit = (*it).begin(); sit != (*it).end(); ++sit)
{
cout << *sit;
sumw += w[*sit - 1]; //w[]数组从下标0开始
sumv += v[*sit - 1]; //v[]数组从下标0开始
}
cout << "}\t\t" << sumw << "\t" << sumv << "\t";
if (sumw <= W)
{
cout << "Yes" << endl;
if (sumv > maxsumv) //比较求出最优方案
{
maxsumw = sumw;
maxsumv = sumv;
maxi = count;
}
}
else
cout << "No" << endl;
count++; //方案编号加1
}
cout << "最佳方案为: ";
cout << "选中物品";
cout << "{";
for (sit = ps[maxi].begin(); sit != ps[maxi].end(); ++sit)
cout << *sit;
cout << "}";
cout << "总重量:" << maxsumw << "总价值:" << maxsumv;
}
int main()
{
int n = 4, W = 6;
int w[] = { 5,3,2,1 };
int v[] = { 4,4,3,1 };
pSet(n);
printf("0/1背包的求解方案\n", n);
Knap(w, v, W);
system("pause");
return 0;
}
【运行截图】
【排列空间】
1. 求解全排列问题
【问题描述】
对于给定的正整数n(n≥1),求1~n的所有全排列。
【问题求解】
这里采用增量蛮力法求解。产生1~3全排列的过程如下:
【代码】
#include<iostream>
#include<vector>
using namespace std;
vector<vector<int> > ps; //存放全排列
void Insert(vector<int> s, int i, vector<vector<int> > &ps1)
{
//在每个集合元素中间插入i得到ps1
vector<int> s1;
vector<int>::iterator it;
for (int j = 0; j < i; j++)
{
s1 = s;
it = s1.begin() + 1; //求出插入位置
s1.insert(it, i);
ps1.push_back(s1); //添加到ps1中
}
}
//permutation [数]排列、置换
void Perm(int n)
{
vector<vector<int> > ps1;
vector<vector<int> >::iterator it;
vector<int> s, s1;
vector<int>::iterator sit;
s.push_back(1);
ps.push_back(s); //添加{1}集合元素
for (int i = 2; i <= n; i++)
{
ps1.clear();
for (it = ps.begin(); it != ps.end(); ++it)
Insert(*it, i, ps1);
ps = ps1;
for (it = ps.begin(); it != ps.end(); ++it)
{
cout << "{";
for (sit = (*it).begin(); sit != (*it).end(); sit++)
cout << " " << *sit << " ";
cout << "}";
}
}
}
int main()
{
int n = 3;
cout << n << "的全排:" << endl;
Perm(n);
cout << endl;
system("pause");
return 0;
}
【算法分析】
对于给定的正整数n,每一种全排列都必须处理,有n!种,所以上述算法的时间复杂度为O(n*n!)。
2. 求解任务分配问题
【问题描述】
有n(n≥1)个任务需要分配给n个人执行,每个任务只能分配给一个人,每个人只能执行一个任务。第i个人执行第j个任务的成本是c[i][j](1≤i,j≤n)。求出总成本最小的分配方案。
【问题求解】
所谓一种分配方案就是由第i个人执行第j个任务,用(a1,a2,…,an)表示,即第1个人执行第a1个任务,第2个人执行第a2个任务,以此类推。全部的分配方案恰好是1~n的全排列。
这里采用增量穷举法求出所有的分配方案ps(全排列),再计算出每种方案的成本,比较求出最小成本的方案,即最优方案。
【代码】
#include<iostream>
#include<vector>
using namespace std;
#define MAXN 105
#define INF 0x3f3f3f3f
int n = 4;
int c[MAXN][MAXN] = { {9,2,7,8},{6,4,3,7},{5,8,1,8},{7,6,9,4} };
vector<vector<int> > ps; //存放全排列
void Insert(vector<int> s, int i, vector<vector<int> > &ps1)
{
vector<int> s1;
vector<int>::iterator it;
for (int j = 0; j < i; j++)
{
s1 = s;
it = s1.begin() + 1;
s1.insert(it, i);
ps1.push_back(s1);
}
}
void Perm(int n)
{
vector<vector<int> > ps1;
vector<vector<int> >::iterator it;
vector<int> s, s1;
s.push_back(1);
ps.push_back(s);
for (int i = 2; i <= n; i++)
{
ps1.clear();
for (it = ps.begin(); it != ps.end(); ++it)
Insert(*it, i, ps1);
ps = ps1;
}
}
void Allocate(int n, int &mini, int &minc)
{
Perm(n);
for (int i = 0; i < ps.size(); i++)
{
int cost = 0;
for(int j=0;j<ps[i].size();j++)
cost += c[j][ps[i][j] - 1];
if (cost < minc)
{
minc = cost;
mini = i;
}
}
}
int main()
{
int mincost = INF, mini;
Allocate(n, mini, mincost);
printf("最优方案:\n");
for (int k = 0; k < ps[mini].size(); k++)
printf(" 第%d个人安排任务%d\n", k + 1, ps[mini][k]);
printf(" 总成本=%d\n", mincost);
system("pause");
return 0;
}
3. 图的深度优先和广度优先遍历算法
图的存储结构:邻接矩阵、邻接表
邻接矩阵的存储方法
邻接矩阵的类型定义
//邻接矩阵的类型定义
#define MAXV 20
#define MAXL 15
typedef struct
{
int no; //顶点编号
char data[MAXL]; //顶点其他信息
} VertexType; // 顶点类型||vertex [数]顶点
typedef struct
{
int edges[MAXV][MAXV]; //邻接矩阵的边数组
int n, e; //顶点数、边数
VertexType vexs[MAXV]; //存放顶点信息
} MGraph; //完整的图邻接矩阵类型
邻接表存储方法
图的邻接表存储方法是一种链式存储结构。图的每个顶点建立一个单链表,第i(0≤i≤n-1)个单链表中的结点表示依附于顶点i的边。每个单链表上附设一个表头结点,将所有表头结点构成一个表头结点数组。
邻接表的类型定义
//邻接表的类型定义
typedef struct ANode
{
int adjvex; //该边的终点编号
int weight; //该边的权值
struct ANode *nextarc; //指向下一条边的指针
} ArcNode; //边结点类型
typedef struct Vnode
{
char data[MAXL]; //顶点其他信息
ArcNode *firstarc; //指向下一条边
} VNode; //邻接表头结点类型
typedef VNode AdjList[MAXV]; //AdiList是邻接表类型
typedef struct
{
AdjList adjlist; //邻接表
int n, e; //图中的顶点数和边数
} ALGraph;
【深度优先遍历】
图的遍历:从给定图中任意指定的顶点(称为初始点)出发,按照某种搜索方法沿着图的边访问图中的所有顶点,使每个顶点仅被访问一次。
深度优先搜索的过程:
(1)从图中某个初始顶点v出发,首先访问初始顶点v。
(2)然后选择一个与顶点v相邻且没被访问过的顶点w为初始顶点,再从w出发进行深度优先搜索。
(3)重复直到图中与当前顶点v邻接的所有顶点都被访问过为止。显然,这个搜索过程是个递归过程。
1. 邻接表求解简单路径
【问题描述】
假设图G采用邻接表存储,设计一个算法判断图G中从顶点u到v是否存在简单路径。
【问题求解】
所谓简单路径是指路径上的顶点不重复,采用深度优先遍历的方法。
【代码】
#include<iostream>
#include<vector>
using namespace std;
//邻接矩阵的类型定义
#define MAXV 20
#define MAXL 15
#define INF 0x3f3f3f3f
int visited[MAXV]; //访问数组
//邻接表的类型定义
typedef struct ANode
{
int adjvex; //该边的终点编号
int weight; //该边的权值
struct ANode *nextarc; //指向下一条边的指针
} ArcNode; //边结点类型
typedef struct Vnode
{
char data[MAXL]; //顶点其他信息
ArcNode *firstarc; //指向下一条边
} VNode; //邻接表头结点类型
typedef VNode AdjList[MAXV]; //AdiList是邻接表类型
typedef struct
{
AdjList adjlist; //邻接表
int n, e; //图中的顶点数和边数
} ALGraph;
//判断G中从顶点u到v是否存在简单路径
bool ExistPath(ALGraph *G, int u, int v)
{
int w;
ArcNode *p;
visited[u] = 1; //置已访问标记
if (u == v) //找到一条路径,返回true
return true;
p = G->adjlist[u].firstarc; //p指向顶点u的第一个相邻点
while (p != NULL)
{
w = p->adjvex; //w为顶点u的相邻顶点
if (visited[w] == 0) //若w顶点未被访问,则递归访问它
{
bool flag = ExistPath(G, w, v);
if (flag) return true;
}
p = p->nextarc; //p指向顶点u的下一个相邻点
}
return false; //如果没有找到,返回false
}
void CreateAdj(ALGraph *&G, int a[][MAXV], int n, int e) { //建立图的邻接表
ArcNode *p;
G = (ALGraph *)malloc(sizeof(ALGraph));
G->n = n;
G->e = e;
for (int k = 0; k < n; k++)
G->adjlist[k].firstarc = NULL;
for (int i = 0; i < n; i++)
for (int j = 0; j < n; j++)
if (a[i][j] != 0 && a[i][j] != INF) {
p = (ArcNode *)malloc(sizeof(ArcNode));
p->adjvex = j;
p->weight = a[i][j];
p->nextarc = G->adjlist[i].firstarc;
G->adjlist[i].firstarc = p;
}
}
void DispAdj(ALGraph *G) {
ArcNode *p;
for (int i = 0; i < G->n; i++) {
cout << i;
p = G->adjlist[i].firstarc;
while (p != NULL) {
cout << "->" << "结点编号:" << p->adjvex << " 边的权值:" << p->weight;
p = p->nextarc;
}
cout << "^" << endl;
}
}
void DestroyAdj(ALGraph *&G) { // 销毁图的邻接表
ArcNode * pre, *p;
for (int i = 0; i < G->n; i++) {
pre = G->adjlist[i].firstarc;
while (pre != NULL) {
p = pre->nextarc;
free(pre);
pre = p;
}
}
free(G);
}
int main()
{
ALGraph *G;
int a[MAXV][MAXV] ={ {0,1,0,0,1},{1,0,1,1,1},{0,1,0,1,0},{0,1,1,0,1},{1,1,0,1,0} };
int n = 5, e = 7;
CreateAdj(G, a, n, e); //创建图的邻接表存储结构G
cout << "图的邻接表如下:" << endl;
DispAdj(G);
for (int i = 0; i < n; i++)
for (int j = 0; j < n; j++) {
memset(visited, 0, sizeof(visited)); // 对数组visited进行初始化
if (ExistPath(G, i, j) && i != j)
cout << "顶点" << i << "到顶点" << j << "存在简单路径" << endl;
}
DestroyAdj(G);
system("pause");
return 0;
}
【广度优先遍历】
广度优先搜索的过程是:
(1)首先访问初始顶点v。
(2)接着访问顶点v的所有未被访问过的邻接点v1,v2,…,vt。
(3)然后再按照v1,v2,…,vt的次序,访问每一个顶点的所有未被访问过的邻接点,依次类推,直到图中所有和初始顶点v有路径相通的顶点都被访问过为止。
以邻接矩阵为图的存储结构,采用广度优先搜索图:
void BFS(MGraph g, int v) //邻接矩阵的BFS算法
{
queue<int> qu; //定义一个队列qu
int visited[MAXV]; //定义存放结点的访问标志的数组
int w, i;
memset(visited, 0, sizeof(visited)); //访问标志数组初始化
printf("%3d", v); //输出被访问顶点的编号
visited[v] = 1; //置已访问标记
qu.push(v); //v进队
while (!qu.empty()) //队列不空时循环
{
w = qu.front(); qu.pop(); //出队顶点w
for (i = 0; i < g.n; i++) //找与顶点w相邻的顶点
if (g.edges[w][i] != 0 && g.edges[w][i] != INF && visited[i] == 0)
{ //若当前邻接顶点i未被访问
printf("%3d", i); //访问相邻顶点
visited[i] = 1; //置该顶点已被访问的标志
qu.push(i); //该顶点进队
}
}
printf("\n");
}
以邻接表为图的存储结构,采用广度优先搜索图:
void BFS(ALGraph *G, int v) //邻接表的BFS算法
{
ArcNode *p;
queue<int> qu; //定义一个队列qu
int visited[MAXV], w; //定义存放顶点的访问标志的数组
memset(visited, 0, sizeof(visited)); //访问标志数组初始化
printf("%3d", v); //输出被访问顶点的编号
visited[v] = 1; //置已访问标记
qu.push(v); //v进队
while (!qu.empty()) //队列不空时循环
{
w = qu.front(); qu.pop(); //出队顶点w
p = G->adjlist[w].firstarc; //找顶点w的第一个邻接点
while (p != NULL)
{
if (visited[p->adjvex] == 0) //若当前邻接顶点未被访问
{
printf("%3d", p->adjvex); //访问相邻顶点
visited[p->adjvex] = 1; //置该顶点已被访问的标志
qu.push(p->adjvex); //该顶点进队
}
p = p->nextarc; //找顶点w的下一个邻接点
}
}
}