【C语言】回溯法的设计与实现

404 阅读4分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

【C语言】回溯法的设计与实现

一、目的

掌握回溯法的设计思想、具体实现和时间复杂度分析。

二、实验内容

先用伪代码或流程图描述利用回溯法解决的算法解决方案,再用程序实现,计算时间复杂度,记录最终测试数据和测试结果 2.1实验内容: 2.1.1 素数环问题 2.1.2 图着色问题 2.1.3 哈密顿回路问题

三、设计和编码

3.1算法设计
3.1.1素数环问题
输入:要填写到环中的整数个数n
输出:素数环的元素a[n]
1.将存储素数环的数组a[]中所以元素初始化为0
2.将a[0]初始化为1, k初始化为1
3. while(k>= 1)
3.1 a[k] = a[k]+1
3.2 while(a[k] <= n)
3.2.1 如果a[k]满足约束条件,结束循环
3.2.2如果a[k]不满足约束条件,a[k] = a[k]+1
3.3 如果a[k]<=n, 且k==n-1,输出素数环,结束程序
3.4如果a[k]<=n,且k<n-1,k= k+1
3.5 否则,a[k]=0, k=k-1,即回溯

3.1.3图着色问题

  1. color[1]=1; //顶点1着颜色1
  2. for (i=2; i<=n; i++) //其他所有顶点置未着色状态
    color[i]=0,
  3. k=0;
    4.循环直到所有顶点均着色
    4.1 k++; //取下一个颜色
    4.2 for(i=2;i<=n; i++) //用颜色k为尽 量多的顶点着色
    4.2.1若顶点i记着色, 则转步骤4.2,考虑下一个顶点;
    4.2.2若顶点i着颜色k与图中与顶点i邻接的顶点不冲突,
    则color[j]=k;
    5.输出k;

3.1.3哈密顿回路问题
dfs(当前位置,走过的点数,过的边权和){
if(已走过n个点 and 当前的点与起点之间有边 and 当前边权为已找到的方案中最小的){
记录这个方案的的路径;
}
遍历与当前节点相连的边{
if(指向的节点没被访问过){
标记为访问过;
在当前路径中加入这个节点;
dfs(指向的节点,点数+1,边权和+这条边边权);
标记为未访问;
在当前路径中去除这个节点;//回溯
}
}
}
main(){
建图;
dfs(任意点,1,0);
输出路径;
}
3.2程序代码
3.2.1.素数环问题

#include<iostream>
using namespace std;
int n;
int a[20];
bool vist[20];
bool isPrime(int x){
    if(x < 2) return false;
    for(int i = 2; i*i <= x; i++){
        if(x%i == 0) return false;
    }
    return true;
}
void print(){
    for(int i = 0; i <= n-1; i++){
        cout << a[i] << " ";
    }
    cout << endl;
}
void dfs(int cur){
    int i;
    if(cur == n && isPrime(a[n-1]+a[0])){
        print();
        return;
    }
    for(i = 2; i <= n; i++){
        if(!vist[i]&&isPrime(i+a[cur-1])){
            a[cur] = i;
            vist[i] = 1;
            dfs(cur+1);
            vist[i] = 0;
        }
    }
}
int main(){
    cin >> n;
    if(n%2 == 0){
        a[0] = 1;
        vist[1] = 1;
        dfs(1);
    } 
    return 0;
}

3.2.2.图着色问题

#include <iostream>
using namespace std;
#define n 5
static int arc[100][100];
static int color[100];
int Ok(int i)
{
	for(int j=0; j<n; j++)
		if(arc[i][j]==1 && color[i]==color[j])
		return 0;
		return 1;
}
 
