连通问题-快速合并法

快速合并法
思想 :节点列表中每个节点初始存放的内容为本节点。节点node1和节点node2连通,那么将node1作为node2的子节点,node2作为node1的父节点,那么node1中存放的内容即为node2。所以在快速合并法中,节点node中存放的内容实际上是节点node的父节点。明白这点,针对一个连通对时,将其中一个节点作为父节点father,另一个作为子节点son,将子节点son的内容存放上father,这样做似乎是正确没有问题,但是仔细想想,son节点中存放的内容是父节点,如果有两个连通对,都是针对一个son的,那么他将有两个父节点,一个存储单元如何存放两个父节点呢?这个问题其实也是可以解决,仔细想想,如果节点node1和node2连通,那么node1中的父节点和node2的父节点肯定也是连通的,递推上去,node1的父节点的父节点....和node2的父节点的父节点....肯定也是连通的,那么在node1的父节点的父节点.....上,最头上的一个节点baseNode(即根节点),它存放的内容应该是自己(它没有父节点),这样把node1的根节点和node2的根节点连接起来(可以让node2根节点做node1根节点的子节点,也可以让node1根节点做node2根节点的子节点,这里将会进一步优化算法的地方),node1和node2就连通的了,问题就解决了。按照此规则,最终所有连通的节点都将会有一个共同的根节点(baseNode)。

伪代码
//节点中的内容----这里假设节点个数为10的情况
int[] nodesContext = {0,1,2,3,4,5,6,7,8,9};
//连通节点对:p、q
//找到节点p的根节点i
for( i = p; i != nodesContext[i]; i = nodesContext[i]);
//找到节点q的根节点j
for( j = q; j != nodesContext[j]; j = nodesContext[j]);

//如果p和q的根节点是相同的,不必执行后续步骤,对新连通节点对重复上述步骤
if( i == j ) continue;

//节点i成为节点j的子节点
nodesContext[i] = j;

代码执行过程数据变化情况:
                               0,1,2,3,4,5,6,7,8,9                0,1,2,3,4,5,6,7,8,9   
connectNodes[0]    nodesContext(BEFORE)        nodesContext(AFTER)        p    q    i     j
1,2                          0,1,2,3,4,5,6,7,8,9                0,2 ,2,3,4,5,6,7,8,9             1    2    1    2
2,4                          0,2,2,3,4,5,6,7,8,9                0,2,4 ,3,4,5,6,7,8,9             2    4    2    4
0,8                          0,2,4,3,4,5,6,7,8,9                8 ,2,4,3,4,5,6,7,8,9             0    8    0    8
6,7                          8,2,4,3,4,5,6,7,8,9                8,2,4,3,4,5,7 ,7,8,9             6    7    6    7
7,3                          8,2,4,3,4,5,7,7,8,9                8,2,4,3,4,5,7,3 ,8,9             7    3    7    3
3,5                          8,2,4,3,4,5,7,3,8,9                8,2,4,5 ,4,5,7,3,8,9             3    5    3    5
9,1                          8,2,4,5,4,5,7,3,8,9                8,2,4,5,4,5,7,3,8,4              9    1    9    4
5,9                          8,2,4,5,4,5,7,3,8,4                8,2,4,5,4,4 ,7,3,8,4             5    9    5    4
9,8                          8,2,4,5,4,4,7,3,8,4                8,2,4,5,8 ,4,7,3,8,4             9    8    4     8

算法的分析
    快速合并法比快速查找法好在它无需遍历节点列表,但也相应增加了追踪两节点是否连通的复杂,因为你不递推到节点node1的根节点,和node2的根节点,你是不知道它俩是否连通。这就带来负面影响,当你最终生成的树的深度较深,递推消耗的时间就会很大,这样合并的效率是否一定会比遍历节点列表快就不好说了,毕竟查找法,是可以直接知道两节点是否连通,只需看看两节点中存放的根节点是否相同,而合并法节点中存放的是父节点。

