数据结构:地图着色问题——图的应用——回溯法

目录

前言

本次解决的问题:用图模拟部分地图,对各省进行着色,要求相邻省所使用的颜色不同,并保证使用的颜色总数最少。

先来一张效果图


一、解决问题的思路

将邻接矩阵创建好了以后,通过回溯函数,在解空间树中搜索所有的可行解,如果着色有冲突,就回溯到上一个节点。一旦到达叶子节点,也就是这个解到头了,就输出这种着色方案。

二、存储结构设计

a

抽象数据类型:

        ADT Graph{

        数据对象V:一个非空集合,该集合中的所有元素具有相同的特性。

        数据关系RR={VR}VR={<x,y> | P(x,y) (x,y∈V)}

基本操作:

        Createmap(&G)

        操作前提:已知图G不存在。

        操作结果:创建图G

        }ADT Graph;

(b) 存储结构:顺序表存储结构

        typedef struct{

           char vertax[MAX][MAX];//顶点所属的省份

           int map[MAX][MAX]; //邻接矩阵

           int n; //顶点个数

           int m;//边的个数

        }Graph;

三、代码

1.创建图函数

这里没什么好说的,都是创建图所需要的输入。顶点个数,顶点所代表的省份也就是顶点名称,边的个数,有边相连的两个顶点。

void Createmap(Graph &G) //创建邻接矩阵
{
	printf("请输入顶点(省份)个数:");
    int f=scanf("%d", &G.n);
    while(f!=1)
	{
		printf("输入值非法!\n");
		printf("请重新输入顶点(省份)个数:");
		fflush(stdin);
		f=scanf("%d", &G.n);
	}
    for(int i=1;i<=G.n;i++)
    {
    	printf("请输入第%d个顶点所代表的省份:",i);
    	cin>>G.vertax[i];
	}
    int u; //顶点1
    int v; //顶点2
    cout << "\n请输入边的个数:";
    cin >> G.m;
    cout << "请输入有边相连的两个顶点u和v:"<< endl;
    for (int i=1;i<=G.m;i++)//为邻接矩阵赋值 
    {
        cin>>u>>v;
        G.map[u][v]=1;
        G.map[v][u]=1;
    }
    cout<<endl;
}

2.判断色号是否相同函数

先判断是否相连,如果相连则判断两个顶点颜色是否一样。如果顶点颜色不冲突,就返回真,反之返回假。

bool Find(Graph G,int t) //判断色号是否相同的函数
{
    for(int j=1;j<t;j++) //判断现在扩展点t和前面t-1个顶点有没有相连的
    {
        if(G.map[t][j]) //如果相连
        {
            if(color[j]==color[t]) //且颜色一样
            {
                return false; //返回false,表示需要换个色号尝试
            }
        }
    }
    return true;
}

3.回溯函数

判断是否到达了叶子节点,如果没有,则开始试探这个顶点着此颜色行不行,行就向下递归,进入下一个节点开始着色试探,不行就换一种颜色继续试探,直到所有颜色被试探完。

void Backtrack(Graph G,int t) //回溯函数
{
    if(t>G.n) //到达叶子节点,打印一种填色方案 
    {
    	answer=0;//找到最少的颜色个数 
        sum++; //方案个数+1
        cout << "第"<< sum << "种方案:";
        for (int i=1;i<=G.n;i++)
        {
            cout << color[i] << " ";
        }
        cout << endl;
    }
    else
    {
        for (int i=1;i<=color_nums;i++) //尝试别的色号
        {
            color[t]=i;  //试探色号i 
            if(Find(G,t)) //如果色号没有撞
            {
                Backtrack(G,t+1); //向下递归 
            }
        }
    }
}

4.整体代码

#include <iostream>
using namespace std;
 
const int MAX=111;//最大存储个数 
typedef struct{
	char vertax[MAX][MAX];//顶点所属的省份 
	int map[MAX][MAX]; //邻接矩阵
	int n; //顶点个数
	int m;//边的个数
}Graph; 

int color[MAX]; //解空间,表示第i个省所填的颜色 
int sum=0; //记录有多少种方案

int color_nums=0; //颜色数量
int answer=1;

void Createmap(Graph &G) //创建邻接矩阵
{
	printf("请输入顶点(省份)个数:");
    int f=scanf("%d", &G.n);
    while(f!=1)
	{
		printf("输入值非法!\n");
		printf("请重新输入顶点(省份)个数:");
		fflush(stdin);
		f=scanf("%d", &G.n);
	}
    for(int i=1;i<=G.n;i++)
    {
    	printf("请输入第%d个顶点所代表的省份:",i);
    	cin>>G.vertax[i];
	}
    int u; //顶点1
    int v; //顶点2
    cout << "\n请输入边的个数:";
    cin >> G.m;
    cout << "请输入有边相连的两个顶点u和v:"<< endl;
    for (int i=1;i<=G.m;i++)//为邻接矩阵赋值 
    {
        cin>>u>>v;
        G.map[u][v]=1;
        G.map[v][u]=1;
    }
    cout<<endl;
}
 
