结构化思考和金字塔结构之:逻辑框架与思维模型

发布于:2023-10-24 ⋅ 阅读:(87) ⋅ 点赞:(0)

作者:禅与计算机程序设计艺术

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算法适用于无负权值图。其算法过程如下所示:

  1. 初始化:源点src设置为最短路径的终点。其余节点的最短路径长度为正无穷大。
  2. 将源点放入优先队列Q中。
  3. 从Q中选取最短的路径的节点,标记该节点为当前节点cur。如果cur等于目的点dst,则退出循环。
  4. 对每条从当前节点cur到其邻居节点adj的边,计算从当前节点cur到邻居节点adj的路径长度:
    • 如果从src到邻居节点adj的路径长度等于正无穷大,则更新邻居节点adj的最短路径长度为当前节点cur的最短路径长度加上边的权重。同时,将邻居节点adj的前驱节点设置为当前节点cur。
    • 如果从src到邻居节点adj的路径长度大于当前节点cur的最短路径长度加上边的权重,则忽略该邻居节点adj。
  5. 将当前节点cur的所有邻居节点加入Q中。
  6. 重复第3~5步,直到Q为空或者找到目的点dst。
  7. 返回从源点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))