文章目录

  • DFS的概念
  • 蓝桥杯:大臣的旅费
  • 问题描述
  • 输入格式
  • 输出格式
  • 解题理解
  • 解题源码
  • 参考目录


DFS的概念

  • 深度优先搜索(depth-first seach,DFS)在搜索到一个新的节点时,立即对该新节点进行遍历;因此遍历需要用先入后出的栈来实现,也可以通过与栈等价的递归来实现。
  • 深度优先搜索也可以用来检测环路:记录每个遍历过的节点的父节点,若一个节点被再次遍历且父节点不同,则说明有环。我们也可以用之后会讲到的拓扑排序判断是否有环路,若最后存在入度不为零的点,则说明有环。
  • 有时我们可能会需要对已经搜索过的节点进行标,修改当前节点状态->遍历下一个节点,比回溯法少了状态回改这一步。

蓝桥杯:大臣的旅费

问题描述
  • 很久以前,T王国空前繁荣。为了更好地管理国家,王国修建了大量的快速路,用于连接首都和王国内的各大城市。
  • 为节省经费,T国的大臣们经过思考,制定了一套优秀的修建方案,使得任何一个大城市都能从首都直接或者通过其他大城市间接到达。同时,如果不重复经过大城市,从首都到达每个大城市的方案都是唯一的
  • J是T国重要大臣,他巡查于各大城市之间,体察民情。所以,从一个城市马不停蹄地到另一个城市成了J最常做的事情。他有一个钱袋,用于存放往来城市间的路费。
  • 聪明的J发现,如果不在某个城市停下来修整,在连续行进过程中,他所花的路费与他已走过的距离有关,在走第x千米到第x+1千米这一千米中(x是整数),他花费的路费是x+10这么多。也就是说走1千米花费11,走2千米要花费23。
  • J大臣想知道:他从某一个城市出发,中间不休息,到达另一个城市,所有可能花费的路费中最多是多少呢?
