【问题标题】:modified depth first traversal of tree树的修改深度优先遍历
【发布时间】:2010-08-08 18:02:15
【问题描述】:

我在接受亚马逊采访时得到了这个问题。 我被要求在不使用递归或堆栈的情况下对树进行深度优先遍历。我可以为每个节点使用一个父指针,作为结构的一部分,但除此之外别无其他。(例如,“访问过的”变量“或任何东西)。 请给我推荐一个算法。

【问题讨论】:

    标签: c data-structures


    【解决方案1】:

    父指针实际上就是你所需要的。诀窍是在遍历树时消耗它。

    丑陋的伪代码:

    cur = treeroot;
    while (1) { // Get to bottom of tree
        if (cur.hasLeft) {
            cur = left;
        } else if (cur.hasRight) {
            cur = right;
        } else {
            break;
    }
    
    // cur is now the bottom node
    
    while (1) {
        doStuff(cur); // output cur, perform op on it, whatever
        if (!cur.hasParent) { // Done with traversal
            break;
        }
        prev = cur; // So we can tell which way we came up to the parent.
        cur = cur.parent;
        if (cur.hasLeft && cur.left == prev) { // Delete left child; it's done
           cur.hasLeft = false;
        } else if (cur.hasRight && cur.right == prev) { // Delete right child; it's done
           // Note: "else" not desirable if a node should not be able to have the same child in two spots
           cur.hasRight = false;
        }
        if (cur.hasLeft) { // Go all the way to the bottom of the left child
            cur = cur.left;
            while (1) {
                if (cur.hasLeft) {
                    cur = cur.left;
                } else if (cur.hasRight) {
                    cur = cur.right;
                } else {
                    break;
                }
            }
        } else if (cur.hasRight) { // Go all the way to the bottom of the right child
            cur = cur.right;
            while (1) {
                if (cur.hasLeft) {
                    cur = cur.left;
                } else if (cur.hasRight) {
                    cur = cur.right;
                } else {
                    break;
                }
            }
        }
    }
    

    【讨论】:

    • 问题在哪里说它是二叉树?或者你可以摧毁树或者有像 hasRight/hasLeft 这样的标志?
    • @Moron:这个解决方案可以很好地扩展到任意数量的孩子,只要有一个定义的遍历顺序。问题并不是说你可以摧毁这棵树。它也没有说你不能。 hasRight/hasLeft 是.left == NULL 的简写,或者其他什么。如果您无法判断节点是否有左子节点,则您无法安全地遍历树。
    • 坦率地说,如果有人给出了一个解决方案来遍历破坏它的树,我会给他们一个艰难的检查 ;-) 当然,我可能不会问这么愚蠢的问题第一名。
    • 谢谢。这段代码似乎工作正常。面试官确实提到我可以假设这棵树是二元的。很抱歉没有具体说明那部分。 n 是的,我确实给了面试官一个严厉的表情,因为他一直在修改问题,直到他最终得到这个 n i cudnt 的答案。我仍然想知道,如果我能做到这一点,而不必破坏树。
    • 在我看来,树的破坏算作中的“或任何东西”,例如,“访问过的”变量”或任何东西
    【解决方案2】:

    对于“hacky”解决方案,您可以使用指针通常是 4 字节对齐的事实(即最后两位为 0)并将这两位用作您访问的标志。

    【讨论】:

    • 聪明,但如果有人在采访中提出这一点,我会非常认真地审视他们。
    • @Bore:我会以同样的眼光看待问这样一个愚蠢问题的人;-)
    • @moron:我有一个想法,我可以这样使用指针。你能详细说明我如何实现这个吗?
    • @word:基本上对于一个节点来说,假设你使用父指针来存储访问标志,那么你可以执行类似的操作:int visited = (node->parent) &0x01 或设置访问标志,如 node->parent = node->parent | 0x01。在这里,您使用最后一位作为已访问标志并使用按位 OR 设置它并使用按位 AND 检索它。我不会在生产代码中推荐这个,只是为了回应一些聪明的 alec 面试官。
    【解决方案3】:

    这是我对二叉树的提议。语言是 C#。它是包含整数的 binarTree 类的私有方法

    private Node DFS(int value)
    {
        Node current = this.root;
        if(current.data == value) return current;
    
        while(true)
        {
            //go down-left as far as possible
            while(current.leftChild != null)
            {
                current = current.leftChild;
                if(current.data == value) return current;
            }
            //leftChild is null, but maybe I can go right from here
            while(current.leftChild == null && current.rightChild != null)
            {
                current = current.rightChild;
                if(current.data == value) return current;
            }
            if(current.leftChild == null && current.rightChild == null)
            {
                // Ok, I got to a leaf. Now I have to get back to the last "crossroads"
                // I went down-left from, but there was also down-right option
                while(current.parent != null &&
                      (current == current.parent.rightChild ||
                       current.parent.rightChild == null))
                {
                    current = current.parent;
                }
                if(current.parent == null) return null;
    
                // Ok If I'm here, that means I found the crossroads mentioned before
                // I'll go down-right once and then I should try down-left again
                current = current.parent.rightChild;
                if(current.data == value) return current;
            }
        }
    }
    

    如果它不是二叉树,那么事情当然会变得更复杂,但逻辑是相似的。在每个级别带走第一个可能的孩子的叶子。一旦你到达一片叶子,你就会上去。每次您查看父母时,您都会检查您应该来自的孩子是否是父母列表中的最后一个。如果没有,你带下一个孩子,然后再下去。如果是,您上去检查以下父级。如果你回到根,你搜索了整棵树。

    编辑

    好吧,搜索和遍历是不同的东西,我的错。这是一些修改后的遍历代码

    预购:

    public void preorderTraversal()
    {
        Node current = this.root;
        Console.WriteLine(" item: {0} ", current.data);
        while(true)
        {
            while(current.leftChild != null)
            {
                current = current.leftChild;
                Console.WriteLine(" item: {0} ", current.data);
            }
            while(current.leftChild == null && current.rightChild != null)
            {
                current = current.rightChild;
                Console.WriteLine(" item: {0} ", current.data);
            }
            if(current.leftChild == null && current.rightChild == null)
            {
                while(current.parent != null &&
                      (current == current.parent.rightChild ||
                       current.parent.rightChild == null))
                {
                    current = current.parent;
                }
                if(current.parent == null)
                {
                    return;
                }
                else
                {
                    current = current.parent.rightChild;
                    Console.WriteLine(" item: {0} ", current.data);
                }
            }
        }
    }
    

    顺序:

    public void inorderTraversal()
    {
        Node current = this.root;
        while(true)
        {
            while(current.leftChild != null)
            {
                current = current.leftChild;
            }
            Console.WriteLine(" item: {0} ", current.data);
            while(current.leftChild == null && current.rightChild != null)
            {
                current = current.rightChild;
                Console.WriteLine(" item: {0} ", current.data);
            }
            if(current.leftChild == null && current.rightChild == null)
            {
                while(current.parent != null &&
                      (current == current.parent.rightChild ||
                       current.parent.rightChild == null))
                {
                        current = current.parent;
                        if(current.rightChild == null)
                        {
                            Console.WriteLine(" item: {0} ", current.data);
                        }
                }
                if(current.parent == null)
                {
                    return;
                }
                else
                {
                    Console.WriteLine(" item: {0} ", current.parent.data);
                    current = current.parent.rightChild;
                }
            }
        }
    }
    

    后购:

    public void postorderTraversal()
    {
        Node current = this.root;
        while(true)
        {
            while(true)
            {
                if(current.leftChild != null)
                {
                    current = current.leftChild;
                }
                else if(current.rightChild != null)
                {
                    current = current.rightChild;
                }
                else
                {
                    break;
                }
            }
            while(current.parent != null &&
                  (current == current.parent.rightChild ||
                   current.parent.rightChild == null))
            {
                Console.WriteLine(" item: {0} ", current.data);
                current = current.parent;
            }
            Console.WriteLine(" item: {0} ", current.data);
            if(current.parent == null)
            {
                return;
            }
            else
            {
                current = current.parent.rightChild;
            }
        }
    }
    

    【讨论】:

    • 它是二叉树,但不是搜索操作。我被要求遍历树。我真的不明白,这个“价值”是什么。
    • @wordwiz 好吧,我的错。我首先阅读了深度,然后立即触发了我脑海中的搜索。对于遍历,我宁愿期待 pre、post 或 inorder。更改我的代码以进行预购遍历是微不足道的。只需将检查是否找到值替换为每次访问时执行的一些操作。中序和后序遍历需要更多更改。
    【解决方案4】:

    如果你有一个父指针,那么你可以在没有堆栈的情况下展开树。放松期间唯一的另一个问题是“我需要去看其他孩子吗?”这可以通过比较指针值来解决,以确定您是刚刚从左孩子还是右孩子(或推广到 N 个孩子)返回。

    编辑

    伪代码(未经测试):

    p_last          = NULL;
    p               = p_head;
    descend         = true;
    
    while (NULL != p)
    {
        p_tmp = p;
    
        if (descend)
        {
            // ... Node processing here...
    
            if (0 == p->num_children)
            {
                // No children, so unwind
                p = p_parent;
                descend = false;
            }
            else
            {
                // Visit first child
                p = p->child[0];
            }
        }
        else
        {
            // Find the child we just visited
            for (i = 0; i < p->num_children; i++)
            {
                if (p_last == p->child[i])
                {
                    break;
                }
            }
            if (i == num_children-1)
            {
                // Processed last child, so unwind
                p = p_parent;
            }
            else
            {
                // Visit next child
                p = p->p_child[i+1];
                descend = true;
            }
        }
    
        p_last = p_tmp;
    }
    

    【讨论】:

    • 显示一些代码?我不确定这是否真的有效,因为访问正确的孩子需要存储在树之外。
    • 懒得去想代码!每次你回到一个节点时,将你刚刚来自的孩子的地址与右孩子的指针进行比较。如果它们相等,则继续放松,否则访问正确的孩子。所以不确定你的意思
    • 此代码不起作用。如果树结构包含一些在某些地方只有正确子节点的节点,则某些节点将不会被访问。此外,您不会标记处理节点的位置 - 这很重要,因为您的代码几乎是自上而下的遍历。
    • 问题只是说树。为什么要假设二叉树?
    • @Borealid:公平点,但正如我所说,“未经测试!”。我不认为它违反了原则(但我会尝试修复它)。
    【解决方案5】:

    这显示了我如何在 C 中执行此操作。它演示了前序和后序遍历,并且适用于每个节点的 0..N 个子节点。

    struct node {
        struct node *parent;
        struct node *child[NCHILD];
    };
    
    void dfs(struct node *head, void (*visit_preorder)(struct node *n), void (*visit_postorder)(struct node *n))
    {
        struct node *current = head;
        struct node *prev = head;
    
        while (current)
        {
            int i;
    
            /* preorder traversal */
            if (prev == current->parent)
                visit_preorder(current);
    
            /* Find first child to visit */
            for (i = NCHILD; i > 0; i--)
            {
                if (prev == current->child[i - 1])
                    break;
            }
            while (i < NCHILD && current->child[i] == NULL)
                i++;
    
            prev = current;
    
            if (i < NCHILD)
            {
                /* Descend to current->child[i] */
                current = current->child[i];
            }
            else
            {
                /* Postorder traversal */
                visit_postorder(current);
    
                /* Ascend back to parent */
                current = current->parent;
            }
        }
    }
    

    【讨论】:

      猜你喜欢
      • 2019-01-08
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-08-10
      • 2012-11-20
      • 1970-01-01
      相关资源
      最近更新 更多