二叉树搜索序列(回溯)

        最近不知道干啥事,好像好多事要干,又好像啥事没有..我的心情be like

         然后我就打开力扣开始看题,按顺序刷题,就看到了这个题目:

         刚一看到,感觉简简单单小回溯,轻松拿捏

         再一看难度:困难,感觉不妙...再仔细一看,好像没有那么简单

         于是我想了一整堂课,终于有了一丢丢思路,然后就写了下来。主要是通过bfs和回溯算法联合使用解决。

 二、解题思路

        1、看示例、了解题意

        题目中说,从左向右遍历一个数组就插入数据就可以生成二叉搜索树。然后给了一个[2,1,3]的实例,如图可以看到它是一层一层插入的。也就是说[2,1,3]和[2,3,1]的差距就在于左右节点插入的顺序不同。因此我们很容易就看出,只要我们通过bfs的方法遍历每一层,然后在每一层通过回溯的方法先插入左节点,得到结果后回溯回来,再插入右节点,就可以完成操作了。

         2、确定数据结构和大致框架

        有了大概思路之后,我们就需要确定需要使用什么样的数据结构和什么样的框架了。

        首先,题目需要返回的类型是List<List<Integer>>,因此我们需要定义一个最终返回结果ans,类型为List<List<Integer>>。然后bfs必不可少的就是队列了,但是我们不选择Queue,因为我们需要进行回溯操作,需要删除指定节点,因此采用List较好。其次,ans保存的是所有可能的路径,因此我们还需要一个List<Integer>类型的变量储存单次的路径。

        确定了数据结构,我们需要大概的框架,如下:

class Solution {
    // 定义全局的变量,也是函数返回值
    List<List<Integer>> ans = new ArrayList<>();
    public List<List<Integer>> BSTSequences(TreeNode root) {
        // 定义队列(存的是节点)
        List<TreeNode> queue = new LinkedList<>();
        //定义单次路径
        List<Integer> path = new ArrayList<>();
        // 判空处理
        if(root == null){
            ans.add(new ArrayList<>(path));
            return ans;
        }
        // bfs老套路,先把头结点放进去,不然循环进不去
        queue.add(root);
        bfs(queue,path);
        // 调用后返回结果
        return ans;
    }
    public void bfs(List<TreeNode> queue,List<Integer> path){
        
    }
}

        咋样,有没有清晰一点?

        有?

        难点还在后面

         主函数大概写好了,那么关键的bfs函数确实是个难点。

        那么我们再写下回溯的框架吧

        

    public void bfs(List<TreeNode> queue,List<Integer> path){
        if(queue.isEmpty()){
            ans.add(new ArrayList<>(path));
            return;
        }
        int size = queue.size();
        // 保存当前队列
        for(int i = 0; i < size; i++){
            // 加节点等一系列操作
            // 再次调用
            bfs(queue,path);
            //当执行到这一步说明一条路径走完了,需要删除当点节点重新选取节点再来一遍
            path.remove(path.size() - 1);
            //恢复队列
        }

        
    }

               大致框架是这个:

                当队列为空的时候,说明节点都已经加入到path里面了,因此path里存的已经是一个完整的路径了,可以加入到ans里去,然后别忘记返回。

                之后循环队列,取出当前队列头部的节点,把它的值放入path中,代表了选择了这个节点,然后再将选择的节点的左右节点加入队列(bfs套路)。放入之后,再次调用bfs循环操作。之后指定到下一步的时候已经需要恢复现场了。

                这里注意!!恢复现场需要恢复两个东西,一个是队列queue、一个是路径path。path比较容易,只需要删除最新加入的那个节点的值就可以了。但是queue稍微麻烦点,不可以通过queue.remove(queue.size() - 1);这种粗暴的方式实现。原因是队列有时候执行到这一步的时候已经是空队列了,执行这一步会报下标越界的错误。

                正确的做法是在进入循环之前,先保存一次队列,然后恢复现场的时候直接让队列等于保存的队列就可以了。总的代码如下:

                

