如何跟踪广度优先搜索的深度?

Posted

技术标签:

【中文标题】如何跟踪广度优先搜索的深度?【英文标题】:How to keep track of depth in breadth first search? 【发布时间】:2015-09-23 16:55:41 【问题描述】:

我有一个树作为广度优先搜索的输入,我想知道算法进展到哪个级别?

# Breadth First Search Implementation
graph =  
    'A':['B','C','D'],
    'B':['A'],
    'C':['A','E','F'],
    'D':['A','G','H'],
    'E':['C'],
    'F':['C'],
    'G':['D'],
    'H':['D']
    


def breadth_first_search(graph,source):
    """
    This function is the Implementation of the breadth_first_search program
    """
    # Mark each node as not visited
    mark = 
    for item in graph.keys():
        mark[item] = 0

    queue, output = [],[]

    # Initialize an empty queue with the source node and mark it as explored
    queue.append(source)
    mark[source] = 1
    output.append(source)

    # while queue is not empty
    while queue:
        # remove the first element of the queue and call it vertex
        vertex = queue[0]
        queue.pop(0)
        # for each edge from the vertex do the following
        for vrtx in graph[vertex]:
            # If the vertex is unexplored
            if mark[vrtx] == 0:
                queue.append(vrtx)  # mark it as explored
                mark[vrtx] = 1      # and append it to the queue
                output.append(vrtx) # fill the output vector
    return output

print breadth_first_search(graph, 'A')

它将树作为输入图,我想要的是,在每次迭代时它都应该打印出正在处理的当前级别。

【问题讨论】:

您是否正在制作自己的 BFS 实施?如果是,它只是你必须使用和维护的 depthCounter。还是您使用任何现成的算法?? 我已经添加了代码,没有现成的算法,只是一个常规的广度优先搜索实现。 【参考方案1】:

实际上,我们不需要额外的队列来存储当前深度的信息,也不需要添加null 来判断是否是当前关卡的结束。我们只需要知道当前关卡包含多少个节点,就可以处理同一关卡的所有节点,处理完当前关卡的所有节点后,将关卡加1。

int level = 0;
Queue<Node> queue = new LinkedList<>();
queue.add(root);
while(!queue.isEmpty())
    int level_size = queue.size();
    while (level_size-- != 0) 
        Node temp = queue.poll();
        if (temp.right != null) queue.add(temp.right);
        if (temp.left != null) queue.add(temp.left);
        
    level++;

【讨论】:

这个答案值得更多赞誉。如果队列已经包含空值,null 解决方案将不起作用。也非常适合不想在其数据结构中强制为空的人 在每个级别的末尾添加 null 会显着改变我们的数据。数据可能是只读的。即使数据不是只读的,这也不是一个好方法。这可能会损害我们的数据完整性。 这是最好的答案,简单,没有多余的空间。它适用于不平衡的树。【参考方案2】:

您不需要使用额外的队列或进行任何复杂的计算来实现您想要做的事情。这个想法很简单。

这不使用除用于 BFS 的队列之外的任何额外空间。

我要使用的想法是在每个级别的末尾添加null。因此,您遇到的空值数量 +1 就是您所处的深度。 (当然终止后它只是level)。

     int level = 0;
     Queue <Node> queue = new LinkedList<>();
     queue.add(root);
     queue.add(null);
     while(!queue.isEmpty())
          Node temp = queue.poll();
          if(temp == null)
              level++;
              queue.add(null);
              if(queue.peek() == null) break;// You are encountering two consecutive `nulls` means, you visited all the nodes.
              else continue;
          
          if(temp.right != null)
              queue.add(temp.right);
          if(temp.left != null)
              queue.add(temp.left);
     

【讨论】:

我喜欢这种方法,但我没有查看队列的双空终止,而是将 while 循环更改为 queue.size() &gt; 1。队列中总是有一个空值来表示深度,所以当只剩下空值时,队列是空的。 在每个级别的末尾添加 null 会显着改变我们的数据。数据可能是只读的。即使数据不是只读的,这也不是一个好方法。这可能会损害我们的数据完整性。【参考方案3】:

