二叉树遍历与序列化

这里写图片描述

遍历

A 先序遍历
中、左、右
递归方式:

 public void preOrderRecur(Node head){
     if (head==null){
         return;
     }
     System.out.print(head.value + " ");
     preOrderRecur(head.left);
     preOrderRecur(head.right);
}

非递归方式:

  1. 申请一个新的栈,记为stack;
  2. 然后将头结点head压入stack中;
  3. 每次从stack中弹出栈顶节点,记为cur,打印cur的值。若cur右孩子不为空,将cur右孩子压入栈中。若cur左孩子不为空,将cur左孩子压入栈中。
  4. 不断重复3,直到stack为空,结束。

B 中序遍历
左、中、右
递归方式:

 public void preOrderRecur(Node head){
     if (head==null){
         return;
     }
     preOrderRecur(head.left);
     System.out.print(head.value + " ");
     preOrderRecur(head.right);
}

非递归方式:
1. 申请一个新的栈,记为stack,申请一个变量记为cur,初始时令cur等于头节点。
2. 将cur压入栈中,对以cur为头节点的子树来说,不断将整颗子树的左边界压入栈中,即不断令cur=cur.left,重复步骤2。
3. 重复步骤2,直到cur为空。此时从stack中弹出一个节点,记为node,打印node的值,并让cur=cur.right,然后重复步骤2。
4. 当stack为空,并且cur为空时,整个过程结束。

template<typename T> void inOrder(BinNode<T> *head) {//非递归
    if (!head) {
        return;
    }
    std::stack<BinNode<T> *> st;
    BinNode<T> *cur = head;
    while (cur||!st.empty()){
        while (cur){
            st.push(cur);
            cur = cur->lc;
        }
        cur = st.top();
        st.pop();
        cout << cur->data << endl;
        cur = cur->rc;
    }
}

C 后序遍历
左、右、中
递归方式:

 public void preOrderRecur(Node head){
     if (head==null){
         return;
     }
     preOrderRecur(head.left);
     preOrderRecur(head.right);
     System.out.print(head.value + " ");
}

非递归方式——两个栈:
1. 申请一个栈,记为s1,将头节点压入s1中。
2. 从s1中弹出一个节点记为cur,将cur的左孩子压入s1,将cur的右孩子压入s1。
3. 在整个过程中,将每一个s1中弹出的节点都压入第二个栈s2中。
4. 不断重复步骤2,3,直到s1为空,过程停止。
5. 顺序打印s2中的节点。

template<typename T> void postOrder2(BinNode<T> *head) {//使用两个栈
    if (!head) {
        return;
    }
    std::stack<BinNode<T> *> st1, st2;
    st1.push(head);
    while (!st1.empty()){
        BinNode<T> *c = st1.top();
        st1.pop();
        st2.push(c);
        if (c->lc) {
            st1.push(c->lc);
        }
        if (c->rc) {
            st1.push(c->rc);
        }
    }
    while (!st2.empty()){
        BinNode<T> *c = st2.top();
        st2.pop();
        cout << c->data << endl;
    }
}

非递归方式——一个栈:
1. 申请一个新的栈,记为stack,将头节点压入stack,同时设置两个变量h与c,在整个过程中,h代表最近一次弹出并打印的节点,c表示stack的栈顶节点,初始时令h为头节点,c为null。
2. 每次令c等于stack的栈顶节点,但是不弹出,此时分三种情况。
(1)如果c的左孩子不为空,并且h不等于c的左孩子也不等于c的右孩子,则把c的左孩子压入stack中。
(2)如果(1)不成立,并且c的右孩子不为空,h不等于c的右孩子,则将c的右孩子压入stack中。
(3)如果(1)(2)都不成立,那么从stack中弹出c并打印,令h等于c。
3. 一直重复步骤2,直到stack为空,过程停止。

template<typename T> void postOrder(BinNode<T> *head) {//使用一个栈
    if (!head) {
        return;
    }
    std::stack<BinNode<T> *> st;
    BinNode<T> *h = head;//记录最近打印的节点
    BinNode<T> *c = NULL;//当前栈顶节点
    st.push(head);
    while (!st.empty()){
        c = st.top();
        if (c->lc && h != c->lc && h != c->rc) {
            st.push(c->lc);
        }
        else if (c->rc && h != c->rc) {
            st.push(c->rc);
        }
        else {
            st.pop();
            cout << c->data << endl;
            h = c;
        }
    }
}

