《算法笔记》读书记录DAY_42

本文介绍了图的深度优先搜索(DFS)遍历方法,通过DFS遍历无向图和有向图的概念,包括连通分量和强连通分量的定义。并提供了一个利用DFS解决通话记录分析问题的例子,要求找出通话时间超过阈值K且人数大于2的‘犯罪团伙’,输出团伙数和头目及成员数。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

CHAPTER_10  提高篇(4)——图算法专题

10.3.1图的遍历—DFS

图的遍历是指对图的所有顶点按一定顺序进行访问。用DFS遍历图,总是以“深度”作为第一关键词,每次都是沿着路径到不能再前进时才退回到最近的岔道口。也就是沿着一条路径直到无法继续前进,才退回到路径上离当前顶点最近的还存在为访问分支顶点的岔道口,并前往访问那些未访问的分支顶点。

例如上面这个例子,沿着V1、V2、V4、V8、V5走至最深,这时候已经无法继续前进了,这个时候回退到最近的V1分岔口,因为V1还有未访问的分支V3,然后同样沿着V3、V6、V7的路径访问走至最无路可走,至此所有顶点访问完毕。

在讲解图的DFS的具体实现前,还需要介绍两个概念:

连通分量:在无向图中,如果两个顶点之间有路径,则称这两个顶点连通。如果G(V,E)的任意两个顶点连通,则图G为连通图;否则,称图G为非连通图,且称其中的极大连通子图为连通分量

强连通分量在有向图中,如果两个顶点可以相互到达,则称这两个顶点强连通。如果G(V,E)的任意两个顶点强连通,则图G为强连通图;否则,称图G为非强连通图,且称其中的极大连通子图为强连通分量

例如下图:图(a)是无向图,V1、V4V5V6V7、V8V9形成了三个连通分量;图(b)是有向图,V1V2V3、V4、V5V6V7V8形成了三个强连通分量。

为了叙述方便,我们暂且将连通分量和强连通分量称为连通块。显然,要遍历整个图,就要对每个连通块进行遍历。如果给定的图是一个连通图或者强连通图,那么只需依次DFS就能遍历完成。遍历的基本思路就是将经过的顶点设置为已访问,下次递归碰到这个顶点时就不会再去处理,直到所有顶点被标记为已访问。

代码实现(邻接表):

const int maxv=1000;            //最大顶点数
vector<int> Adj[maxv];          //图的邻接表实现
bool isArrive[maxv]={0};        //标志顶点i是否被访问
int n;                          //实际顶点数

void DFS(int u,int depth) {     //u为当前访问的顶点标号,depth为深度 
	isArrive[u]=true;           //访问顶点u
	//如果要对u进行一些操作,可以在此处进行
	for(int i=0;i<Adj[i].size();i++) {   //遍历所有u可以到达的顶点 
		int v=Adj[u][i];
		if(isArrive[v]==0) {
			DFS(v,depth+1);
		}
	} 
} 

void DFSTrave() {               //一次DFS只能访问完一个连通块 
	for(int i=0;i<n;i++) {      //遍历所有顶点,访问所有连通块 
		if(isArrive[i]==0) {
			DFS(i,1);
		}
	}
}

下面通过一道例题,来练习图的DFS。

题目:

警方有一批通话记录,其中记录了若干人之间的通话时间。如果A与B有通话时间,则称A与B有关系。如果A与B有关系的同时B与C也有关系,警方就将A、B、C归为一组。通过这种方式警方将它们分为若干组。

现在给定一个值K,只要一个组的总通话时间超过K并且组内人数大于2,就将该组定为“犯罪团伙”,其中通话时间最多的那个人就是头目。现给定通话记录,要求输出“犯罪团伙“的个数,并按头目姓名字典序从小到大的顺序输出每个”犯罪团伙“的头目姓名和成员数。

输入格式:

每个输入包含一个测试用例。对于每个用例,第一行输入两个正整数N和K(N,K<=1000),N表示通话记录的数目,K为阈值。

接下来输入N行,每行包括每段通话的信息:姓名1 姓名2 通话时间,其中姓名由三个字母组成,通话时间是一个小于1000的正整数。

输出格式:

对于每个输入。第一行输出犯罪团伙的数目,接下来按照头目姓名字典序从小到大,每个犯罪团伙单独输出一行,每个犯罪团伙输出其头目姓名和成员人数。

需要注意的是,当没有犯罪团伙时直接输出0即可。

输入样例:

8 59

AAA BBB 10

BBB AAA 20

AAA CCC 40

DDD EEE 5

EEE DDD 70

FFF GGG 30

GGG HHH 20

HHH FFF 10

输出样例:

2

AAA 3

GGG 3

思路:

我们用无向图模型来解决问题,其中每个顶点的点权表示该人的总通话时长,同时还需要抽象出每条边的边权来表示通话时长,每个组在图上表示为连通块。在某些情况下我们要对通话时长进行累加,例如读入A与B通话时间T1,又读入了B与A的通话时长T2,实际在无向图模型上A与B之间只存在一条权值为T1+T2的边。

步骤1-1:首先要解决的问题是姓名与编号的对应关系。我们使用map<string ,int>直接建立字符串与整数的映射。确定好每个姓名对应的编号,我们就能在读入数据时建立相应编号的顶点,并且为数据建立图模型。

步骤1-2:根据题目要求,我们需要获得每个人的通话总时长,实际上就是获得图中每个顶点的权值。我们可以在读入数据时就进行处理,假设读入A与B的通话时长T,那么A与B的点权都增加T。

步骤2-1:进行DFS遍历每个连通块,目的是获得每个连通块的顶点个数、最大权值顶点、边权之和。每个连通块的顶点个数即为递归层数depth;DFS可以设置一个数据maxNode记录该连通块中的最大权值顶点。

步骤2-2:在DFS中,我们还要获得该连通块的边权之和,这实际上就是该连通块上所有顶点的点权之和的1/2(想想为什么?)。

步骤3:每次DFS遍历完一个连通块,判断是否为犯罪团伙。如果是,则将该连通块信息存储下来,同时犯罪团伙数加1。

需要注意的是,题目要求输出按头目姓名字典序由小到大,我们可以使用map<string,int>才存储每个犯罪团伙信息,这样map自动按键值string递增排序了。

参考代码:

其实本题也可以用并查集解决,在使用并查集时,要注意合并时总是保持点权更大的节点为集合的根节点。同时为了记录总边权和总人数,需要定义数组来存放。这里留作自行思考。

#include<iostream>
#include<string>
#include<map>
#include<vector>
#include<utility>
#include<algorithm>
using namespace std;

const int maxn=2010;            //最大顶点数 
int n,k,nodeNum=0;              //n为通话记录,k为阈值,nodeNum为当前顶点个数
int depth,sum;                  //depth记录顶点数,sum记录点权之和 
bool isArrive[maxn]={0};        //记录每个顶点是否被访问 
map<int,string> intToString; 
map<string,int> stringToInt;    //姓名和编号间的转换
map<string,int> gang;           //记录犯罪团伙 
pair<int,int> maxNode;          //maxNode记录每个连通块头目,第一个值表示顶点编号,第二个值表示权 

struct graph {                  //图的邻接表 
	int nodeW;                  //点权 
	vector<int> Adj;            //记录每个顶点的边 
}G[maxn];

int change(string str) {                                  //将姓名str转换为整数编号 
	map<string,int>::iterator it=stringToInt.find(str);
	if(it!=stringToInt.end()) {                           //如果str已经出现过 
		return it->second;                                //返回str对应的整数 
	}
	else {
		stringToInt[str]=nodeNum;                         //str对应nodeNum 
		intToString[nodeNum]=str;                         //nodeNum对应str
		return nodeNum++;                                 //返回当前编号,之后顶点数加1 
	}
}

void DFS(int u,int &depth) {                              //一次DFS递归遍历一个连通块,depth为递归深度 
	isArrive[u]=1;                                        //设置u已经访问
	sum+=G[u].nodeW;                                      //将点权累加 
	if(G[u].nodeW>maxNode.second) {                       //更新最大权值顶点编号 
		maxNode.first=u;
		maxNode.second=G[u].nodeW;
	}
	for(int i=0;i<G[u].Adj.size();i++) {                  //遍历u的所有边 
		int v=G[u].Adj[i];
		if(isArrive[v]==0) {                              //如果邻接点v没有访问 
			DFS(v,++depth);                               //递归进入下一层 
		}
	}                    
}

int main() {
	string str1,str2;
	int w;
	cin>>n>>k;
	for(int i=0;i<n;i++) {                                 //为输入数据创建图模型 
		cin>>str1>>str2>>w;
		int id1=change(str1);                              //获得str1对应的编号 
		int id2=change(str2);                              //获得str2对应的编号 
		G[id1].nodeW+=w;                                   //id1号顶点点权增加w 
		G[id2].nodeW+=w;                                   //id2号顶点点权增加w
		if(find(G[id1].Adj.begin(),G[id1].Adj.end(),id2)==G[id1].Adj.end()) {      //如果id1的邻接表中没有到id2的边 
			G[id1].Adj.push_back(id2);                                             //将到id2的边加入id1的邻接表 
		}
		if(find(G[id2].Adj.begin(),G[id2].Adj.end(),id1)==G[id2].Adj.end()) {      //如果id2的邻接表中没有到id1的边 
			G[id2].Adj.push_back(id1);                                             //将到id1的边加入id2的邻接表 
		}
	}
	for(int i=0;i<nodeNum;i++) {                            //遍历每个顶点 
		if(isArrive[i]==0) {                                //如果顶点i未被访问 
			depth=1;
			maxNode=make_pair(0,0);
			sum=0;                                          //每次遍历新连通块前将depth,maxNode,sum初始化 
			DFS(i,depth);                                   //i所在连通块遍历完毕 
			sum=sum/2;                                      //该连通块总边权等于总点权的一半
			if(sum>k&&depth>2) {                            //如果是犯罪团伙 
				gang[intToString[maxNode.first]]=depth;     //将犯罪团伙头目名字和人数记录 
			}
		}
	}
	cout<<gang.size()<<endl;                                //按格式输出信息
	if(gang.size()!=0) {
		for(map<string,int>::iterator it=gang.begin();it!=gang.end();it++) {
		cout<<it->first<<' '<<it->second<<endl;
		}
	}
	return 0;
}

 

