DFS及应用

DFS及应用

    • DFS遍历图
        • 代码步骤
        • 代码实现
        • 例题
            • 求解图中的连通分量问题
            • POJ 2815 城堡问题
    • DFS寻找到某个结点N的路径
        • 代码步骤
        • 代码实现
        • 例题
            • N皇后问题(经典例题)
            • POJ 1724 Roads
            • POJ 3984 迷宫问题

图G采用邻接表的方式进行存储

struct Edge{
	int d;//边的终点
	int l;//边的权值
};
vector<vector<Edge>> G(MAXV);

DFS遍历图

深度优先遍历图上的所有结点

代码步骤

  1. 若结点v为旧点,则返回
  2. 访问结点v
  3. 深搜结点v的所有相邻的结点

代码实现

const int MAXV = 1010;
int visited[MAXV] = { 0 };  //将所有的点都标记为新点

void DFS(v)
{
	if (visited[v])
	{
		...
		return;
	}
	...
	visited[v] = 1;
	对和v相邻的每个结点u
	{
		DFS(u);
	}
}

int main()
{
	int n; //n为图中顶点的个数
	for(int i = 1;i<=n;i++)
	{
		if(!visited[v])    //如果结点v未被访问,则结点v所在的连通分量都未被访问
			DFS(v);		   //深搜之后,结点v所在的连通分量中的结点都被遍历一遍
	}
}

例题

求解图中的连通分量问题
#include 
#include 
#include 

using namespace std;

const int MAXV = 1000;


struct Edge {
	int d;
};
vector<vector<Edge>> G(MAXV);
int visited[MAXV] = { 0 };
int maxNum = 0;		//所有连通分量中结点个数的最大值
int num = 0;			//当前搜索中结点的个数

void dfs(int v)
{
	if (visited[v])
	{
		return;
	}
	visited[v] = 1;
	num++;
	for (int i = 0; i < G[v].size(); i++)
	{
		dfs(G[v][i].d);
	}
}


int main()
{
	int m;
	while (scanf("%d", &m) != EOF)
	{
		maxNum = 0;
		for (int i = 1; i <= MAXV; i++)
			G[i].clear();
		memset(visited, 0, sizeof(visited));
		for (int i = 1; i <= m; i++)
		{
			int s, d;
			Edge r;
			cin >> s >> d;
			r.d = d;
			G[s].push_back(r);
			r.d = s;
			G[d].push_back(r);
		}
		for (int i = 1; i <= MAXV; i++)
		{
			if (!visited[i])
			{
				num = 0;
				dfs(i);
				maxNum = max(maxNum, num);
			}
		}
		cout << maxNum << endl;
	}

	system("pause");
	return 0;
}
POJ 2815 城堡问题

思路:简单的dfs应用,每次深搜都会遍历一个连通分量
求解图中的连通分量的个数所有连通分量中结点个数的最大值
连通分量的个数:RoomNum
所有连通分量中结点个数的最大值:maxRoomArea

//POJ 2815 城堡问题
#include 
#include 

using namespace std;

const int MAXV = 60;
int G[MAXV][MAXV];
int maxRoomArea = 0; 
int RoomArea = 0;	//当前的房间的面积
int RoomNum = 0;	//房间的数量
int visited[MAXV][MAXV];

void DFS(int i, int j)
{
	if (visited[i][j])
		return;
	RoomArea++;
	visited[i][j] = 1;
	if ((G[i][j] & 1) == 0)
		DFS(i, j - 1);
	if ((G[i][j] & 2) == 0)
		DFS(i-1, j );
	if ((G[i][j] & 4) == 0)
		DFS(i, j + 1);
	if ((G[i][j] & 8) == 0)
		DFS(i+1, j);
}

