zoukankan      html  css  js  c++  java
  • 邓俊辉数据结构学习-5-树

    树的一些结论

    度: 一个节点的孩子数称为度。

    一颗树的边数 = 节点数n - 1 意义在于衡量算法复杂度时使用。

    树的特性

    路径(通路) + 环路

    通路: a, b 之间通过节点连接成的路。

    * 长度:所有边的数目(有些文献使用节点定义长度)

    环路:通路的俩个节点彼此短路,即重合构成环路。eg. a - b - c - d - b

    连通图:节点之间均有路径

    无环图:没有环路

    重要特性

    1. 所谓的树就是无环连同图。极小连通图,极大无环图。
    2. 任一节点到根的路径唯一。因此path(v,r) = path(r); (v 就是vertex的意思), 因此我们可以通过
    3. 路劲的长度来衡量书中的一个节点。
    4. 等价类:长度相同的节点,我们称之为等价类。

    简化

    路径,节点,子树均可以相互指代,原因是节点到根路径唯一。所以在后面树的遍历中,要明白访问节点和
    访问子树的概念是不同的,虽然都是使用同一个节点进行代表

    深度: 一个节点的深度就是节点的长度。就是节点到根的路径的长度

    半线性: 前驱唯一性得以满足,但后继唯一性不满足。

    叶子: 没有后代的节点称为叶子。

    树的高度: 一个深度最大的叶子节点的深度称为这颗树的高度。 这一概念可以推给子树的高度。

    根节点的高度就是整树的高度。 某个子节点的高度是这个节点作为子树根节点的高度。

    具有一个节点的树的高度为0。 没有节点的树,也就是空树的高度为-1。

    注意区分一个节点的高度和深度。

    节点的深度是节点到根的路径。节点的高度是节点所代表的子树总,具有最大深度最大的某个节点深度

    树为什么要这样表示

    1. 父亲表示法: 向下查询孩子节点的时候,需要遍历整个表,看谁的父亲是这个节点,才能找到孩子节点。
    2. 孩子表示法: 向上查询父亲的时候,同样需要遍历整个表,看谁的孩子是这个节点,才能找到父亲节点。
    3. 长子+兄弟法:最能体现树的本质。

    二叉树

    重要概念

    1. 真二叉树
      • 所有节点的出度均为偶数。将一颗二叉树转化为真二叉树,有模糊概念,需要到具体应用时才能理解。
    2. 如何使用二叉树来描述多叉树
      • 没有错,真的可以。

    遍历

    先序遍历

    递归版本
    void preTraverseRecur( BinNode * x, VST visit)
    {
        if(!x)
            return ;
        visit(x->data);
        preTraverseRecur(x->lchild, visit);
        preTraverseRecur(x->rchild, visit);
    }
    
    

    有一个理解错误的概念,就是先序遍历,我们做的是先访问根节点,然后访问左子树,然后访问右子树
    注意这里标记出来的点,我之前理解的概念为访问左孩子这个节点。有什么不一样吗?因为一个节点可以表示
    一个节点,同时也可以表示一颗树。(将其视为这颗树的根)。所以我们访问节点,就是访问节点,访问子树
    则意味着访问子树内的所有节点。

    所以先序遍历的理解是:先访问root节点,然后访问左子树,访问右子树。

    迭代版本

    版本A

    这个代码感觉自己已经背会了,不知道怎么写思路了。

    void preTraverseIter(BinNode * x, VST visit)
    {
        stack<BinNode *> s;
        s.push(x); //根节点入栈
        while(s.empty())
        {
            auto ret = s.top(); s.pop();
            visit(ret->data);
            if(x->rchild)  //注意先将右子树入栈
                s.push(x->rchild);
            if(x->lchild)
                s.push(x->lchild);
        }
    }
    

    要点

    1. 需要开始,因此需要将根节点压入栈,然后右子树进栈,然后左子树进栈,然后新一轮判断。
    2. 必须先右再左,因为栈是filo。
    3. 这个感觉其实很奇怪,就是利用栈来模拟整个递归过程。

    版本B

    void preTraverseIter(BinNode * x, VST visit)
    {
        stack<BinNode *>tmp;
        while(true)
        {
            visitAlongLC(x, visit, tmp); //有左树,走左树,同时将每个经历的右树入栈
            if(tmp.empty())
                return ;
            x = tmp.top(); //走向最近的右树
            tmp.pop();
        }
    }
    
    void visitAlongLC(BinNode *x, VST visit, stack<BinNode *> &tmp)
    {
        while(x)
        {
            visit(x->data);
            tmp.push(x->rc);
            x = x->lc;
        }
    }
    

    慢慢有点感觉了,就是有左走左,没左就走一步右,然后继续循环。有点绕大圈的感觉是不是。

    中序遍历

    递归版本
    void inTraverseRecur( BinNode * x, VST visit)
    {
        if(!x)
            return ;
        preTraverseRecur(x->lchild, visit);
        visit(x->data);
        preTraverseRecur(x->rchild, visit);
    }
    
    
    迭代版本
    void inTraverseIter(BinNode * x, VST visit)
    {
        stack<BinNode *> S;
        while(true)
        {
            {//goAlongLc
            while(x)
            {
                S.push(x);
                x = x->lc;
            }
            }
            if(S.empty())
                break;
            x = S.top();
            S.pop();
            visit(x);
            x = x->rc;
        }
    }
    

    还是无法具体描述这种感觉,需要仔细和递归版本比较。递归版本来看。就是有左边就一直走左边。
    走到左边不能走为止,然后回朔,访问最近的左节点(也就是不能左边空节点的根),然后走向这个节点
    的右子树。

    后序遍历

    递归版本
    void postTraverseRecur( BinNode * x, VST visit)
    {
        if(!x)
            return ;
        preTraverseRecur(x->lchild, visit);
        preTraverseRecur(x->rchild, visit);
        visit(x->data);
    }
    
    
    迭代版本

    最喜欢的就是迭代版本的后序遍历,因为真的很巧妙。它恰好是先根,再右子树,再左树的倒序输出。
    明显就是使用俩个栈就可以实现了。

    void postTraverseIter(BinNode * x, VST visit)
    {
        stack<BinNode * > helper;
        stack<BinNode * > output;
        helper.push(x);
        while(!helper.empty())
        {
            x = helper.top();
            helper.pop();
            output.push(x);
            if(HasLc(x))
                helper.push(x->lc);
            if(HasRc(c))
                helper.push(x->rc);
        }
        while(!output.empty())
        {
            visit(output.top());
            output.pop();
        }
    }
    

    注意这里是先压左树再压右树,和先序遍历的方式刚好相反。

    层次遍历

    层次遍历,自上而下
    void levelTraverse(BinNode * x, VST visit)
    {
        queue<BinNode *> Q;
        Q.push(x);
        while(!Q.empty())
        {
            x = Q.front();
            Q.pop();
            visit(x);
            if(HasLc(x))
                Q.push(x->lc);
            if(HasRc(x))
                Q.push(x->rc);
        }
    }
    
    层次遍历,自下而上

    哇!这个实现也非常巧妙

    void levelReverseTraverse(BinNode * x, VST visit)
    {
        queue<BinNode *> Q;
        stack<BinNode *> S;
        Q.push(x);
        while(!Q.empty())
        {
            x = Q.front();
            Q.pop();
            S.push(x);
            if(HasRc(x))
                Q.push(x->rc);
            if(HasLc(x))
                Q.push(x->lc);
        }
        while(!S.empty())
        {
            visit(S.top());
            S.pop();
        }
    }
    

    根据前序或者后序配合中序还原树

    这个代码自己花了3个小时才搞出来。

    BinNode * rebuild(vector<int> preorder, vector<int> inorder)
    {
        return buildHelper(preorder.begin(), preorder.end(), inorder.begin(), inorder.end());
    }
    using iter = vector<int>::iterator;
    BinNode * buildHelper(iter p1, iter p2, iter i1, iter i2)
    {
        if(p1 >= p2 || i1 >= i2)
            return nullptr;
        BinNode * root = new BinNode();
        root->val = *p1;
        auto ret = find(i1, i2, root->val);
        int len = ret - i1;
        p1++;
        root->lc = buildHelper(p1, p1 + len, i1, ret);
        root->rc = buildHelper(p1+len, p2, ret + 1, i2); 
        return root;
    }
    

    时刻要记得自己是[low, high) 的方式,还是[low, high]的方式在操纵。大部分情况下是[low, high)的方式
    因为在c++中的迭代器就是给你这样的方式。

    这里可以优化的一个点就是find这里。在leetcode上看到的做法是直接使用散列表。因为preorder和inorder
    中的那个数都是相同的,所以讲preorder中的数字映射到inorder中的位置。这样以O(1)的时间得到

    同样,使用后序遍历结合中序遍历的情况下也能建树

    BinNode * rebuild(vector<int> postorder, vector<int> inorder)
    {
        return buildHelper(postorder.begin(), postorder.end(), inorder.begin(), inorder.end());
    }
    using iter = vector<int>::iterator;
    BinNode * buildHelper(iter p1, iter p2, iter i1, iter i2)
    {
        if(p1 >= p2 || i1 >= i2)
            return nullptr;
        BinNode * root = new BinNode();
        root->val = *(p2 - 1);
        auto ret = find(i1, i2, root->val);
        int len = ret - i1;
        root->lc = buildHelper(p1, p1 + len, i1, ret);
        root->rc = buildHelper(p1+len, p2 - 1, ret + 1, i2); 
        return root;
    }
    

    插入和删除

    插入节点

    这个其实就是基本功夫的考验。这里主要注意的是我们这次有parent节点。

    succ,计算后继节点

    如何计算后继节点,首先必须要深深理解中序遍历。所谓后继节点,就是当前节点中序遍历的下一个节点。

    BinNode * succ()
    {
        BinNode * s = this;
        if(this->rc) // 如果有右树,就一定在右树之中
        {
            s = rc;
            while(s) s = s->lc; 
        }
        else{ // 如果该节点没有右树
            while(s == s->parent->rc)  s = s->parent; // 那么该节点一定是最右边的孩子。因此首先需要找到
                                                      // 其右树开始的分叉
            s = s->parent;                            // 此时的s一定是在上一节点的左树之中,它的parent
                                                      // 就是后继 
        } 
    }
    

    不是特别特别的明白。但是这里其实也不是那么重要。

  • 相关阅读:
    步步为营VS 2008 + .NET 3.5(3) C# 3.0新特性之Automatic Properties(自动属性)、Object Initializers(对象初始化器)、Collection Initializers(集合初始化器)和Extension Methods(扩展方法)
    新瓶旧酒ASP.NET AJAX(10) 客户端脚本编程(Sys.Services命名空间下的类)
    步步为营VS 2008 + .NET 3.5(2) VS 2008新特性之JavaScript Intellisense and Debugging(JavaScript的智能感知和调试)
    新瓶旧酒ASP.NET AJAX(8) 客户端脚本编程(Sys.Net命名空间下的WebRequestManager、WebRequest、WebRequestExecutor和XMLHttpExecutor)
    步步为营VS 2008 + .NET 3.5(11) DLINQ(LINQ to SQL)之大数据量分页、延迟执行和日志记录
    步步为营VS 2008 + .NET 3.5(14) XLINQ(LINQ to XML)之针对XML文件的添加、查询、更新和删除
    [翻译]ASP.NET 2.0中的健康监测系统(Health Monitoring)(3) 触发自定义事件
    稳扎稳打Silverlight(2) 1.0实例之支持录音和回放的钢琴(Silverlight+ASP.NET AJAX+DLINQ)
    步步为营VS 2008 + .NET 3.5(7) LINQ查询操作符之First、FirstOrDefault、Last、LastOrDefault、ElementAt、ElementAtOrDefault、Contains、Any、All、Count、LongCount、Sum、Min、Max、Average、Aggregate、Cast、DefaultIfEmpty、SequenceEqual、OfType、ToArray、ToList、ToDictionary
    步步为营VS 2008 + .NET 3.5(1) VS 2008新特性之Multi Targeting(多定向)、Web Designer and CSS(集成了CSS的web设计器)和Nested Master Page(嵌套母版页)
  • 原文地址:https://www.cnblogs.com/patientcat/p/9720290.html
Copyright © 2011-2022 走看看