树的总结(遍历,BST,AVL原型,练习题)

Posted littlepage

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了树的总结(遍历,BST,AVL原型,练习题)相关的知识,希望对你有一定的参考价值。

一.抽象数据类型

1.顺序存储

使用数组存储
父亲索引为 n
左孩子 2*n 右孩子 2*n+1

2.链式存储

typedef struct TNode *Position;
typedef Position BinTree; /* 二叉树类型 */
struct TNode{ /* 树结点定义 */
    ElementType Data; /* 结点数据 */
    BinTree Left;     /* 指向左子树 */
    BinTree Right;    /* 指向右子树 */
};

二、二叉树的性质

1.二叉树第i层最大结点数为:2^(i-1),i>=1

2.深度为k的二叉树最大结点总数为:2^k-1,k>=1

3.对任何非空二叉树T,若n0表示叶子结点个数、n2是度为2的非叶子结点个数,那么二者满足关系n0=n2+1

void PreOrderTraversal(BinTree BT){
    if(BT){
        printf("%d",BT->Data);
        PreOrderTraversal(BT->Left);
        PreOrderTraversal(BT->Right);
    }
}

中序后序同理,把打印放在中间和后面,这里不加以赘述。

3.2.非递归

以链式中序为例

void InOrderTraversal(BinTree BT){
    BinTree T=BT;
    Stack S=CreatSatck(MaxSize);
    while(T||!IsEmpty(S)){
        Push(S,T);
        T=T->Left;
    }
    if(!IsEmpty(S)){
        T=Pop(S);
        printf("%5d",T->Data);
        T=T->Right;
    }
}
3.3.利用队列进行层序遍历
void levelOrderTraversal(Tree *tr){
    queue<Tree*> que;
    Tree *t=tr;
    if(t==NULL) return;
    que.push(t);
    while(!que.empty()){
        t=que.front();
        que.pop();
        printf("%d
",t->val);
        if(t->left!=NULL) que.push(t->left);
        if(t->right!=NULL) que.push(t->right);
    }
}
3.4.已知先序中序求后序
#include <cstdio>
using namespace std;
int post[] = {3, 4, 2, 6, 5, 1};
int in[] = {3, 2, 4, 1, 6, 5};
void pre(int root, int start, int end) {
    if(start > end) return ;
    int i = start;
    while(i < end && in[i] != post[root]) i++;
    printf("%d ", post[root]);
    pre(root - 1 - end + i, start, i - 1);
    pre(root - 1, i + 1, end);
}
 
int main() {
    pre(5, 0, 5);
    return 0;
}
3.5.已知中序后序求先序
#include <cstdio>
using namespace std;
int pre[] = {1, 2, 3, 4, 5, 6};
int in[] = {3, 2, 4, 1, 6, 5};
void post(int root, int start, int end) {
    if(start > end) 
        return ;
    int i = start;
    while(i < end && in[i] != pre[root]) i++;
    post(root + 1, start, i - 1);
    post(root + 1 + i - start, i + 1, end);
    printf("%d ", pre[root]);
}

int main() {
    post(0, 0, 5);
    return 0;
}
3.6.先序构建树
TreeNode* buildTree(int root, int start, int end) {
    if(start > end) return NULL;
    int i = start;
    while(i < end && in[i] != pre[root]) i++;
    TreeNode* t = new TreeNode();
    t->left = buildTree(root + 1, start, i - 1);
    t->right = buildTree(root + 1 + i - start, i + 1, end);
    t->data = pre[root];
    return t;
}

三、活用树的遍历

3.1.PAT Advanced 1020 Tree Traversals

参考 https://pintia.cn/problem-sets/994805342720868352/problems/994805485033603072

这是一道考察树的构成,后序中序转前序,前序构造树,树层序遍历的一道题目,知识点考察很多

原题翻译:
已知后序遍历,中序遍历,求层序遍历
输入:一共有多少值
第一行为后序遍历,第二行为中序遍历
输出:层序遍历

Sample Input:
7
2 3 1 5 7 6 4
1 2 3 4 5 6 7
Sample Output:
4 1 6 3 5 7 2

根据二中的方法,我们可以进行活用

#include <iostream>
#include <vector>
#include <queue>

using namespace std;

struct TreeNode{//树的抽象类型
    int val;
    TreeNode *left;
    TreeNode *right;
};

vector<int> pre,in,post,ans;
queue<TreeNode*> que;

void preOrder(int root,int start,int end){//由中序后序建立前序
    if(start>end) return;
    int i=0;
    while(i<=end&&in[i]!=post[root]) i++;
    pre.push_back(post[root]);
    preOrder(root-1-end+i,start,i-1);
    preOrder(root-1,i+1,end);
}

TreeNode* buildTree(int root,int start,int end){//由前序中序构建树
    if(start>end) return NULL;
    int i=0;
    TreeNode *t=new TreeNode();
    while(i<=end&&in[i]!=pre[root]) i++;
    t->val=pre[root];
    t->left=buildTree(root+1,start,i-1);
    t->right=buildTree(root+1+i-start,i+1,end);
    return t;
}

void levelOrder(TreeNode *tree){//层序遍历树
    que.push(tree);
    while(!que.empty()){
        TreeNode *tmp=que.front();
        ans.push_back(tmp->val);
        que.pop();
        if(tmp->left!=NULL) que.push(tmp->left);
        if(tmp->right!=NULL) que.push(tmp->right);
    }

}
int main()
{
    int N;
    scanf("%d",&N);
    post.resize(N);in.resize(N);
    for(int i=0;i<N;i++) scanf("%d",&post[i]);
    for(int i=0;i<N;i++) scanf("%d",&in[i]);
    preOrder(N-1,0,N-1);
    TreeNode *tree=buildTree(0,0,N-1);
    levelOrder(tree);
    for(int i=0;i<ans.size();i++)
        if(i!=ans.size()-1) cout<<ans[i]<<" ";
        else cout<<ans[i];
    system("pause");
    return 0;
}

查看柳婼大神的博客,我们可以知道,有更简单的方法,仅仅加一个索引值,便可以达到相同的效果,代码如下:

#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;
struct node{
    int index;
    int val;
};
bool cmp(node n1,node n2){
    return n1.index<n2.index;
}
vector<int> in,post;
vector<node> ans;

void preOrder(int root,int start,int end,int index){
    if(start>end) return;
    int i=0;
    while(i<=end&&in[i]!=post[root]) i++;
    ans.push_back({index,post[root]});
    preOrder(root-1-end+i,start,i-1,2*index+1);
    preOrder(root-1,i+1,end,2*index+2);
}
int main()
{
    int N;
    scanf("%d",&N);
    post.resize(N);in.resize(N);
    for(int i=0;i<N;i++) scanf("%d",&post[i]);
    for(int i=0;i<N;i++) scanf("%d",&in[i]);
    preOrder(N-1,0,N-1,0);
    sort(ans.begin(),ans.end(),cmp);
    for(int i=0;i<N;i++)
        if(i!=N-1) printf("%d ",ans[i].val);
        else printf("%d",ans[i].val);
    system("pause");
    return 0;
}
3.2.PAT Advanced 1086 Tree Traversals Again 参考PAT官网原题

这道题比上面一题简单,简单叙述

给先序遍历的进栈出栈过程,求后序遍历。
输入:数量 进栈出栈过程
输出:后序遍历

Sample Input:
6
Push 1
Push 2
Push 3
Pop
Pop
Push 4
Pop
Pop
Push 5
Push 6
Pop
Pop
Sample Output:
3 4 2 6 5 1

我们可以知道,先序遍历的出栈就是中序遍历打印,于是代码如下:

#include <iostream>
#include <vector>
#include <stack>
#include <cstring>

using namespace std;

vector<int> pre,in,post,val;

void postorder(int root,int start,int end){//中序,后序找前序
    if(start>end) return ;
    int root_index=0;
    while(root_index<=end&&in[root_index]!=pre[root]) root_index++;
    postorder(root+1,start,root_index-1);
    postorder(root+1+root_index-start,root_index+1,end);
    post.push_back(pre[root]);
}
int main()
{
    stack<int> sta;
    int k=0,N;
    char ch[5];int num;
    scanf("%d",&N);
    while(~scanf("%s",&ch)){
        if(strlen(ch)==4) {
            scanf("%d",&num);
            pre.push_back(num);
            sta.push(num);
        }else{
            in.push_back(sta.top());
            sta.pop();
            //if(in.size()==N) break;//测试代码的时候,可以把这句加上,因为官方使用的是文件测,所以支持~scanf的写法
        }
    }
    postorder(0,0,N-1);
    for(int i=0;i<N;i++)
        if(i!=N-1) printf("%d ",post[i]);
        else printf("%d",post[i]);

    system("pause");
    return 0;
}

四、BST树

原型以及增删

C语言BST树原型,BST树在进行增删的时候效率没有链表高,但是在查找的时候比较快,这边以BST原型,用C程序构造一个BST树。

#ifndef BSTTREE_H_INCLUDED
#define BSTTREE_H_INCLUDED

#define ElementType int
#define Position BSTTree*


typedef struct BSTTree{
    ElementType Data;
    struct BSTTree *Left;
    struct BSTTree *Right;
}BSTTree;



Position FindMin(BSTTree *bstTree){//递归查找
    if(!bstTree) return NULL;
    else if(!bstTree->Left) return bstTree;
    else return FindMin(bstTree->Left);
}

Position FindMax(BSTTree *bstTree){//循环查找
    if(bstTree)
        while(bstTree->Right) bstTree=bstTree->Right;
    return bstTree;
}

BSTTree* Insert(ElementType x,BSTTree *bstTree){
    if(bstTree){
        bstTree=malloc(sizeof(struct BSTTree));
        bstTree->Data=x;
        bstTree->Left=NULL;
        bstTree->Right=NULL;
    }else{
        if(x<bstTree->Data) bstTree->Left=Insert(x,bstTree->Left);
        else if(x>bstTree->Data) bstTree->Right=Insert(x,bstTree->Right);
        //else 啥也不做
    }
    return bstTree;
}

BSTTree* Delete(ElementType x,BSTTree *bstTree){
    Position Tmp;
    if(!bstTree) printf("not find");
    else if(x<bstTree->Data) bstTree->Left=Delete(x,bstTree->Left);//左递归删除
    else if(x>bstTree->Data) bstTree->Right=Delete(x,bstTree->Right);//右递归删除
    else
        if(bstTree->Left&&bstTree->Right){//左右两个孩子
            Tmp=FindMin(bstTree->Right);//右子树找最小元素填充删除节点
            bstTree->Data=Tmp->Data;
            bstTree->Right=Delete(bstTree->Data,bstTree->Right);//继续删除右子树最小元素
        }else{//有一个孩子或者没有孩子
            Tmp=bstTree;
            if(!bstTree->Left)
                bstTree=bstTree->Right;//直接赋值有孩子
            else if(!bstTree->Left)
                bstTree=bstTree->Left;//直接赋值左孩子
            free(Tmp);//进行释放
        }
    return bstTree;
}
#endif // BSTTREE_H_INCLUDED

五、AVL树

5.1.AVL树概念

什么是AVL树,比BST树多了一个平衡因子(Balance Factor),简称BF:BF(T)=hl-hr,其中hl和hr为左右子树的高度。

当平衡因子大于1的时候,我们就可以进行旋转操作

1.RR旋转(右单旋)

技术图片

2.LL旋转(左单旋)

技术图片

3.LR旋转(左右双旋)

技术图片

5.2.AVL树原型
#define ElementType int
typedef struct AVLNode *Position;
typedef Position AVLTree; /* AVL树类型 */
struct AVLNode{
    ElementType Data; /* 结点数据 */
    AVLTree Left;     /* 指向左子树 */
    AVLTree Right;    /* 指向右子树 */
    int Height;       /* 树高 */
};
5.3.AVL树LL旋转
AVLTree SingleLeftRotation(AVLTree A){
/* 注意:A必须有一个左子结点B */
/* 将A与B做左单旋,更新A与B的高度,返回新的根结点B */
    AVLTree B = A->Left;
    A->Left = B->Right;
    B->Right = A;
    A->Height = Max( GetHeight(A->Left), GetHeight(A->Right) ) + 1;
    B->Height = Max( GetHeight(B->Left), A->Height ) + 1;

    return B;
}
5.4.AVL树LR旋转
AVLTree DoubleLeftRightRotation ( AVLTree A )
{ /* 注意:A必须有一个左子结点B,且B必须有一个右子结点C */
  /* 将A、B与C做两次单旋,返回新的根结点C */

    /* 将B与C做右单旋,C被返回 */
    A->Left=SingleRightRotation(A->Left);
    /* 将A与C做左单旋,C被返回 */
    return SingleLeftRotation(A);
}
5.5.AVL树插入函数
AVLTree Insert( AVLTree T, ElementType X )
{ /* 将X插入AVL树T中,并且返回调整后的AVL树 */
    if ( !T ) { /* 若插入空树,则新建包含一个结点的树 */
        T = (AVLTree)malloc(sizeof(struct AVLNode));
        T->Data = X;
        T->Height = 0;
        T->Left = T->Right = NULL;
    } /* if (插入空树) 结束 */

    else if ( X < T->Data ) {
        /* 插入T的左子树 */
        T->Left = Insert( T->Left, X);
        /* 如果需要左旋 */
        if ( GetHeight(T->Left)-GetHeight(T->Right) == 2 )
            if ( X < T->Left->Data )
               T = SingleLeftRotation(T);      /* 左单旋 */
            else
               T = DoubleLeftRightRotation(T); /* 左-右双旋 */
    } /* else if (插入左子树) 结束 */

    else if ( X > T->Data ) {
        /* 插入T的右子树 */
        T->Right = Insert( T->Right, X );
        /* 如果需要右旋 */
        if ( GetHeight(T->Left)-GetHeight(T->Right) == -2 )
            if ( X > T->Right->Data )
               T = SingleRightRotation(T);     /* 右单旋 */
            else
               T = DoubleRightLeftRotation(T); /* 右-左双旋 */
    } /* else if (插入右子树) 结束 */

    /* else X == T->Data,无须插入 */

    /* 别忘了更新树高 */
    T->Height = Max( GetHeight(T->Left), GetHeight(T->Right) ) + 1;

    return T;
}

六、BST树和AVL树练习

6.1.浙大习题7-4 是否是同一棵二叉搜索树

给定一个插入序列就可以唯一确定一棵二叉搜索树。然而,一棵给定的二叉搜索树却可以由多种不同的插入序列得到。例如分别按照序列{2, 1, 3}和{2, 3, 1}插入初始为空的二叉搜索树,都得到一样的结果。于是对于输入的各种插入序列,你需要判断它们是否能生成一样的二叉搜索树。

输入格式:

输入包含若干组测试数据。每组数据的第1行给出两个正整数N (≤10)和L,分别是每个序列插入元素的个数和需要检查的序列个数。第2行给出N个以空格分隔的正整数,作为初始插入序列。最后L行,每行给出N个插入的元素,属于L个需要检查的序列。

简单起见,我们保证每个插入序列都是1到N的一个排列。当读到N为0时,标志输入结束,这组数据不要处理。

输出格式:

对每一组需要检查的序列,如果其生成的二叉搜索树跟对应的初始序列生成的一样,输出“Yes”,否则输出“No”。

输入样例:

4 2
3 1 4 2
3 4 1 2
3 2 4 1
2 1
2 1
1 2
0

输出样例:

Yes
No
No

这道题,仅仅需要进行构造一棵BST树,然后进行判断先序遍历的打印即可,AC代码如下。

#include <iostream>

using namespace std;

string traver_ini,traver_cmp;

struct BST{
    int val;
    BST *left;
    BST *right;
};
BST *ini,*cmp;
BST* BST_insert(BST *b,int val){
    if(b==NULL) {
        b=new BST();
        b->val=val;
        return b;
    }
    if(val<b->val) b->left=BST_insert(b->left,val);
    else if(val>b->val) b->right=BST_insert(b->right,val);
    return b;
}
void pre(BST *b,string& str){
    if(b==NULL) return;
    str+=(b->val+'0');
    pre(b->left,str);
    pre(b->right,str);
}
int main()
{
    int M,N,tmp;
    while(1){
        scanf("%d",&M);
        if(M==0) break;
        scanf("%d
",&N);
        ini=NULL;
        for(int i=0;i<M;i++){
            scanf("%d",&tmp);
            ini=BST_insert(ini,tmp);
        }
        traver_ini="";
        pre(ini,traver_ini);
        while(N--){
            cmp=NULL;
            for(int i=0;i<M;i++){
                scanf("%d",&tmp);
                cmp=BST_insert(cmp,tmp);
            }
            traver_cmp="";
            pre(cmp,traver_cmp);
            if(traver_cmp==traver_ini) printf("Yes
");
            else printf("No
");
        }
    }
    return 0;
}
6.2.PAT Advanced 1066 Root of AVL Tree (25 分) 参考PAT官网
简单叙述:
给定插入元素个数n
插入n个元素后,输出根节点元素值
Sample Input 1:
5
88 70 61 96 120
Sample Output 1:
70
Sample Input 2:
7
88 70 61 96 120 90 65
Sample Output 2:
88

解题方法,构造一棵AVL树,然后插入,求根即可

#include <iostream>

using namespace std;

struct node{//AVL节点原型
    int val;
    node *left,*right;
};
node *rotateRight(node *root){//右多,进行RR旋转
    node *t=root->right;
    root->right=t->left;
    t->left=root;
    return t;
}
node *rotateLeft(node *root){//左多,进行LL旋转
    node *t=root->left;
    root->left=t->right;
    t->right=root;
    return t;
}
node *rotateLeftRight(node *root){//左多,进行LR旋转
    root->left=rotateRight(root->left);
    return rotateLeft(root);
}
node *rotateRightLeft(node *root){//右多,进行RL旋转
    root->right=rotateLeft(root->right);
    return rotateRight(root);
}
int getHeight(node *root){//获得高度
    if(root==NULL) return 0;
    return max(getHeight(root->left),getHeight(root->right))+1;
}
node *insert(node *root,int val){
    if(root==NULL){//空的时候
        root=new node();
        root->val=val;
        root->left=root->right=NULL;
    }else if(val<root->val){//插左边
        root->left=insert(root->left,val);
        if(getHeight(root->left)-getHeight(root->right)==2)
            root=val<root->left->val?rotateLeft(root):rotateLeftRight(root);
            //如果比左还要小,只需要进行LL旋转,否则需要LR旋转
    }else{//插右边
        root->right=insert(root->right,val);
        if(getHeight(root->right)-getHeight(root->left)==2)
            root=val>root->right->val?rotateRight(root):rotateRightLeft(root);
            //如果比右还要打,只需进行RR旋转,否则需要RL旋转
    }
    return root;
}
int main()
{
    int n,tmp;cin>>n;
    node* root=NULL;
    for(int i=0;i<n;i++){
        cin>>tmp;
        root=insert(root,tmp);
    }
    cout<<root->val;
    system("pause");
    return 0;
}

以上是关于树的总结(遍历,BST,AVL原型,练习题)的主要内容,如果未能解决你的问题,请参考以下文章

树的平衡 AVL Tree

AVL树平衡旋转详解

数据结构 - 从二叉搜索树说到AVL树之二叉搜索树的操作与详解(Java)

108. 将有序数组转换为二叉搜索树-BST与AVL (leetcode)

徒手写的AVL竟然比STL中的红黑树效率更高?✨

大二博客总结第四周