树
一.抽象数据类型
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\n",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",&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\n"); else printf("No\n"); } } 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; }