1021 Deepest Root (25 point(s))

本文详细解析了PAT-A级考试中DeepestRoot问题的解决方案,通过并查集和深度优先搜索(DFS)算法,寻找使得树高度最大的根节点,即最深根。文章探讨了连通组件的计算、图的存储方式,并提供了完整的C++代码实现。

1021 Deepest Root (25 point(s))

A graph which is connected and acyclic can be considered a tree. The height of the tree depends on the selected root. Now you are supposed to find the root that results in a highest tree. Such a root is called the deepest root.

Input Specification:

Each input file contains one test case. For each case, the first line contains a positive integer N (≤10​4​​) which is the number of nodes, and hence the nodes are numbered from 1 to N. Then N−1 lines follow, each describes an edge by given the two adjacent nodes' numbers.

Output Specification:

For each test case, print each of the deepest roots in a line. If such a root is not unique, print them in increasing order of their numbers. In case that the given graph is not a tree, print Error: K components where K is the number of connected components in the graph.

Sample Input 1:

5
1 2
1 3
1 4
2 5

Sample Output 1:

3
4
5

Sample Input 2:

5
1 3
1 4
2 5
3 4

Sample Output 2:

Error: 2 components

考察知识点:连通分支、DFS

注意点:

1. 并查集求连通分支的方法;

2. 图的存储方法;

3. DFS求树的深度以及各结点所在的层次

思路:

先给出结论,一棵有N个结点的树,一定有N-1条边(离散数学中有证明)。因此,首先用并查集来计算连通分支数,如果不连通输出,否则说明这一定是一棵树,没有环。

下一个任务是确定以哪一个结点为根,树有最大的深度。采用遍历每一个结点的方法,暴力求解,问题转化为已知根节点求树的深度。用数组visit[]、level[]分别记录第i个结点是否已经被访问以及所在的层次,使用DFS搜索。

参考大神链接:https://blog.youkuaiyun.com/richenyunqi/article/details/79551890

图相关的题目在近几年PAT-A中越来越多,需要重视!

#include<iostream>
#include<cstdlib>
#include<cstring>
#include<vector>
using namespace std;
const int MAX = 1e4+7;
int Tree[MAX];
int findRoot(int x){//寻找根结点 
	if(Tree[x]==-1) return x;
	else{
		int temp = findRoot(Tree[x]);
		Tree[x] = temp;
		return temp;
	}
}
bool visit[MAX];
int maxLevel[MAX];//第i个结点作为根节点时,树的最大深度
int level[MAX];//当前根节点下,第i个结点的层次(深度) 
vector<int> graph[MAX];
void dfs(int v,int curRoot){//寻找以curRoot为根节点,从v开始的各个结点的层次 
	visit[v]=true;
	for(int i=0;i<graph[v].size();i++){//遍历每个邻接结点 
		int k = graph[v][i];//当前邻接结点 
		if(!visit[k]){
			level[k]=level[v]+1;
			maxLevel[curRoot]=max(level[k],maxLevel[curRoot]);//跟新最大树深度 
			dfs(k,curRoot); 
		}
	} 
}
int main(void){
	int n,a,b;cin>>n;
	memset(Tree,-1,sizeof(Tree));
	for(int i=1;i<=n-1;i++){
		cin>>a>>b;
		int na = findRoot(a);
		int nb = findRoot(b);
		if(na!=nb) Tree[na]=nb;
		graph[a].push_back(b);
		graph[b].push_back(a);
	}
	int bCnt=0;//计算连通分支数 
	for(int i=1;i<=n;i++){
		if(Tree[i]==-1) bCnt++;
	}
	if(bCnt>=2) cout<<"Error: "<<bCnt<<" components"<<endl;
	else{
		for(int i=1;i<=n;i++){
			memset(level,0,sizeof(level));
			memset(visit,false,sizeof(visit));
			dfs(i,i);//深度优先搜索
		} 
		int deepest = 0;
		for(int i=1;i<=n;i++) deepest=max(deepest,maxLevel[i]);
		for(int i=1;i<=n;i++){
			if(maxLevel[i]==deepest) cout<<i<<endl;
		}
	}
	return 0;
}

 

