一个很容易引发血案的数据结构 - 经典二叉树问题

    下面是一些二叉树的经典算法,写一写就当复习了。其中的二叉树递归、非递归遍历,由遍历结果构造二叉树等,都是最基础的东西,而二叉树转链表,路径求和等是在leetcode上遇到,则是希望和不小心点错了才进来的朋友们分享分享。二叉树能玩的远不止这些,还有太多代码没有整理到一起,以后再贴。

 

0  二叉树节点的定义

    下面定义的节点是不带parent指针的,因为迄今为止我遇见的树,还都是没有parent指针的,除了红黑树。即使有parent有时会方便一些(比如path sum),但是没有它也可以想办法。

template <class T>
struct tnode
{
    T val;
    tnode * l;
    tnode * r;
    tnode(T _v, tnode * _l=0, tnode * _r=0):val(_v), l(_l), r(_r){}
};

 

1  递归遍历

template<class T>
void inorderTraversal(tnode<T> * r)
{
    if(!r) return;
    inorderTraversal(r->l);
    cout <<r->val<<ends;
    inorderTraversal(r->r);
}

template<class T>
void postorderTraversal(tnode<T> * r)
{
    if(!r) return;
    postorderTraversal(r->l);
    postorderTraversal(r->r);
    cout <<r->val<<ends;
}

template<class T>
void preorderTraversal(tnode<T> * r)
{
    if(!r) return;
    cout <<r->val<<ends;
    preorderTraversal(r->l);
    preorderTraversal(r->r);
}

 

2  删除一个二叉树最简单的办法当然也是递归了。写上下面这个函数是为了提醒自己,new了一定要记得delete。

template<class T>
void erase(tnode<T> * r)
{
    if(!r) return;
    erase(r->l);
    erase(r->r);
    delete r;
}

 

3  非递归遍历

简单的记忆方法:

    先序:访问、压右指针、左路下降、遇空弹栈 (但注意下面代码中初始栈中的监视哨)。

    中序:左路下降并一路压栈、遇空弹栈往右走

    后序:一个栈记录指针,一个栈记录即将访问的是该指针的左子树还是右子树;左路下降并压栈,遇空弹栈,往右边走,走完右边再访问父节点。

template<class T>
void preorderTraversal_stack(tnode<T> * r)
{
    stack<tnode<T> *> s;
    tnode<T> * p = r;
    s.push(0);
    while(p)
    {
        cout <<p->val<<ends;
        if(p->r)
            s.push(p->r);
        if(p->l)
            p = p->l;
        else
        {
            p = s.top();
            s.pop();
        }
    }
}

template<class T>
void inorderTraversal_stack(tnode<T> * r)
{
    stack<tnode<T> *> stk;
    tnode<T> * p = r, * q;

    while(stk.size() || p)
    {
        if(p)
        {
            stk.push(p);
            p = p->l;
        }
        else
        {
            p = stk.top();
            stk.pop();
            cout <<p->val<<ends;
            p = p->r;
        }
    }
}

template<class T>
void postorderTraversal_stack(tnode<T> * r)
{
    stack<tnode<T> *> s;
    stack<char> f;
    tnode<T> * p = r;
    while(s.size() || p)
    {
        if(p)
        {
            s.push(p);
            f.push(0);
            p = p->l;
        }
        else
        {
            p = s.top();
            if(f.top() == 0)
            {
                f.top() = 1;
                p = p->r;
            }
            else
            {
                cout <<p->val<<ends;
                s.pop();
                f.pop();
                p=0;
            }
        }
    }

}

4  二叉树变成单链表(先序顺序),right指向下一个节点,left指向空。

    记得有道题是将二叉树变成有序的双向链表,注意此题与之不同。

void flatten(tnode<int> *r) {
    stack<tnode<int> *> s;
    s.push(0);
    tnode<int> *p = r;
    while(r)
    {
        if(r != p)
        {
            p->l = 0;
            p = p->r = r;
        }
        if(r->r)
            s.push(r->r);
        if(r->l)
            r = r->l;
        else
        {
            r = s.top();
            s.pop();
        }
    }
}

 

5  二叉树转双链表(中序)。如果是二叉搜索树,按中序转换成的双链表当然直接就是有序的了。

   简单写了个递归版本,把左子树变双链表,把右子树变双链表,然后“通通连起来”~

template <class  T>
tnode<T> * tree2list(tnode<T> * root)
{
    if(root == 0) return 0;
    if(root->l==0) return root;
    tnode<T> * l = tree2list(root->l);
    tnode<T> * r = tree2list(root->r);
    if(l)
    {
        while(l->r)
            l = l->r;
        root->l = l;
        l->r = root;
    }
    if(r)
    {
        root->r = r;
        r->l = root;
    }
    while(root->l)
        root = root->l;
    return root;
}

 

 

6,已知中序遍历和先序遍历,构造出二叉树(未考虑有重复值的情况)。

    下面代码用的是递归方法,传值都用引用了,是为了调用的效率能快一些。当然其实可以用iterator代替下标from和to了。

    此外虽然在http://www.cnblogs.com/zhchngzng/articles/4097798.html中我猛喷了vector,但有些题是在leetcode上做的,为了submit方便所以只好跟着用。

int find(vector<int> & v, int from, int to, int target)
{
    for(; from<=to; from++)
        if(v[from] == target)   return from;
    return -1;
}