维护一个队列,存储BFS队列中对应节点的深度。示例代码供您参考:

queue bfsQueue, depthQueue;
bfsQueue.push(firstNode);
depthQueue.push(0);
while (!bfsQueue.empty()) 
    f = bfsQueue.front();
    depth = depthQueue.front();
    bfsQueue.pop(), depthQueue.pop();
    for (every node adjacent to f) 
        bfsQueue.push(node), depthQueue.push(depth+1);
     

此方法简单而幼稚,对于 O(1) 额外空间,您可能需要@stolen_leaves 的答案。

【讨论】:

【参考方案4】:

试着看看这篇文章。它使用变量 currentDepth

跟踪深度

https://***.com/a/16923440/3114945

对于您的实现,请跟踪最左边的节点和深度变量。每当从队列中弹出最左边的节点时,您就知道您达到了一个新的级别并增加了深度。

因此,您的根是级别 0 的 leftMostNode。那么最左边的子节点是 leftMostNode。一旦你点击它,它就会变成第 1 级。这个节点最左边的孩子是下一个leftMostNode,依此类推。

【讨论】:

【参考方案5】:

使用此 Python 代码,您可以通过仅在队列中遇到新深度的节点后增加深度来保持每个节点从根开始的深度。

    queue = deque()
    marked = set()
    marked.add(root)
    queue.append((root,0))

    depth = 0
    while queue:
        r,d = queue.popleft()
        if d > depth: # increase depth only when you encounter the first node in the next depth               
            depth += 1
        for node in edges[r]:
            if node not in marked:
                marked.add(node)
                queue.append((node,depth+1))

【讨论】:

【参考方案6】:

如果您的树是完美平衡的(即每个节点具有相同数量的子节点),这里实际上有一个简单、优雅的解决方案,时间复杂度为 O(1),空间复杂度为 O(1)。我发现这很有用的主要用例是遍历二叉树,尽管它可以轻松适应其他树大小。

这里要实现的关键是,与上一层相比,二叉树的每一层包含的节点数量正好是上一层的两倍。这允许我们在给定树深度的情况下计算任何树中的节点总数。例如,考虑以下树:

这棵树的深度为 3 和 7 个节点。我们不需要计算节点的数量来解决这个问题。我们可以用公式在 O(1) 时间内计算它:2^d - 1 = N,其中d 是深度,N 是节点总数。 (在三叉树中,这是 3^d - 1 = N,在每个节点有 K 个子节点的树中,这是 K^d - 1 = N)。所以在这种情况下,2^3 - 1 = 7。

为了在进行广度优先搜索时跟踪深度,我们只需要反转这个计算。虽然上面的公式允许我们在给定d 的情况下求解N,但我们实际上想在给定N 的情况下求解d。例如,假设我们正在评估第 5 个节点。为了弄清楚第 5 个节点的深度,我们采用以下等式:2^d - 1 = 5,然后简单地求解d,这是基本代数:

如果d 不是整数,则向上取整(一行中的最后一个节点始终是整数)。考虑到这一点,我提出了以下算法来在广度优先遍历期间识别二叉树中任何给定节点的深度:

    让变量visited等于0。 每次访问节点时,将 visited 加 1。 每增加一次visited,计算节点的深度为depth = round_up(log2(visited + 1))

您还可以使用哈希表将每个节点映射到其深度级别,但这确实会将空间复杂度增加到 O(n)。这是该算法的 php 实现:

<?php
$tree = [
    ['A', [1,2]],
    ['B', [3,4]],
    ['C', [5,6]],
    ['D', [7,8]],
    ['E', [9,10]],
    ['F', [11,12]],
    ['G', [13,14]],
    ['H', []],
    ['I', []],
    ['J', []],
    ['K', []],
    ['L', []],
    ['M', []],
    ['N', []],
    ['O', []],
];