算法优化
    接下来考虑一下如何改进快速合并法,如果要提高后续查找两节点连通性的效率,那么最好就是让最终生成的树不要太深。想到合并的过程中-“可以让node2根节点做node1根节点的子节点,也可以让node1根节点做node2根节点的子节点”,这个过程其实是可以优化,来减少生成树的深度的。当你的node1的根节点生成的树tree1,node2的根节点生成的树tree2,tree1的深度假设为5,tree2的深度为3,这时你是将tree1的根节点连到tree2根节点上,当个子节点呢?还是将tree2的根节点连接到tree1根节点上,当子节点呢?很明显,如果你将tree1的根节点连到tree2上,那么生成的树treeUnion深度为6,而将tree2的根节点连到tree1上,生成的树深度为5。所以采用一种规则:始终将深度小的树接到深度大的树上,这样能保证最后合成的树的深度是最小的。
    怎样做到将深度小的树连接到深度大的树上呢?这就需要对每棵树的深度做记录,在每颗树的根节点记录当前树的深度。
伪代码改动:
//初始所有节点的深度为1
for(i = 0; i < nodeCount; i++)//nodeCount为节点数
      deep[i] = 1;


//找到节点p的根节点i
for( i = p; i != nodesContext[i]; i = nodesContext[i]);
//找到节点q的根节点j
for( j = q; j != nodesContext[j]; j = nodesContext[j]);

if(deep[i] < deep[j])
{ nodesContext[i] = j; deep[j] = max(deep[i]+1,deep[j]); }
else
{ nodesContext[j] = i; deep[i] = max(deep[j]+1,deep[i]); }


      通过上面的优化,树的深度有很大改善,这样递推到根所花费的时间会大大减少。到此,似乎已经ok了,但是一些书籍中还提供了更好的方法,能够进一步对树的深度进行压缩,争取做到每个节点尽可能的连接到根节点上,这样递推的时间是最短的,效率也会有很大的提升。这种方法就叫做路径压缩法,路径压缩法有很多,拿个比较好理解的等分路径压缩法来对上述代码再做一次改动。
伪代码改动:
//找到节点p的根节点i
for( i = p; i != nodesContext[i]; i = nodesContext[i])
    nodesContext[i] = nodesContext[nodesContext[i]];
//找到节点q的根节点j
for( j = q; j != nodesContext[j]; j = nodesContext[j]);
    nodesContext[j] = nodesContext[nodesContext[j]];

     通过这样压缩的树,在节点数较多的情况下,基本是一颗扁平的树,查找连通性效率很高。

