C++ - 二叉树OJ题

news2024/11/29 11:41:59

 二叉树的两种层序遍历

在写之前,我们先来看两种二叉树的层序遍历:


1.给你二叉树的根节点 root ,返回其节点值的 层序遍历 。 (即逐层地,从左到右访问所有节点)。

输入:root = [3,9,20,null,null,15,7]
输出:[[3],[9,20],[15,7]]
示例 2:

这是最基本的层序遍历,可以借用队列,这里我们就借用 vector 和 队列来实现:
 

class Solution {
public:
    vector<vector<int>> levelOrder(TreeNode* root) {
        vector<vector<int>> vv;
        queue<TreeNode*> q;
        int levelsize = 0;

        if(root)
        {
            q.push(root);
            levelsize = 1;
        }

        while(!q.empty())
        {
            vector<int> v;
            for(int i = 0; i < levelsize;i++)
            {
                TreeNode* front = q.front();
                q.pop();
                v.push_back(front->val);

                if(front->left)
                {
                    q.push(front->left);
                }

                if(front->right)
                {
                    q.push(front->right);
                }
            }
            
            vv.push_back(v);
            levelsize = q.size();
        }

        return vv;
    }
};

2.给你二叉树的根节点 root ,返回其节点值 自底向上的层序遍历 。 (即按从叶子节点所在层到根节点所在的层,逐层从左向右遍历)

输入:root = [3,9,20,null,null,15,7]
输出:[[15,7],[9,20],[3]]

这里就要从底向上遍历,其实和上述也是差不多的,简单实现就是先用上述的 题目1 当中计算出正向的层序遍历结果,然后使用 reserve ()函数反转 vector 当中存储的结果即可:
 

class Solution {
public:
    vector<vector<int>> levelOrder(TreeNode* root) {
        vector<vector<int>> vv;
        queue<TreeNode*> q;
        int levelsize = 0;

        if(root)
        {
            q.push(root);
            levelsize = 1;
        }

        while(!q.empty())
        {
            vector<int> v;
            for(int i = 0; i < levelsize;i++)
            {
                TreeNode* front = q.front();
                q.pop();
                v.push_back(front->val);

                if(front->left)
                {
                    q.push(front->left);
                }

                if(front->right)
                {
                    q.push(front->right);
                }
            }
            
            vv.push_back(v);
            levelsize = q.size();
        }

        return vv;
    }
};

 二叉树的最近公共祖先

法一: 

给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。

百度百科中最近公共祖先的定义为:“对于有根树 T 的两个节点 p、q,最近公共祖先表示为一个节点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”

输入:root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 1
输出:3
解释:节点 5 和节点 1 的最近公共祖先是节点 3 。

 如上述例子,7 和 4  的最近公共祖先是 2 ,0 和 8 的最近公共祖先是 1 ;4 和 8 的最近公共祖先是3,······

通过多种例子,我们发现 最近公共祖先 一定在两个孩子的中间,也就是说,最近公共祖先 的两个孩子一个在其左子树,一个在其右子树。这样的话就好判断了。

然后题目当中也有说明,比如 5 和 4 的最近公共祖先就是5 ,也就是孩子本身也可以是最近公共祖先。

而且我们发现,像上述 5 和 4 这种情况,出现在根结点的话,我们是直接可以找出最近公共祖先的,就是根结点;像上述例子,3 和 8;  3 和  5······这些孩子的最近公共祖先都是3。所以我们可以对这种特殊情况进行优先处理。

按照上述的思路说明,我们只需要依次判断每一个结点是否在两个孩子的中间,依次判断这个结点左子树和右子树,用4个布尔值,pleft  pright  qleft  qright 用这四个布尔值的变量来记录,p q 两个孩子到底在当前结点的那一边,知道遍历完整颗树。

class Solution {
public:
    bool Find(TreeNode* root, TreeNode* ptr)
    {
        if(root == nullptr)
            return false;

        return root == ptr
        || Find(root->left, ptr) 
        || Find(root->right, ptr);
    }

    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
        if(root == nullptr)
            return nullptr;

        // 当其中一个孩子是根结点的情况,根结点一定是 最近公共祖先
        if(root == q || root == p)
        {
            return root;
        }

        bool pleft , pright , qleft , qright;

        pleft = Find(root->left, p);
        pright = !pleft;