int main()
{
	int n, m;//n为行数,m为列数
	scanf("%d", &n);
	scanf("%d", &m);
	for (int i = 1; i <= n; i++)
	{
		for (int j = 1; j <= m; j++)
		{
			cin >> G[i][j];
			visited[i][j] = 0;
		}
	}
	for (int i = 1; i <= n; i++)
	{
		for (int j = 1; j <= m; j++)
		{
			if (!visited[i][j])
			{
				RoomNum++;RoomArea = 0;
				DFS(i, j);
				maxRoomArea = max(maxRoomArea, RoomArea);

			}
		}
	}
	cout << RoomNum << endl;
	cout << maxRoomArea << endl;
	return 0;
}

DFS寻找到某个结点N的路径

图为连通的,一般只有一个连通分量

代码步骤

  1. 若结点v为终点N,则返回
  2. 遍历结点v的所有相邻的结点u
  3. 先判断结点u是否被访问,如果没有被访问,那么访问结点u,并dfs(u)
  4. 状态还原:将结点u的状态还原: vis[u] = 0; …

代码实现

模板一 先dfs,然后再访问结点v

const int MAXV = 1010;
int visited[MAXV] = { 0 };  //将所有的点都标记为新点
int N;                      //N为终点

void DFS(v)
{
	if (v == N)
	{
		...
		return;
	}
	visited[v] = 1;
	对和v相邻的每个结点u
	{
		if(!visited[u])
		{
			DFS(u);
		}

	}
	visited[v] = 0;   //有的题需要将原来的状态还原,再进行下一次的循环
}

int main()
{
	int n; //n为图中顶点的个数
	DFS(1);		   //深搜之后,结点v所在的连通分量中的结点都被遍历一遍
}

模板二 先访问结点v,然后再dfs

const int MAXV = 1010;
int visited[MAXV] = { 0 };  //将所有的点都标记为新点
int N;                      //N为终点

void DFS(v)
{
	if (v == N)
	{
		...
		return;
	}
	对和v相邻的每个结点u
	{
		if(!visited[u])
		{
			visited[u] = 1;
			DFS(u);
			visited[u] = 0;   //有的题需要将原来的状态还原,再进行下一次的循环
		}

	}
}

int main()
{
	int n; //n为图中顶点的个数
	visited[1] = 1;
	DFS(1);		   //深搜之后,结点v所在的连通分量中的结点都被遍历一遍
}

例题

N皇后问题(经典例题)

思路:按照行号进行深搜
初始状态:第0行
目标状态:第n行

//N皇后
#include 
#include 

using namespace std;

const int MAXN = 100;  //最多的行数
int n;     
int pos[MAXN];       //pos[i]:第i行皇后所放的列数
int total;				//放置的方案数
int visited[3][MAXN];    //visited[0]:保存列的信息;visited[1]:保存主对角线的信息;visited[0]:保存辅对角线的信息


void dfs(int row)
{
	//按照行号来深搜,row为当前搜索的行数
	if (row == n)
	{
		total++;
		for (int i = 0; i < n; i++)
		{
			for (int j = 0; j < n; j++)
			{
				if (j == pos[i]) cout << "*"<<" ";
				else cout << "o" << " ";
			}
			cout << endl;
		}
		cout << endl;
		return;
	}
	for (int i = 0; i < n; i++)
	{
		if (!visited[0][i] && !visited[1][row - i + n] && !visited[2][row + i])
		{
			pos[row] = i;
			visited[0][i] = 1;
			visited[1][row - i + n] = 1;
			visited[2][row + i] = 1;
			dfs(row + 1);
			visited[0][i] = 0;
			visited[1][row - i + n] = 0;
			visited[2][row + i] = 0;
		}
	}
}

int main()
{
	total = 0;
	scanf("%d", &n);
	memset(visited, 0, sizeof(visited));
	dfs(0);
	cout << total << endl;
	system("pause");
	return 0;
}
POJ 1724 Roads

dfs+剪枝 模板题
思路:使用可行性剪枝最优性剪枝来减少dfs的时间复杂度