<think>好的,我现在需要解释用户提供的代码片段`day_date_list.extend(day_datas)`。首先,我应该确认自己对这个语句的理解是否正确。`extend()`是Python中列表的一个方法,用于将一个可迭代对象的所有元素添加到列表末尾。这里的`day_date_list`应该是一个列表,而`day_datas`可能是另一个列表或可迭代对象。 接下来,我需要思考用户可能的疑问点。他们可能想知道`extend()`和`append()`的区别,或者这个操作的具体效果。例如,如果`day_datas`是一个列表,使用`extend()`会将它的元素逐个添加,而`append()`则会将其作为单个元素添加。我需要用例子来说明这一点,比如原来的列表是`[1,2,3]`,`extend([4,5])`会变成`[1,2,3,4,5]`,而`append([4,5])`则变成`[1,2,3,[4,5]]`。 然后,我需要考虑用户的使用场景。可能他们在处理日期数据,比如将多个日期的数据合并到一个列表中。例如,假设`day_date_list`存储了某几天的日期,而`day_datas`是另一批日期,使用`extend()`可以将两者合并。需要提醒用户确保`day_datas`确实是可迭代的,否则会抛出错误,比如如果`day_datas`是整数,就会报错。 另外,要注意`extend()`方法会直接修改原列表,而不是返回新列表。这和`+`操作符不同,后者会创建新列表。例如,`a = a + b`和`a.extend(b)`效果相同,但`extend()`更高效,尤其是处理大数据时。 可能用户在使用过程中遇到了问题,比如元素没有正确添加,或者类型错误。需要指出常见的错误,比如`day_datas`不是可迭代对象,或者误用了`append`导致结构变化。 最后,总结这个语句的作用,强调它用于合并列表元素,并提醒注意事项,比如可迭代性、原地修改等。确保解释清晰,结构合理,帮助用户理解并正确使用`extend()`方法。</think>我们来逐步解释代码`day_date_list.extend(day_datas)`的作用和原理: --- ### 1. **基本定义** - `day_date_list`:是一个Python列表(List)对象 - `day_datas`:需要被合并到`day_date_list`中的另一个可迭代对象(如列表、元组、集合等) --- ### 2. **`.extend()`方法的作用** - **功能**:将`day_datas`中的所有元素**逐个添加**到`day_date_list`的末尾 - **与`.append()`的区别**: - `.append(x)`:将整个`x`作为单个元素添加到列表 - `.extend(x)`:将`x`中的每个元素拆开后再逐个添加 #### 示例对比: ```python a = [1, 2, 3] b = [4, 5] # 使用 append a.append(b) # 结果: [1, 2, 3, [4, 5]] # 使用 extend a.extend(b) # 结果: [1, 2, 3, 4, 5] ``` --- ### 3. **典型使用场景** 假设你正在处理日期数据: ```python day_date_list = ["2023-10-01", "2023-10-02"] day_datas = ["2023-10-03", "2023-10-04"] day_date_list.extend(day_datas) # 结果: ["2023-10-01", "2023-10-02", "2023-10-03", "2023-10-04"] ``` --- ### 4. **注意事项** 1. **可迭代性要求**:`day_datas`必须是可迭代对象(如列表、元组),否则会抛出`TypeError` ```python # 错误示例(整数不可迭代) day_datas = 100 day_date_list.extend(day_datas) # TypeError ``` 2. **原地修改**:`.extend()`会直接修改原列表`day_date_list`,而不是返回新列表 3. **性能优势**:相比`+`操作符(如`new_list = a + b`),`.extend()`更高效,尤其适合合并大型列表 --- ### 5. **等价实现** 若不用`.extend()`,可以用循环模拟: ```python for item in day_datas: day_date_list.append(item) ``` --- ### 总结 `day_date_list.extend(day_datas)`的作用是:将`day_datas`中的所有元素逐个添加到`day_date_list`末尾,最终实现两个列表的**扁平化合并**。使用时需确保`day_datas`为可迭代对象。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值