算法分析设计——递归&&回溯

目录

一、装载问题

二、N皇后问题

1.N皇后

2.排列组合

3.找素数

三、图的m着色问题

四、最大团问题

五、批作业处理调度


一、装载问题

1.递归法+dp

#include <iostream>//回溯
#include <algorithm>
using namespace std;

const int N = 1000;
int n, m, maxval = 0;
int w[N], val[N];//重量和价值
int x[N];//存储选择物品的编号
int op[N];//临时变量

void dfs(int i, int tw, int tv, int op[]) {
    if (i > n) { 
        if (tw <= m && tv > maxval) { // 找到一个满足条件的更优解保存
            maxval = tv;
            for (int j = 1; j <= n; j++) {
                x[j] = op[j];
            }
        }
    } 
    else { // 尚未找完所有物品
        op[i] = 1; // 选取第 i 个物品
        dfs(i + 1, tw + w[i], tv + val[i], op);
        
        op[i] = 0; // 不选取第 i 个物品,回溯
        dfs(i + 1, tw, tv, op);
    }
}

int main()
{
    cin >> n >> m;
    for (int i = 1; i <= n; i++) {
        cin >> w[i] >> val[i];
    }
    dfs(1, 0, 0, op);
    cout << maxval<<endl;
    for (int i = 1; i <= n; i++) {
        if(x[i]) cout<<i<<" ";//选择物品编号
    }
    return 0;
}



#include<iostream>//dp
#include<algorithm>
using namespace std;

const int N = 1005;
int v[N];    // 体积
int w[N];    // 价值 
int f[N][N];// f[i][j]表示在j体积下前i个物品的最大价值 

int main() 
{
    int n, m;   
    cin >> n >> m;
    for(int i = 1; i <= n; i++) 
        cin >> v[i] >> w[i];

    for(int i = 1; i <= n; i++)
    {
        for(int j = 0; j <= m; j++)
        {
            if(j < v[i]) //  当前背包容量装不进第i个物品,则价值等于前i-1个物品
                f[i][j] = f[i - 1][j];
            else    
                f[i][j] = max(f[i - 1][j], f[i - 1][j - v[i]] + w[i]);
        }           
    }
    cout << f[n][m] << endl;
    return 0;
}

二、N皇后问题

1.N皇后

#include <iostream>
using namespace std;

const int N = 11;
char q[N][N];//存储棋盘
bool dg[N * 2], udg[N * 2], cor[N];//点对应的两个斜线以及列上是否有皇后
int n;

void dfs(int r)
{
    if(r == n)//放满了棋盘,输出棋盘
    {
        for(int i = 0; i < n; i++)
        {
            for(int j = 0; j < n; j++)
                cout << q[i][j];
            cout << endl;
        }
        cout << endl;
        return;
    }

    for(int i = 0; i < n; i++)//第r行,第i列是否放皇后
    {
        if(!cor[i] && !dg[i + r] && !udg[n - i + r])//不冲突,放皇后
        {
            q[r][i] = 'Q';
            cor[i] = dg[i + r] = udg[n - i + r] = 1;//对应的 列, 斜线 状态改变
            dfs(r + 1);//处理下一行
            cor[i] = dg[i + r] = udg[n - i + r] = 0;//恢复现场
            q[r][i] = '.';
        }
    }
}
int main()
{
    cin >> n;
    for (int i = 0; i < n; i ++ )
        for (int j = 0; j < n; j ++ )
            q[i][j] = '.';
    dfs(0);
    return 0;
}

2.排列组合

 题目描述

从 1∼n 这 n 个整数中随机选出 m 个,输出所有可能的选择方案。

输入格式
两个整数 n,m ,在同一行用空格隔开。

输出格式
按照从小到大的顺序输出所有方案,每行 1 个。

首先,同一行内的数升序排列,相邻两个数用一个空格隔开。

其次,对于两个不同的行,对应下标的数一一比较,字典序较小的排在前面(例如 1 3 5 7 排在 1 3 6 8 前面)。

数据范围
n>0 ;0≤m≤n ;n+(n−m)≤25

输入样例:

5 3

输出样例:

1 2 3 
1 2 4 
1 2 5 
1 3 4 
1 3 5 
1 4 5 
2 3 4 
2 3 5 
2 4 5 
3 4 5 
#include<iostream>
#include<algorithm>
#include<cstring>

using namespace std;

const int N = 1e6 + 10;
int n, m, a[N], b[N];

void dfs(int x, int st, int p)
{
	if (x == p)
	{
		for (int i = 0; i < p; i++)//限定打印个数
		{
			cout << a[i]<< " ";
		}
		cout << endl;
		return;
	}
	for (int i = st; i <= n; i++)//不逆序就是字典序的核心
	{
		if (b[i] == 0)
		{
			a[x] = i;
			b[i] = 1;
			dfs(x + 1, i + 1, p);
			b[i] = 0;
		}
	}
}
int main()
{
	cin >> n >> m;
	dfs(0, 1, m);
	return 0;
}

