概览

  1. 二叉树的链表和静态链表的定义
  2. 二叉树的遍历(先序遍历、中序遍历、后序遍历、层次遍历,前三者又分为递归和非递归)
  3. 重建二叉树
  4. 二叉排序树(BST)
  5. 完全二叉树(CBT)
  6. 平衡二叉树(AVL)
  7. 红黑树

 


二叉树结点的定义

struct Node{
    int val;
    Node *lchild,*rchild;
    Node(int v):val(v),lchild(nullptr),rchild(nullptr){}
};

树的内容主要围绕在树的遍历中展开,有先序遍历,中序遍历,后序遍历和层序遍历。其中,前三种遍历又分为递归式写法和非递归式的写法,递归式的写法更简洁,是重点,是DFS的思想;非递归式代码比较繁琐。层序遍历则是BFS的思想,借助队列实现。

1、二叉树的遍历

先序遍历、中序遍历、后序遍历的递归写法以及层次遍历不再讨论。这里主要记录一下后序遍历的非递归写法。非递归写法需要借助栈实现,当访问到结点v时,栈中存放的元素恰为结点v的所有父节点。

//非递归后序遍历
void postOrderTraversal(Node* root)
{
    stack<Node*> st;
    Node* pNode=root;
    Node* pLastVisit=nullptr;//标记上一次刚访问过的结点
    while(pNode || !st.empty()){
        if(pNode!=nullptr){
            st.push(pNode);
            pNode=pNode->lchild;
        }else{
            pNode=st.top();
            //如果栈顶元素有右孩子且右孩子尚未被访问
            if(pNode->rchild!=nullptr && pNode->rchild!=pLastVisit){
                pNode=pNode->rchild;
                st.push(pNode);
                pNode=pNode->lchild;
            }else{
                printf("%d ",pNode->val);
                st.pop();
                pLastVisit=pNode;
                pNode=nullptr;//访问过的结点置空,防止其再次入栈
            }
        }
    }
}

 

2、重建二叉树

常考的点有利用二叉树的前序序列+中序序列,或后序序列+中序序列,或层序序列+中序序列构建二叉树(构建的树唯一);还有前序序列+后序序列构建二叉树(构建的树可能不唯一)。

(1)、前序序列+中序序列:

Node* buildBiTree(vector<int> pre,int preL,int preR,vector<int> in,int inL,int inR)
{
    if(preL>preR || inL>inR) return nullptr;
    int rootVal=pre[preL];
    Node* root=new Node(rootVal);
    int pos=inL;//记录根结点在中序序列中出现的位置
    while(in[pos]!=rootVal) pos++;

    int leftCnt=pos-inL;//左子树的结点个数
    root->lchild=buildBiTree(pre,preL+1,preL+leftCnt,in,inL,pos-1);
    root->rchild=buildBiTree(pre,preL+leftCnt+1,preR,in,pos+1,inR);
    return root;
}

(2)、后序序列+中序序列:(这个与"前序+中序"思路一样)

Node* buildBiTree(vector<int> post,int postL,int postR,vector<int> in,int inL,int inR)
{
    if(postL>postR || inL>inR) return nullptr;
    int rootVal=post[postR];
    Node* root=new Node(rootVal);
    int pos=inL;//记录根结点在中序序列中出现的位置
    while(in[pos]!=rootVal) pos++;

    int leftCnt=pos-inL;//左子树的结点个数
    root->lchild=buildBiTree(post,postL,postL+leftCnt-1,in,inL,pos-1);
    root->rchild=buildBiTree(post,postL+leftCnt,postR-1,in,pos+1,inR);
    return root;
}

(3)、层序序列+中序序列:(这个PAT还没有考过)

Node* buildBiTree(vector<int> layer,vector<int> in,int inL,int inR)
{
    if(layer.size()==0 || inL>inR) return nullptr;
    int rootVal=layer[0];
    Node* root=new Node(rootVal);
    int pos=inL;
    while(in[pos]!=rootVal) pos++;

    vector<int> layerLeft,layerRight;//存放左、右子树的层序序列
    for(int i=1;i<layer.size();i++){
        int j;
        for(j=inL;j<pos;j++){
            if(layer[i]==in[j]){
                layerLeft.push_back(layer[i]);
                break;
            }
        }
        if(j==pos) layerRight.push_back(layer[i]);
    }
    root->lchild=buildBiTree(layerLeft,in,inL,pos-1);
    root->rchild=buildBiTree(layerRight,in,pos+1,inR);

    return root;
}