function bfs($tree) 
    $queue = new SplQueue();
    $queue->enqueue($tree[0]);
    $visited = 0;
    $depth = 0;
    $result = [];

    while ($queue->count()) 

        $visited++;
        $node = $queue->dequeue();
        $depth = ceil(log($visited+1, 2));
        $result[$depth][] = $node[0];


        if (!empty($node[1])) 
            foreach ($node[1] as $child) 
                $queue->enqueue($tree[$child]);
            
        
    
    print_r($result);


bfs($tree);

哪些打印:

    Array
    (
        [1] => Array
            (
                [0] => A
            )

        [2] => Array
            (
                [0] => B
                [1] => C
            )

        [3] => Array
            (
                [0] => D
                [1] => E
                [2] => F
                [3] => G
            )

        [4] => Array
            (
                [0] => H
                [1] => I
                [2] => J
                [3] => K
                [4] => L
                [5] => M
                [6] => N
                [7] => O
            )

    )

【讨论】:

【参考方案7】:

在 Java 中是这样的。 这个想法是看父母来决定深度。

//Maintain depth for every node based on its parent's depth
Map<Character,Integer> depthMap=new HashMap<>();    

queue.add('A');
depthMap.add('A',0); //this is where you start your search

while(!queue.isEmpty())

   Character parent=queue.remove();
   List<Character> children=adjList.get(parent);
   for(Character child :children)
   
      if (child.isVisited() == false) 
           child.visit(parent);
           depthMap.add(child,depthMap.get(parent)+1);//parent's depth + 1
         

   


【讨论】:

这将导致无限循环。你需要检查孩子是否已经访问过 for(String c:children) if(!depthMap.containsKey(c)) depthMap.put(c,depthMap.get(parent)+1);//父母的深度+1队列.add(c); 【参考方案8】:

在探索图形时,使用字典来跟踪每个节点的级别(距起点的距离)。

Python 示例:

from collections import deque

def bfs(graph, start):
    queue = deque([start])
    levels = start: 0
    while queue:
        vertex = queue.popleft()
        for neighbour in graph[vertex]:
            if neighbour in levels:
                continue
            queue.append(neighbour)
            levels[neighbour] = levels[vertex] + 1
    return levels

【讨论】:

【参考方案9】:

设置一个变量cnt并将其初始化为队列的大小cnt=queue.size(),现在每次弹出时递减cnt。当cnt 变为0 时,增加BFS 的深度,然后再次设置cnt=queue.size()

【讨论】:

那是很多写操作。写操作确实需要 CPU 周期。【参考方案10】:

我用python写了一个简单易读的代码。

class TreeNode:
    def __init__(self, x):
        self.val = x
        self.left = None
        self.right = None

class Solution:
    def dfs(self, root):
        assert root is not None
        queue = [root]
        level = 0
        while queue:
            print(level, [n.val for n in queue if n is not None])
            mark = len(queue)
            for i in range(mark):
                n = queue[i]
                if n.left is not None:
                    queue.append(n.left)
                if n.right is not None:
                    queue.append(n.right)
            queue = queue[mark:]
            level += 1

用法,

# [3,9,20,null,null,15,7]
n3 = TreeNode(3)
n9 = TreeNode(9)
n20 = TreeNode(20)
n15 = TreeNode(15)
n7 = TreeNode(7)
n3.left = n9
n3.right = n20
n20.left = n15
n20.right = n7
DFS().dfs(n3)

结果

0 [3]
1 [9, 20]
2 [15, 7]

【讨论】:

以上是关于如何跟踪广度优先搜索的深度?的主要内容,如果未能解决你的问题,请参考以下文章

如何在广度优先搜索中追踪路径?

在深度优先搜索中跟踪和返回路径

深度优先搜索法和广度优先搜索法

深度优先搜索和广度优先搜索的深入讨论

深度优先搜索和广度优先搜索的比较与分(转)

基本算法——深度优先搜索(DFS)和广度优先搜索(BFS)