3.找素数

题目描述

已知n个整数x1,x2,x3,...,xn,以及1个整数k( k < n)。从n个整数中任选k个整数相加,可分别得到一系列的和。列如当n = 4,k = 3,4个整数分别为3,7,12,19时,可得全部的组合与它们的和为:
3+7+12=22
3+7+19=29
7+12+19=38
3+12+19=34
现在,要求你计算出和为素数共有多少种
例如上例,只有一种的和为素数:3+7+19=29。

输入
第一行两个空格隔开的整数n,k( 1 <= n <=20,k < n )。
第二行n个整数,分别为x1,x2,x3,...,xn( 1 <= xi  <= 5 * 106)


输出
输出一个整数表示种类数。


样例输入

4 3
3 7 12 19

样例输出

1
#include<iostream>
#include<cstring>
#include<algorithm>

using namespace std;
typedef long long LL;

const int N = 1e6 + 10;
LL n, m, a[N], b[N], ans, res;

int cal(int x)//判断素数
{
    if (x < 2) return 0;
    for (int i = 2; i <= x / i; i++)
    {
        if (x % i == 0) return 0;
    }
    return 1;
}

void dfs(int x, int st, int p)
{
    if (x == p + 1)
    {
        if (cal(res) == 1) ans++;
        return;
    }
    for (int i = st; i <= n; i++)//避免重复
    {
        if (b[i] == 0)
        {
            res += a[i];
            b[i] = 1;
            dfs(x + 1, i + 1, p);
            res -= a[i];
            b[i] = 0;
        }
    }
    return;
}

int main()
{
    cin >> n >> m;
    for (int i = 1; i <= n; i++) cin >> a[i];
    //sort(a + 1, a + n + 1);//排不排序都行的
    dfs(1, 1, m);
    cout << ans << endl;
    return 0;
}

三、图的m着色问题

给定无向连通图G=(V, E)和m种不同的颜色,用这些颜色为图G的各顶点着色,每个顶点着一种颜色。是否有一种着色法使G中相邻的两个顶点有不同的颜色

这个问题是图的m可着色判定问题。若一个图最少需要m种颜色才能使图中每条边连接的两个顶点着不同颜色,则称这个数m为该图的色数。求一个图的色数m的问题称为图的m可着色优化问题。

#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;

const int N = 100;
int n;//顶点数
int m;//颜色数
int a[N][N];//图的邻接矩阵
int x[N];//当前的解向量
int sum = 0;//解的数量
 
bool ok(int t) {
    int i;
    for (i = 1; i < n; i++) {//修正同色判断函数的循环范围
        if ((a[t][i] == 1) && (x[i] == x[t]))
            return false;
    }
    return true;
}

void BackTrack(int t) {
    int i;
    if (t > n) {
        sum++;
        cout << "第" << sum << ":种方案" << endl;//输出当前解的编号
        for (i = 1; i <= n; i++) {
            cout << x[i] << " ";//按照节点顺序输出颜色
        }
        cout << endl;
    }
    else
    {
        for (i = 1; i <= m; i++) {
            x[t] = i;
            if (ok(t))  BackTrack(t + 1);
            x[t] = 0;
        }
    }
}
 
int main() {
    cin >> n >> m;
    for (int i = 0; i < n; i++) {//初始化邻接矩阵为0
        for (int j = 0; j < n; j++) {
            a[i][j] = 0;
        }
    }
    
    int u, v;//读入边,构建邻接矩阵
    while (cin >> u >> v) {
        a[u - 1][v - 1] = 1;
        a[v - 1][u - 1] = 1;
    }
 
    BackTrack(1);//从第1个节点开始
    cout << "一共有" << sum << "个解。" << endl;//输出解的数量
    return 0;
}

四、最大团问题

问题描述:一个无向图G中寒顶点个数最多的完全子图成为最大团。输入含n个顶点(编号为1~n),m条边的无向图,求其最大团的顶点个数。其实就是就是给你一个无向图,让你找出这个无向图中顶点数最多的完全图(图中顶点两两连通 )

输入描述:输入多个测试用例,每个测试用例的第一行包含两个正整数n,m,接下来m行,每行两个整数s,t 表示顶点 s 与顶点 t 之间有一条边,以输入n=0,m=0结束,规定1<=n<=50并且1<=m<=300

#include<iostream>
#include<cstring>
#include<algorithm>

using namespace std;
const int N=1000;

int n, m;//无向图中顶点个数与边的个数
int a[N][N];//无向图矩阵
int ans;//用于记录当前最优解
int cnt;//当前深度遍历时已经纳入最大团的顶点数目
int x[N];// (x[i]==1表示将编号为i的顶点放入解向量中)
 
bool place(int t){
	for (int j = 1; j < t; j++) 
	{
		if (x[j] == 1 && a[t][j] == 0) 	
		   return false;
	}
	return true;
}