(4)、前序序列+后序序列:构建的二叉树可能不唯一,当无法确定某个节点是左孩子还是右孩子的时候,统一视为左孩子。详见1119 Pre- and Post-order Traversals

Node* buildBiTree(vector<int> pre,int preL,int preR,vector<int> post,int postL,int postR)
{
   //注意边界
    if(preL>preR || postL>postR) return nullptr;
    if(preL==preR) return new Node(pre[preL]);

    int rootVal=pre[preL];//前序序列的首个元素即根结点
    Node* root=new Node(rootVal);
    int temp=pre[preL+1];//根结点后面的一个元素,即根结点的孩子
    int pos=postL;//记录元素temp在后序序列的位置
    while(post[pos]!=temp) pos++;

    int leftCnt=pos-postL+1;//以temp为根结点的子树的结点个数
    if(leftCnt==preR-preL){//说明结点temp是根结点rootVal唯一的一个孩子,但不确定是左还是右
        root->lchild=buildBiTree(pre,preL+1,preL+leftCnt,post,postL,pos);
        root->rchild=nullptr;
    }else{
        root->lchild=buildBiTree(pre,preL+1,preL+leftCnt,post,postL,pos);
        root->rchild=buildBiTree(pre,preL+leftCnt+1,preR,post,pos+1,postR-1);
    }
    return root;
}

 

3、二叉搜索树(BST)

二叉搜索树的数据域满足:左子树<根结点<右子树,利用这个特点,对二叉搜索树进行中序遍历,得到的结果序列是有序的。这是个很有用的性质,但是需要注意的是,在以往的刷题过程中,当遇到二叉搜索树的题目时(一般会给出前序序列),还是用前面那种【前序+中序】的方式构建二叉搜索树,这样做费时费力。因为,对于二叉搜索树,利用前序序列作为结点的插入顺序,可直接构建二叉树。这里需要明确的关系是:二叉搜索树的树形结构是由插入序列决定的,同样一组数,若插入的顺序不同,则得到的BST也不同。特别地,若以该二叉树的前序序列作为插入序列,则得到的二叉树的前序序列就是原插入序列。有点绕...,如插入顺序是 5,3,7,4,2,8,6 ,则得到的BST为

但是,以 5,3,2,4,7,6,8 的顺序插入得到的结果是相同的,而{5,3,2,4,7,6,8}恰为这棵树的前序序列。因此,我想借此说明的就是——若要判定某个序列是否为BST的前序序列,可以将其作为插入序列快速构建好BST,然后再遍历得到该BST的前序序列,将该序列与原序列比较即可。

插入操作:

void insert(Node* &root,int key)
{
    if(root==nullptr){
        root=new Node(key);
        return;
    }
    if(key<root->val) insert(root->lchild,key);
    else insert(root->rchild,key);
}

删除操作:暂时不写

 

4、完全二叉树(CBT)

对于完全二叉树而言,除了可以用常见的链式存储外,更方便的是用数组存储(下标从1开始),一般涉及完全二叉树的题目时,应首先想到这个方法。用数组存储,有如下的性质:(假设共有n个结点)

(1)、某结点下标为i,其左孩子结点为2*i,右孩子结点为2*i+1(如果有孩子的话)

(2)、最后一个非叶结点为在n/2处

(3)、判断结点是否为空时,检查该结点的下标idx是否大于n

(4)、顺序遍历数组1~n的结果恰是该完全二叉树的层序遍历序列

熟悉这些性质,当涉及CBT的题目时,就不应该傻不拉几的按照一般的做法去思考了。利用好性质,代码量可以大幅度减少,节约时间。(教训较大的两道题,1064 Complete Binary Search Tree,1147 Heaps)

比如,遍历一棵完全二叉树,要先用常规方法那样建树,然后遍历吗?没必要啊!以中序遍历CBT为例,如下:

int CBT[N];//从下标为1处开始存放
void inOrderTraversal(int root)
{
    if(root>n) return;
    inOrderTraversal(root*2);//遍历左子树
    //do something here
    inOrderTraversal(root*2+1);
}

 

5、平衡二叉树(AVL)

具体的讲解《算法笔记》里已经讲的很清楚了,没什么好说的。模板,记住。例题:1066 Root of AVL Tree1123 Is It a Complete AVL Tree

 

6、红黑树(具体的实现待考完再补充)例题:1135 Is It A Red-Black Tree

 

内容来源于网络如有侵权请私信删除
你还没有登录,请先登录注册
  • 还没有人评论,欢迎说说您的想法!