数据结构—树

文章目录

  • 9.树
    • (1).树的基本概念
      • #1.基本定义
      • #2.树的广义表表示法
      • #3.基本术语
    • (2).树的存储结构
      • #1.标准形式(常用)
      • #2.逆存储形式
      • #3.孩子兄弟存储法
    • (3).并查集
      • #1.我们到底想解决什么问题
      • #2.并查集结点
      • #2.Find(查)
      • #3.Union(并)
      • #4.例子
    • (4).树的遍历
      • #1.前序遍历
      • #2.后序遍历
      • #3.遍历的非递归实现
        • i.非递归前序遍历
        • ii.非递归后序遍历
      • #4.层序遍历
    • (5).树的线性表示
      • #1.双亲表示法
      • #2.层号表示法
    • 小结

9.树

  接下来我们就要走出一对一的线性结构,来研究一下一对多的树形结构了,树的结构其实非常常见,比如我们在对文件分类的时候,同一个目录下有多个文件、目录,而子目录下还有更多文件,这就是一个比较典型的一对多关系。

(1).树的基本概念

#1.基本定义

  树是由 n ( n ≥ 0 ) n(n\ge0) n(n0)个结点组成的有限集T,它或为空树( n = 0 n=0 n=0);或为非空树。对于非空树T,它满足以下两个条件:

  • 1.有一个特定的结点,称之为根结点(root)
    1. 除根结点以外,其余的结点分成 m ( m > 0 ) m(m>0) m(m>0)个互不相交的有限集 T 0 , T 1 , T 2 , . . . , T m − 1 T_0, T_1, T_2,..., T_{m-1} T0,T1,T2,...,Tm1,其中每个集合都是一棵树,称为根结点的子树

  也就是说,树的定义本身是一个递归定义,例如下图:
p23

  其中A是根结点,B,C和D分别构成了三棵子树,你可以依次递归地判断下去,直到遇到一棵空树

#2.树的广义表表示法

  例如对于上面这棵树,我们可以表示为A(B(E, F), C, D(G(H))),字母后接的括号内包含该结点所有的子结点