void dfs(int i) {
	if (i > n) {//递归出口:  所有顶点都已处理完毕
		if (cnt > ans)  ans = cnt;//替换最优解
		return;
	}
	if (place(i)) {//如果满足连通条件则将顶点放入解向量中
		x[i] = 1;
		cnt++;
		dfs(i + 1);
		x[i] = 0;//回溯
		cnt--;
	}
	if (cnt + n - i >=ans) {//利用一个简单的限界函数进行剪枝
    //只有当当前已经纳入结果的顶点数加上剩余未判断的顶点数大于等于当前最大解时才往下递归
		x[i] = 0;
		dfs(i + 1);
	}
}

int main() {
	int s, t;
	while (true) {
		cin >> n >> m;
		if (n == 0 && m == 0)  break;
		memset(a, 0, sizeof a);  memset(x, 0, sizeof x);
		cnt = 0; ans = 0;

		for (int i = 1; i <= m; i++) {
			cin >> s >> t;
			a[s][t] = 1;
			a[t][s] = 1;
		}
		dfs(1);
		cout << "最大团中的顶点数量为 " << ans << endl;
	}
	return 0;
}

五、批作业处理调度

#include <iostream>
using namespace std;

int n, bestf;   //f的最优值
int f;   //用于记录前i个作业在机器2上完成处理的时间和
int M[100][100];   //M[i][j]表示第i个作业在机器j上需要处理的时间
int x[100];   //x[i]表示第i个处理的作业为x[i]
int bestx[100];   //x[i]的最优值
int f1;   //作业在机器1上完成处理的时间
int f2[100];   //f2[i]表示第i个作业在机器2上完成处理的时间

void Swap(int &a,int &b){
    int temp;
    temp=a; a=b; b=temp;
}

void Backtrack(int i){
    if(i>n) {
        if(f<bestf)   //更新最优解
        {
            for(int j=1;j<=n;j++)
                bestx[j]=x[j];
            bestf=f;
        }
    }
    else
    {
        for(int j=i;j<=n;j++)   //控制展开i-1层结点的各个分支。例如当i=1时,表示在整棵排列树的根结点处,刚要开始探索结点,这时可以展开的分支有1、2、3……
        {
            f1+=M[x[j]][1];   //计算第i层(个)作业在机器1上的完成处理的时间
            if(f2[i-1]>f1)   f2[i]=f2[i-1]+M[x[j]][2];//如果第(i-1)个作业在机器2上的完成处理的时间大于第i个作业在机器1上的完成处理的时间,那么第i个作业想进入机器2,就要等第(i-1)个作业在机器2上完成后再说
            else    f2[i]=f1+M[x[j]][2];//否则,第i个作业可以在机器1上完成处理后直接进入机器2。
               
            f+=f2[i];   //计算完第i个作业在机器2上的完成处理的时间,就可以计算出前i个作业在机器2上完成处理的时间和了
            if(f<bestf)   //截止到这,已经得到一个前i个作业在机器2上完成处理的时间和f,如果f比之前记录的前i个作业在机器2上的完成处理的时间和的最优值bestf都要小,就可以生成第i层结点的孩子结点,继续探索下一层
            {
                Swap(x[i],x[j]);   //把处于同一层的并且使f更小的那个结点拿过来,放到正在探索的这个结点处(这里结合排列数的图可以更好地理解)
                Backtrack(i+1);   //继续探索以第i层结点为根结点的子树
                Swap(x[i],x[j]);   //探索完要回溯时,只要做探索前的反“动作”就可以了
            }
           f-=f2[i];   //探索完要回溯时,只要做探索前的反“动作”就可以了
           f1-=M[x[j]][1];   //探索完要回溯时,只要做探索前的反“动作”就可以了
        }
    }
}
void inPut()   //输入函数
{
    cout<<"请输入作业的个数n:"<<endl;
    cin>>n;
    cout<<"请分别输入n个作业在机器1和机器2上各自需要处理的时间(分两行):"<<endl;
    for(int i=1;i<=2;i++)
    {
        for(int j=1;j<=n;j++)
            cin>>M[j][i];
    }
}

void initialize()   //初始化函数
{
    for(int i=1;i<=n;i++)
        x[i]=i;   //初始化当前作业调度的一种排列顺序
    bestf=10000;   //此问题是得到最佳作业调度方案以便使其完成处理时间和达到最小,所以当前最优值bestf应该初始化赋值为较大的一个值
}

void outPut()   //输出函数
{
    cout<<"这"<<n<<"个作业的最佳调度顺序为:"<<endl;
    for(int i=1;i<=n;i++)
        cout<<bestx[i]<<" ";
    cout<<endl;
    cout<<"该作业调度的完成时间和为:"<<endl;
    cout<<bestf<<endl;
}

int main()
{
    inPut();
    initialize();
    Backtrack(1);
    outPut();
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

大小胖虎

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值