输入格式
  • 输入的第一行包含一个整数n,表示包括首都在内的T王国的城市数
  • 城市从1开始依次编号,1号城市为首都。
  • 接下来n-1行,描述T国的高速路(T国的高速路一定是n-1条
  • 每行三个整数Pi,Qi,Di,表示城市Pi和城市Qi之间有一条高速路,长度为Di千米。
输出格式
  • 输出一个整数,表示大臣J最多花费的路费是多少。
解题理解
  • 从题目出发,这道题的想法也就是要在一个无向无环图中,找到一条最长径,即求出树的直径。
解题源码
  • 第一个版本:直接使用邻接矩阵dfs遍历以每一个顶点开始的最长径,取最大的一条,在蓝桥杯的评测记录上,这种解法通过了前三个测试用例,第四个测试用例:1.超时,2.且栈内存超过上限
#include <iostream>
#include <vector>
using namespace std;

void findroad(vector<vector<int> > & Graph, vector<int> & visted,\
 int Graph_v_id, int maxroad);
 
int cost(int road);
int realmaxroad = 0; 

int main() {
	int v_nums;
	// 保存图的顶点数 
	cin >> v_nums;
	// 开始使用邻接矩阵进行建图 
	vector<vector<int> > Graph(v_nums + 1, vector<int>(v_nums + 1, 0));
	// 每个顶点的遍历情况 
	vector<int> visted(v_nums + 1, 0);
	int v1, v2, v1tov2;
	// 记录v_nums - 1条路径情况 
	for (int i = 0; i < v_nums - 1; ++i) {
		cin >> v1 >> v2 >> v1tov2;
		Graph[v1][v2] = v1tov2;
		Graph[v2][v1] = v1tov2;
	}
	
	// 开始找到最长的一条路 
	int maxroad = 0; 
	for (int i = 1; i < visted.size(); ++i) {
		// 从首都开始查找
		findroad(Graph, visted, i, maxroad);
		// 重新置0 
		std::fill(visted.begin(), visted.end(), 0); 
		maxroad = 0; 
	}
	// 费用计算
	int maxcost = cost(realmaxroad);
	cout << maxcost << endl;	
	return 0;
}

void findroad(vector<vector<int> > & Graph, vector<int> & visted,\
 int Graph_v_id, int maxroad) { 
 	// 修改当前节点状态
	visted[Graph_v_id] = 1; 
 	// 找当前节点的邻接点 
 	for(int i = 1; i < visted.size(); ++i) {
 		// 如果这两个城市之间能够直达,同时没有被遍历过 
 		if (Graph[Graph_v_id][i] && !visted[i]) {
 			  // 将当前的路径长度记录下来 
			 maxroad += Graph[Graph_v_id][i];
			 if(realmaxroad < maxroad) {
	 			realmaxroad = maxroad;
	 		 } 
		     findroad(Graph, visted, i, maxroad);
		     // 路径长度回溯,记录其他支路计算 
		     maxroad -= Graph[Graph_v_id][i]; 
		 }
	 } 
 }
 
 int cost (int road) {
	return (road * 10 + (road + 1) * road / 2);
 }
  • 第二个版本:参考别人的解释,由树的直径的定义和关系解,使用两遍dfs即可求得:先从任意一点P出发,找离它最远的点Q,再从点Q出发,找离它最远的点W,W到Q的距离就是树的直径,在刚刚原来的代码上稍加修改就可,但是第四个测试用例也会出现内存超限的问题。
#include <iostream>
#include <vector>
using namespace std;

void findroad(vector<vector<int> > & Graph, vector<int> & visted,\
 int Graph_v_id, int maxroad);
 
int cost(int road);
int realmaxroad = 0; 
// 第一次dfs中最远的那个点 
int second_v_id = 0;

int main() {
	int v_nums;
	// 保存图的顶点数 
	cin >> v_nums;
	// 开始使用邻接矩阵进行建图 
	vector<vector<int> > Graph(v_nums + 1, vector<int>(v_nums + 1, 0));
	// 每个顶点的遍历情况 
	vector<int> visted(v_nums + 1, 0);
	int v1, v2, v1tov2;
	// 记录v_nums - 1条路径情况 
	for (int i = 0; i < v_nums - 1; ++i) {
		cin >> v1 >> v2 >> v1tov2;
		Graph[v1][v2] = v1tov2;
		Graph[v2][v1] = v1tov2;
	}
	
	// 开始找到最长的一条路 
	int maxroad = 0; 
	// 第一次dfs,从首都开始查找
	findroad(Graph, visted, 1, maxroad);
	// 重新置0 
	std::fill(visted.begin(), visted.end(), 0); 
	maxroad = 0; 
	// 第二次dfs 
	findroad(Graph, visted, second_v_id, maxroad);
	
	cout << realmaxroad << endl;
	// 费用计算
	int maxcost = cost(realmaxroad);
	cout << maxcost << endl;	
	return 0;
}

void findroad(vector<vector<int> > & Graph, vector<int> & visted,\
 int Graph_v_id, int maxroad) { 
 	// 修改当前节点状态
	visted[Graph_v_id] = 1; 
 	// 找当前节点的邻接点 
 	for(int i = 1; i < visted.size(); ++i) {
 		// 如果这两个城市之间能够直达,同时没有被遍历过 
 		if (Graph[Graph_v_id][i] && !visted[i]) {
 			  // 将当前的路径长度记录下来 
			 maxroad += Graph[Graph_v_id][i];
			 if(realmaxroad < maxroad) {
	 			realmaxroad = maxroad;
	 			// 记录当前最远的这个点的id
				second_v_id = i; 
	 		 } 
		     findroad(Graph, visted, i, maxroad);
		     // 路径长度回溯,记录其他支路计算 
		     maxroad -= Graph[Graph_v_id][i]; 
		 }
	 } 
 }
 
 int cost (int road) {
	return (road * 10 + (road + 1) * road / 2);
 }

因为我使用的是邻接矩阵的方式来存图,容易出现爆内存的情况,所以再改用邻接链表的方式来存储图,除此之外,极端情况下可以采用2倍边数的二维数组来存储所有的边,不过这样的话查找一条边是否存在的时候的时间复杂度会很高,如下:

// 开始使用边表建图,边一共有v_nums - 1条,这里为了方便
// 对于Graph[0],这是输入时,一条边的前顶点
// 对应的Graph[1],这里是输入时,一条边的后顶点 
// 所以这里数组的下标和顶点顺序已经无关,查找一条边是否存在的时间复杂度最坏为O(n).
vector<vector<int> > Graph(2, vector<int>(v_nums - 1, 0));

在第二种采用邻接链表进行存储图,四个评测用例全通过。

// 采用邻接链表的方式来存储图
#include <iostream>
#include <vector>
#include <stdlib.h> 
using namespace std;

// 邻接边表结构
typedef struct v_node {
	int v_id;
	int e_weight;	// 顶点表中的那个顶点到这个顶点的距离 
	v_node * next;
}v_node; 

// dfs找当前节点最长径 
void findroad(vector<v_node*> & GraphNode, vector<int> & visted,\
  int Graph_v_id, int maxroad);
// 旅费计算 
int cost(int road);
// 建图 
void buildGraph(vector<v_node*> & GraphNode, int v1, int v2, int v1tov2);
// 找v1到v2的边的长度,存在返回边长,不存在返回0 
int findEdge(vector<v_node*> & GraphNode, int v1, int v2);

int realmaxroad = 0; 
// 第一次dfs中最远的那个点 
int second_v_id = 0;

int main() {
	int v_nums;
	// 保存图的顶点数 
	cin >> v_nums;
	// 顶点数组(保存边表第一个指针就可以了) 
	vector<v_node*> GraphNode(v_nums + 1, NULL);
	// 开始进行建图   
	// 每个顶点的遍历情况 
	vector<int> visted(v_nums + 1, 0);
	int v1, v2, v1tov2;
	// 记录v_nums - 1条路径情况 
	for (int i = 0; i < v_nums - 1; ++i) {
		cin >> v1 >> v2 >> v1tov2;
		buildGraph(GraphNode, v1, v2, v1tov2);
	}
	// 开始找到最长的一条路 
	int maxroad = 0; 
	// 第一次dfs,从首都开始查找
	findroad(GraphNode, visted, 1, maxroad);
	// 重新置0 
	std::fill(visted.begin(), visted.end(), 0); 
	maxroad = 0; 
	// 第二次dfs 
	findroad(GraphNode, visted, second_v_id, maxroad);
	
	//cout << realmaxroad << endl;
	// 费用计算
	int maxcost = cost(realmaxroad);
	cout << maxcost << endl;	
	return 0;
}

void findroad(vector<v_node*> & GraphNode, vector<int> & visted,\
 int Graph_v_id, int maxroad) { 
 	// 修改当前节点状态
	visted[Graph_v_id] = 1; 
 	// 找当前节点的邻接点 
 	for(int i = 1; i < visted.size(); ++i) {
 		// 如果这两个城市之间能够直达,同时没有被遍历过 
 		int v1tov2 = findEdge(GraphNode, Graph_v_id, i);
 		if (v1tov2 && !visted[i]) {
 			  // 将当前的路径长度记录下来 
			 maxroad += v1tov2;
			 if(realmaxroad < maxroad) {
	 			realmaxroad = maxroad;
	 			// 记录当前最远的这个点的id
				second_v_id = i; 
	 		 } 
		     findroad(GraphNode, visted, i, maxroad);
		     // 路径长度回溯,记录其他支路计算 
		     maxroad -= v1tov2; 
		 }
	 } 
 }
 
 int cost (int road) {
	return (road * 10 + (road + 1) * road / 2);
 }
 
 void buildGraph(vector<v_node*> & GraphNode, int v1, int v2, int v1tov2) {
	// 如果是第一个 
	if(!GraphNode[v1]) {
		v_node* node = (v_node*)malloc(sizeof(v_node));
		node->v_id = v2;
		node->e_weight = v1tov2;
		node->next = NULL;
		// 链到顶点数组上去 
		GraphNode[v1] = node;
	} 
	// 如果不是第一个,加到最后一个next上去 
	else {
		v_node* oldnode = GraphNode[v1];
		while(oldnode->next){
			oldnode = oldnode->next;
		} 
		v_node* node = (v_node*)malloc(sizeof(v_node));
		node->v_id = v2;
		node->e_weight = v1tov2;
		node->next = NULL;
		oldnode->next = node;
	}
	// 同理v2也要这样处理
	// 如果是v2的第一个 
	if(!GraphNode[v2]) {
		v_node* node = (v_node*)malloc(sizeof(v_node));
		node->v_id = v1;
		node->e_weight = v1tov2;
		node->next = NULL;
		// 链到顶点数组上去 
		GraphNode[v2] = node;
	} 
	// 如果不是第一个,加到最后一个next上去 
	else {
		v_node* oldnode = GraphNode[v2];
		while(oldnode->next){
			oldnode = oldnode->next;
		} 
		v_node* node = (v_node*)malloc(sizeof(v_node));
		node->v_id = v1;
		node->e_weight = v1tov2;
		node->next = NULL;
		oldnode->next = node;
	}
}

int findEdge(vector<v_node*> & GraphNode, int v1, int v2) {
	if (!GraphNode[v1]) {
		return 0;
	} else {
		v_node * node = GraphNode[v1];
		do {
			if (node->v_id == v2) {
				return node->e_weight;
			}
			node = node->next;
		}
		while (node); 
	}
	return 0;
}
  • 第三个版本:树形DP,做不来了。
参考目录
  • 树的直径