树(实践篇)
树(实践篇)
二叉树
-
定义
struct treenode { int val; treenode* left; treenode* right; treenode(int val): val(val),left(NULL),right(NULL){ } };
-
遍历
-
递归法
void qianxu(treenode* cur,……) { if(cur==NULL) return; //在此行处理中间节点即前序遍历 qianxu(cur->left,……); //在此行处理中间节点即中序遍历 qianxu(cur->right,……); //在此行处理中间节点即后序遍历 }
-
迭代法:栈
- 普适三种遍历的模板
vector<int> bianli(treenode* root) { // vector<int> result; stack<treenode*> st; if(root!=NULL) st.push(root); while(st.size()) { treenode* node=st.top(); //每次取出栈头后弹出,再按照一定顺序重新入栈 if(node!=NULL) //先判断一下栈头是不是空节点,空节点和非空节点的处理逻辑不一样 { st.pop(); if(node->right) st.push(node->right); //先将右儿子入栈 st.push(node); //再将中间节点(也就是node)入栈 st.push(NULL); //中间节点入栈后紧跟一个空节点做标记 if(node->left) st.sush(node->left); //最后将左儿子入栈 //这样出栈的顺序就会是左中右,即中序遍历 //若先将中间节点入栈,出栈就是左右中,即后序遍历 //若将中间节点最后入栈,出栈就是中左右,即前序遍历 }else//一旦遇到空节点,说明接下去的是中间节点,需要进行处理 { st.pop(); //弹出空节点 treenode* node=st.top(); st.pop(); //弹出栈头 // result.push_back(node->val);在此行处理中间节点 } // return result; }
-
仅适用前序遍历(简单写法)
vector<int> qianxu(treenode* root) { stack<treenode*> stk; //建栈 // vector<int> result; if(root==NULL) return result; stk.push(root); //根节点入栈 while(!stk.empty()) //栈不为空时往下遍历 { treenode* node=stk.top(); stk.pop(); //每次遍历时取出栈头 // result.push_back(node->val);在此行处理中间节点 if(node->right) stk.push(node->right); if(node->left) stk.push(node->left); //先放右儿子再放左儿子,因为栈先入后出 } return result; }
-
层序遍历:队列
vector<int> cengxu(treenode* root) { queue<treenode*> q; // vector<vector<int>> result; // if(root==NULL) return result; q.push(root); while(q.size()) { int size=q.size(); //确定本层的节点个数,后续for循环遍历本层所有节点 // vector<int> vec; for(int i=0;i<size;i++) { treenode* node=q.front(); q.pop(); //要把弹出队头的操作放在for循环当中,弹出一次就代表遍历了一个节点 // vec.push_back(node->val);在此行处理中间节点 if(node->left) q.push(node->left); if(node->right) q.push(node->right); //将当前节点的孩子节点入队 } // result.push_back(vec); } return result; }
-
-
二叉树相关解题方法
-
递归函数三部曲:1.确定返回值和参数,2.确定终止条件,3.确定单层处理逻辑
-
什么时候需要返回值:当左右孩子的一些赋值操作是依赖于当前结点的处理逻辑时,就需要返回值
-
构造二叉树:递归地为中间节点的左右孩子直接赋值
cur->left=递归函数(参数1,参数2,……); cur->right=递归函数(参数1,参数2,……);
-
求二叉树最大高度:左右子树高度的赋值操作是依赖于递归函数return回来的值的
int leftdepth=getdepth(root->left); int rightdepth=getdepth(root->right); int depth=1+max(leftdepth,rightdepth);
-
-
搜索整棵树和搜索半边树的区别
-
搜索整棵树:搜索完左子树和右子树后才会return
left=递归函数(root->left); right=递归函数(root->right);
-
搜索半边树:只要符合终止条件就return,不再搜索另半边
if (递归函数(root—>left) return; if (递归函数(root->right) return;
-
-
-
在四种遍历框架下,在处理当前结点处,编写对应的处理逻辑
-
前序遍历:先做处理操作,再递归左右子树,从根到叶往下遍历
-
适用于
-
求二叉树深度:深度是从上往下计算的,需要前序遍历递归+回溯从上往下找到目标结点
-
求二叉树的路径:先把中间结点放入数组,然后递归左右子树,才能实现从根到叶的遍历(PS:求路径问题往往需要使用回溯算法,通过回溯才能遍历所有路径)
-
-
-
中序遍历:中序遍历二叉搜索树得到有序数组,常用中序遍历解决二叉搜索树问题
-
适用于
-
判断二叉树是否是二叉搜索树:中序遍历二叉搜索树得到的序列是有序序列
- 暴力法:中序遍历得到有序数组,在判断数组是否有序
- 递归法:在中序遍历的模板下设一个root的前驱结点pre,如果pre的值大于root的值,即说明不是二叉搜索树:
if(!pre&&root->val<=pre->val) return false;
-
求二叉搜索树的最小绝对差
-
暴力法:中序遍历得到有序数组,再在数组上操作求最小绝对差
-
递归法:在中序遍历的模板下设一个root的前驱结点pre,每次求一下root结点减去pre结点的值:
if(pre!=NULL) result=min(result,root->val-pre->val);
-
-
-
-
后序遍历:先递归左右子树,再做处理操作,从叶到根往上遍历
-
适用于
- 求二叉树高度:高度是从下往上计算的,计算当前结点的高度值依赖于左右子树的高度值,所以当前结点的高度计算必须放在左右子树的递归之后(PS:求最大高度=求最大深度)
- 判断一棵树是不是平衡二叉树:先递归求解左右高度,然后在当前结点处处理:只要左右高度差>1,就将当前结点的高度赋为-1,return 高度是-1代表false(PS:注意也要判断一下左右子树是否平衡,即
if(leftdepth==-1) return -1;if(rightdepth==-1) return -1
- 求两结点的最近公共祖先:先递归左右子树,判断是否两结点分别在左右子树中,以此判断当前结点是不是最近公共祖先、以及如果不是的话都在左子树就return left,都在右子树return right
-
不适用于
- 反转二叉树:会导致某些结点反转两次
-
-
层序遍历:逐层遍历,逐层操作
- 适用于
- 求二叉树高度:每遍历一层就将高度加一,直到遍历到叶节点,以此来求高度
- 适用于
-
-
-
其他问题
-
二叉树的数组解法
-
理论基础:第i个结点的左儿子是2 *i,右儿子是2 *i+1,父结点是i/2
int h[N],size;//下标决定了结点间的关系,h存放结点的值,size是二叉树的结点个数
-
求两结点最近公共祖先:取两结点下标a和b,较大的那个不断/=2,直到a==b
int find(int a,int b) { while(a!=b) { if(a>b) a/=2; else b/=2; } return a; }
-
求两结点间的距离:先求两结点的最近公共祖先,再从祖先开始往下两次循环分别找到两结点
-
-
二叉搜索树
-
插入操作:当前结点为空说明找到了插入点则new一个结点,data大于当前节点值就去右子树寻找插入点,data小于当前结点值就去左子树寻找插入点
-
递归法
treenode* insert(treenode* root,int data) { if(root==NULL) { treenode* root=new treenode(data); } if(data>root->val) root->right=insert(data); if(data<root->val) root->left=insert(data); return root; }
-
迭代法:设cur为当前结点,pre为cur的父结点,先循环让cur指向插入点,然后new一个结点 ,让pre指向它即可
treenode* insert(treenode*root,int val) { if(root==NULL) { treenode* node=new treenode(val); return node; } treenode* cur=root; treenode* pre=root; while(cur!=NULL) { pre=cur; if(val>cur->val) cur=cur->right; if(val==cur->val) return root; if(val<cur->val) cur=cur->left; } treenode* node=new treenode(val); if(val>pre->val) pre->right=node; else pre->left=node; return root; }
-
-
删除操作:有一个孩子,删掉结点孩子补位;有两个孩子,把左孩子放到右孩子最左边的结点下
treenode* deletenode(treenode* root,int key) { if(root==NULL) return root; if(root->val==key) { if(root->left==NULL) return root->right; //返回补位节点 else if(root->right==NULL) return root->left; //返回补位节点 else { treenode* node=root->right; while(node->left!=NULL) node=node->left; node->left=root->left; treenode* tmp=root; root=root->right; delete tmp; return root; //返回补位节点 } } //原本是root的地方,return root->right 或 root->left ,即实现了删除root的操作 if(root->val>key) root->left=deletenode(root->left,key); //如果key小于当前root的值,就去左子树里面删 //root->left=deletenode()使得root->left接收了 下一层递归里面返回的那个补位节点 if(root->val<key) root->right=deletenode(root->right,key); //如果key大于当前root的值,就去右子树里面删 return root; }
-
构造操作
-
构造普通二叉搜索树:重复执行插入操作即可
-
有序序列构造二叉平衡搜索树:寻找数组的中间点分割,分割点即当前节点,递归处理左右
treenode* gouzao(vector<int> &num,int left,int right) { if(left>right) return NULL; int mid=left+(right-left)/2; treenode* node=new treenode(num[mid]); node->left=gouzao(num,left,mid-1); node->right=gouzao(num,mid+1,right); return node; }
-
-
查找操作
-
递归
treenode* search(treenode* root,int key) { if(root==NULL||root->val==key) return T; else if(key<root->val) return search(root->left,key); else return search(root->right,key); }
-
迭代
treenode* search(treenode* root,int key) { while(root!=NULL&&key!=root->val) { if(key<root->val) root=root->left; else root=root->right; } return root; }
-
-
-
平衡二叉树
-
定义
typedef struct avlnode { int val; int height; avlnode *parent; avlnode *left; avlnode *right; avlnode(int data): val(data), height(1), left(NULL), right(NULL) { } };
-
求高度
int getheight(avlnode *root) { if (root->left == NULL && root->right == NULL) return 1; else if (root->right == NULL) return root->left->height + 1; else if (root->left == NULL) return root->right->height + 1; else return max(root->left->height, root->right->height); }
-
求BF
int getbf(avlnode *root) { if (root == NULL || (root->left == NULL && root->right == NULL)) return 0; else if (root->right == NULL) return root->left->height; //左子树比较高,bf是正的 else if (root->left == NULL) return -root->right->height; //右子树比较高,bf是负的 else return root->left->height - root->right->height; }
-
左旋操作
avlnode *leftrotate(avlnode *root) { avlnode *oldroot = root; avlnode *newroot = root->right; avlnode *parent = root->parent; //1.用newroot替换oldroot的位置 if (parent != NULL) { if (oldroot->parent->val > oldroot->val) parent->left = newroot; else parent->right = newroot; } newroot->parent = parent; //2.把newroot的左儿子传递给oldroot当右儿子 oldroot->right = newroot->left; if (newroot->left != NULL) newroot->left->parent = oldroot; //3.把oldroot传递给newroot当左儿子 newroot->left = oldroot; oldroot->parent = newroot; //4.更新高度 oldroot->height = getheight(oldroot); newroot->height = getheight(newroot); return newroot; }
-
右旋操作
avlnode *rightrotate(avlnode *root) { avlnode *oldroot = root; avlnode *newroot = root->left; avlnode *parent = root->parent; if (parent != NULL) { if (oldroot->parent->val > oldroot->val) parent->left = newroot; else parent->right = newroot; } newroot->parent = parent; oldroot->left = newroot->right; if (newroot->right != NULL) newroot->right->parent = oldroot; newroot->right = oldroot; oldroot->parent = newroot; oldroot->height = getheight(oldroot); newroot->height = getheight(newroot); return newroot; }
-
构造操作:不断进行插入操作即可
avlnode *insert(avlnode *root, int data) { if (root == NULL) { root = new avlnode(data); return root; } //如果root为空,直接创建即可 avlnode* oldroot=root; if (data < root->val) { if (root->left == NULL) { root->left = new avlnode(data); root->left->parent = root; } else { insert(root->left, data); } } else if (data > root->val) { if (root->right == NULL) { root->right = new avlnode(data); root->left->parent = root; } else { insert(root->right, data); } } //递归往下把新结点创建好插入 root->height = getheight(root); //更新root的高度 if (getbf(root) == 2) { if (getbf(root->left) == -1) { root->left = leftrotate(root->left); } //先左旋转 root = rightrotate(root); } //对root进行右旋操作 if (getbf(root) == -2) { if (getbf(root->right) == 1) { root->right = rightrotate(root->right); } //先右旋转 root = leftrotate(root); } //对root进行左旋操作 return oldroot; }
-
-
并查集:数组p[i],下标i是元素x,p[i]的值是x的根结点;数组size[i]的值是集合的大小
-
初始化:最开始每个元素根结点都是自己;每个集合大小都是1
void init(int n){ for(int i=0;i<n;i++) { p[i]=i; size[i]=1; } }
-
find(int x):返回x根结点的函数
int find(int x) { if(p[x]!=x) p[x]=find(p[x]); return p[x]; }
-
合并两个集合:让a集合的根结点等于b;将两个集合的大小相加得新集合的大小
void hebing(int a,int b) { size[find(b)]+=size[find(a)]; p[find(a)]=find(); }
-
判断两个元素是否在同一个集合:即判断两个元素的根结点是否相同
bool panduan(int a,int b) { if(find(a)==find(b)) return true; else return false; }
-
-
小Tip
-
递归法判断两颗树是否相同,终止条件有四个
if(root1!=NULL&&root2==NULL) return false; else if(root1==NULL&&sroot2!=NULL) return false; else if(root1==NULL&&root2==NULL) return true; else if(root1->val!=root2->val) return false;
-
-