不论是递归还是非递归方式,时间复杂度都为O(N),N为二叉树节点数,额外空间复杂度为O(L),L为二叉树层数。

D 按层遍历
宽度优先遍历,通过使用队列实现。通常要求按层打印时,要求连同行号一起打印出来。
使用两个变量可以实现按行打印,定义两个变量last与nlast,last表示正在打印的当前行的最右节点,当cur=last时,表示改行打印完,换行后令last=nlast。nlast表示下一行最右节点,在每次往队列里插入新的节点后,令nlast等于新的节点。

template<typename T> void layerOrder(BinNode<T> *head) {//按层遍历并且换行
    if (!head) {
        return;
    }
    BinNode<T> *last = head, *nlast = head;
    std::queue<BinNode<T> *> qu;
    qu.push(head);
    while (!qu.empty()){
        BinNode<T> *cur = qu.front();
        qu.pop();
        cout << cur->data;
        if (cur->lc) {
            qu.push(cur->lc);
            nlast = cur->lc;
        }
        if (cur->rc) {
            qu.push(cur->rc);
            nlast = cur->rc;
        }
        if (cur == last) {
            cout << endl;
            last = nlast;
        }
    }
}

二叉树序列化与反序列化

当程序运行时二叉树是存储在内存中,但是有时想要将二叉树持久化保存为文档的形式,成为二叉树序列化。将存储的二叉树恢复,称为反序列化。
二叉树->字符串(序列化)
字符串->二叉树(反序列化)
序列化方法:
1. 根据先序遍历序列化
2. 根据中序遍历序列化
3. 根据后序遍历序列化
4. 根据按层遍历序列化

扫描二维码关注公众号,回复: 1048464 查看本文章

例:先序遍历对二叉树进行序列化
1. 假设序列化结果为str,初始时str为空字符串。
2. 先序遍历二叉树如果遇到空节点,在str末尾加上”#!”。
3. 如果遇到不为空的节点,例如值为3,则在str末尾加上”3!”。
这里写图片描述

先序遍历二叉树反序列化,先将str变为数组,数组代表二叉树先序遍历的节点顺序。
这里写图片描述

平衡二叉树(AVL树)

1.空树是平衡二叉树
2.如果一棵树不为空,并且其中所有的子树都满足各自的左子树与右子树的高度差都不超过1。
这里写图片描述
如果上方两个为平衡二叉树,下面一个为非平衡二叉树。

例题:给定一个二叉树的头结点head,判断一颗二叉树是否为平衡二叉树。
分析:后序遍历的方法,每次比较以当前节点为头结点的左子树的深度与右子树深度是否相差小于等于1,若左右其中一个不为平衡树,则整颗树不为平衡树。

template<typename T> int isBalance(BinNode<T> *head) { //0为非平衡树,非0为平衡树
    if (!head) {
        return 1;
    }
    int l = isBalance(head->lc);
    if (l == 0) {
        return 0;
    }
    int r = isBalance(head->rc);
    if (r == 0) {
        return 0;
    }
    if (abs(l - r) > 1) {
        return 0;
    }
    if (l >= r) {
        return l + 1;
    }
    else {
        return r + 1;
    }
}

搜索二叉树

每颗子树的头结点的值都比各自左子树上的suoyou节点值要大,也都比各自右子树上的所有节点的值都小。
分析:可以通过中序遍历来检查是否为搜索二叉树。

满二叉树

除了最后一层的节点无任何子节点外,剩下每一层上的节点都有两个子节点。
如果一颗满二叉树层数为L,节点数为N,则 N=2L1 L=log2(N+1)

完全二叉树

除了最优一层之外,其他每一层的节点都是满的,最后一层如果也是满的则为满二叉树,也是完全二叉树,最后一层如果不满,缺少的节点也全部的集中在右边,也是一颗完全二叉树。
例题:给定一个树的头结点head,判断一棵树是否为完全二叉树。
分析:
1.采用按层遍历二叉树的方式,从每层的左边向右边遍历所有节点。
2.如果当前节点有右孩子没有左孩子,返回false
3.如果当前节点不是左右孩子都有,那之后的节点全为叶子节点,否则返回false
4.如果遍历过程中没有返回false,则遍历结束后返回true。

