树(实践篇)

树(实践篇)

二叉树

  • 定义

    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;
        
posted @ 2022-11-22 11:32  pinoky  阅读(20)  评论(0编辑  收藏  举报