        qleft = Find(root->left, q);
        qright = !qleft;

        if(pleft && qleft)
        {
            return lowestCommonAncestor(root->left,p,q);
        }
        else if(pright && qright)
        { 
            return lowestCommonAncestor(root->right,p,q);
        }
            return root;
    }
};

上述方法的时间复杂度是 O(N^2),非常高,因为从根结点开始判断每一个结点是不是最近共有祖先都需要遍历这个结点的左右子树,看是不是左右两边一边一个结点。这实际上是一种暴力求解,可以看下面运行结果:
 

 其实,判断两个孩子是否分别在在某一个结点的左右子树当中,对于二叉搜索树来说就是针对性求解,只需判断结点的值的大小就可以,如果是二叉搜索树的暴力求解,复杂度可以达到O(N)。

 法二:

 其实还有更好的方式,如果写过链表的相交这道OJ题的话,比较好理解。

其实,寻找最近共同祖先这道题,在最近共同祖先结点位置其实就相当于是链表的交点,从根结点开始,到最近共同祖先结点,再到两个孩子,是有路径的。

如果看做是链表的相交的话,有两种情况:


三叉链:
 

 这种情况就是三叉链的情况。

二叉链:
 

这种方法,使用前序遍历,从根节点开始遍历路径,如果不是要找的孩子结点就入栈(先入栈在判断是不是要找的孩子结点,考虑出不出栈),比如下图例子:

 前序从根结点开始走,遍历3  和 5 都不是要找的孩子结点,那么都按前序遍历的顺序入栈。

之所以要把遍历过程当中不是 孩子结点的 入栈,是因为,就算不是要找的结点,但是这个结点可能是 路径 结点祖先 当中的其中一个。

当遇到 6 的之后,先入栈,然后判断。

然后再找 4

 同眼按照前序来进行寻找,找到3 和 5 都不是,都入栈;6 的左右子树都是空,左右子树都返回 false,然后从 6 回溯到 5 的时候,就要把 6 出栈;递归回溯返回到 5 ,从 5 的右子树开始寻找, 2  不是,入栈;遍历 7 ,7不是 入栈,同样  从 7 回溯到 2 的时候,就要把 7 出栈 ,递归回溯到2 之后,访问其右子树,找到4 ,是就入栈,判断。

由上述过程,当把两个孩子都找到之后,我们得到了两个栈的结果,一个存储的是找到 6 孩子的路径;一个存储的是 找到 4 孩子的路径,如下所示:

 得到上述两个栈之后,我们把这两个栈,同时依次出栈;比如上述,栈1 出栈6 之后,栈2 跟着出栈 4 ;

按照上述的顺序,知道两个栈的栈顶元素相等,比如上述出栈到 5 的时候,就相等了,而第一个相等的结点,就是最近共同祖先。

 其实上述出栈思想,就和在相交链表当中,使用快慢指针来寻找交点的思想是一样的,只不过出栈是从尾部开始,而快慢指针是从头开始。

完整代码:

class Solution {
public:
    // 查找孩子结点,算出根结点到 孩子结点路径的函数
    bool Find(TreeNode* root, TreeNode* ptr , stack<TreeNode*>& st)
    {
        // 递归终止条件,当遍历到最后的叶子结点的左右子树nullptr时候,说明下面已经没有结点了
        // 更不可能有要找的孩子,所以返回 false
        if(root == nullptr)
        {
            return false;
        }
        
        // 不管当前 root 指向的结点是不是 要找的孩子结点 都先入栈
        st.push(root);
        
        // 如果 root 是要找的孩子,就直接返回 true
        if(root == ptr)
        {
            return true;
        }
        
        // 递归调用 左子树,找到就返回 true ;没找到就继续往下走
        if(Find(root->left , ptr , st))
            return true;
        // 递归调用 右子树, 找到就返回 true ;此时没有找到就说明这个结点不是要找到结点
        if(Find(root->right , ptr , st))
            return true;
        
        // 走到这说明此时 root 指向的结点不是我们要找到孩子结点
        // 所以就出栈
        st.pop();
        // 出栈之后就返回 false,直接否定掉当前结点,回溯或者 向下迭代
        return false;
    }

    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {

        // 用两个栈来存储 从根结点到两个孩子的路径
        stack<TreeNode*> qst, pst;
        Find(root , p , pst);  // 寻找 p 孩子的路径
        Find(root , q , qst);  // 寻找 q 孩子的路径
        
        // 前两个 while 循环用于判断和删除掉两个栈当中,
        // 其中一个栈多出来的元素
        while(qst.size() > pst.size())
        {
            qst.pop();
        }

        while(qst.size() < pst.size())
        {
            pst.pop();
        }
        
        // 此时就同时删除,因为两个栈此时的元素相等
        // 直到 两栈栈顶元素相等
        while(qst.top() != pst.top())
        {
            qst.pop();
            pst.pop();
        }
        
        // 随便返回一个栈的栈顶元素
        return pst.top();
    }
};