剪枝:当前搜索的状态不合法时,就跳到当前的搜索(使用 continue

可行性剪枝:当到达结点a时,花费已经超过给定的K时,则剪枝

最优性剪枝:

  1. 到达a时,路径长度已经超过之前的最短路径,则剪枝
  2. 到达a,且花费和之前的搜索相同时,但最短路径大于之前的,则剪枝。这种剪枝需要开辟数组来保存中间结果,mid[i][j]:到达结点 i ,且花费为 j 元时的最短路径
#include  
#include 
#include 


using namespace std;

struct Edge {
	int d, l, c;		//边的终点、边的长度、边的花费
};

const int INF = 0x3f3f3f3f;

vector<vector<Edge>> G(110);
int minLen = INF;   //最短路径长度
int totalLen;	  //当前搜索路径的长度
int totalCost;    //当前搜索路径的花费
int visited[110] = { 0 };
int K, N, R;

int mid[110][10010];  //mid[i][j]表示到达结点i,且花费为j元时的最短路径长度

void dfs(int v)
{
	if (v == N)
	{
		minLen = min(minLen, totalLen);
		return;
	}
	for (int i = 0; i < G[v].size(); i++)
	{
		Edge r = G[v][i];
		//可行性剪枝
		if (totalCost + r.c > K)
			continue;
		//最优性剪枝
		if (totalLen + r.l >= minLen)
			continue;
		if (totalLen + r.l >= mid[r.d][totalCost + r.c])
			continue;
		visited[r.d] = 1;
		totalLen += r.l;
		totalCost += r.c;
		mid[r.d][totalCost] = totalLen;
		dfs(r.d);
		//当去搜索下一个结点时,该结点可能通过r.d,故将r.d还原
		visited[r.d] = 0;
		totalLen -= r.l;
		totalCost -= r.c;
	}
}

int main()
{

	Edge r; int s;
	cin >> K >> N >> R;
	for (int i = 0; i < R; i++)
	{
		cin >> s >> r.d >> r.l >> r.c;
		G[s].push_back(r);
	}
	fill(mid[0], mid[0] + 110 * 10010, INF);
	totalLen = 0;
	totalCost = 0;
	visited[1] = 1;
	dfs(1);
	if (minLen < INF)
	{
		cout << minLen << endl;
	}
	else
	{
		cout << "-1" << endl;
	}
	return 0;

}

POJ 3984 迷宫问题
#include 
#include 
#include 
#include 

using namespace std;
const int INF = 0x3f3f3f3f;

int G[7][7];
int vis[7][7];
int len;		//当前搜索路径的长度
int minLen;      //最短路径的长度

struct Node {
	int x, y;
	Node(int a, int b)
	{
		x = a;
		y = b;
	}
};
vector<Node> path;
vector<Node> minPath;

void dfs(int i, int j)
{
	if (i == 5 && j == 5) 
	{
		len++;
		path.push_back(Node(i, j));
		if (len < minLen)
		{
			minLen = len;
			minPath.assign(path.begin(), path.end());

		}
		return ;
	}
	vis[i][j] = 1;
	len++;
	path.push_back(Node(i, j));

	if (!vis[i][j - 1] && G[i][j - 1] != 1)
	{
		dfs(i, j - 1);
	}
	if (!vis[i][j + 1] && G[i][j + 1] != 1)
	{
		dfs(i, j + 1);
	}
	if (!vis[i-1][j] && G[i-1][j] != 1)
	{
		dfs(i-1, j );
	}
	if (!vis[i+1][j] && G[i+1][j] != 1)
	{
		dfs(i+1, j);
	}
	vis[i][j] = 0;
	len--;
	path.pop_back();
}

int main()
{
	len = 0;
	minLen = INF;
	fill(G[0], G[0] + 7 * 7, 1);
	memset(vis, 0, sizeof(vis));
	for (int i = 1; i <= 5; i++)
	{
		for (int j = 1; j <= 5; j++)
		{
			cin >> G[i][j];
		}
	}
	dfs(1, 1);
	for (int i = 0; i < minPath.size(); i++)
	{
		cout << "(" << minPath[i].x - 1 << ", " << minPath[i].y - 1 << ")" << endl;
	}
	system("pause");
	return 0;
}

你可能感兴趣的:(数据结构与算法)