class Solution {
    List<List<Integer>> ans = new ArrayList<>();
    public List<List<Integer>> BSTSequences(TreeNode root) {
        List<TreeNode> queue = new LinkedList<>();
        List<Integer> path = new ArrayList<>();
        if(root == null){
            ans.add(new ArrayList<>(path));
            return ans;
        }
        queue.add(root);
        bfs(queue,path);
        return ans;
    }
    public void bfs(List<TreeNode> queue,List<Integer> path){
        if(queue.isEmpty()){
            ans.add(new ArrayList<>(path));
            return;
        }
        int size = queue.size();
        List<TreeNode> copy = new ArrayList<>(queue);
        for(int i = 0; i < size; i++){
            TreeNode head = queue.get(i);
            path.add(head.val);
            queue.remove(i);
            if(head.left != null)
                queue.add(head.left);
            if(head.right != null)
                queue.add(head.right);
            bfs(queue,path);
            path.remove(path.size() - 1);
            queue = new ArrayList<>(copy);
            //queue.remove(queue.size() - 1);
        }

        
    }
}

        最后运行的结果如下:

         好像不是特别好,于是我又在题解里找了份比较好的回溯。写的相当的nice啊

         代码贴下去了:

class Solution {
    private LinkedList<Integer> path = new LinkedList<>();
    private List<List<Integer>> result = new LinkedList<>();
    public List<List<Integer>> BSTSequences(TreeNode root) {
        Deque<TreeNode> dq = new LinkedList<>();
        if (root != null) {
            dq.offer(root);
        }
        dfs(dq);
        return result;
    }
    public void dfs(Deque<TreeNode> dq) {
        if (dq.isEmpty()) {
            result.add(new ArrayList<Integer>(path));
            return;
        }
        int size = dq.size();
        while(size > 0) {
            TreeNode cur = dq.pollFirst();
            path.add(cur.val);
            int children = 0;
            if (cur.left != null) {
                children++;
                dq.offerLast(cur.left);
            }
            if (cur.right != null) {
                children++;
                dq.offerLast(cur.right);
            }
            dfs(dq);
            while (children > 0) {
                dq.pollLast();
                children--;
            }
            dq.offerLast(cur);
            path.removeLast();
            size--;
        }
    }
}