二叉搜索树与双向链表

二叉搜索树与双向链表_牛客题霸_牛客网 (nowcoder.com) 

输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的双向链表。如下图所示:

 

注意:

  • 1.要求不能创建任何新的结点,只能调整树中结点指针的指向。当转化完成以后,树中节点的左指针需要指向前驱,树中节点的右指针需要指向后继
  • 2.返回链表中的第一个节点的指针
  • 3.函数返回的TreeNode,有左右指针,其实可以看成一个双向链表的数据结构
  • 4.你不用输出双向链表,程序会根据你的返回值自动打印输出

 要把二叉搜索树修改为有序链表,肯定是用中序。

当然,最简单的方法就是,用一个数组,比如容 vector,然后用二叉搜索树走中序,把结果存到 vector 当中,然后利用vector,创建链表;

但是题目当中要求了 空间复杂度为 O(1),不能创建新的空间。

我们先来看中序的结构:

 中序的操作过程在访问左右子树的中间,那么这个题目当中的中序就要修改 二叉搜索数的当中的指针链接。

但是,修改链接的话,我们就需要找到上一个,也就是说在链表当中的前驱结点,所以我们考虑,在inOrder ()函数当中多传入一个参数,prev指针,这个指针指向中序访问的上一个结点。

可以传入之后,我们每一次函数递归,就去把递归之前,当前函数栈帧的结点指针传入到下一个函数栈帧当中。

传参之后,在以后的某一个结点时候,我可以直到上一个结点是什么,那么我就可以把当前结点的 left(前驱指针)指向中序的上一个,把中序上一个结点的right(后继指针)指向当前结点。

需要注意的是,prev在传参的时候,不能直接用指针来传,我们要保证在这么多次递归传参的时候,传的prev指针都是同一个,所以,这里要借助引用来传参,如果是C语言的话,就要借助二级指针。如下所示:
 

 此时你可能会有疑问,如果在 8 的左边还有一个结点,修改 8 的left指针不会让 8 左边这个结点丢失吗?

 根本不会,因为中序遍历是要先遍历左子树,然后在去访问根结点的,也就是说,当访问到4,回溯到 6 的时候,那么就会 修改 6 和 4 的链接关系,紧接着就要访问 6 的右子树,而 要修改 8 的话,要先访问到 7 ,把 7 链接修改之后,回溯到8 才能访问到8,而且修改 8 指向的 7 的左指针,要再次回溯到 6 的时候才会修改。

 代码实现:
 

class Solution {
public:
	void inOrder(TreeNode* ptr, TreeNode*& prev)
	{
		if(ptr == nullptr)
			return;

		inOrder(ptr->left , prev);

		// 中序修改结点指针链接

        // 让当前结点的left 指针指向 中序遍历的上一个结点
		ptr->left = prev;

        // 如果上一个中序遍历结点不为空,就让上述一个结点的 right 指针指向当前结点
		if(prev)
			prev->right = ptr;

        // prev 向后迭代
		prev = ptr;

		inOrder(ptr->right, prev);
	}

    TreeNode* Convert(TreeNode* pRootOfTree) {
		// 中序修改链接关系
        TreeNode* prev = nullptr;
		inOrder(pRootOfTree , prev);

		TreeNode* head = pRootOfTree;
		while(head && head->left)
		{
			head = head->left;
		}

		return head;
    }
};

从前序与中序遍历序列构造二叉树

 题目链接:105. 从前序与中序遍历序列构造二叉树 - 力扣(LeetCode)

给定两个整数数组 preorder 和 inorder ,其中 preorder 是二叉树的先序遍历, inorder 是同一棵树的中序遍历,请构造二叉树并返回其根节点。

