关于树的总结
一、树的基础了解
(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); }


查看4道真题和解析