三 、尾声

        最近题写的不多,题解就更少了。但是我想把为数不多的题解写的有趣一些,因此加了一些表情包,希望大家喜欢。

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论
#include <iostream> #include<stack> #include<queue> using namespace std; const int LH=1; //左子比右子高1 const int EH=0; //左右子一样高 const int RH=-1;//右子比左子高1 const int MAX_NODE_NUM=20; //结点数目上限 class AvlNode { int data; int bf; //平衡因子 AvlNode *lchild; AvlNode *rchild; friend class AVL_Tree; }; class AVL_Tree { public: int Get_data(AvlNode *p) { return p->data; } void Create_AVl(AvlNode *&T) //建 { cout<<"输入平衡二叉树的元素,输入-1代表结束输入:"; int num[MAX_NODE_NUM]; int a,i=0; while(cin>>a && a!=-1) { num[i]=a; i++; } if(num[0]==-1) { cout<<"平衡为空"<<endl; T=NULL; return; } int k=i; bool taller=false; for(i=0;i<k;i++) Insert_Avl(T,num[i],taller);//逐个进行插入,插入过程看下面的示意图 cout<<"_____建完成____"<<endl; } void L_Rotate(AvlNode *&p) { //以p为根节点的二叉排序进行单向左旋处理 AvlNode *rc=p->rchild; p->rchild=rc->lchild; rc->lchild=p; p=rc; } void R_Rotate(AvlNode *&p) { //以p为根节点的二叉排序进行单向右旋处理 AvlNode *lc=p->lchild; p->lchild=lc->rchild; lc->rchild=p; p=lc; } void Left_Balance(AvlNode *&T) { //以T为根节点的二叉排序进行左平衡旋转处理 AvlNode *lc,*rd; lc=T->lchild; switch(lc->bf) { case LH: //新结点插在T的左孩子的左子上,做单向右旋处理 T->bf=lc->bf=EH; R_Rotate(T); break; case RH: //新结点插在T的左孩子的右子上,要进行双旋平衡处理(先左后右) rd=lc->rchild; switch(rd->bf) { case LH: //插在右子的左孩子上 T->bf=RH; lc->bf=EH; break; case EH: T->bf=lc->bf=EH; break; case RH: T->bf=EH; lc->bf=LH; break; } rd->bf=EH; L_Rotate(T->lchild);//先对T的左子进行单向左旋处理 R_Rotate(T); //再对T进行单向右旋处理 } } void Right_Balance(AvlNode *&T) { //以T为根节点的二叉排序进行右平衡旋转处理 AvlNode *rc,*ld; rc=T->rchild; switch(rc->bf) { case RH: //新结点插在右孩子的右子上,进行单向左旋处理 T->bf=rc->bf=EH; L_Rotate(T); break; case LH: //新结点插在T的右孩子的左子上,要进行右平衡旋转处理(先右再左) ld=rc->lchild; switch(ld->bf) { case LH: T->bf=LH; rc->bf=EH; break; case EH: T->bf=rc->bf=EH; break; case RH: T->bf=EH; rc->bf=RH; break; } ld->bf=EH; R_Rotate(T->rchild);//先对T的右子进行单向右旋处理 L_Rotate(T); //再对T进行单向左旋处理 } } bool Insert_Avl(AvlNode *&T,int num,bool &taller) //插入 { //若在平衡二叉树中不存在结点值和num一样大小的结点 //则插入值为num的新结点,并返回true //若因为插入而使得二叉排序失去平衡,则做平衡旋转处理 //taller反映是否长高 if(!T) { //插入新结点,长高,taller为true T=new AvlNode; T->data=num; T->lchild=T->rchild=NULL; T->bf=EH; taller=true; } else { if(num==T->data) { //不重复插入 taller=false; return false; } if(num<T->data) //继续在T的左子中进行搜索 { if(!Insert_Avl(T->lchild,num,taller))//插入不成功 return false; if(taller) //已插入T的左子,且左子长高 { switch(T->bf) { case LH: /*————————————————————— / 插入前左子高于右子,需要进行做平衡处理 / 不管是单向左旋处理,还是先左后右平衡处理 / 处理结果都是使得插入新结点后,的高度不变 /—————————————————————*/ Left_Balance(T); taller=false; break; case EH: //插入前左右子等高,现在插入新街点后,左子比右子高 T->bf=LH; taller=true; break; case RH: //插入前右子比左子高,现在新结点插入左子后,变为左右子等高 T->bf=EH; taller=false; break; } } } else { //num>T->data 在T的右子中继续搜索 if(!Insert_Avl(T->rchild,num,taller)) return false; if(taller) { switch(T->bf) { case LH: //插入前左子比右子高,现在插入T的右子后,左右子等高 T->bf=EH; taller=false; break; case EH: //插入前左右子等高,现在插入后,右子比左子高 T->bf=RH; taller=true; break; case RH: //插入前右子比坐子高,插入后,排序失去平衡,需要进行右平衡处理 Right_Balance(T); taller=false; break; } } } } return true; } bool Search_Avl(AvlNode *T,int num,AvlNode *&f,AvlNode *&p) //搜索 { //用p带回查找到的顶点的地址,f带回p的双亲结点 p=T; while(p) { if(p->data==num) return true; if(p->data>num) { f=p; p=p->lchild; } else { f=p; p=p->rchild; } } return false; } void Delete_AVL(AvlNode *&T,int num) //删除,删除后没有回溯到根节点,算法有错,待日后修改完善,有心的朋友可以自己加一个栈或者其他方式来实现 { /*--------------------------------------------------------- / 从中删除一个节点后,要保证删后的还是一棵平衡二叉树, / 删除前,首先是在查找是否有这个结点,用p指向该结点, / 用f指向p的双亲结点,这个结点在中的位置有下面四种情况: / / 1:如果p指向的结点是叶子结点,那么直接将f指针的左子或者 / 右子置空,然后删除p结点即可。 / / 2:如果p指向的结点是只有左子或右子,那么只需要让p结点 / 原来在f中的位置(左子或右子)用p的子代替即可。 / 代替后,要修改f的平衡因子,在失去平衡的时候,要调用相应的 / 做平衡旋转或右平衡旋转进行恢复. / / 3:如果p所指向的结点是根节点,那么直接将根节点置空 / / 4:如果p所指向的结点左右子都非空,为了删除p后原序列的顺 / 序不变,就需要在原序列中先找出p的直接前驱(或者直接后继) / 结点用那个结点的值来代替p结点的值,然后再删掉那个直接前 / 驱(或者直接后继)结点。 / 其中s指向的是要删除的结点,也就是p的直接前驱,q指向的是 / s的双亲结点,此时,应该看s的平衡因子,在会出现失去平衡的 / 情况时,就要根据实际情况采用左平衡旋转或是右平衡旋转,让 / 恢复平衡,这点和插入操作时是相对应的。 / / 在中序遍历序列中找结点的直接前驱的方法是顺着结点的左孩子 / 的右链域开始,一直到结点右孩子为空为止。 /---------------------------------------------------------*/ AvlNode *f=NULL; AvlNode *p=NULL; AvlNode *q=NULL; AvlNode *s=NULL; if(Search_Avl(T,num,f,p)) { if(p->lchild && p->rchild) //左右子均存在时 { q=p; s=p->lchild; while(s->rchild) { q=s; s=s->rchild; } p->data=s->data; if(q!=p) { //q结点的右子高度减少1 //所以平衡因子会+1 q->rchild=s->lchild; switch(q->bf) { //删除前右子高,现在就变成一样高 case RH: q->bf=EH; break; //删除前等高,现在就变成左子比右子高 case EH: q->bf=LH; break; //删除前左子高,现在左子又高了一,所以失去平衡 case LH: q->bf=EH; Left_Balance(q); break; } } else { //p的左子的右子为空时 //q结点也就是p结点,由于s的右子为空 //所以q结点的左子高度降低1 //平衡因子-1 q->lchild=s->lchild; switch(q->bf) { case LH: q->bf=EH;break; case EH: q->bf=RH;break; case RH: q->bf=EH; Right_Balance(q); break; } } delete s; cout<<"删除结点成功"<<endl; return ; } else { if(!p->lchild) { q=p; p=p->rchild; } else { q=p; p=p->lchild; } if(!T) { T->bf=EH; T=p; } else if(q==f->lchild) { f->lchild=p; switch(f->bf) { case LH: f->bf=EH; break; case EH: f->bf=RH; break; case RH: f->bf=EH; Right_Balance(f); break; } } else { f->rchild=p; switch(f->bf) { case RH: f->bf=EH; break; case EH: f->bf=LH; break; case LH: f->bf=EH; Left_Balance(f); break; } } delete q; cout<<"删除结点成功"<<endl; return; } } else { cout<<"要删除的结点不存在"<<endl; return; } } InOrder_Traverse(AvlNode *T) //中序遍历 { stack<AvlNode *> s; AvlNode *p=T; while(p || !s.empty()) { if(p) { s.push(p); p=p->lchild; } else { p=s.top(); s.pop(); cout<<p->data<<" "; p=p->rchild; } } } void Level_Traverse(AvlNode *T) //层次遍历 { queue<AvlNode *> q; AvlNode *p=T; q.push(p); while(!q.empty()) { p=q.front(); q.pop(); cout<<p->data<<" "; if(p->lchild) q.push(p->lchild); if(p->rchild) q.push(p->rchild); } } }; //测试文件"main.cpp" //#include"tree.h" int main() { AVL_Tree tree; AvlNode *root=NULL; cout<<"____建立平衡二叉树____"<<endl; tree.Create_AVl(root); cout<<"中序遍历二叉树为:"; tree.InOrder_Traverse(root); cout<<endl; cout<<"层次遍历二叉树为:"; tree.Level_Traverse(root); cout<<endl; int num; bool taller=false; cout<<"输入你要插入的结点的值:"; cin>>num; tree.Insert_Avl(root,num,taller); cout<<"中序遍历二叉树为:"; tree.InOrder_Traverse(root); cout<<endl; AvlNode *f=NULL; AvlNode *p=NULL; cout<<"输入你要搜索的结点的值:"; cin>>num; if(tree.Search_Avl(root,num,f,p)) { cout<<"查找得到的结点值为:"<<tree.Get_data(p)<<"的地址为:"<<p<<endl; if(f==NULL) cout<<"因为结点"<<tree.Get_data(p)<<"是根结点,所以没有双亲结点"<<endl; else cout<<"该结点的双亲结点的值为:"<<tree.Get_data(f)<<endl; } else cout<<"查找的结点不存在"<<endl; cout<<"输入你要删除的结点的值:"; cin>>num; tree.Delete_AVL(root,num); cout<<"中序遍历二叉树为:"; tree.InOrder_Traverse(root); cout<<endl; return 0; }

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

少๑渊

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值