过程解析可以查看下面博客:由前序和中序创建二叉树_前序和中序构建二叉树_chihiro1122的博客-CSDN博客 

代码:

class Solution {
public:
    TreeNode* _build(vector<int>& preorder, vector<int>& inorder,
                    int& prei, int inbegin, int inend)
        {
            if(inbegin > inend)
                return NULL;

            TreeNode* root = new TreeNode(preorder[prei]);
            int rooti = inbegin;
            while(rooti <= inend)
            {
                if(preorder[prei] == inorder[rooti])
                    break;

                ++rooti;
            }

            // [inbegin, rooti - 1] root [ rooti + 1, inend]
            ++prei;
            root->left = _build(preorder, inorder, prei, inbegin, rooti - 1);
            root->right = _build(preorder, inorder, prei ,rooti + 1 , inend);
            return root;
        }


    TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
        int i = 0;
        return _build(preorder,inorder , i , 0, inorder.size() - 1);
    }
};

 从中序与后序遍历序列构造二叉树

题目链接:106. 从中序与后序遍历序列构造二叉树 - 力扣(LeetCode) 

 给定两个整数数组 inorder 和 postorder ,其中 inorder 是二叉树的中序遍历, postorder 是同一棵树的后序遍历,请你构造并返回这颗 二叉树 。

 有中序和后序创建二叉树也是和上一个题类似的。只不过现在我们需要用 后序的最后一个来确定根是哪个,在中序当中确定该根结点的左右子树。

而且,因为在后序当中寻找根,根是后序当中的最后一个,所以,我们要先创建右子树,再创建左子树。

因为,假设当前结点有右子树,那么在后序当中的下一个结点就是该结点的右子树的根结点。

以上就是 中序和后序构建 与 前序和中序构建二叉树两题当中的不同点,除此之外,基本一致,这就不再用代码实现。

 二叉树的前序遍历,非递归实现

 非递归模拟递归来实现的话,主要是要模拟实现递归当中的栈帧。如下例子:
 

 

 上述树如果是前序来遍历的话,先是从 3  开始,遍历 左路上述的结点,而左路就是  3-5-6 这个表示左路,然后再从左路的叶子结点开始反着遍历 左路结点 的右子树:

 这就是前序遍历的基本过程,如果我们要用非递归实现的话,左路结点好遍历,那么反过来调用左路结点的右子树,我们如果和寻找呢?其实,这就要用到栈先进后出的体系结构了。

我们用栈把 遍历左路结点的 路径存储到栈当中,当左路访问完之后,就去访问栈顶元素的右子树,从右子树开始,在访问从该右子树根结点的左路结点,再把该路径入栈,当这条左路结点路径访问到 NULL 最后的时候,就再次找栈顶元素,遍历栈顶元素的右子树。

以上就是使用栈模拟的过程,如上述的反复就可以 用 前序的顺序来遍历完整个二叉树。

 第一步从根结点开始入栈就如下图所示:
 

 遍历到 6 之后,到最后了,就访问栈顶元素的右子树的左路结点,把 栈顶元素 6 出栈。

发现 6 右子树是空,访问到空,接着访问栈顶元素 5 的左路结点,然后入栈:
 

 如上进行反复就是前序遍历了

总结一下:

  • 1.我们访问结点是要用指针来迭代访问的,假设我们使用cur 来迭代访问,当cur 指向一个结点的时候,就代表这要访问从这个结点开始的 左路结点路径
  • 2.而在栈当中存储的结点是访问过了 的,在栈当中存储的结点是访问这些结点的右子树。 

 代码实现:
 

class Solution {
public:
    vector<int> preorderTraversal(TreeNode* root) {
        TreeNode* cur = root;  // 用于迭代的指针
        vector<int> v;         // 用于存储前序输出结果

        // 如果是空树直接返回 空 vector
        // 因为上述 cur 是直接被 root 赋值,所以这里不用判断,下述也会走出空的输出
        if(root == nullptr)
            return v;

        // 模拟栈帧的栈
        stack<TreeNode*> st;

        // 外层循环控制整个树的遍历,相当于是递归当中的函数层级
        while(cur || !st.empty())
        {
            // 内层循环控制 访问 左路结点
            while(cur)
            {
                v.push_back(cur->val);
                st.push(cur);
                cur = cur->left;
            }

            // 取出栈顶元素
            TreeNode* top = st.top();
            // 出栈栈顶元素
            st.pop();

            //cur 指向空之后,cur指向栈顶元素的右子树的根结点
            cur = top->right;
        }

        return v;
    }
};

