【问题标题】:Calculating maximum mutually independent set of nodes with iterative approach用迭代方法计算最大相互独立的节点集
【发布时间】:2019-11-18 05:39:20
【问题描述】:

我们从一棵 n 叉树开始。每个分支代表“依赖”(即父母依赖于孩子)。我需要找到相互独立的节点的最大和。

递归解决方案相当简单:

int calc_max()
{
    int child_max = 0;
    foreach (var n in nodes)
    {
        child_max += n.calc_max();
    }

    return Math.Max(this.value, child_max);
}

现在我很难为此提供非递归解决方案。这将需要后序遍历。已经回答了二叉树的迭代发布顺序here。但即使使用该解决方案,我也无法从该解决方案中删除递归。

【问题讨论】:

    标签: algorithm graph-algorithm


    【解决方案1】:

    我有一些 SO 答案,它们展示了如何将算法的任何递归表达式转换为非递归表达式。

    让我们再做一次。

    我将使用 C,因为它更清楚地用于此目的:

    int calc_max(NODE *node) {
      int child_sum = 0;
      for (int i = 0; i < node->n_children; ++i) 
        child_sum += calc_max(node->children[i]);
      return MAX(node->value, child_sum);
    }
    

    现在使用显式堆栈、用作“返回寄存器”的变量和goto 来模拟递归调用,以模拟由callreturn 指令执行的跳转。为了清楚起见,让一些东西在伪代码中。

    int calc_max(NODE *node) {
      int rtn_val; // Simulated register for function return value.
     start:
      int child_sum = 0;
      for (int i = 0; i < node->n_children; ++i) {
        <<save params and locals on frame stack>>
        node = node->children[i];
        goto start;
       rtn:
        child_sum += rtn_val;
      }
      // Simulate using a register to return a result.
      int rtn_val = MAX(node->value, child_sum);
      if (<<frame stack isn't empty>>) {
        <<restore params and locals from frame stack>>
        goto rtn;
      }
      return rtn_val;
    }
    

    需要保存的参数和局部变量是nodeichild_sum。这样我们就可以填写伪代码了。

    typedef struct node {
      struct node *children[8];
      int n_children, value;
    } NODE;
    
    #define MAX(A, B) ((A) > (B) ? A : B)
    
    int calc_max(NODE *node) {
      int i, child_sum, rtn_val, sp = 0;
      struct stack_frame {
        NODE *node;
        int i, child_sum;
      } stk[1000];
     start:
      child_sum = 0;
      for (i = 0; i < node->n_children; ++i) {
        stk[sp++] = (struct stack_frame){.node = node, .i = i, .child_sum = child_sum};
        node = node->children[i];
        goto start;
       rtn:
        child_sum += rtn_val;
      }
      rtn_val = MAX(node->value, child_sum);
      if (sp > 0) {
        --sp;
        node = stk[sp].node;
        i = stk[sp].i;
        child_sum = stk[sp].child_sum;
        goto rtn;
      }
      return rtn_val;
    }
    

    这应该可以正常工作。我还没有测试过。 gotos 不是很漂亮,但如果你愿意,可以通过一些代数将它们变成 whilefor 循环。以下是步骤。

    for 循环更改为while 并将rtn 之后的语句移动到goto rtn 之前执行。那么rtn本身就可以移到while之前:

    int calc_max(NODE *node) {
      int i, child_sum, rtn_val, sp = 0;
      struct stack_frame {
        NODE *node;
        int i, child_sum;
      } stk[1000];
     start:
      i = child_sum = 0;
     rtn:
      while (i < node->n_children) {
        stk[sp++] = (struct stack_frame){.node = node, .i = i, .child_sum = child_sum};
        node = node->children[i];
        goto start;
      }
      rtn_val = MAX(node->value, child_sum);
      if (sp > 0) {
        --sp;
        node = stk[sp].node;
        i = stk[sp].i;
        child_sum = stk[sp].child_sum;
        child_sum += rtn_val;
        ++i;
        goto rtn;
      }
      return rtn_val;
    }
    

    现在通过复制i = child_sum = 0,我们可以将goto start的目标移动到rtn

    int calc_max(NODE *node) {
      int i, child_sum, rtn_val, sp = 0;
      struct stack_frame {
        NODE *node;
        int i, child_sum;
      } stk[1000];
      i = child_sum = 0;
     rtn:
      while (i < node->n_children) {
        stk[sp++] = (struct stack_frame){.node = node, .i = i, .child_sum = child_sum};
        node = node->children[i];
        i = child_sum = 0;
        goto rtn;
      }
      rtn_val = MAX(node->value, child_sum);
      if (sp > 0) {
        --sp;
        node = stk[sp].node;
        i = stk[sp].i;
        child_sum = stk[sp].child_sum;
        child_sum += rtn_val;
        ++i;
        goto rtn;
      }
      return rtn_val;
    }
    

    现在请注意,第一个goto rtn 可以消除。 while 循环在没有它的情况下做同样的事情:

    int calc_max(NODE *node) {
      int i, child_sum, rtn_val, sp = 0;
      struct stack_frame {
        NODE *node;
        int i, child_sum;
      } stk[1000];
      i = child_sum = 0;
     rtn:
      while (i < node->n_children) {
        stk[sp++] = (struct stack_frame){.node = node, .i = i, .child_sum = child_sum};
        node = node->children[i];
        i = child_sum = 0;
      }
      rtn_val = MAX(node->value, child_sum);
      if (sp > 0) {
        --sp;
        node = stk[sp].node;
        i = stk[sp].i;
        child_sum = stk[sp].child_sum;
        child_sum += rtn_val;
        ++i;
        goto rtn;
      }
      return rtn_val;
    }
    

    最后我们可以将最后一个goto rtn 替换为一个无限循环,其中我们将return 从函数中(跳出循环),除非goto rtn 会循环:

    int calc_max(NODE *node) {
      int i, child_sum, rtn_val, sp;
      struct stack_frame {
        NODE *node;
        int i, child_sum;
      } stk[1000];
    
      sp = i = child_sum = 0;
      while (1) {
        while (i < node->n_children) {
          stk[sp++] = (struct stack_frame){.node = node, .i = i, .child_sum = child_sum};
          node = node->children[i];
          i = child_sum = 0;
        }
        rtn_val = MAX(node->value, child_sum);
        if (sp <= 0) return rtn_val;
        node = stk[--sp].node;
        i = stk[sp].i + 1;
        child_sum = stk[sp].child_sum + rtn_val;
      }
    }
    

    我可能很容易在此过程中犯错。我手头没有编译器来做任何测试。但是基本的想法是合理的,结果应该是相当有效的。 It compiles to 33 x86 instructions.

    在你完成了其中的一些之后,摆脱goto 的代数技巧就陷入了模式。但是有通用的自动算法可以做同样的事情。构建一个可以自动进行这种转换的工具会相当简单。

    现在gotos 已经消失了,您可以翻译回您选择的语言。

    这个方法的好处是你不需要弄明白特定算法的操作语义。只需从递归表达式开始并模拟编译器所做的事情,然后用代数转换以获得看起来不错的形式。除了愚蠢的错误,它必须有效。

    【讨论】:

      猜你喜欢
      • 2012-01-25
      • 2013-01-23
      • 1970-01-01
      • 2013-05-22
      • 2017-06-04
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多