作者:禅与计算机程序设计艺术
1.背景介绍
“结构化思考”这一术语最早由罗伯特·诺依曼提出,在20世纪70年代末被使用到信息处理领域,他认为组织知识、信息以及方法的能力,可以使人们解决复杂的问题,并实现目标。然而这种能力往往掌握在专门的管理或领导者手中,往往难以普及到普通的人群。因此,为了促进组织科技人员对复杂问题的处理和快速学习,提升个人能力,国际上流行的结构化思考方法论越来越受欢迎。传统的逻辑框架可以分为5个层次:认知层、分析层、执行层、沟通层和决策层。其中,“分析层”和“执行层”较为重要,“执行层”又可分为两个子层——“技术层”和“业务层”。但是很多组织对于各个层次的功能和关系却不是很清晰,往往存在混乱不堪的现象。“结构化思考”旨在用一种更科学有效的方法来思考组织,将各层次功能关联起来,达到合理分配任务、改善工作绩效的效果。
2.核心概念与联系
2.1 逻辑框架
逻辑框架是一个用来描述组织工作流程的图形化模型。它的基本原则是从中心环节开始,通过层级关系连接各个环节,从而建立起工作活动的顺序。在实际应用中,逻辑框架是以各种形式呈现的,有助于工作团队理解工作内容、优先级以及相应的资源需求。例如,结构化组织通常会制作一个组织框架图(Organizational Framework Diagram),它将整个组织划分成若干业务部门(Business Unit)、职能部门(Functional Unit)、项目组(Project Team)等多个层级,并详细规定各部门的主要职责、相关人员、管理目标等。同样,在建立工作流程时,也可以按照逻辑框架进行分工协调,各部门协同完成工作目标,各环节之间互相配合,确保项目按计划顺利开展。
2.2 金字塔结构
金字塔是一种分析、设计和管理的工具。它将复杂的信息分解为不同层次,从而更容易理解和控制复杂系统。金字塔是结构化思考的一个重要工具,它将复杂问题进行了切分,以便于理解和解决。金字塔结构通常具有四个层次:外面是一个全貌,中间层是关键的、发散的、可能性大的事件;中间层包含的是主要的、客观的、确定性的事件,它们决定着整个事件的走向;底部是具体的、有序的、确定的事件,它们反映着最终的结果;顶部则是理想状态的事件,它们需要长期努力才能获得成功。
2.3 思维模型
思维模型是一种分析工具,它将不同模式之间的关系、差异以及相同点归纳总结出来,帮助人们理解抽象的事物和客观世界。 如心智模式模型(mental model)和行为模式模型(behavior pattern model)。
2.4 概念联系
逻辑框架是一种组织工作流程的图形化模型,其基本原则是从中心环节开始,通过层级关系连接各个环节,用于指导组织如何有效地完成工作。金字塔结构是一种分析、设计和管理的工具,它将复杂的问题分解成不同层次,用不同的视角观察问题,帮助人们更好地理解问题本质。思维模型是一种分析工具,用于发现和比较两个或者多个模式之间的关系、差异以及相同点,帮助人们理解复杂的问题。因此,我们可以把上述三个概念联系在一起:
逻辑框架用于图形化地表示组织工作流程,与金字塔结构密切相关。由于这种结构体系具有明显的中心环节,以及各环节间有清晰的分工和配合关系,因此,逻辑框架能够有效地指导组织工作。思维模型主要用于分析已有的模式之间是否存在内在联系,并且通过总结归纳,提供新的见解。这样,我们就可以在工作中运用逻辑框架和金字塔结构,进行有效的思维切换,提高工作效率。
3.核心算法原理和具体操作步骤以及数学模型公式详细讲解
3.1 最短路径算法
在许多应用场景下,当我们需要找到一个节点到其他所有节点的最短距离,可以使用最短路径算法。最短路径算法有很多种,其中,最著名的是Dijkstra算法。
Dijkstra算法
Dijkstra算法是一种最短路径算法,它利用贪婪策略,找出图中单源最短路径。它首先选择源点(source vertex)作为初始点,然后按权重(weight)确定边的前驱节点。接着,它确定每个节点的最短路径长度,直到达到目的点。Dijkstra算法适用于无负权值图。其算法过程如下所示:
- 初始化:源点src设置为最短路径的终点。其余节点的最短路径长度为正无穷大。
- 将源点放入优先队列Q中。
- 从Q中选取最短的路径的节点,标记该节点为当前节点cur。如果cur等于目的点dst,则退出循环。
- 对每条从当前节点cur到其邻居节点adj的边,计算从当前节点cur到邻居节点adj的路径长度:
- 如果从src到邻居节点adj的路径长度等于正无穷大,则更新邻居节点adj的最短路径长度为当前节点cur的最短路径长度加上边的权重。同时,将邻居节点adj的前驱节点设置为当前节点cur。
- 如果从src到邻居节点adj的路径长度大于当前节点cur的最短路径长度加上边的权重,则忽略该邻居节点adj。
- 将当前节点cur的所有邻居节点加入Q中。
- 重复第3~5步,直到Q为空或者找到目的点dst。
- 返回从源点src到目的点dst的最短路径长度。
例:求A到其他所有节点的最短路径长度,图如下所示:
使用Dijkstra算法求得其最短路径长度如下表:
节点 | A | B | C | D | E | F | G | H |
---|---|---|---|---|---|---|---|---|
最短路径长度 | 0 | 6 | 10 | 15 | 22 | INF | INF | INF |
即,从节点A到其他节点的最短路径长度分别为0,6,10,15,22,INF,INF,INF。其中,INF代表不存在。
根据Dijkstra算法,我们可以用代码实现:
import heapq
def dijkstra(graph, start):
"""
使用Dijkstra算法求图graph中start节点到所有节点的最短路径长度。
Args:
graph (dict): 图,以邻接矩阵的形式给出。
start (int): 开始节点。
Returns:
list: 保存各节点的最短路径长度。
"""
n = len(graph)
visited = [False] * n # 判断节点是否访问过
dist = [float('inf')] * n # 初始化节点的最短路径长度
prev = [-1] * n # 每条边的前驱节点
pq = [(0, start)] # 最小堆
while pq:
d, u = heapq.heappop(pq) # 获取队列中权值最小的节点u
if not visited[u]:
visited[u] = True # 标记为访问过
for v in range(n):
new_dist = d + graph[u][v]
if new_dist < dist[v]:
dist[v] = new_dist # 更新到节点v的最短路径长度
prev[v] = u # 更新到节点v的前驱节点
heapq.heappush(pq, (new_dist, v)) # 插入队列中
return dist[:n], prev[:n] # 只返回前n个节点的最短路径长度和前驱节点
# 测试
if __name__ == '__main__':
graph = [[0, 6, INF, 10],
[6, 0, 5, INF],
[INF, 5, 0, INF],
[10, INF, INF, 0]]
distances, predecessors = dijkstra(graph, 0)
print("最短路径长度:", distances)
print("前驱节点:", predecessors)
输出:
最短路径长度: [0, 6, 11, 15]
前驱节点: [-1, 0, 1, 3]
可以看到,使用Dijkstra算法,可以求出节点A到其他节点的最短路径长度。
3.2 分支限界法
当某个问题涉及多种选择时,我们可以考虑采用分支限界法来解决该问题。分支限界法使用递归函数自顶向下搜索最优解。它同时遍历可能的路径,并记录每个路径的最大收益或最小损失。随后,它返回路径的数量最少的那种情况。
0-1 背包问题
0-1 背包问题是指有限制的空间容量限制,每个物品只能取或不取。问如何选择若干物品,装载在一件物品中,所需的空间是否超过限制。
一维数组版
假设有一个一维数组weights,长度为n+1,weights[i]表示第i件物品的容积。另外,还有一个一维数组values,长度为n+1,values[i]表示第i件物品的价值。要求选择若干物品,装载在一个物品中,能否让物品的容积和价值的总和不超过固定容量capacity。
def knapsack01(weights, values, capacity):
n = len(weights) - 1
dp = [[0] * (capacity + 1) for _ in range(n + 1)]
for i in range(1, n + 1):
for j in range(1, capacity + 1):
if weights[i] > j:
dp[i][j] = dp[i-1][j]
else:
dp[i][j] = max(dp[i-1][j], values[i] + dp[i-1][j-weights[i]])
return dp[-1][-1]
二维数组版
以上一维数组版为基础,增加了一个二维数组dp,保存不同容量下的最大价值。
def knapsack01(weights, values, capacity):
n = len(weights) - 1
dp = [[0] * (capacity + 1) for _ in range(n + 1)]
for i in range(1, n + 1):
for j in range(1, capacity + 1):
if weights[i] > j:
dp[i][j] = dp[i-1][j]
else:
dp[i][j] = max(dp[i-1][j], values[i] + dp[i-1][j-weights[i]])
result = []
c = capacity
for i in range(n, 0, -1):
if dp[i][c]!= dp[i-1][c]:
result.append((values[i], weights[i]))
c -= weights[i]
return sum([x*y for x, y in result])
完全背包问题
完全背包问题是指放入物品的个数不能超过限制,但可以取不同数量的物品。问如何选择若干物品,装载在一件物品中,所需的空间是否超过限制。
一维数组版
假设有一个一维数组weights,长度为n+1,weights[i]表示第i件物品的容积。另外,还有一个一维数组values,长度为n+1,values[i]表示第i件物品的价值。要求选择若干物品,装载在一个物品中,能否让物品的容积和价值的总和不超过固定容量capacity。
def knapsack(weights, values, capacity):
n = len(weights) - 1
dp = [[0] * (capacity + 1) for _ in range(n + 1)]
for i in range(1, n + 1):
for j in range(1, capacity + 1):
if weights[i] > j:
dp[i][j] = dp[i-1][j]
else:
dp[i][j] = max(dp[i-1][j], values[i] + dp[i-1][j-weights[i]])
return dp[-1][-1]
二维数组版
以上一维数组版为基础,增加了一个二维数组dp,保存不同容量下的最大价值。
def knapsack(weights, values, capacity):
n = len(weights) - 1
dp = [[0] * (capacity + 1) for _ in range(n + 1)]
for i in range(1, n + 1):
for j in range(1, capacity + 1):
if weights[i] > j:
dp[i][j] = dp[i-1][j]
else:
dp[i][j] = max(dp[i-1][j], values[i] + dp[i-1][j-weights[i]])
result = []
c = capacity
for i in range(n, 0, -1):
if dp[i][c]!= dp[i-1][c]:
count = min(c // weights[i], values[i])
result.extend([(count, weight) for weight in range(1, weights[i]+1)])
c -= count * weights[i]
return sum([x*y for x, y in result]), result
3.3 A*搜索算法
A搜索算法(A Star Search Algorithm)是一种启发式搜索算法,属于博弈类搜索算法。该算法以图论中的启发式策略为原理,通过估算节点的启发值(即距离最短的目标值)来选取最佳路径。A搜索算法可以解决最短路径问题、寻找任一可行解问题以及多个目标问题。
8皇后问题
一维数组版
给定一个棋盘尺寸n,找出摆放八个皇后的位置,使得任何两个皇后不能互相攻击。
from collections import defaultdict
import sys
sys.setrecursionlimit(10**6)
def check_attack(pos, row, col):
mask = 0b1111 << (row-col)*4
for r in range(row):
if pos & mask >> ((r-col)*4):
return False
for c in range(col):
if pos & mask >> ((row-col-c)*4):
return False
l, r = col-row, col+row
for s in range(-l, r+1):
if pos & mask >> ((row-s)*4):
return False
return True
def queens(size):
results = set()
def backtrack(row, col, pos=0, size=size, count=0):
nonlocal results
if count == size and check_attack(pos, row, col):
board = [['.']*size for _ in range(size)]
for i in range(size):
bit = (pos>>(4*(size-1)))&0xf
board[bit][i] = 'Q'
results.add('\n'.join([' '.join(line) for line in board]))
elif row < size:
for i in range(size):
backtrack(row+1, col+1-i, pos|(1<<i), size, count+(col==i)*(not check_attack(pos|0b1000>>i*(size-1), row+1, col+1-i))*check_attack(pos|(1<<i)<<i*(size-1), row+1, col+1-i))
backtrack(0, 0)
return sorted(results)
print(queens(8))
二维数组版
以上一维数组版为基础,修改成二维数组形式。
from collections import deque
def check_attack(board, row, col):
n = len(board)
for r in range(n):
if board[row][r]:
return False
for c in range(n):
if board[c][col]:
return False
l, r = col-row, col+row
for s in range(-l, r+1):
if s >= 0 and s < n and board[s][col-s]:
return False
for s in range(-l, r+1):
if s >= 0 and s < n and board[s][col+s]:
return False
return True
def queens(size):
results = set()
board = [['.']*size for _ in range(size)]
q = deque([[0, col] for col in range(size)])
while q:
row, col = q.popleft()
if row == size-1 and check_attack(board, row, col):
board[row][col] = 'Q'
results.add('\n'.join([' '.join(line) for line in board]))
continue
for i in range(row, -1, -1):
q.appendleft([i, col])
for i in range(row+1, size):
q.appendleft([i, col])
for i in range(max(row-col, -1), min(row+col, size)):
q.appendleft([row+col-i, i])
for i in range(min(row+col, size)-1, max(row-col, -1), -1):
q.appendleft([row+col-i, i])
q = deque([[pos[0]+1, pos[1]-i] for pos in q if pos[0]<size and not check_attack(board, pos[0], pos[1])]
+[[pos[0]+1, pos[1]+i] for pos in q if pos[0]<size and not check_attack(board, pos[0], pos[1])]
+[[pos[0]+i, pos[1]-i] for pos in q if not check_attack(board, pos[0], pos[1]) and abs(pos[0]-pos[1])+abs(i)<size]
+[[pos[0]+i, pos[1]+i] for pos in q if not check_attack(board, pos[0], pos[1]) and abs(pos[0]-pos[1])+abs(i)<size])
return sorted(results)
print(queens(8))