<think>嗯,用户想用PLC实现计算二维数组中连通区域面积的算法。这个问题其实挺有挑战的,因为PLC原本是为工业控制设计的,处理图像算法需要一些技巧。用户之前提到过边缘计算的概念[^3],这个思路很重要。PLC作为边缘设备,确实适合做这种实时性要求高的图像处理。不过PLC编程和计算机编程很不一样,需要特别注意资源限制。让我回忆下连通算法的核心:扫描标记。这需要嵌套循环遍历二维数组,用BFS或DFS标记相邻的像素点。但PLC的内存和处理能力有限,直接移植DFS可能会爆栈。所以考虑用逐行扫描+并查集的方:第一次遍历时标记临时编号,第二次遍历向上合并区域。这样可以避免递归,用有限的寄存器实现。步骤应该是:(1)初始化标记数组(2)第一行独立标记(3)后续行比较相邻和上方元素(4)处理冲突编号(5)汇总统计面积。特别要注意PLC的扫描周期特性。算法必须拆解成多个周期完成,比如把大图像分割处理,结果存在保持型寄存器里。还需要考虑边界情况,比如设备重启时的数据恢复问题。用户还提到过“从底层PLC到信息化的距离”[^3],这个算法实现后,通过OPCUA协议把结果传到上位机就是很好的信息化案例。不过当前重点还是先搞定基础算法。</think> 以下是针对 PLC 实现二维数组连通区域面积计算的完整算法方案,结合工业控制系统的特性进行设计: --- ### **核心算法:扫描标记(适用于PLC)** **步骤分解:** 1. **初始化标记数组** - 创建与原数组同尺寸的 `visited[][]` 标记数组(初始化为0)。 - 定义面积结果寄存器 `area_count`(初始为0)。 2. **逐行扫描像素**(通过嵌套循环实现) ```structured-text FOR row := 1 TO height DO FOR col := 1 TO width DO IF input_array[row][col] = 目标值 AND visited[row][col] = 0 THEN // 发现新连通区域 current_area := 0 CALL BFS(row, col, current_area) // 执行广度优先搜索 area_count := area_count + current_area // 累加总面积 END_IF END_FOR END_FOR ``` 3. **BFS 搜索子程序**(避免递归,用队列实现) ```structured-text PROCEDURE BFS(start_row, start_col, VAR current_area) // 初始化队列 Queue := [(start_row, start_col)] visited[start_row][start_col] := 1 current_area := 1 WHILE Queue 非空 DO (r, c) := Queue.dequeue() // 检查相邻的4个方向(上、下、左、右) FOR EACH 方向 (dr, dc) IN [(0,1), (1,0), (0,-1), (-1,0)] DO nr := r + dr, nc := c + dc IF nr 在 [1,height] 且 nc 在 [1,width] 且 input_array[nr][nc] = 目标值 AND visited[nr][nc] = 0 THEN visited[nr][nc] := 1 current_area := current_area + 1 Queue.enqueue((nr, nc)) END_IF END_FOR END_WHILE END_PROCEDURE ``` --- ### **PLC实现关键优化** 1. **资源受限处理** - **队列长度限制**:预设最大队列大小(如100),超限时分割区域多次处理(需记录断点)。 - **内存优化**:用`BYTE`数组存储标记(单像素占1字节),避免使用浮点数。 2. **实时性保障** - **分块扫描**:大数组分割为区块(如16x16),每扫描周期处理1块。 - **状态保持**:用`RETAIN`变量保存扫描位置和中间结果,确保扫描中断后恢复。 3. **邻域搜索加速** ```structured-text // 使用位运算快速判断邻域 IF (input_array[r][c] AND 0x0F) = 目标值 THEN // 低4位存储类型 // 执行标记 END_IF ``` --- ### **示例:PLC结构化文本(ST)代码片段** ```structured-text FUNCTION_BLOCK AreaCalculator VAR_INPUT arr: ARRAY[1..32, 1..32] OF BYTE; // 假设32x32数组 target: BYTE; END_VAR VAR_OUTPUT total_area: INT; END_VAR VAR visited: ARRAY[1..32, 1..32] OF BOOL; queue: ARRAY[1..100] OF POINT; // 队列结构 front, rear: INT; END_VAR METHOD BFS : INT VAR_INPUT r, c: INT; END_VAR // ...实现上述BFS逻辑 METHOD Main // 初始化 total_area := 0; visited := 0; // 双层循环扫描 FOR r := 1 TO 32 DO FOR c := 1 TO 32 DO IF arr[r][c] = target AND NOT visited[r][c] THEN area_temp := 0; BFS(r, c, area_temp); total_area := total_area + area_temp; END_IF; END_FOR; END_FOR; END_METHOD END_FUNCTION_BLOCK ``` --- ### **适用场景与局限性** - **工业场景**:产线缺陷检测(如玻璃面板裂痕面积统计)[^3] - **性能边界**: - 可处理约 500x500 像素(典型 PLC 内存限制在 1MB) - 单区域最大约 10,000 像素(受队列容量限制) - **边缘协同**:大面积图像可分割后由多个 PLC 并行处理,结果上传至边缘网关聚合[^3] --- ### **备选方案:并查集算法** 若需处理动态更新场景(如实时扩展区域): 1. 初始每个像素为独立集合 2. 扫描时合并相邻集合 3. 根节点计数器统计区域面积 *优点:避免重复扫描;缺点:PLC 实现更复杂* ---
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值