bool Find(Graph G,int t) //判断色号是否相同的函数
{
    for(int j=1;j<t;j++) //判断现在扩展点t和前面t-1个顶点有没有相连的
    {
        if(G.map[t][j]) //如果相连
        {
            if(color[j]==color[t]) //且颜色一样
            {
                return false; //返回false,表示需要换个色号尝试
            }
        }
    }
    return true;
}
 
void Backtrack(Graph G,int t) //回溯函数
{
    if(t>G.n) //到达叶子节点,打印一种填色方案 
    {
    	answer=0;//找到最少的颜色个数 
        sum++; //方案个数+1
        cout << "第"<< sum << "种方案:";
        for (int i=1;i<=G.n;i++)
        {
            cout << color[i] << " ";
        }
        cout << endl;
    }
    else
    {
        for (int i=1;i<=color_nums;i++) //尝试别的色号
        {
            color[t]=i;  //试探色号i 
            if(Find(G,t)) //如果色号没有撞
            {
                Backtrack(G,t+1); //向下递归 
            }
        }
    }
}
 
void Print() 
{
	printf("\n最少需要%d种颜色",color_nums);
}

int main()
{
	cout << "用图模拟部分地图,对各省进行着色,要求相邻省所使用的颜色不同,并保证使用的颜色总数最少\n"<< endl;
	Graph G;

    Createmap(G);
    while(answer)
    {
    	color_nums++;//颜色总数+1 
    	Backtrack(G,1);	
	}
	 
    Print();
    return 0;
}

总结

回溯算法采取的是这条路走不通就返回换下一条路走的思想,我觉得是蛮力法的优化算法,它避免了蛮力法穷举式的搜索。这是一种具有深度优先的策略,也就是如果某一个节点满足约束条件,则继续向下一个节点试探,直到找到解为止。虽然回溯法有限界和剪枝函数减小了搜索范围,但本身是类似于穷举思想,所以时间复杂度仍然很高,但也因此解决问题的范围很广。

  • 10
    点赞
  • 72
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
回溯法(Backtracking)是一种暴力搜索算法,它尝试在所有可能的解中寻找正确的解。回溯法通常用于组合问题,其中我们尝试从一组可能的解中选择一个最佳解。 地图着色问题是指在地上给不同的区域着不同的颜色,使得相邻的区域颜色不同。这是一个经典的组合问题,可以使用回溯法解决。 下面是 Java 代码实现: ```java public class MapColoring { // 地邻接矩阵 private int[][] map; // 区域颜色 private int[] color; // 区域数量 private int n; public MapColoring(int[][] map, int n) { this.map = map; this.n = n; this.color = new int[n]; } public void colorMap() { if (backtrack(0)) { printSolution(); } else { System.out.println("No solution exists."); } } private boolean backtrack(int area) { // 如果所有区域都已经着色,返回 true if (area == n) { return true; } // 尝试给当前区域着色 for (int c = 1; c <= 4; c++) { if (isSafe(area, c)) { color[area] = c; // 递归着色下一个区域 if (backtrack(area + 1)) { return true; } // 如果不能着色,则回溯 color[area] = 0; } } return false; } private boolean isSafe(int area, int c) { // 检查相邻区域是否有着相同的颜色 for (int i = 0; i < n; i++) { if (map[area][i] == 1 && c == color[i]) { return false; } } return true; } private void printSolution() { System.out.println("Solution exists:"); for (int i = 0; i < n; i++) { System.out.println("Area " + (i + 1) + " is colored with " + color[i]); } } } ``` 在上述代码中,`backtrack()` 方法用于尝试给当前区域着色,并递归着色下一个区域,如果不能着色,则回溯。`isSafe()` 方法用于检查相邻区域是否有着相同的颜色。`printSolution()` 方法用于输出结果。 如果地可以被正确着色,则 `colorMap()` 方法将打印结果。否则,它将打印“无解”。 下面是一个例子: ```java public static void main(String[] args) { int[][] map = { {0, 1, 1, 1}, {1, 0, 1, 0}, {1, 1, 0, 1}, {1, 0, 1, 0} }; MapColoring mc = new MapColoring(map, 4); mc.colorMap(); } ``` 输出结果为: ``` Solution exists: Area 1 is colored with 1 Area 2 is colored with 2 Area 3 is colored with 3 Area 4 is colored with 2 ``` 在这个例子中,地可以被正确着色,其中每个区域都着有不同的颜色。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值