tnode<int> *buildTreeFromPreAndIn(vector<int> &preorder, int prefrom, int preto, vector<int> &inorder, int infrom, int into) {
        if(preto < prefrom || into < infrom || prefrom < 0 || infrom < 0 || preto >= preorder.size() || into >= inorder.size())
            return 0;
        tnode<int> * r = new tnode<int>(preorder[prefrom]);
        int it = find(inorder, infrom, into, r->val);
        if(it == -1) return 0;
        tnode<int> * rl = buildTreeFromPreAndIn(preorder, prefrom+1, prefrom+(it-infrom), inorder, infrom, it-1);
        tnode<int> * rr = buildTreeFromPreAndIn(preorder, prefrom+(it-infrom)+1, preto, inorder, it+1, into);
        r->r = rr;
        r->l = rl;
        return r;
}

tnode<int> *buildTreeFromPreAndIn(vector<int> &preorder, vector<int> &inorder) {
        if(!preorder.size() || !inorder.size() || preorder.size() != inorder.size()) return 0;
        return buildTreeFromPreAndIn(preorder, 0, preorder.size()-1, inorder, 0, inorder.size()-1);
}

 

7,已知中序遍历和后序遍历,构造二叉树(未考虑有重复值的情况)

tnode<int> *buildTreeFromInAndPost(vector<int> &inorder, int infrom, int into, vector<int> &postorder, int postfrom, int postto) {
        if(into < infrom || postto < postfrom || infrom < 0 || postfrom < 0 || into >= inorder.size() || postto >= postorder.size())
            return 0;
        tnode<int> * r = new tnode<int>(postorder[postto]);
        int it = find(inorder, infrom, into, r->val);
        if(it == -1) return 0;
        tnode<int> * rl = buildTreeFromInAndPost(inorder, infrom, it-1, postorder, postfrom,             postfrom+(it-infrom)-1);
        tnode<int> * rr = buildTreeFromInAndPost(inorder, it+1,   into, postorder, postfrom+(it-infrom), postto-1);
        r->r = rr;
        r->l = rl;
        return r;
    }

tnode<int> *buildTreeFromInAndPost(vector<int> &preorder, vector<int> &inorder) {
        if(!preorder.size() || !inorder.size() || preorder.size() != inorder.size()) return 0;
        return buildTreeFromInAndPost(preorder, 0, preorder.size()-1, inorder, 0, inorder.size()-1);
    }

8,计算每条完全路径的加和

    因为是求加和,我干脆用traits限定只能用于T为常规变量的二叉树。想要用path这个东西来做别的事,比如计算Max sum within the path什么的只要稍微改改就好啦。这不是重点,重点是下面:

template <class T>
vector<vector<T>> pathSum(tnode<T> * p, const T & target)
{
    vector<vector<T>> ret;
    if(is_pod<T>::value == 0)
        return ret;
    vector<tnode<T>*> tr_ptr;
    vector<T> tr_val;
    T sum = 0;
    stack<tnode<T> *> s;
    s.push(0);
    while(p)
    {
        tr_ptr.push_back(p);
        sum += p->val;
        tr_val.push_back(p->val);
        if(p->r)
            s.push(p);
        if(p->l)
            p = p->l;
        else
        {
            if(!p->r && sum == target)
            {
               ret.push_back(tr_val);
            }
            p = s.top();
            if(!p) break;
            s.pop();

            while(tr_ptr.back()!=p)
            {
                sum -= tr_val.back();
                tr_val.pop_back();
                tr_ptr.pop_back();
            }
            p = p->r;
        }
    }
    return ret;
}

 

9  以上代码的简单测试

main()
{
    ios::sync_with_stdio(0);
    tnode<int> * root;

    root = new tnode<int> (1,
                              new tnode<int> (2,
                                         new tnode<int> (4,
                                                         0,
                                                         new tnode<int> (8)),
                                         new tnode<int> (5,
                                                         new tnode<int> (7),
                                                         0)),
                              new tnode<int> (3,
                                         0,
                                         new tnode<int> (6)));


/*
    vector<vector<int>> ret = pathSum(root, 15);
    for(int i=0; i<ret.size(); i++)
    {
        for(int j=0; j<ret[i].size(); j++)
            cout <<ret[i][j]<<" ";
        cout <<endl;
    }
    erase(root);
// */

/*
    postorderTraversal_stack(root);
    cout <<endl;
    flatten(root);
    preorderTraversal_stack(root);
    cout <<endl;
    erase(root);
// */

/*
    int n = 8;
    int pre[] = {1,2,4,8,5,7,3,6};
    int in[]  = {4,8,2,7,5,1,3,6};
    int post[]= {8,4,7,5,2,6,3,1};
    vector<int> preorder(pre, pre+n);
    vector<int> inorder(in, in+n);
    vector<int> postorder(post, post+n);

    root = buildTreeFromPreAndIn(preorder, inorder);
    preorderTraversal(root);
    cout <<endl;
    erase(root);

    root = buildTreeFromInAndPost(inorder, postorder);
    preorderTraversal(root);
    cout <<endl;
    erase(root);
//  */
}

 

10  Binary Search Tree Iterator: (nonrecursive inorder travesal)

 1 class BSTIterator {
 2 public:
 3     stack<TreeNode*> s;
 4     void pushn(TreeNode * p)
 5     {
 6         for(; p; p=p->left)
 7             s.push(p);
 8     }
 9     
10     BSTIterator(TreeNode *root) {
11         pushn(root);
12     }
13 
14     /** @return whether we have a next smallest number */
15     bool hasNext() {
16         return s.size();
17     }
18 
19     /** @return the next smallest number */
20     int next() {
21         TreeNode * p = s.top();
22         int nVal = p->val;
23         s.pop();
24         pushn(p->right);
25         return nVal;
26     }
27 };

 

二叉树能干的事情远不止这些,未完待续。

posted @ 2014-11-15 18:33  chng  阅读(272)  评论(0编辑  收藏  举报
BackToTop