template<typename T> bool isComplete(BinNode<T> *head) {
    if (!head) {
        return true;
    }
    std::queue<BinNode<T> *> qu;
    qu.push(head);
    bool leafFlag = false;
    while (!qu.empty()){
        BinNode<T> *cur = qu.front();
        qu.pop();
        if(leafFlag == false){
            if (cur->lc == NULL && cur->rc != NULL) {
                return false;
            }
            if (cur->lc == NULL || cur->rc == NULL) {
                leafFlag = true;
            }
        }
        else {
            if (cur->lc != NULL || cur->rc != NULL) {
                return false;
            }
        }
        if (cur->lc) {
            qu.push(cur->lc);           
        }
        if (cur->rc) {
            qu.push(cur->rc);
        }
    }
    return true;
}

后继节点

一个节点的后继节点是指,这个节点在中序遍历序列中的下一个节点。
例题:节点含有指向其父节点的指针。给定一个节点,找到其后继节点。
分析:
1.若有右子树,则为右子树最左边的节点。
2.若无右子树,若其为父节点的左孩子,则后继节点为其父节点。
3.若无右子树,若其为父节点的右孩子,则上溯到其在一个节点curNode的左子树上,curNode为后继节点。
4.一直上溯,到空节点,则没有后继节点。

前驱节点

这个节点在中序遍历中的上一个节点。

例题:

题一:

这里写图片描述
分析:
这里写图片描述

题二:

一颗二叉树原本是搜索二叉树,但是其中两个节点调换了位置,使得这颗二叉树不再是搜索二叉树,请找出这两个错误节点。
分析:1.对二叉树进行中序遍历,依次出现的节点值会一直升序,如果两个节点值降序,则为出错处。
2.如果在中序遍历时节点出现了两次降序,第一个错误的节点为第一次降序时较大的节点,第二个错误的节点为第二次降序时较小的节点。
3.如果中序遍历时节点值只出现了一次降序,第一个错误节点为这次降序较大的节点,第二个错误节点为这次降序较小的节点。

题三

从一颗二叉树的节点A出发,可以向上或者向下走,但沿途的节点只能经过一次,当到达节点B时,路径上的节点数叫做A到B的距离。给定一颗二叉树的头结点head,求整棵树上的节点间的最大距离。
分析:
可以使用后序遍历的方法。
最远距离会出现在如下三种情况:
1.head的左子树上的最大距离。
2.head的右子树上的最大距离。
3.head左子树上离head左孩子的最远距离,加上head自身,再加上head右子树上离head右孩子的最远距离,也就是两个节点分别来自于head两侧子树的情况。
上述三个中的最大值就是一head为头的整棵树上的最大距离。
步骤为:
1.后序遍历执行如下步骤。
2.假设子树头结点为h,处理h左子树得到两个信息,左子树上的最远距离记为Lmax1,左子树上距离h左孩子的最远距离记为Lmax2。处理h的右子树得到h右子树上的最大距离记为Rmax1,距离h右孩子的最远距离为Rmax2。那么跨节点h情况下的最大距离为Lmax2+1+Rmax2,这个值与Lmax1,Rmax2比较,最大值为,以h为头的树上的最大距离。
3.Lmax2+1就是h左子树上距离h最远的点到h的距离。
Rmax2+1就是h右子树上距离h最远的点到h的距离。
选二者中最大的一个作为h树上距离h最远的距离返回。
4.返回长度为2的数组。

template<typename T> void maxDistance(BinNode<T> *head, int distance[2]) {
    int leftDistance[2] = { 0,0 };//第一个为当前点的最长距离,第二个为当前点最深距离
    int rightDistance[2] = { 0,0 };
    if (!head) {
        distance[0] = 0;
        distance[1] = 0;
        return;
    }
    maxDistance(head->lc, leftDistance);
    maxDistance(head->rc, rightDistance);
    int one;
    int two;
    if (leftDistance[0] > rightDistance[0]) {
        if (leftDistance[0] > leftDistance[1] + rightDistance[1] + 1) {
            one = leftDistance[0];
        }
        else {
            one = leftDistance[1] + rightDistance[1] + 1;
        }
    }
    else {
        if (rightDistance[0] > leftDistance[1] + rightDistance[1] + 1) {
            one = rightDistance[0];
        }
        else{
            one = leftDistance[1] + rightDistance[1] + 1;
        }
    }
    if (leftDistance[1] > rightDistance[1]) {
        two = leftDistance[1] + 1;
    }
    else {
        two = rightDistance[1] + 1;
    }
    distance[0] = one;
    distance[1] = two;
}