二叉树的中序,后序遍历,非递归实现

 中序和后序在模拟栈上面和 前序是一样,只是在访问结点的时机不同。

在走左路结点路径的时候:

前序是先访问结点,后入栈。

中序则是先入栈,当某一条左路结点路径走到空的时候,就要出栈,中序就是要访问出栈结点,代码实现:
 

class Solution {
public:
    vector<int> inorderTraversal(TreeNode* root) {
        TreeNode* cur = root;  // 用于迭代的指针
        vector<int> v;         // 用于存储前序输出结果

        // 如果是空树直接返回 空 vector
        // 因为上述 cur 是直接被 root 赋值,所以这里不用判断,下述也会走出空的输出
        if(root == nullptr)
            return v;

        // 模拟栈帧的栈
        stack<TreeNode*> st;

        // 外层循环控制整个树的遍历,相当于是递归当中的函数层级
        while(cur || !st.empty())
        {
            // 内层循环控制 访问 左路结点
            // 中序只在访问左路结点时候,只入栈不访问
            while(cur)
            {
                st.push(cur);
                cur = cur->left;
            }

            // 取出栈顶元素
            TreeNode* top = st.top();
            // 出栈栈顶元素
            st.pop();
            v.push_back(top->val);// 出栈的时候才访问

            //cur 指向空之后,cur指向栈顶元素的右子树的根结点
            cur = top->right;
        }

        return v;
    }
};

后序的访问时机在右子树访问结束之后,在原本的前序和中序当中无法直接套用,直接再次控制。

首先我们想到,当一个结点的左右子树都访问过了,那么这个结点就是可以访问的了,如下例子:
 

 当访问到 6 的时候,在访问其左右子树,发现都是 空,说明 6 这个结点就可以访问了。换个说法,当从某个结点的右子树回溯带该结点的时候,说明这个结点就可以访问了。比如 5 这个结点,当我们从 2 回溯到 5 的时候,说明此时5 就是可以访问的结点了,此时就应该把 5 push_back到 vector 当中。

但是,此时又多出一个问题,我们如果知道 此时的 5 的右子树已经访问过了呢?

 按照我们之前在前序当中说的过程,5 总共要 路过 两次,在这两次当中我们该如何判断,哪一次是 右子树的回溯呢?

相信你已经发现了:

  • 如果 5 的右子树没有访问过,那么此时上一次访问的结点就是 5 的左子树的根结点,也就是 6;
  • 如果 5 的右子树访问过了,那么此时上一次访问的结点U就是 5 的右子树的根结点,也就是 2;

 按照这个逻辑,我们只需要从记录 结果的 vector 当中取出上一次访问的结果,和右子树进行对比,就知道此时是到底是那一次 路过了

而且在访问结点的情况有两种,一种是访问叶子结点,此时条件是 右孩子是空;另一种是访问左右都有孩子(子树)的结点,此时就是要按照我们上述的描述来进行判断。 

 代码实现:

class Solution {
public:
    vector<int> postorderTraversal(TreeNode* root) {
        TreeNode* cur = root;  // 用于迭代的指针
        vector<int> v;         // 用于存储前序输出结果
        TreeNode* prev = nullptr;

        // 如果是空树直接返回 空 vector
        // 因为上述 cur 是直接被 root 赋值,所以这里不用判断,下述也会走出空的输出
        if(root == nullptr)
            return v;

        // 模拟栈帧的栈
        stack<TreeNode*> st;

        // 外层循环控制整个树的遍历,相当于是递归当中的函数层级
        while(cur || !st.empty())
        {
            // 内层循环控制 访问 左路结点
            // 中序只在访问左路结点时候,只入栈不访问
            while(cur)
            {
                st.push(cur);
                cur = cur->left;
            }

            // 取出栈顶元素
            TreeNode* top = st.top();

            // 当 当前结点的右子树为空,或者 当前结点的右子树的根结点是上一次访问过的
            // 都说明 右子树是访问过的,该结点现在就可以访问
            if(top->right == nullptr || top->right == prev)
            {
                prev = top;
                 // 出栈栈顶元素
                st.pop();
                v.push_back(top->val);// 出栈的时候才访问
            }
            else
            {
                //cur 指向空之后,cur指向栈顶元素的右子树的根结点
                cur = top->right;
            }
        }

        return v;
    }
};

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1008131.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

