关于树的总结
一、树的基础了解
(1)开始的开始,肯定是先要了解树是什么。首先,树,显然是有根节点,叶子节点的,对每个节点而言他们都可能存在孩子节点,即在下面连着他们的节点。我们将一个节点下面连有的节点数称之为度。所谓根节点,就是最原始开端的顶点,所谓叶节点就是最后没有再连接下一层节点的最后节点,所以叶节点的度是0。我们知道一般树通过横向连接节点,并去掉原右连接,可以得到一颗简单的二叉树。因此,我们多数问题都是围绕二叉树展开的。
(2)那么,二叉树有哪几种呢? 二叉树从结构上分为,完全二叉树和满二叉树,从功能上分为搜索二叉树,平衡二叉树。完全二叉树包含满二叉树,完全二叉树,是指最后一层不一定全满但一定是节点左倾的二叉树。搜索二叉树是指左树<根节点<右树,且左右树符合这个特点的二叉树,搜索二叉序的中序遍历是一个升序的序列。平衡二叉树是指,左右树的高度差<=1的二叉(搜索)树,其诞生是为了让二叉搜索树不“偏大头”。
(3)接着,我们需要知道我们对树要进行什么操作。其实,不管对什么数据结构,无非四个操作增删查改,那我们的树也不例外。树对应的操作包括,建树、遍历树、验证树、修改树。每一个操作都很麻烦。因此,有必要好好总结下。
二、树的定义
直接看代码吧。
struct TreeNode{ int val; TreeNode *left; TreeNode *right; TreeNode():val(0),left(nullptr),right(nullptr){} TreeNode(int n):val(n),left(nullptr),right(nullptr){} }
三、建树---递归
因为我们的二叉树分成四种,因此,建树也需要分开讨论。
(1) 建一棵普通二叉树
首先要明确建立一棵树的话,如果未声明是特殊树的话,除非是直接给定层序遍历序列,否则单靠一个遍历序列是不足以建树的。一般需要前序遍历序列或者后序遍历序列才能建立一个二叉树。那么如何通过两种遍历序列得到建树呢。得明白三种遍历序列的特点。第一,前序遍历从左往后的第一个节点就是根节点,并且之后的节点排布是先左树节点后右树节点;第二,中序遍历是先左节点再根节点最后右节点,因此,找到根节点,其左边就是左树节点,其右边就是右树节点;第三,后序遍历,最后面一定是根节点,而且之前的节点排布是先左节点后右节点。下面写上有前序+中序建树,以及由后序+中序建树的代码实现:
/*前序+中序建树*/ TreeNode * buildBT(vector<int>&preorder,int pre_left,int pre_right,vector<int>&inorder,int in_left,int in_right){ //base case if(in_left>in_right) return nullptr; //operations TreeNode *head=new TreeNode(preorder[pre_left]); for(int i=0;i<inorder.size();++i){ if(inorder[i]==head->val){ head->left=buildBt(preorder,pre_left+1,pre_left+i-in_left,inorder,in_left,i-1); head->right=buildBT(preorder,pre_left+i-in_left+1,pre_left-in_left+in_right,inorder,i+1,in_right); break; } } return head; } /*后序+中序建树*/ TreeNode * buildBT(vector<int>&postorder,int post_left,int post_right,vector<int>&inorder,int in_left,int in_right){ //base case if(in_left>in_right) return nullptr; //operations TreeNode *head=new TreeNode(postorder[post_right]); for(int i=0;i<inorder.size();++i){ if(inorder[i]==head->val){ head->right=buildBT(postorder, post_right-in_right+i,post_right-1,inorder,i+1,in_right); head->left=buildBT(postorder,post_right-in_right+in_left,post_right-in_right+i-1,inorder,in_left,i-1); break; } } return head; }
/*层序递归建树---难一点*/ //注意几个点:第一,需要传入根节点; //第二,需要判断子节点序号是否小于数组长度; //第三,一开始,当index为1时,要根根节点赋值。 TreeNode *buildBT(TreeNode *root,vector<int>&levelorder,int index){ if(index==0) root=new TreeNode(levelorder[index]); int left=2*index+1;right=2*index+2; if(left>levelorder.size()-1) return root; if(left<levelorder.size()){ root->left=new TreeNode(levelorder[left]); buildBT(root->left,levelorder,left); } if(right<levelorder.size()){ root->right=new TreeNode(levelorder[right]); buildBT(root->right,levelorder,right); } return root; }
(2)建一棵搜索二叉树
注意几个点:第一,要先确定根节点,然后放入根节点;第二,确定新元素插入的位置,如果小于根节点就在左树上,如果左节点已经存在,就递归把左节点当根节点找位置插入该元素;反之,在右树上,同理,左节点已存在则递归。
/*建树函数*/ TreeNode* buildBST(TreeNode* root, int val) { //base case if(!root) root= new TreeNode(val); //operations if(val<root->val) root->left=buildBST(root->left,val); if(val>root->right) root->right=buildBST(root->right,val); return root; } /*主函数内实现建树*/ TreeNode* root =nullprt; for (int i = 0; i < v.size(); ++i) { root=buildBST(root, v[i]); //更新树 }
(3) 建一棵平衡树---难
<1>首先是要重新定义结构体,多一个数据:高度
struct balancedBT { int val; int height; balancedBT* left; balancedBT* right; balancedBT() :val(0), height(0), left(nullptr), right(nullptr) {} balancedBT(int n) :val(n), height(1), left(nullptr), right(nullptr) {} };
<2>接着是要获取树的高度:
int height(balancedBT* root) { //base case //if (!root) return 0; ////operations //return max(height(root->left), height(root->right)) + 1; //非递归 if (!root) return 0; queue<balancedBT*>q; q.push(root); int res = 0; while (!q.empty()) { //queue<TreeNode*>tmp; int size = q.size(); for (int i = 0; i < size; ++i) { balancedBT* cur = q.front(); if (cur->left) q.push(cur->left); if (cur->right) q.push(cur->right); q.pop(); } ++res; } return res; }
<3>再是明确怎么调整搜索树:分为左树左节点,右树右节点,左树右节点和右树左节点,对应LL、RR、LR、RL
下面分别图示如何调整:
LL型:
//LL型调整 balancedBT* ll_rotate(balancedBT* root) { //右旋 balancedBT* new_root = root->left; root->left = new_root->right; new_root->right = root; //更新高度 new_root->height = max(height(new_root->left), height(new_root->right)) + 1; root->height = max(height(root->left), height(root->right)) + 1; //返回结果 return new_root; }
RR型:
//RR型调整 balancedBT* rr_rotate(balancedBT* root) { //左旋 balancedBT* new_root = root->right; root->right = new_root->left; new_root->left = root; //更新高度 new_root->height = max(height(new_root->left), height(new_root->right)) + 1; root->height = max(height(root->left), height(root->right)) + 1; //返回结果 return new_root; }
LR型:
//LR型调整 balancedBT* lr_rotate(balancedBT* root) { //先对左节点左旋再对根节点右旋,即先rr_rotate,再ll_rotate root->left = rr_rotate(root->left); return ll_rotate(root); }
RL型:
//RL型调整 balancedBT* rl_rotate(balancedBT* root) { //先对右节点右旋,再对根节点左旋 root->right = ll_rotate(root->right); return rr_rotate(root); }
<4>然后,还获得平衡因子:
//获取平衡因子 int BF(balancedBT *root) { int left_height = root->left ? root->left->height : 0; int right_height = root->right ? root->right->height : 0; return (left_height-right_height); }
于是,就可以调整了:
//调整平衡二叉树 balancedBT* balance(balancedBT* root,int val) { if (BF(root) > 1 && val < root->left->val) return ll_rotate(root); if (BF(root) < -1 && val > root->right->val) return rr_rotate(root); if (BF(root) > 1 && val > root->left->val) return lr_rotate(root); if (BF(root) < -1 && val < root->right->val) return rl_rotate(root); return root; }
<5>最后就是结合二叉搜索树的建立方法,建立平衡二叉树:
//建树(插入)函数 balancedBT* buildBalancedBT(balancedBT *root,int val) { if (!root) root= new balancedBT(val); if (val < root->val) root->left = buildBalancedBT(root->left, val); if (val > root->val) root->right = buildBalancedBT(root->right, val); //更新高度 root->height = max(height(root->left), height(root->right)) + 1; return balance(root,val); } /*主函数内实现建树*/ balancedBT* root3 = nullptr; for (int i = 0; i < v.size(); ++i) { root3=buildBalancedBT(root3, v[i]);//每次都修改根节点,所以必须给root3重新赋值 }
四、遍历树---递归、栈、队列
DFS式遍历
(1)前序遍历:
注意非递归实现的步骤:
先遍历到左边底部,边遍历边打印和入栈
再在栈内,不断pop掉节点,并检查节点是否有右节点,如有则继续遍历到左边底部,并入栈节点。void preorderSearch(TreeNode *root) { //1.递归实现 //base case //if (!root) return; ////operations //cout << root->val << " "; //preorderSearch(root->left); //preorderSearch(root->right); //2.非递归实现,利用栈 stack<TreeNode*>s; TreeNode* cur = root; //先搜索到左边最底并打印 while (cur) { cout << cur->val << " "; s.push(cur); cur = cur->left; } //然后遍历右边的节点 while (!s.empty()) { cur = s.top(); s.pop(); if (cur->right) { //继续搜索到左边底 cur = cur->right; while (cur) { cout << cur->val << " "; s.push(cur); cur = cur->left; } } } }
(2)中序遍历
注意非递归遍历的步骤:
先遍历到左边底部,并入栈,不打印
再在栈内,不断pop掉节点并打印,然后检查是否有右节点,如有,则继续遍历到左边底部,入栈节点void inorderSearch(TreeNode* root) { //1. 递归实现 //base case //if (!root) return; ////operations //inorderSearch(root->left); //cout << root->val << " "; //inorderSearch(root->right); //2. 非递归实现 stack<TreeNode*>s; TreeNode* cur = root; //先搜索到左边最底,但不打印 while (cur) { s.push(cur); cur = cur->left; } //然后遍历右边的节点 while (!s.empty()) { cur = s.top(); s.pop(); cout << cur->val << " "; //继续搜索到左边底 if (cur->right) { cur = cur->right; while (cur) { s.push(cur); cur = cur->left; } } } }
(3) 后序遍历---难一点
注意非递归遍历的步骤:
类似层序遍历,先入栈根节点
再在栈内,检查节点是否无左右节点或左右节点被遍历过了,如是,则打印该节点,并标记该节点为已遍历:pre=cur;
否则,就依次入栈右节点,左节点。void postorderSearch(TreeNode *root) { //1.递归实现 //base case //if(!root) return; ////operations //postorderSearch(root->left); //postorderSearch(root->right); //cout << root->val << " "; //2. 非递归实现---这个会难很多,比较费解,先记住!!! stack<TreeNode*>s; TreeNode* cur = root; TreeNode* pre = nullptr; s.push(root); while (!s.empty()) { cur = s.top(); if ((!cur->left && !cur->right) || (pre && (pre == cur->left || pre == cur->right))) { cout << cur->val << " "; pre = cur; s.pop(); } else { if (cur->right) s.push(cur->right); if (cur->left) s.push(cur->left); } } }
BFS式遍历,即层序遍历---队列
void levelorderSearch(TreeNode *root) { queue<TreeNode* >q; q.push(root); while (!q.empty()) { TreeNode* cur = q.front(); q.pop(); cout << cur->val << " "; if (cur->left) q.push(cur->left); if (cur->right) q.push(cur->right); } }
五、验证树---递归、队列
(1)是否是完全二叉树---队列
我们通过改变层序遍历队列的入队规则,来实现这一功能,具体见博文:刷 树专题 有感bool isValidCBT(TreeNode *root){ queue<TreeNode*>q;q.push(root); TreeNode *front=nullptr; while(front=q.front()){//放入非空节点的左右节点 q.push(front->left); q.push(front->right); q.pop(); } while(!q.empty()){ TreeNode *cur=q.front(); if(cur!=nullptr) return 0; q.pop(); } return 1; }
(2)验证是否是满二叉树---递归
bool isValidFBT(TreeNode *root){ //base case if(!root) return 1; if((!root->left&&root->right)||(root->left&&!root->right)) return 0; //operations return isValidFBT(root->left)&&isValidFBT(root->right); }
(3)验证是否是有效二叉搜索树---递归
bool isValidBST(TreeNode *root){ return helper(root,LONG_MIN,LONG_MAX); } bool helper(TreeNode* root,long long lower,long long upper){ //base case if(root->val<=lower||root->val>=upper) return 0; //operations:维护最大最小值 return helper(root->left,lower,root->val)&&helper(root->right,root->val,upper); }
(4)验证是否是平衡二叉树---递归、队列
首先必须了解下如何获取一棵二叉树的深度,有两种方法,一种是层序遍历不断更新队列,来记录最大深度
一种是直接递归实现。下面先写一下这两种方法.//获取树的深度 //方法一:非递归实现 int depth1(TreeNode* root) { queue<TreeNode*>q; q.push(root); int res = 0; while (!q.empty()) { queue<TreeNode*>tmp; while (!q.empty()) { TreeNode* cur = q.front(); if (cur->left) tmp.push(cur->left); if (cur->right) tmp.push(cur->right); q.pop(); } q=tmp; ++res; } return res; } //方法二:递归实现 int depth2(TreeNode* root) { //base case if (!root) return 0; //operations return max(depth2(root->left), depth2(root->right)) + 1; }
那么,接下来就是如何验证是否是平衡二叉树,抓住平衡二叉树的特点:左右树的深度差不超过1。
有两种方法,一种是再计算深度的同时,判断是否符合平衡条件,一种是分开,先计算深度,再判断是否符合平衡条件。/*方法一:计算深度的同时,判断是否符合平衡条件*/ bool isBalancedBT(TreeNode *root){ return depth(root)!=-1; } int depth(TreeNode *root){ //base case if(!root) return 0; //operations int left=depth(root->left); if(left==-1) return -1; int right=depth(root->right); if(right==-1) return -1; return abs(left-right)<2 ? max(left,right)+1:-1; } /*方法二:先计算深度,再判断是否符合平衡条件*/ bool isBalancedBT(TreeNode *root){ return abs(depth(root->left)-depth(root->right))<2&&isBalancedBT(root->left)&&isBalancedBT(root->right); } int depth(TreeNode *root){ //base case if(!root) return 0; //operations return max(depth(root->left),depth(root->right))+1; }
六、修改树
搜索二叉树的插入与删除
插入很简单,跟建立的函数一样的,不再说明。
删除就会比较麻烦,需要分两种情况。
(1)如果删除节点只含左树或右树,那么只需要修改指针就行了,就像删除链表节点一样。
(2)如果删除节点有左右树,那么就需要分两种情况考虑,如图所示
如图所示,删除A节点(A节点是树内的某一个节点)
为保证搜索二叉树中序遍历结果是排序的特点,我们可以用删除节点在中序遍历中的前驱或后继来替代删除的节点。
我们需要找到A的前驱(后继),同时获取A的前驱(后继)的父节点,然后直接将A的前驱(后继)的值赋给删除节点即可,然后再改变指针指向就行了。//需要一个查找函数 //搜索二叉树查找 TreeNode * searchBSTnode(TreeNode* root, int val) { //recursion //base case if (!root) return nullptr; if (root->val == val) return root; //operations if (val < root->val) searchBSTnode(root->left, val); else if(val > root->val) searchBSTnode(root->right, val); } ////////////////////////////////////////////// bool deleteBSTnode(TreeNode *root,int val){ //先查找 TreeNode *node=searchBSTnode(root,val); if(!node) return 0; //删除 if(!node->left&&!node->right) free(node); else if(node->left&&!node->right){ TreeNode *tmp=node; node=node->left; free(tmp); } else if(node->right&&!node->left){ TreeNode *tmp=node; node=node->right; free(tmp); } else{ TreeNode *node1=node->left,* node2=node1->right; if(!node2){ node->val=node1->val; TreeNode *tmp=node1; node->left=node1->left; free(tmp); } else{ while(node2->right){//找前驱 node1=node2; node2=node2->right; } node->val=node2->val; TreeNode *tmp=node2; node1->right=node2->left; free(tmp); } } return 1; }
平衡二叉树的插入和删除
插入跟建立一样。
删除的话,就是先用搜索二叉树的方法删除后,再调用balance函数调整就可了。
但其实这里还并不是很确定!!!balancedBT* searchbalancedBTnode(balancedBT* root, int val) { //recursion //base case if (!root) return nullptr; if (root->val == val) return root; //operations if (val < root->val) searchbalancedBTnode(root->left, val); else if (val > root->val) searchbalancedBTnode(root->right, val); } //平衡二叉树删除 balancedBT* deletebalancedBT(balancedBT* root, int val) { //先查找 balancedBT* node = searchbalancedBTnode(root, val); if (!node) return nullptr; //删除 if (!node->left && !node->right) free(node); else if (node->left && !node->right) { balancedBT* tmp = node; node = node->left; free(tmp); } else if (node->right && !node->left) { balancedBT* tmp = node; node = node->right; free(tmp); } else { balancedBT* node1 = node->left, * node2 = node1->right; if (!node2) { node->val = node1->val; balancedBT* tmp = node1; node->left = node1->left; free(tmp); } else { while (node2->right) {//找前驱 node1 = node2; node2 = node2->right; } node->val = node2->val; balancedBT* tmp = node2; node1->right = node2->left; free(tmp); } } return balance(root,val); }