<think> 我们已有的代码是查找指定区域内的凹点(使用凸包缺陷检测)。现在需要在这些凹点的基础上,确定凹陷的方向。 步骤: 1. 理解凸包缺陷:OpenCV的`convexityDefects`函数返回一个数组,其中每个元素包含四个值:[起点索引,终点索引,最远点(凹点)索引,距离] 2. 对于每一个凹点(最远点),我们可以得到它在轮廓中的坐标。 3. 我们要确定凹陷的方向,即这个凹点相对于其相邻点(起点和终点)的凹陷方向。 方法: 对于每一个缺陷(defect),我们有三个关键点:起点(start)、终点(end)和凹点(far)。我们可以利用这三个点来定义凹陷的方向。 具体思路: 我们可以计算向量$\overrightarrow{start \to far}$和向量$\overrightarrow{end \to far}$,然后计算这两个向量的夹角(内角)。但这个夹角可能不是我们想要的方向。 另一种方法:我们想要的是凹陷的朝向,也就是从凹点(far)指向凹陷内部的方向。我们可以考虑使用角平分线的方向。 然而,更简单的方法是:因为凹陷是由起点、终点和凹点构成的三角形,我们可以取这个三角形的重心方向(从far点指向三角形重心的向量),这个方向大致就是凹陷的内部方向。 但是,还有一种更直接的方法:利用起点和终点的中点,然后从far点指向这个中点的方向就是凹陷的方向。 具体步骤: 1. 计算起点和终点的中点:$mid = \left(\frac{start_x+end_x}{2}, \frac{start_y+end_y}{2}\right)$ 2. 那么从far点指向mid点的向量$\overrightarrow{far \to mid}$的方向就是凹陷的方向。 注意:这个向量是从far点指向中点,所以方向是从凹点指向凹陷内部。 我们也可以将这个向量归一化,然后以角度形式表示方向(例如与x轴正方向的夹角)。 代码步骤: 假设我们已经有了轮廓(contours)和凸包(hull),然后计算凸包缺陷。 接下来,对于每一个缺陷: - 获取起点、终点、凹点的坐标 - 计算起点和终点的中点 - 计算中点减去凹点得到的向量 - 计算该向量的角度(使用atan2) 注意:由于图像坐标系中y轴向下,所以角度计算时要注意方向。 下面我们给出代码框架: 假设我们已经有了以下代码: contours, _ = cv2.findContours(...) hull = cv2.convexHull(contours[0], returnPoints=False) defects = cv2.convexityDefects(contours[0], hull) 然后遍历defects: 注意:defects是一个三维数组,形状为(n,1,4),其中4个值分别是[start_index, end_index, far_index, distance] 代码示例: ```python import cv2 import numpy as np # 假设已经找到了轮廓,这里以第一个轮廓为例 # 轮廓检测等代码省略... # 计算凸包和凸包缺陷 hull = cv2.convexHull(contour, returnPoints=False) defects = cv2.convexityDefects(contour, hull) if defects is not None: for i in range(defects.shape[0]): s, e, f, d = defects[i, 0] start = tuple(contour[s][0]) end = tuple(contour[e][0]) far = tuple(contour[f][0]) # 计算起点和终点的中点 mid_point = ((start[0] + end[0]) // 2, (start[1] + end[1]) // 2) # 计算方向向量(从中点指向far点?不,我们定义的方向是从far点指向中点,因为中点位于凹陷内部) # 向量 = (mid_point[0] - far[0], mid_point[1] - far[1]) vector = (mid_point[0] - far[0], mid_point[1] - far[1]) # 计算这个向量的角度(以弧度表示),使用atan2(dy, dx) angle_rad = np.arctan2(vector[1], vector[0]) # 注意:图像坐标系,y轴向下 angle_deg = np.degrees(angle_rad) % 360 # 转换为0到360度 # 此时angle_deg就是凹陷的方向(从凹点指向凹陷内部的方向) # 如果需要,可以在这个位置绘制方向线 # 例如:从far点画一条线到mid_point cv2.line(img, far, mid_point, (0,255,0), 2) cv2.circle(img, far, 5, (0,0,255), -1) cv2.circle(img, start, 5, (255,0,0), -1) cv2.circle(img, end, 5, (255,0,0), -1) # 也可以标注角度 cv2.putText(img, f"{angle_deg:.1f} deg", far, cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255,255,0), 2) ``` 注意:这里我们使用起点和终点的中点来代表凹陷内部的方向,这种方法对于对称的凹陷效果较好。如果凹陷不对称,可能不是最精确的方向,但通常是一个合理的近似。 另外,我们计算的角度是相对于图像坐标系(x轴向右,y轴向下的笛卡尔坐标系)的。如果需要,可以转换为其他方向表示。 如果用户希望得到更精确的凹陷方向,可以考虑使用起点、终点、凹点构成的三角形的重心方向(即三点坐标的平均值): center = ((start[0]+end[0]+far[0])//3, (start[1]+end[1]+far[1])//3) vector = (center[0]-far[0], center[1]-far[1]) 两种方法都可以,但使用中点更简单。 总结:我们通过计算起点和终点的中点,然后得到从凹点指向该中点的向量,从而确定凹陷的方向,并计算出角度。 注意:如果凹陷很浅,可能需要忽略(根据距离阈值d,即凹点到凸包的距离)。我们这里直接处理所有缺陷点,用户可以根据d的值来过滤。 最后,如果用户需要的是凹陷的朝向(例如,朝上、朝下等),则可以根据角度值判断: - 0度:向右 - 90度:向下 - 180度:向左 - 270度:向上 但注意,由于图像坐标系y轴向下,所以这里的角度方向与数学上的笛卡尔坐标系一致(x轴向右,y轴向上)是相反的。不过,我们计算的角度是相对于图像坐标系(y轴向下)的,所以: - 0度:向右(正x方向) - 90度:向下(正y方向) - 180度:向左(负x方向) - 270度:向上(负y方向) 因此,可以直接根据角度值判断方向。 下面我们用代码示例展示完整过程(包括读取图像,轮廓检测等): </think> # 使用OpenCV检测凹陷方向的方法 在OpenCV中,可以通过分析轮廓的凸包缺陷(convexity defects)来检测凹陷点及其方向。以下是如何在已有凹点检测代码基础上确定凹陷方向的详细方法: ## 核心概念与方法 ### 1. 凸包缺陷基础 OpenCV的`convexityDefects`函数返回的每个缺陷包含四个关键信息: - `start_point`:缺陷起始点索引 - `end_point`:缺陷结束点索引 - `far_point`:凹点索引(距离凸包最远的点) - `depth`:凹陷深度 凹陷方向可以通过分析这三个关键点构成的三角形来确定。 ### 2. 凹陷方向计算原理 1. 构造向量: - $\overrightarrow{SF} = far\_point - start\_point$ - $\overrightarrow{EF} = far\_point - end\_point$ 2. 计算法向量: - 凹陷方向垂直于$\overrightarrow{SF}$和$\overrightarrow{EF}$的角平分线 - 使用叉积计算垂直方向:$\overrightarrow{direction} = \overrightarrow{SF} \times \overrightarrow{EF}$ 3. 角度计算: - 方向角度$\theta = \arctan2(dy, dx)$ - 其中$dx$和$dy$是法向量的分量 ## 代码实现 ```python import cv2 import numpy as np import math # 检测轮廓和凸包 image = cv2.imread('input.jpg') gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) _, thresh = cv2.threshold(gray, 127, 255, 0) contours, _ = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE) for contour in contours: # 计算凸包和凸缺陷 hull = cv2.convexHull(contour, returnPoints=False) defects = cv2.convexityDefects(contour, hull) if defects is not None: for i in range(defects.shape[0]): # 提取凸缺陷信息 s, e, f, d = defects[i, 0] start = tuple(contour[s][0]) end = tuple(contour[e][0]) far = tuple(contour[f][0]) # 1. 计算向量 sf_vector = (far[0] - start[0], far[1] - start[1]) ef_vector = (far[0] - end[0], far[1] - end[1]) # 2. 计算角平分线向量 bisector = ( (sf_vector[0] + ef_vector[0]) / 2, (sf_vector[1] + ef_vector[1]) / 2 ) # 3. 计算垂直方向(法向量) # 法向量计算:垂直向量就是(-dy, dx)或(dy, -dx) normal = (-bisector[1], bisector[0]) # 4. 计算方向角度(弧度) angle = math.atan2(normal[1], normal[0]) # 5. 将弧度转换为角度(0-360度) angle_deg = (math.degrees(angle) + 360) % 360 # 6. 可视化 # 绘制关键点 cv2.circle(image, start, 8, (255, 0, 0), -1) # 蓝色: 起点 cv2.circle(image, end, 8, (0, 255, 0), -1) # 绿色: 终点 cv2.circle(image, far, 8, (0, 0, 255), -1) # 红色: 凹点 # 绘制方向线(从凹点沿法向延伸) direction_length = 50 end_point = ( int(far[0] + direction_length * math.cos(angle)), int(far[1] + direction_length * math.sin(angle)) ) cv2.arrowedLine(image, far, end_point, (255, 255, 0), 2) # 标注角度 cv2.putText(image, f"{angle_deg:.1f}°", (far[0] + 15, far[1] - 15), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 255), 2) # 显示结果 cv2.imshow('Concavity Directions', image) cv2.waitKey(0) cv2.destroyAllWindows() ``` ## 关键步骤详解 1. **向量计算**: - 计算从起点到凹点的向量$\overrightarrow{SF}$ - 计算从终点到凹点的向量$\overrightarrow{EF}$ $$SF = (far_x - start_x, far_y - start_y)$$ $$EF = (far_x - end_x, far_y - end_y)$$ 2. **角平分线计算**: - 求两个向量的平均值: $$bisector = \left( \frac{SF_x + EF_x}{2}, \frac{SF_y + EF_y}{2} \right)$$ 3. **法向量计算**: - 旋转90度获得垂直方向: $$normal = (-bisector_y, bisector_x)$$ 4. **角度计算**: - 使用`atan2`函数计算角度: $$\theta = \operatorname{atan2}(normal_y, normal_x)$$ - 转换为0-360度范围 ## 结果解读 1. **方向表示**: - 0°:向右 → - 90°:向下 ↓ - 180°:向左 ← - 270°:向上 ↑ 2. **方向线可视化**: - 起点(蓝色点) - 终点(绿色点) - 凹点(红色点) - 方向箭头(黄色)表示凹陷方向 ## 应用场景 1. **手势识别**:确定手指间的凹陷方向[^1] 2. **物体姿态分析**:分析物体凹陷部位朝向 3. **工业缺陷检测**:检测零件凹陷的方向性特征 4. **生物形态分析**:研究生物器官的凹陷结构
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值