靠差异化上了短剧“牌桌”后,百度准备怎么做生态?

从最初的野蛮生长到如今的百花齐放&#xff0c;短剧市场已然进入了质量与创意的竞争。 据《中国网络视听发展研究报告》数据显示&#xff0c;行业内重点网络微短剧上线数量从2021年的58部&#xff0c;飙升到2022年的172部。相比起前几年处于风口时的爆发式增长&#xff0c;“分…

广读论文核心思路汇总笔记 (一些有意思的论文and论文在研究的一些有意思的问题or场景应用)

思路可借鉴和学习 On the Generalization of Multi-modal Contrastive Learning CoRR, ICML&#xff08;2023&#xff09; 摘要&#xff1a;多模态对比学习&#xff08;MMCL&#xff09;最近引起了广泛关注&#xff0c;因为它在视觉任务上的表现优于其他方法&#xff0c;这些方…

ASP.NET dotnet 3.5 实验室信息管理系统LIMS源码

技术架构&#xff1a;ASP.NET dotnet 3.5 LIMS作为一个信息管理系统&#xff0c;它有着和ERP、MIS之类管理软件的共性&#xff0c;如它是通过现代管理模式与计算机管理信息系统支持企业或单位合理、系统地管理经营与生产&#xff0c;最大限度地发挥现有设备、资源、人、技术的…

Apifox 关于token的使用方式

前言&#xff0c;关于token的使用&#xff0c;仅做了简单的demo测试token效果。 1.手动登录获取token 顾名思义&#xff0c;因为只有登录之后才有token的信息&#xff0c;所以在调用其他接口前需要拥有token才能访问。 操作步骤 1)添加环境变量、全局参数 这里拿测试环境举…

vue基础 组合式和响应式 模板语法 计算属性

模板语法 | Vue.js 根据文档 组合式和响应式 响应式 响应api单网页实例式 组合式 组合式api单网页实例 模板语法 文本插值 {{msg}} 最基本的数据绑定形式是文本插值&#xff0c;它使用的是“Mustache”语法 (即双大括号)&#xff1a; <script setup> import {onMo…

Promethues(五)查询-PromQL 语言-保证易懂好学

一、介绍 普罗米修斯提供了一种称为PromQL&#xff08;普罗米修斯查询语言&#xff09;的函数式查询语言&#xff0c;允许用户实时选择和聚合时间序列数据。 表达式的结果可以显示为图形&#xff0c;在 Prometheus 的表达式浏览器中显示为表格数据&#xff0c;也可以通过 HTT…

EMQX的docker部署与使用(mqtt)

1&#xff1a;先创建一个挂载emqx的目录 2&#xff1a;docker拉去emqx docker pull emqx/emqx:latest2-1&#xff1a;先启动一次eqmx&#xff0c;然后停止&#xff0c;删除容器 docker run -d --name emqx --privilegedtrue -p 1883:1883 -p 8883:8883 -p 8083:8083 -p 8084…

Spring Bean生命周期图扩展接口介绍spring的简化配置

目录 1. 生命周期简图 2. 扩展接口介绍 2.1 Aware接口 2.2 BeanPostProcessor接口 2.3 InitializingBean 2.4 DisposableBean 2.5 BeanFactoryPostProcessor接口 3. spring的简化配置 3.1 项目搭建 3.2 Bean的配置和值注入 3.3 AOP的示例 1. 生命周期简图 2. 扩展接…

Mobileye CEO来华:只有能控制住成本的公司,才能活下来

‍作者|德新 编辑|王博 上午9点近一刻&#xff0c;Mobileye CEO Amnon Shuashua步入酒店的会议室。由于Amnon本人是以色列希伯来大学的计算机科学教授&#xff0c;大部分人更习惯称他为「教授」。 时近以色列的新年&#xff0c;这趟教授的中国之行安排十分紧凑。 他率领了一…

IP地址在各行业中的应用场景