题四

给定一颗二叉树的头结点head,已知其中所有节点的值都不一样,找到含有节点最多的搜索二叉子树,并返回这棵树的头结点。如下
这里写图片描述
分析:以节点node 为头的树种,最大的搜索二叉树只可能来源于如下两种情况。
1.来自node左子树上的最大搜索二叉树是以node左孩子为头的,并且来自node右子树上的最大搜索二叉子树是以node右孩子为头的,node左子树上的最大值小于node的节点值,node右子树上的最小值大于node的值,那么以节点node为头的整棵树都是搜索二叉树。
2.如果不满足第一种情况,说明以节点node为头的数整体不能连成搜索二叉树,这种情况下,以node为头的的树上的最大搜索二叉树来自node左子树或右子树上最大的搜索二叉树。
步骤如下:
1.整体为后序遍历。
2.遍历的当前节点为cur,先遍历cur的左子树并收集4个信息,分别为左子树上最大搜索二叉树的头结点,节点数,最大值,最小值。再遍历右子树搜集4个信息。
3.根据步骤2搜集到的信息,判断第一种情况是否满足,如果满足返回cur,如果不满足返回cur的左子树或右子树中最大的树。

template<typename T> void maxSearchTree(BinNode<T> *head, BinNode<T> **maxHead, T *maxValue, T *minValue, int *num) {
    if (!head) {
        *maxHead = NULL;
        *maxValue = INT16_MIN;
        *minValue = INT16_MAX;
        *num = 0;
        return;
    }
    BinNode<T> *leftHead=NULL, *rightHead=NULL;
    T leftMax=0, rightMax=0, leftMin=0, rightMin=0;
    int leftNumber, rightNumber;
    maxSearchTree(head->lc, &leftHead, &leftMax, &leftMin, &leftNumber);
    maxSearchTree(head->rc, &rightHead, &rightMax, &rightMin, &rightNumber);
    if (leftHead == head->lc && rightHead == head->rc) {
        if (leftHead == NULL && rightHead == NULL) {
            if (head->data < leftMin) {
                *minValue = head->data;
            }
            else {
                *minValue = leftMin;
            }
            if (head->data > rightMax) {
                *maxValue = head->data;
            }
            else {
                *maxValue = rightMax;
            }
            *maxHead = head;
            *num = 1 + leftNumber + rightNumber;
            return;
        }
        if (leftHead != NULL && rightHead == NULL) {
            if (head->data > leftMax) {
                *maxHead = head;
                *minValue = leftMin;
                *maxValue = head->data;
                *num = 1 + leftNumber;
                return;
            }
            else {
                *maxHead = leftHead;
                *minValue = leftMin;
                *maxValue = leftMax;
                *num = leftNumber;
                return;
            }
        }
        if (leftHead == NULL && rightHead != NULL) {
            if (head->data < rightMin) {
                *maxHead = head;
                *minValue = head->data;
                *maxValue = rightMax;
                *num = rightNumber + 1;
                return;
            }
            else {
                *maxHead = rightHead;
                *minValue = rightMin;
                *maxValue = rightMax;
                *num = rightNumber;
                return;
            }
        }
        if (leftHead != NULL && rightHead != NULL) {
            if (head->data > leftMax && head->data < rightMin) {
                if (head->data < leftMin) {
                    *minValue = head->data;
                }
                else {
                    *minValue = leftMin;
                }
                if (head->data > rightMax) {
                    *maxValue = head->data;
                }
                else {
                    *maxValue = rightMax;
                }
                *maxHead = head;
                *num = 1 + leftNumber + rightNumber;
                return;
            }
            else {
                if (leftNumber > rightNumber) {
                    *minValue = leftMin;
                    *maxValue = leftMax;
                    *maxHead = leftHead;
                    *num = leftNumber;
                }
                else {
                    *minValue = rightMin;
                    *maxValue = rightMax;
                    *maxHead = rightHead;
                    *num = rightNumber;
                }
                return;
            }
        }

    }
    else {
        if (leftNumber > rightNumber) {
            *minValue = leftMin;
            *maxValue = leftMax;
            *maxHead = leftHead;
            *num = leftNumber;
        }
        else {
            *minValue = rightMin;
            *maxValue = rightMax;
            *maxHead = rightHead;
            *num = rightNumber;
        }
        return;
    }
}

猜你喜欢

转载自blog.csdn.net/weixin_37895339/article/details/79324845