void ColorGraph()
{
	int k=0;
	int flag=1//表示图中还有尚未着色的顶点
	while(flag==1)
{
	k++;
	flag=0;
	for(int i=0; i<n; i++)
	{
		if(color[i]==0)
		{
		color[i]=k;
		if(!Ok(i))
	{
	color[i]=0;
	flag=1;
	}
}
int main()
{
cout<<"输入颜色的个数:";  
    int m;  
    cin>>m;  
    cout<<"输入无向图点和边的关系:"<<endl;  
    for(int i=0; i<n; i++)  
        for(int j=0; j<n; j++)  
        {  
            int a;  
            cin>>a;  
            arc[i][j]=a;  
        }  
    ColorGraph();  
cout<<"从1到5序号填色分别为:"<<endl;
for(int i=0; i<n; i++)
{
cout<<color[i]<<" ";
}
cout<<endl;
return 0;
}

3.2.3.哈密顿回路问题

#include<iostream>
#include<vector>
using namespace std;
const int INF=0x7fffffff;
int n,m,minn=INF,min_path[50001],flag[50001],map[1000][1000],fflag=0;
 
vector <int> now_path;
 
void dfs(int x,int s,int tot){
    if(s==n){
        if(map[x][1]!=INF&&tot+map[x][1]<minn){
            fflag=1;
            minn=tot+map[x][1];
            for(int i=0;i<n;i++)
                min_path[i]=now_path[i];
        }
        return;
    }
    for(int i=1;i<=n;i++){
        if(map[x][i]!=INF&&!flag[i]){
            flag[i]=1;
            now_path.push_back(i);
            dfs(i,s+1,tot+map[x][i]);
            flag[i]=0;
            now_path.pop_back();
        }
    }
}
 
int main(){
    cin>>n>>m;//n为节点个数,m为边个数 
    int x,y,z;
    for(int i=0;i<=n;i++){
        for(int j=0;j<=n;j++){
            map[i][j]=INF;
        }
    }
    for(int i=0;i<m;i++){
        cin>>x>>y>>z;
        map[x][y]=map[y][x]=z;
    }
    flag[1]=1;
    dfs(1,1,0);
    if(fflag){
        cout<<1<<"-"; 
        for(int i=0;i<n-1;i++){
            cout<<min_path[i]<<"-";
        }
        cout<<1;
    }
    else
        cout<<"No Solution!";
    return 0;
}

四、运行结果及分析

4.1运行结果 4.1.1素数环问题

image.png

时间复杂度:O(n^n)

4.1.2图着色问题

image.png

时间复杂度:O(n^2)

4.1.3哈密顿回路问题

image.png

时间复杂度:O(n^2)

4.2分析 确定了问题的解空间结构后,回溯法将从开始结点(根结点)出发,以深度优先的方式搜索整个解空间。开始结点成为活结点,同时也成为扩展结点。在当前的扩展结点处,向纵深方向搜索并移至一个新结点,这个新结点就成为一个新的活结点,并成为当前的扩展结点。如果在当前的扩展结点处不能再向纵深方向移动,则当前的扩展结点就成为死结点。此时应往回移动(回溯)至最近的一个活结点处,并使其成为当前的扩展结点。回溯法以上述工作方式递归地在解空间中搜索,直至找到所要求的解或解空间中已无活结点时为止。
运用回溯法解题的关键要素有以下三点:
(1) 针对给定的问题,定义问题的解空间;
(2) 确定易于搜索的解空间结构;
(3) 以深度优先方式搜索解空间,并且在搜索过程中用剪枝函数避免无效搜索

五、实验小结

回溯法中,每次扩大当前部分解时,都面临一个可选的状态集合,新的部分解就通过在该集合中选择构造而成。这样的状态集合,其结构是一棵多叉树,每个树结点代表一个可能的部分解,它的儿子是在它的基础上生成的其他部分解。树根为初始状态,这样的状态集合称为状态空间树。
回溯法对任一解的生成,一般都采用逐步扩大解的方式。每前进一步,都试图在当前部分解的基础上扩大该部分解。它在问题的状态空间树中,从开始结点(根结点)出发,以深度优先搜索整个状态空间。这个开始结点成为活结点,同时也成为当前的扩展结点。在当前扩展结点处,搜索向纵深方向移至一个新结点。这个新结点成为新的活结点,并成为当前扩展结点。如果在当前扩展结点处不能再向纵深方向移动,则当前扩展结点就成为死结点。此时,应往回移动(回溯)至最近的活结点处,并使这个活结点成为当前扩展结点。回溯法以这种工作方式递归地在状态空间中搜索,直到找到所要求的解或解空间中已无活结点时为止。
回溯法与穷举法有某些联系,它们都是基于试探的。穷举法要将一个解的各个部分全部生成后,才检查是否满足条件,若不满足,则直接放弃该完整解,然后再尝试另一个可能的完整解,它并没有沿着一个可能的完整解的各个部分逐步回退生成解的过程。而对于回溯法,一个解的各个部分是逐步生成的,当发现当前生成的某部分不满足约束条件时,就放弃该步所做的工作,退到上一步进行新的尝试,而不是放弃整个解重来。