1、互联网交易、支付反欺诈 通过分析IP应用场景、IP地址的出现位置的离散程度、分布情况综合用户行为及时间判断IP地址风险程度&#xff0c;过滤机器流量。在登陆、交易、支付等多个环节结合多重验证等技术减少欺诈行为。 2、P2P平台反“羊毛党” 通过分析IP应用场景、位置信…

得帆云“智改数转,非同帆响”-AIGC+低代码PaaS平台系列白皮书,正式发布!

5月16日下午&#xff0c;由上海得帆信息技术有限公司编写&#xff0c;上海市工业互联网协会指导的以“智改数转&#xff0c;非同帆响”为主题的《得帆云 AIGC低代码PaaS平台系列白皮书》正式在徐汇西岸国际人工智能中心发布。 本次发布会受到了上海市徐汇区政府、各大媒体和业内…

c刷题(四)

获得月份天数 获得月份天数_牛客题霸_牛客网 这道题可以用switch case语句解&#xff0c;不过这道题更简单的方法是数组&#xff0c;关键点在于判断是否为闰年。 #define _CRT_SECURE_NO_WARNINGS 1 #include <stdio.h> #include<assert.h> int year_run(int n) …

Go 异常处理

代码在执行的过程中可能因为一些逻辑上的问题而出现错误 func test1(a, b int) int {result : a / breturn result } func main() {resut : test1(10, 0)fmt.Println(resut) }panic: runtime error: integer divide by zero goroutine 1 [running]: …

【数据库】数据库系统概论(一)— 概念

theme: qklhk-chocolate 基本概念 数据 描述事物的符号记录称为数据。 记录时是计算机中表示和存储数据的一种格式或一种方法。 数据库 数据库是长期存储在计算机内、有组织、可共享的大量数据的集合。 数据库中的数据按一定的数据模型组织、描述和储存。具有较小冗余度…

我的创作纪念日(第1024天)

机缘 当我开始在CSDN上创作时&#xff0c;我的初心主要是出于对技术的热爱和对知识分享的渴望。我一直以来都对计算机科学和技术领域充满兴趣&#xff0c;并且热衷于学习和探索新的技术知识和应用。通过在CSDN上发表文章和分享我的经验和见解&#xff0c;我希望能够与更多的技…

基于webman的CMS,企业官网通用PHP后台管理系统

2023年9月11日10:47:00 仓库地址&#xff1a; https://gitee.com/open-php/zx-webman-website 还有laravelscui的版本目前还未开源&#xff0c;电商laravel版本差不多&#xff0c;后续在移植webman 算是比较标准的phpvue的项目 CMS&#xff0c;企业官网通用PHP后台管理系统 …

UE5 Foliage地形植被实例删不掉选不中问题

目前问题测试发生在5.2.1上 地形上先填充后刷的植被删不掉 首先这个就是bug&#xff0c;大概看到说是5.3上能解决了&#xff0c;对此我只能吐槽ue5上地形植被bug太多了 什么nanite还能产生bug&#xff0c;不过这次又不是&#xff0c;整个删掉instance可以删除所有植被&#…

C++项目实战——基于多设计模式下的同步异步日志系统-⑨-同步日志器类与日志器建造者类设计

文章目录 专栏导读Logger类设计同步日志器类设计同步日志器测试日志器建造者模式设计抽象日志器建造者类派生局部日志器建造者日志器建造者类测试 同步日志器类与日志器建造者类整理 专栏导读 &#x1f338;作者简介&#xff1a;花想云 &#xff0c;在读本科生一枚&#xff0c;…

将阿里云盘挂载到本地磁盘-CloudDrive工具使用教程

CloudDrive是什么&#xff1f; 支持将115、沃家云盘、天翼云盘、阿里云盘、WebDAV挂载到本地并创建本地磁盘。 CloudDrive是一个全方位的云存储管理平台&#xff0c;旨在无缝集成多个云存储服务&#xff0c;将它们统一整合到一个界面中。 使用CloudDrive&#xff0c;您可以轻松…

Python 图形化界面基础篇:监听按钮点击事件

Python 图形化界面基础篇&#xff1a;监听按钮点击事件 引言 Tkinter 库简介步骤1&#xff1a;导入 Tkinter 模块步骤2&#xff1a;创建 Tkinter 窗口步骤3&#xff1a;创建按钮和定义事件处理函数步骤4&#xff1a;创建显示文本的标签步骤5&#xff1a;启动 Tkinter 主事件循环…