#3.基本术语

  • 结点:树中的每个独立单元
  • 结点的度(次数):一个结点的子树的个数称为结点的度(类比图中结点的出度/入度)
  • 叶子结点:度为0的结点称为叶子结点,他没有子树
  • 分支结点:度不为0的结点
  • m次完全树:假设树T是一棵m次数,如果T中非叶子结点的次数都为m,则称树T为一棵m次完全树
  • 双亲结点和子结点:结点的子树的根结点称为该结点的子结点,而该结点也称为子树根结点的双亲结点(父结点)
  • 兄弟结点:如果结点k有两个或两个以上子结点,则结点k的所有子结点互为兄弟结点
  • 路径:对于树种的任意两个不同的结点 k i k_i ki k j k_j kj,如果 k i k_i ki出发能够“自上而下地”通过树种的结点到达结点 k j k_j kj,则称 k i k_i ki k j k_j kj存在一条路径
  • 路径长度:路径中所包含的边的数量称为路径长度(路径的长度等于这条路径上的结点个数-1
  • 结点的祖先:如果从结点 k i 0 k_{i_0} ki0到结点 k i n k_{i_n} kin有路径 ( k i 0 , k i 1 , . . . , k i n ) (k_{i_0}, k_{i_1}, ..., k_{i_n}) (ki0,ki1,...,kin),则称 k i 0 , k i 1 , . . . , k i n − 1 k_{i_0}, k_{i_1},..., k_{i_{n-1}} ki0,ki1,...,kin1都是结点 k i n k_{i_n} kin的祖先
  • 结点的后代:结点 k i 1 , k i 2 , . . . , k i n k_{i_1},k_{i_2},...,k_{i_n} ki1,ki2,...,kin都是结点 k i 0 k_{i_0} ki0的后代
  • 结点的层次:将根的层次定义为0,其余结点所在的层次都等于其父结点的层次+1(一般也有将根结点层次定义为1的,空树的层次则为0,这里采取ECNU数据结构课中给出的定义
  • 高度(深度):树中层次最大的结点的层次称为树的深度
    在这里插入图片描述

  我们再拿这张图举个例子, k 0 k_0 k0的层次是0, k 1 , k 4 , k 5 k_1, k_4, k_5 k1,k4,k5的层次是1,以此类推,并且该树的深度为3

  • 有序树:如果给定的m次树,结点的各子树从左到右是有次序(不可交换)的,那么称该树为有序树
    p24

  例如在这里我们就使用了一棵有序树来表示 ( A + B ) × 5 ÷ ( 2 × ( C − D ) ) (A+B)\times5\div(2\times(C-D)) (A+B)×5÷(2×(CD))的值,有趣的是,如果你已经提前了解了二叉树的前、中、后序遍历之后,你就会发现,表达式树的前、中、后序遍历得到的序列正是前缀、中缀、后缀表达式,比如我们用后序遍历可以得到: A B + 5 ∗ 2 C D − ∗ / AB+5*2CD-*/ AB+52CD/,是不是还挺神奇的,之后我们会再讲到树的遍历

  • 森林 m ( m ≥ 0 ) m(m\ge0) m(m0)棵互不相交的树的集合

(2).树的存储结构

  说了这么多,接下来我们应该看看怎么把一棵树存储起来了,不过因为树是非线性结构,我们必须采取一些措施把它映射到一个线性的结构上去,否则我们就没有办法存储了。

#1.标准形式(常用)

  我们采取如下的结构体来存储树中的结点:

struct TreeNode
{
    int val;
    TreeNode* child[MAXN];
};

  很简单的结构,首先存储自己结点的值,然后存储所有子结点的指针,以便于找到所有的子结点,它的存储结构图如下:
p25

  还是比较好理解的对吧,这里向上的小箭头代表空指针,意味着存储空间的此位不存在子结点,不过这样的结构很有可能造成比较严重的空间浪费,因此在C++中我们可以采取vector来替代原生数组:

struct TreeNode
{
    int val;
    vector<TreeNode*> child;
};

  同理,能够更好利用零散空间的链式存储也比较适合这种场景,我们可以把存储子结点的存储结构换成链表,例如:

struct TreeNode
{
    int val;
    list<TreeNode*> child;
};

  带有vector的在C语言中不好实现,但是使用链表的实现起来就没有那么复杂了,不过这二者对于我们来说不会有很大的差异,因为后续的操作多数都是要对整个包含子结点的存储结构进行遍历的,因此我们可能不太需要随机访问

  这种存储结构对于双亲找子的过程非常方便,我们只要知道一个结点,就可以很轻松地找到它的所有子结点,但是如果我们知道了一个结点,希望找到它的双亲结点,在这种存储方式之下就不是很容易了,这个问题其实看起来就和链表的某个结点希望找到其上一个结点一样,如果我们不额外耗费一点空间,那肯定是需要耗费更多的时间来完成这件事的

#2.逆存储形式

  我们把每个结点都存自己的子结点的过程逆转一下,每个结点都存自己的双亲结点,结构体变成这样:

struct TreeNode
{
    int val;
    TreeNode* parent;
};

  这样就可以非常容易地找到自己的双亲结点了,它的存储形式是这样:
p26

  但是这样一来,想找到自己的子结点又变得困难了起来,所以怎么办呢?这还不好办,就像链表为了快速找到前后结点一样,我们设置一个双向的结点就可以了:

struct TreeNode
{
    int val;
    TreeNode* parent;
    vector<TreeNode*> child;
};

  这就完美了,虽然每个结点占用的空间可能比较大,但是至少我们在时间上有了很大的优化,无论是找子结点还是找双亲结点都可以直接通过指针跳转了

#3.孩子兄弟存储法

  这回我们就不存全部的结点了,我们每个结构体中只存储两个结点的指针:自己的第一个子结点以及自己右边的第一个兄弟结点,结构体定义如下:

struct TreeNode
{
    int val;
    TreeNode* firstChild;
    TreeNode* nextSibling;
};

p27

  这种存储方式的好处是,我们可以很好地解决上面几种存储方法空间消耗很大的问题,同时我们把多叉树通过这种方式转换成了二叉树,我们对于二叉树的一些操作对于这种存储方式的多叉树也是可以使用的

(3).并查集

#1.我们到底想解决什么问题

  我们怎么表示一个集合呢?其实用什么都行,比如我上学期C++课上写过一个用数组实现的集合:

class integerSet
{
private:
    int* arr;
    size_t _size;
    size_t _capacity;
public:
    static constexpr size_t npos = -1;
    struct bad_integerSet : public std::exception
    {
        bad_integerSet() = default;
        const char* what() const throw ()
        {
            return "bad_integerSet";
        }
    };
public:
    integerSet() = delete;
    integerSet(size_t size)
        : arr(new int[size+1] {0})
        , _size(0), _capacity(size) {}
    integerSet(const integerSet& s1);
    integerSet(integerSet&& s1) noexcept;
    ~integerSet();

    size_t find(int elem) const;
    void insert(int elem);

    integerSet setunion(const integerSet& s1) const;
    integerSet setdifference(const integerSet& s1) const;
    integerSet setintsection(const integerSet& s1) const;
    integerSet setsymmetricdifference(const integerSet& s1) const;

    integerSet& operator=(const integerSet& s1);
    int& operator[](size_t _index);
    bool operator==(const integerSet& s1) const;

    size_t size() const;
    size_t capacity() const;
    bool isSubset(const integerSet& s1) const;
    bool isMember(int m) const;
    bool isEmpty() const;
    void clear();
    void erase(int elem);
};

  它的实现你倒是确实可以试一试,不是很困难,不过如果你的集合无序,有一些操作做起来会比较困难,所以C++的STL中提供的集合是基于红黑树(set)/哈希表(unordered_set) 实现的,但是这样就有点麻烦了

  我们接下来要考虑的问题,是比较简单的,假设说我们的一个数组中存在很多个集合,并且这些集合之间不存在交集(也就是说这些集合一起构成了这个数组的划分),我们如何在更快的时间内,表示出其中每一个元素的集合所属关系呢?要明确的一点是:在我们当前研究的集合中,元素a在集合A中,元素b在集合B中,如果元素b也在集合A中,那么集合A和集合B是一个集合,也就是说,一旦在两个集合中出现同一个元素,那么这两个集合就可以直接合并,那我们是不是可以用树来表达这个结构呢?

  例如对于三个集合:{0, 6, 7, 8}, {1, 4, 9}, {2, 3, 5},假设我们把第一个元素作为根,那么就有:
p28

  哇哦,我们用双亲存储法构建这棵树,就可以把一个集合描述出来,那么如果 S 1 S_1 S1中的结点4,正好也属于 S 0 S_0 S0呢?就比如下面这样:
p29

  这种情况下出现了多对一的结构,那么理论上讲我们用就不能描述了,可能需要用到的结构,但是我们好像忘了点什么事情:

在我们当前研究的集合中,元素a在集合A中,元素b在集合B中,如果元素b也在集合A中,那么集合A和集合B是一个集合

  既然4是 S 0 S_0 S0 S 1 S_1 S1共有的元素,那么集合 S 0 S_0 S0和集合 S 1 S_1 S1应当是一个集合,所以我们直接把 S 1 S_1 S1的根合并到 S 0 S_0 S0去,形成一个更大的集合,这样就可以把这些数据全都加到同一个集合里面去了:
p30

  在这种集合中,我们怎么判定两个元素是否属于同一个集合呢?这个也简单,因为每个结点都可以找到自己的双亲结点,所以我们只要一直判断到根结点,看看两个元素是不是能够找到同一个根结点即可

  所以综上,我们的集合只需要两个操作Union(并)Find(查) 两个操作即可,我们研究的这一类集合,也就叫做并查集

#2.并查集结点

  并查集的每个结点要怎么存呢?其实比较简单,对于一个数组,下标是它的值,数组内对应的数字是它的双亲结点,而对于根结点,可以存一个负数(表示整个根结点下有多少个结点),也可以存自己的下标(这意味着如果找到下标和存储值相同的,就已经找到根了)

  所以在这里我们采用前者,前者对于一些问题的解决来说非常方便,比如找出朋友圈中人数最多的朋友圈的数量,需要注意的是,如果我们采取前者,在初始化的时候就需要我们把数组每一个元素对应的值都改成-1,这样每一个元素都是自己的根,对于后续我们的操作会非常方便

#2.Find(查)

  先说查是因为并操作也需要先依赖于查操作,所以我们可以给出如下代码:

int find(vector<int>& dsu, int x)
{
    if (dsu[x] < 0) {
        return x;
    }
    return find(dsu, dsu[x]);
}

  非常简单的函数,如果找到一个值小于0的,就直接返回,如果不是,则继续向着根找,直到找到一个小于0的就结束了,这样是不是很简单呢?判断两个元素是不是在一个集合里的操作就是这么简单

#3.Union(并)

  然后就是并,在这里我们采取一个简单策略,如果x和y需要合并,那就直接把y对应的整棵树合并到x上面去,代码如下:

int Union(vector<int>& dsu, int x, int y)
{
    int fx{ find(dsu, x) }, fy{ find(dsu, y) };
    if (fx != fy) {
        dsu[fx] += dsu[fy];
        dsu[fy] = fx; 
    }
    return fx;
}

  我们首先找到两个结点对应的根,如果两个对应相同的根,就不需要合并,但如果是不同的根,我们直接把y对应的整棵树都合并进x对应的树,所以在这里只要让fy指向fx,把fx的值加上fy的值即可

#4.例子

  就是这样,并查集的代码加起来20行不到,就可以实现我们需要的功能了,所以来看看这个例子:
p31

  这里我直接给代码了:

#include <iostream>
#include <vector>
using namespace std;

int find(vector<int>& circle, int x)
{
    if (circle[x] < 0) {
        return x;
    }
    else {
        return circle[x] = find(circle, circle[x]);
    }
}

int Union(vector<int>& circle, int rootX, int rootY)
{
    int fx{ find(circle, rootX) }, fy{ find(circle, rootY) };
    if (fx != fy) {
        circle[fx] += circle[fy];
        circle[fy] = fx; 
    }
    return fx;
}

int main()
{
    int N{ 0 }, M{ 0 };
    cin >> N >> M;
    vector<int> circle(N + 3, -1);
    for (int i = 0; i < M; i++) {
        int cnt{ 0 }, root{ 0 }, in{ 0 };
        cin >> cnt;
        for (int j = 0; j < cnt; j++) {
            cin >> in;
            if (j == 0) {
                root = in;
            }
            else if (in != root) {
                int rt{ find(circle, in) };
                if (root != rt) {
                    root = Union(circle, rt, root);
                }
            }
        }
    }
    int min_val{ 0 };
    for (auto& i : circle) {
        if (i < min_val) min_val = i;
    }
    cout << -min_val << endl;
    return 0;
}

  return circle[x] = find(circle, circle[x]);这里利用了一个压缩存储的特性,如果一个元素找到的不是整棵树的根,那就把它合并到根上去,这样可以让一棵树变得更 “扁”,从而提高并查集的查找效率。

(4).树的遍历

  我们约定,遍历树的时候总是从左到右,例如:
p32

  其中,橙色箭头是向前的过程,蓝色箭头是回溯的过程,我们一直向左找到最左,如果没有子结点了就开始回溯,就这样一直搜索,直到最终回到根结点上,这样的过程实际上是深度优先搜索(Depth-First Search) 的搜索模式,接下来我们就来看看怎么实现对于树的遍历

  你有没有觉得,这个问题用递归好像很好解决呢?

#1.前序遍历

  首先是前序遍历,又称先序遍历,即每次遇到一个结点,首先把结点本身的值打印出来,然后再打印子结点的值,所以我们可以很轻松地写出递归版本的前序遍历代码:

void PreOrderTraversal(const TreeNode* root)
{
    if (!root) {
        cout << root->val << " ";
        for (const auto child : root) {
            PreOrderTraversal(child);
        }
    }
}

  这就结束了?这就结束了,因为树的定义本身是递归的,所以每一个子结点都可以对应一颗子树,而子树的遍历流程跟双亲结点一模一样,所以我们可以用一个非常非常简单的递归函数完成前序遍历的流程

#2.后序遍历

  后序遍历就是在所有的子结点打印完再打印本身的值,所以体现到代码上就是:

void PreOrderTraversal(const TreeNode* root)
{
    if (!root) {
        for (const auto child : root) {
            PreOrderTraversal(child);
        }
        cout << root->val << " ";
    }
}

  这俩真是一个比一个简单,后序遍历只是打印顺序有差异,所以我们只需要把cout打印放到递归的后面,就实现了!这多简单啊,对吧?

#3.遍历的非递归实现

  那么有一些很讨厌的题目,要求你用非递归的方式去实现一遍前序和后序遍历,这当然是可以做的,这就需要我们用一个栈去手动模拟递归遍历树的流程

i.非递归前序遍历
void NoRecursionPreOrderTraversal(const TreeNode* root)
{
    stack<const TreeNode*> st;
    st.push(root);
    while (!st.empty()) {
        const TreeNode* ptr{ st.top() };
        st.pop();
        cout << ptr->val << " ";
        for (int j = ptr->child.size() - 1; j >= 0; j--) {
            st.push(ptr->child[j]); // 我们需要倒过来入栈,才能保证后面遍历顺序正确
        }
    }
}

  那么对于下面这棵树,我们就很容易得到它的正确遍历结果了:0 1 4 5 6 2 3 7 8 9,你可以自己用这段代码试试看
p33

ii.非递归后序遍历
void NoRecursionPostOrderTraversal(const TreeNode* root, int _Msize)
{
    stack<const TreeNode*> st;
    vector<int> mark(_Msize * 2, 0);
    st.push(root);
    mark[0] = 0;
    while (!st.empty())
    {
        const TreeNode* ptr{ st.top() };
        if (mark[st.size() - 1] == 0 && !ptr->child.empty())
        {
            mark[st.size() - 1] = 1;
            for (int j = ptr->child.size() - 1; j >= 0; j--)
            {
                if (!ptr->child.empty())
                {
                    st.push(ptr->child[j]);
                    mark[st.size() - 1] = 0;
                }
            }
        }
        if (st.top()->child.empty() || mark[st.size() - 1] == 1) {
            cout << st.top()->val << " ";
            st.pop();
        }
    }
}

  后序遍历则要复杂的多,参数部分我们还要传入树的结点个数,以便mark数组正常工作,这里我不做过多解释,这一部分可以选择性学习。

#4.层序遍历

  层序遍历就是如同下图的树,我们从根结点开始一层一层打印出来,比如这里的层序遍历结果应该就是0 1 2 3 4 5 6 7 8 9
p33

  所以怎么写呢,我们发现这个东西有点像广度优先搜索(Breath-First Search),所以我们需要采用队列来实现层序遍历:

void LevelOrderTraversal(const TreeNode* root)
{
    queue<const TreeNode*> qt;
    qt.push(root);
    while (!qt.empty()) {
        const TreeNode* ptr{ qt.front() };
        qt.pop();
        for (const auto c : ptr->child) {
            qt.push(c);
        }
        cout << ptr->val << " ";
    }
}

  非常简单,我们每次取出队首元素之后,把这个结点的所有子结点全都入队,然后把它打印出来即可。

(5).树的线性表示

#1.双亲表示法

  双亲表示法是比较简单的表示,比如我们有 n n n个结点,我们依次给出这 n n n个结点的双亲结点的编号(如果是根节点,双亲结点的编号为-1),然后我们需要还原出这棵树来,怎么做呢?

如果你比较厉害,实际上双亲表示法已经足够你做完树的各种操作了,在这里我们只是简单完成一下从双亲表示法到标准存储的转换,理论上讲,我们可以在 O ( n ) O(n) O(n)的时间复杂度内完成这个过程,想法很简单:我们首先新建这 n n n个结点并把它们存储起来,然后去遍历存储了双亲编号的数组,然后把它们添加到对应结点的child数组里面去即可,所以我们可以很快写出下面的代码:

TreeNode* buildTree(const vector<int>& parents)
{
    size_t n{ parents.size() };
    vector<TreeNode*> nodes;
    TreeNode* root{ nullptr };
    for (int i = 0; i < n; i++) {
        nodes.push_back(new TreeNode{ i, vector<TreeNode*>{} });
    }
    for (int i = 0; i < n; i++) {
        if (parents[i] != -1) {
            nodes[parents[i]]->child.push_back(nodes[i]);
        }
        else root = nodes[i];
    }
    return root;
}

  所以就是这样,我们把某个子结点直接链接到对应的双亲结点上去即可。

#2.层号表示法

  我们根据树的前序遍历顺序,依次给出结点的值以及其所在的层号,然后据此建立一棵树,比如(0,A) (1,B) (2,D) (2,E) (1,C)是一棵二叉树,还原的方法其实并不困难,如果层号比上一个结点层号大一个,则说明这个结点是上一个结点的子结点,如果比上一个结点小,则说明回到了已记录结点的某一层的另一棵子树上,所以我们可以写出以下代码:

struct TreeNode
{
    TreeNode* parent;
    char c;
    vector<TreeNode*> child;
};

struct LevelTreeNode
{
    int level;
    char c;
};

void buildTree(vector<TreeNode*>& nodes, const vector<LevelTreeNode*>& lnodes)
{
    int current_level{ 1 }; // 用current_level记录当前层号
    nodes[0]->c = lnodes[0]->c; // 前序遍历的第一个结点一定是根
    TreeNode* last_root{ nodes[0] }; // 记录上一层的根结点
    for (int i = 1; i < lnodes.size(); i++) {
        if (lnodes[i]->level != current_level) {  // 仅在不同层的时候需要特殊处理
            if (lnodes[i]->level > current_level) { // 如果层号变大,说明所有后续结点都是子结点
                last_root = *(last_root->child.begin() + last_root->child.size() - 1); // 找到上一个根结点的双亲结点最后的子结点(其实就是当前遍历到结点对应的根)
                current_level = lnodes[i]->level; // 层号变更
            }
            else {
                int dif{ current_level - lnodes[i]->level }; // 如果层号变小,则开始回溯
                while (dif != 0) { // 回溯到同一层
                    last_root = last_root->parent;
                    dif--;
                } 
                current_level = lnodes[i]->level;
            }
        }
        // 标准建树操作
        nodes[i]->parent = last_root;
        nodes[i]->c = lnodes[i]->c;
        last_root->child.push_back(nodes[i]);
    }
}

  nodes是对应所有结点的vector,这里只是用来保存各个结点的信息,而lnodes则是一个包含层号的结点vector,它内部还包含了前序遍历的顺序

  在这里,我们采取了一个辅助手段——保存结点的双亲结点,我们前面已经很轻松地解决了双亲结点建树的问题,所以只要我们能够根据层号和前序遍历顺序还原出双亲的对应关系,我们就可以很轻松地把这个问题解决了。

小结

  这一篇中我们比较详细地介绍了关于树的一些内容,不过树的内容还远不止这些,未来的两篇分别是二叉树树的应用,敬请期待。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

PHPExcel 导出Excel报错:PHPExcel_IOFactory::load()

背景 近期在做 excel文件数据导出时&#xff0c;遇到如下报错&#xff1a; iconv(): Detected an illegal character in input string场景&#xff1a;计划任务后台&#xff0c;分步导出 大数据 excel文件发现在加载文件时&#xff0c;会有报错 报错信息 如下&#xff1a; {&q…

锂电池污水如何处理

锂电池是目前应用广泛的重要电池类型&#xff0c;然而其生产过程和废弃处理中产生的污水对环境造成了不可忽视的影响。本文将探讨锂电池污水的处理方法&#xff0c;以期为环境保护和可持续发展作出贡献。 首先&#xff0c;了解锂电池污水的组成是解决问题的关键。锂电池污水通…

物理层之奈氏准则和香农定理

学习的最大理由是想摆脱平庸&#xff0c;早一天就多一份人生的精彩&#xff1b;迟一天就多一天平庸的困扰。各位小伙伴&#xff0c;如果您&#xff1a; 想系统/深入学习某技术知识点… 一个人摸索学习很难坚持&#xff0c;想组团高效学习… 想写博客但无从下手&#xff0c;急需…

DevEco Studio对同一套HarmonyOS代码进行多设备端预览

鸿蒙代码有一个很大的优势 不需要其他的语法 只需要一套HarmonyOS代码 就可以在 手机 平板 电脑上运行 我们可以在DevEco Studio预览器上 点击如下图指向位置 弹出的这个窗口中 我们将右上角的开关勾选上 这样 我们调试器向下滚动 就可以看到多端预览的一个效果了

Glare or Gloom, I Can Still See You – End-to-End Multi-Modal Object Detection

SENSOR-AWARE MULTI-MODAL FUSION G-log(-log(U))&#xff0c;U&#xff5e;Uniform[0,1] 辅助信息 作者未提供代码

编译器设计03-后端概述

后端处理概述 后端处理&#xff1a;中间代码生成&#xff0c;目标代码生成&#xff0c;贯穿各个阶段的优化。 后端处理犹如得出中文文章&#xff0c;当阅读完英语文章后&#xff0c;你的脑海中就有清晰的“中间代码”了&#xff0c;想写作的时候就心中有数&#xff0c;核心论…

基于PLC的物料分拣控制传送带控制系统设计

wx供重浩&#xff1a;创享日记 对话框发送&#xff1a;物料分拣 获取完整论文报告PLC梯形图工程源文件 传送带在先进制造领域中扮演着极其重要的角色。它可以搬运货物、分拣物品、代替人的繁重劳动。可以实现生产的机械化和自动化&#xff0c;能在有害环境下操作以保护人身安全…

OpenAi Q* (Q Star)项目入门介绍

为初学者解释 Open Ai 的 Q*(Q Star) Q* 的两个可能来源。 1)Q 可能是指 "Q-learning",这是一种用于强化学习的机器学习算法。 Q 名称的由来*:把 "Q*"想象成超级智能机器人的昵称。 Q 的意思是这个机器人非常善于做决定。 它从经验中学习,就像你从玩…

活动回顾|阿里云云原生 Serverless 技术实践营 深圳站回放PPT下载

11月24日“阿里云云原生 Serverless 技术实践营”深圳站圆满落幕。活动受众以关注 Serverless 技术的开发者、企业决策人、云原生领域创业者为主&#xff0c;活动形式为演讲、动手实操&#xff0c;让开发者通过一个下午的时间增进对 Serverless 技术的理解&#xff0c;快速上手…

Windows10 下 CUDA 新旧多版本共存

前言 为了运行一个 Tensorflow1.6.0 的项目&#xff0c;CPU 跑了三个多小时才完成一个 epoch&#xff0c;还得用 GPU。但这个版本的 TF 只能在 10 以下的 CUDA 平台&#xff0c;但是以前的 Pytorch 也要继续啊。所以需要在本地 Windows 下安装多个版本 CUDA&#xff0c;其实有两…

java springboot中使用 AOP监听方法执行周期

首先 我们在 pom.xml 中 dependencies标签中加入依赖 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId> </dependency>然后 我们随便创建一个类 编写代码如下 package com.ex…

JavaEE进阶学习:读取和存储对象

到了学习 JavaEE 这块要有一个思想,实现一个功能的时候,先考虑下有没有实现对应功能的注解. 在 Spring 中想要更简单的存储和读取对象的核心是使用注解&#xff0c;也就是我们接下来要学习 Spring 中的相关注解&#xff0c;来存储和读取 Bean 对象 1.存储 Bean 对象 之前我们…

从零开始学习管道:管道程序的优化和文件描述符继承问题

&#x1f4df;作者主页&#xff1a;慢热的陕西人 &#x1f334;专栏链接&#xff1a;Linux &#x1f4e3;欢迎各位大佬&#x1f44d;点赞&#x1f525;关注&#x1f693;收藏&#xff0c;&#x1f349;留言 本博客主要内容管道后续的完善&#xff0c;以及解决管道继承多个文件描…

element 的 Notification 通知,自定义内容

通知事件&#xff1a; // 商户后台通知 MerchantBackgroundNotice() {// 禁止消息通知弹出多条if(this.notifyInstance) {this.notifyInstance.close();}const h this.$createElement; // 创建文本节点this.notifyInstance this.$notify({showClose: false, // 禁止关闭按钮…

Vue路由器(详细教程)

路由&#xff1a; 1.理解&#xff1a;一个路由(route)就是一组映射关系&#xff08;key-value)&#xff0c;多个路由需要路由器&#xff08;router&#xff09;进行管理。 2.前端路由&#xff1a;key是路径&#xff0c;value是组件。 1、先安装vue-router路由 npm i vue-route…

美女骑士开箱VELO Angel TT,银色天使,无痛骑行

阳光、女孩、自行车&#xff0c;脸上的笑容或明媚&#xff0c;或神秘&#xff0c;或青涩&#xff0c;在这个时候&#xff0c;世界上没有什么比骑行女孩更美的了&#xff01;      在北京&#xff0c;有一个热爱骑行的女孩&#xff0c;名叫季思铭&#xff0c;目前是中国农业…

电子学会C/C++编程等级考试2021年12月(二级)真题解析

C/C++等级考试(1~8级)全部真题・点这里 第1题:统计指定范围里的数 给定一个数的序列S,以及一个区间[L, R], 求序列中介于该区间的数的个数,即序列中大于等于L且小于等于R的数的个数。 时间限制:1000 内存限制:65536输入 第一行1个整数n、,分别表示序列的长。(0 < n…

NX二次开发UF_CURVE_ask_parameterization 函数介绍

文章作者&#xff1a;里海 来源网站&#xff1a;https://blog.csdn.net/WangPaiFeiXingYuan UF_CURVE_ask_parameterization Defined in: uf_curve.h int UF_CURVE_ask_parameterization(tag_t object, double param_range [ 2 ] , int * periodicity ) overview 概述 Retu…

回归预测 | MATLAB实现SMA+WOA+BOA-LSSVM基于黏菌算法+鲸鱼算法+蝴蝶算法优化LSSVM回归预测

回归预测 | MATLAB实现SMAWOABOA-LSSVM基于黏菌算法鲸鱼算法蝴蝶算法优化LSSVM回归预测 目录 回归预测 | MATLAB实现SMAWOABOA-LSSVM基于黏菌算法鲸鱼算法蝴蝶算法优化LSSVM回归预测效果一览基本介绍程序设计参考资料 效果一览 基本介绍 MATLAB实现SMAWOABOA-LSSVM基于黏菌算法…

基于ssm的网上订餐系统

一、系统架构 前端&#xff1a;jsp | js | css | jquery 后端&#xff1a;spring | springmvc | mybatis 环境&#xff1a;jdk1.7 | mysql | maven | tomcat 二、代码与数据库 三、功能介绍 01. web端-首页 02. web端-我的餐车 03. web端-我的订单 04. web端-用户中心 05. web…