65.9K
CodeProject 正在变化。 阅读更多。
Home

以树形图为支持的自顶向下分析方法

starIconstarIconstarIconstarIconstarIcon

5.00/5 (2投票s)

2023年7月22日

CPOL

5分钟阅读

viewsIcon

3213

downloadIcon

54

树形图用作自顶向下分析方法的基础

目录

  1. 引言
  2. 背景
    1. yEd 图形编辑器
    2. 逐步求精、结构化编程、自顶向下分析方法
  3. 将典型结构转化为树
  4. 实际案例:N皇后问题
    1. 增量分析
    2. 转化为代码
  5. 关注点
  6. 链接
  7. 历史

引言

我使用树形图作为自顶向下分析方法的辅助工具。因为一张纸永远不够大。以下是我的做法。

背景

yEd 图形编辑器

yEd 是由 yWorks 制作的免费图形编辑器。yWorks 是一家围绕一个库开发的公司,该库允许轻松地将图形集成到分层应用程序中。

yEd 有两种版本:Java 应用程序或浏览器在线版本。

我使用 yEd 的主要原因是其出色的自动布局功能。每次我在树中进行更改时,只需单击一下,树就会根据设置重新构建。

逐步求精、结构化编程、自顶向下分析方法

三个名称,或多或少代表相似的范例和相同的结果:一个将算法转化为结构化程序的指导方针,也有助于定义算法本身。

  • 逐步求精是一种通用的方法,并非专门针对编程
  • Edsger W. Dijkstra 等人的结构化编程
  • Nicklaus Wirth 的自顶向下分析方法

我并不拘泥于其中一种方法,而是混合使用这三种。

将典型结构转化为树

程序流程图是源代码到二维图形的翻译,问题在于它无助于定义算法。

借助这些分析方法,我们从一个普遍的想法开始,然后逐步求精,树有助于在不同级别上可视化算法解决方案。

基本原理是从一个复杂的单一问题开始,将其分解为几个更简单的问题,并不断细化,直到每个小问题都足够简单,可以无需进一步解释就能编写代码。

元素 图形

矩形
条件
菱形。
循环
圆角矩形
模式 树形翻译
序列

序列 A
序列 B
选择

如果 A 为真
    序列 A
结束 if
选择

如果 A 为真
    序列 A
else
    默认序列
结束 if
选择

如果 A 为真
    序列 A
否则如果 B 为真
    序列 B
else
    默认序列
结束 if
重复

当 A 为真时
    序列 A
结束 while
重复

do
    序列 A
当 A 为真时
重复

do
    序列 A
直到 A 为真

您可以随意使用任何形状和模式来满足您的需求,但如果您共享树形图,则需要解释它们。

模式 树形翻译
接下来
Start
当达到结束条件时
    序列 A
    步长
结束 while
计数器= 起始值
当计数器 <= 结束值时
    序列 A
    计数器= 计数器+ 步长
结束 while

当有足够的信息可以转化为代码时,您就停止深入细节。

实际案例:N皇后问题

目标是在相同大小的棋盘上放置 8 个皇后。同样的问题也可以用不同大小的棋盘来解决。

手动解决。仅描述方法的大致思路。

  • 如果棋盘已满,则这是一个解决方案。
  • 对于新的一行,搜索第一个可能的皇后位置。然后转到下一行。
  • 对于已有的皇后,搜索下一个可能的位置。然后转到下一行。
  • 当一行中没有更多可能的位置时,返回上一行并移动皇后。

该方法称为**回溯**。

增量分析

我将使用一种回溯版本的求解算法。基本上,求解算法会寻找所有解决方案,我将在找到第一个解决方案后停止它。

注释 树形翻译
N皇后求解器
需要棋盘大小
需要存储每个皇后的位置
皇后的位置就是结果

我选择了一个非递归的回溯算法。它是一个大循环,内部有两个部分:一个前向循环和一个回溯部分。

模式 树形翻译
当搜索未完成时
    前进
    回溯
End While

 

注释 树形翻译
需要跟踪当前行
如果列在棋盘上,则尝试它,否则回溯
前进

如果位置是空的,
下一行和第 0 列,
否则下一列 回溯
上一行
下一列

现在,只需检查给定单元格是否为空或被占用。

注释 树形翻译
位置为空,如果
没有皇后占用该单元格
在垂直线或对角线上
最后

转化为代码

// NQueens Solver
#include <iostream>
using namespace std;

// Persistant Initialisation
const int BSize = 8;
int QPos[BSize + 1];

int IsFree(int Row) {
	for (int Index = 1; Index < Row; Index++) {
		// Check column
		if (QPos[Row] == QPos[Index])
			return 0;
		// Check diag /
		if (QPos[Row] == QPos[Index] + (Row - Index))
			return 0;
		// Check diag \
		if (QPos[Row] == QPos[Index] - (Row - Index))
			return 0;
	}
	return 1;
}

int NQ_Solver() {
	// Local Initialisation
	int Row = 1;
	QPos[Row] = 0;
	// BackTracking Search
	while (Row > 0) {
		if (QPos[Row] < BSize) {
			// Go Forward
			if (IsFree(Row)) {
				// Ok, Solution or Next Row
				if (Row < BSize) {
					Row++;
					QPos[Row] = 0;
				} else {
					//	Solution found, Abort
					return 1;
				}
			} else {
				// Next Column
				QPos[Row]++;
			}
		} else {
			// Go Backward
			Row--;
			QPos[Row]++;
		}
	}
	return 0;
}

int main() {
	cout << "NQueens Solver" << endl;
// Call Solver
	if (NQ_Solver()) {
		// Result
		for (int Index = 1; Index <= BSize; Index++) {
			cout << " " << QPos[Index] + 1;
		}
		cout << endl;
		// Board Solution
		for (int Index = 0; Index < BSize; Index++) {
			for (int Col = 0; Col < BSize; Col++) {
				if (QPos[Col + 1] == Index) {
					cout << " *";
				} else {
					cout << " .";
				}
			}
			cout << endl;
		}
		cout << endl;
	}
	cout << "fini" << endl;
	return 0;
}

NQueens.cpp 在 TopDown.zip 中.

结果

NQueens Solver
 1 5 8 6 3 7 2 4
 * . . . . . . .
 . . . . . . * .
 . . . . * . . .
 . . . . . . . *
 . * . . . . . .
 . . . * . . . .
 . . . . . * . .
 . . * . . . . .

fini

关注点

在创建算法时,构建树形图非常有助于我可视化想法,但由于树的形状会随着每次添加而改变,因此操作很繁琐。

相对于流程图的主要优点在于,我们不必拘泥于编程语言的语法,而是可以利用这种方法来组织算法的高级描述。

yEd 的自动布局功能简化了树形图的创建过程。

历史

  • 2022 年 9 月 11 日:初稿
  • 2023 年 7 月 22 日:首次发布
© . All rights reserved.