【笔记-模板】二叉搜索树-平衡树

news/2024/11/6 19:18:48/文章来源:https://www.cnblogs.com/ThySecret/p/18524697

二叉搜索树

www.luogu.com.cn

定义

二叉搜索树(\(\text{Binary Search Tree}\))是一种形状如二叉树的数据结构,用于快速查找增加删除操作,它有如下几个特殊性质:

  1. 空树是二叉搜索树。
  2. 若二叉搜索树的左子树不为空,则其左子树上所有点的附加权值均小于其根节点的值。
  3. 若二叉搜索树的右子树不为空,则其右子树上所有点的附加权值均大于其根节点的值。
  4. 二叉搜索树的左右子树均为二叉搜索树。

对于一个二叉搜索树,进行操作所花费的平均时间和这个二叉树的高度成正比。对于一个有 \(n\) 个结点的二叉搜索树中,这些操作的最优时间复杂度为 \(O(\log n)\),最坏为 \(O(n)\)

操作过程

定义

对于一个二叉搜索树的每一个节点,都要存储以下几样东西:

  1. 左子树节点和右子树节点:lr
  2. 这一个节点上存储的权值:val
  3. 表示这个节点权值出现次数的计数器:cnt
  4. 代表这个树上的大小(即子树和自己大小之和):size
struct BST
{int l, r;       // 左右子树节点int val;        // 这一个节点上存储的权值int cnt, size;  // 节点权值出现次数的计数器和这个树的大小
}tr[N];

与此同时,由于操作中需要不断插入新的数,因此还需要两个变量分别储存树的下标和当前的下标,我们用 rootidx 表示:

int root, idx;        // root表示这个树的根的下标,idx记录着当前的下标

创建新节点

创建一个新的节点就是将这个节点全部初始化,同时返回原来的下标值:

int new_node(int k)                    // 创建新节点
{tr[idx].val = k;                // 将权值修改tr[idx].size = tr[idx].cnt = 1; // 默认的子树的大小以及出现的次数肯定为1return idx;                     // 返回用过的下标(以后会用)
}

上传信息

像线段树一样,二叉搜索树同样也有需要维护的信息:每个子树的本身大小,有二叉树的性质可得出:

\[\text{tr[u].size = tr[tr[u].l].size + tr[tr[u].r].size + tr[u].cnt} \]

void pushup(int u)    // 上传信息
{tr[u].size = tr[tr[u].l].size + tr[tr[u].r].size + tr[u].cnt;
}

初始化

为了防止有时候询问的二叉搜索树为空,我们可以先在树中加入两个哨兵:\(\text{-INF}\)\(\text{INF}\),在不断的插入中,他们会始终在树的左右两端,从而有效防止查询越界。

void build()
{new_node(-INF), new_node(INF);  // 添加两个哨兵以防止越界REroot = 1, tr[1].r = 2;          // 让根节点为1,右子树节点为2pushup(root);                   // 上传更新节点大小
}

插入

根据二叉搜索树的性质可得,对于每个节点 \(u\) 以及要插入的权值 \(k\),可以分为四种情况:

  1. \(u = k\) 时,直接在该节点的计数变量上 cnt ++
  2. \(u < k\) 时,递归到该节点的右子树节点继续插入。
  3. \(u > k\) 时,递归到该节点的左子树节点继续插入。
  4. \(u = 0\) 时,则说明没有这个节点,直接利用 idx 创建一个。

\(\bold tips\):在递归完之后要 pushup 一遍,从而维护每个子树的大小。

void insert(int &u, int k)
{if (u == 0) u = new_node(k);            // 如果节点为0,则直接新建一个else{if (tr[u].val == k) tr[u].cnt ++;   // 如果相等,则在计数器上加一个else{if (tr[u].val > k) insert(tr[u].l, k);  // 向左递归else insert(tr[u].r, k);                // 向右递归}}pushup(u);                              // 上传信息
}

删除

原理和插入相似,不多赘述,一共五种情况:

  1. \(u = k\) 时,直接在该节点的计数变量上 cnt --
  2. \(u\) 节点为叶子节点时,直接 u = 0
  3. \(u < k\) 时,递归到该节点的右子树节点继续删除。
  4. \(u > k\) 时,递归到该节点的左子树节点继续删除。
  5. \(u = 0\) 时,则说明没有这个节点,直接 return 掉 。

\(\bold tips\):在递归完之后要 pushup 一遍,从而维护每个子树的大小。

void del(int &u, int k)
{if (u == 0) return ;                // 如果节点为0,直接returnif (tr[u].val == k)                 // 如果权值相等,分三类讨论{if (tr[u].cnt > 1) tr[u].cnt --;        // 计数器大于1时,直接剪掉else {if (tr[u].l || tr[u].r)             // 不是叶子节点时{if (!tr[u].r) del(tr[u].r, k);  // 如果有右子树,向右递归else del(tr[u].l, k);           // 否则向左递归}else u = 0;         // 是叶子节点直接改为0}}else if (tr[u].val > k) del(tr[u].l, k);    // 向左递归else del(tr[u].r, k);                       // 向右递归pushup(u);                      // 上传信息
}

找前驱

一个数 \(x\) 的前驱被定义为小于 \(x\) 的最大的数。

对于一个节点 \(u\) 的权值以及权值 \(k\),它的查询分为以下三种情况:

  1. \(u = 0\) 时,此时说明找到了最左端,应当 return -INF
  2. \(u \ge k\) 时,说明 $k $的前驱还可能在当前节点的左子树,向左递归。
  3. 其余的情况则说明 \(u \le k\),此时分为两种情况,一是前驱就是 \(u\),二是前驱在右子树当中,因此在两者中取 \(\max\)
int get_prev(int u, int k)            // 找前驱
{if (u == 0) return -INF;        // 如果节点不存在,则直接返回-INFif (tr[u].val >= k) return get_prev(tr[u].l, k);    // k小于当前权值,向左递归return max(get_prev(tr[u].r, k), tr[u].val);        // 否则在两者之中取最大值
}

找后驱

一个数 \(x\) 的后驱被定义为大于 \(x\) 的最小的数。

原理同上

int get_next(int u, int k)            // 找后驱
{if (u == 0) return INF;         // 如果节点不存在,则直接返回INFif (tr[u].val <= k) return get_next(tr[u].r, k);    // k大于当前权值,向右递归return min(get_next(tr[u].l, k), tr[u].val);        // 否则在两者之中取最小值
}

通过排名找权值

排名定义为比当前数小的数的个数 \(+1\)。若有多个相同的数,应输出最小的排名。

对于一个节点 \(u\) 以及 \(rank\) 值,可以分为以下四种情况:

  1. \(\text{tr[u].val} = 0\) 时,说明这个权值无限大,返回 \(\text{INF}\)
  2. \(\text{tr[tr[u].l].size } \ge \text{rank}\),说明权值在左子树里面,向左递归。
  3. \(\text{tr[tr[u].l].size + tr[u].cnt} \ge \text{rank}\),由于上面的限制条件,说明该节点权值即为答案。
  4. 否则向右进行递归,并且注意右子树中的 $rank $ 值是相对的。
int get_val_by_rank(int u, int rank)
{if (u == 0) return INF;     // 如果不存在这个节点,说明超出了最大值,返回INFif (tr[tr[u].l].size >= rank) return get_val_by_rank(tr[u].l, rank);    // 如果左子树的大小大于rank值,则说明权值在左子树里面,向左递归if (tr[tr[u].l].size + tr[u].cnt >= rank) return tr[u].val;             // 如果左子树的大小加上当前节点数大于rank值,收到前面条件的限制,说明val值为答案return get_val_by_rank(tr[u].r, rank - tr[tr[u].l].size - tr[u].cnt);   // 否则向右递归,并且向右递归时的rank值是相对于右子树的
}

通过权值找排名

原理和上面反过来差不多

int get_rank_by_val(int u, int k)
{if (u == 0) return 0;       // 如果不存在这个节点,则直接返回0if (tr[u].val == k) return tr[tr[u].l].size + 1;        // 如果节点权值等于k,返回这个排名的大小+1if (tr[u].val > k) return get_rank_by_val(tr[u].l, k);  // 如果节点权值大于k,则向左侧递归查找return tr[tr[u].l].size + tr[u].cnt + get_rank_by_val(tr[u].r, k);  // 否则应该向右递归,并且加上左子树和节点的大小之和
}

模板代码

// Problem: P5076 【深基16.例7】普通二叉树(简化版)
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P5076
// Memory Limit: 256 MB
// Time Limit: 1000 ms
// 
// Powered by CP Editor (https://cpeditor.org)#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
// #define int long longconst int N = 1e5 + 9;
const int INF = 2147483647;struct BST
{int l, r;       // 左右子树节点int val;        // 这一个节点上存储的权值int cnt, size;  // 节点权值出现次数的计数器和这个树的大小
}tr[N];
int root, idx;        // root表示这个树的根的下标,idx记录着当前的下标void pushup(int u)    // 上传信息
{tr[u].size = tr[tr[u].l].size + tr[tr[u].r].size + tr[u].cnt;
}int new_node(int k)                    // 创建新节点
{tr[++ idx].val = k;             // 将权值修改tr[idx].size = tr[idx].cnt = 1; // 默认的子树的大小以及出现的次数肯定为1return idx;                     // 返回用过的下标(以后会用)
}void build()
{new_node(-INF), new_node(INF);  // 添加两个哨兵以防止越界REroot = 1, tr[1].r = 2;          // 让根节点为1,右子树节点为2pushup(root);                   // 上传更新节点大小
}void insert(int &u, int k)
{if (u == 0) u = new_node(k);            // 如果节点为0,则直接新建一个else{if (tr[u].val == k) tr[u].cnt ++;   // 如果相等,则在计数器上加一个else{if (tr[u].val > k) insert(tr[u].l, k);  // 向左递归else insert(tr[u].r, k);                // 向右递归}}pushup(u);                              // 上传信息
}void del(int &u, int k)
{if (u == 0) return ;                // 如果节点为0,直接returnif (tr[u].val == k)                 // 如果权值相等,分三类讨论{if (tr[u].cnt > 1) tr[u].cnt --;        // 计数器大于1时,直接剪掉else {if (tr[u].l || tr[u].r)             // 不是叶子节点时{if (!tr[u].r) del(tr[u].r, k);  // 如果有右子树,向右递归else del(tr[u].l, k);           // 否则向左递归}else u = 0;         // 是叶子节点直接改为0}}else if (tr[u].val > k) del(tr[u].l, k);    // 向左递归else del(tr[u].r, k);                       // 向右递归pushup(u);                      // 上传信息
}int get_rank_by_val(int u, int k)
{if (u == 0) return 0;       // 如果不存在这个节点,则直接返回0if (tr[u].val == k) return tr[tr[u].l].size + 1;        // 如果节点权值等于k,返回这个排名的大小+1if (tr[u].val > k) return get_rank_by_val(tr[u].l, k);  // 如果节点权值大于k,则向左侧递归查找return tr[tr[u].l].size + tr[u].cnt + get_rank_by_val(tr[u].r, k);  // 否则应该向右递归,并且加上左子树和节点的大小之和
}int get_val_by_rank(int u, int rank)
{if (u == 0) return INF;     // 如果不存在这个节点,说明超出了最大值,返回INFif (tr[tr[u].l].size >= rank) return get_val_by_rank(tr[u].l, rank);    // 如果左子树的大小大于rank值,则说明权值在左子树里面,向左递归if (tr[tr[u].l].size + tr[u].cnt >= rank) return tr[u].val;             // 如果左子树的大小加上当前节点数大于rank值,收到前面条件的限制,说明val值为答案return get_val_by_rank(tr[u].r, rank - tr[tr[u].l].size - tr[u].cnt);   // 否则向右递归,并且向右递归时的rank值是相对于右子树的
}int get_prev(int u, int k)            // 找前驱
{if (u == 0) return -INF;        // 如果节点不存在,则直接返回-INFif (tr[u].val >= k) return get_prev(tr[u].l, k);    // k小于当前权值,向左递归return max(get_prev(tr[u].r, k), tr[u].val);        // 否则在两者之中取最大值
}int get_next(int u, int k)            // 找后驱
{if (u == 0) return INF;         // 如果节点不存在,则直接返回INFif (tr[u].val <= k) return get_next(tr[u].r, k);    // k大于当前权值,向右递归return min(get_next(tr[u].l, k), tr[u].val);        // 否则在两者之中取最小值
}signed main()
{// ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);build();int n; cin >> n;while (n --){int op, k; cin >> op >> k;if (op == 5) insert(root, k);else if (op == 1){insert(root, k);cout << get_rank_by_val(root, k) - 1<< '\n';del(root, k);}else if (op == 2) cout << get_val_by_rank(root, k + 1) << '\n';else if (op == 3) cout << get_prev(root, k) << '\n';else cout << get_next(root, k) << '\n';}return 0;
}

平衡树

www.luogu.com.cn

www.luogu.com.cn

定义

平衡树简介

使用搜索树的目的之一是 缩短插入、删除、修改和查找(插入、删除、修改都包括查找操作)节点的时间。 关于查找效率,如果一棵树的高度为 \(h\),在最坏的情况,查找一个关键字需要对比 \(h\) 次,查找时间复杂度(也为平均查找长度 \(\text{ASL,Average Search Length}\))不超过 \(O(h)\)。一棵理想的二叉搜索树所有操作的时间可以缩短到 \(O(\log n)\)\(n\) 是节点总数)。 然而 \(O(h)\) 的时间复杂度仅为理想情况。在最坏情况下,搜索树有可能退化为链表。想象一棵每个结点只有右孩子的二叉搜索树,那么它的性质就和链表一样,所有操作(增删改查)的时间是 \(O(n)\)。 可以发现操作的复杂度与树的高度 \(h\) 有关。由此引出了平衡树,通过一定操作维持树的高度(平衡性)来降低操作的复杂度。

一个平衡树有如下几个定义:

  1. 它是一个二叉搜索树
  2. 对于任意一个节点的子树,每一个节点的左子树和右子树的高度差最多为 \(1\)

旋转Treap

简介

\(\text{Treap}\)(树堆)是一种 弱平衡二叉搜索树。它同时符合二叉搜索树和堆的性质,名字也因此为 \(\text{tree}\)(树)和 \(\text{heap}\)(堆)的组合。

其中对于堆的性质是:

  • 子节点值(\(\textit{priority}\))比父节点大或小(取决于是小根堆还是大根堆)。

https://oi-wiki.org/ds/images/treap-treap-example.svg

操作过程

旋转

旋转 \(\text{Treap}\)通过 左旋右旋 的方式维护平衡树的平衡,通过调整堆的优先级从而达到平衡操作。

旋转操作的含义:

  • 在不影响搜索树性质的前提下,把和旋转方向相反的子树变成根节点(如左旋,就是把右子树变成根节点)
  • 不影响性质,并且在旋转过后,跟旋转方向相同的子节点变成了原来的根节点(如左旋,旋转完之后的左子节点是旋转前的根节点)

https://oi-wiki.org/ds/images/treap-rotate.svg

代码思路需要慢慢悟出来

void zig(int &u)                            // 右旋
{int v = tr[u].l;                        // v是节点u的左子节点tr[u].l = tr[v].r, tr[v].r = u, u = v;  // 依次更新节点pushup(tr[u].r), pushup(u);             // 上传标记
}void zag(int &u)                            // 左旋
{int v = tr[u].r;                        // v是节点u的右子节点tr[u].r = tr[v].l, tr[v].l = u, u = v;  // 依次更新节点pushup(tr[u].l), pushup(u);             // 上传标记
}

插入

和二叉搜索树差不多,只不过要在插入的过程中维护好堆的性质。

void insert(int &u, int k)
{if (u == 0) u = new_node(k);            // 如果节点为0,则直接新建一个else{if (tr[u].val == k) tr[u].cnt ++;   // 如果相等,则在计数器上加一个else{if (tr[u].val > k){insert(tr[u].l, k);     // 向左递归if (tr[tr[u].l].key > tr[u].key) zig(u); // 如果不满足堆的性质,右旋}else{insert(tr[u].r, k);     // 向右递归if (tr[tr[u].r].key < tr[u].key) zag(u); // 如果不满足堆的性质,左旋}}}pushup(u);                              // 上传信息
}

删除

同样维护好堆的性质

void del(int &u, int k)
{if (u == 0) return ;                // 如果节点为0,直接returnif (tr[u].val == k)                 // 如果权值相等,分三类讨论{if (tr[u].cnt > 1) tr[u].cnt --;        // 计数器大于1时,直接剪掉else {if (tr[u].l || tr[u].r)             // 不是叶子节点时{if (!tr[u].r || tr[tr[u].l].key > tr[tr[u].r].key) // 有右子树或者左子树的key大于右子树的key{zig(u);             // 右旋del(tr[u].r, k);    // 向右递归}else {zag(u);             // 左旋del(tr[u].l, k);    // 否则向左递归}}else u = 0;         // 是叶子节点直接改为0}}else if (tr[u].val > k) del(tr[u].l, k);    // 向左递归else del(tr[u].r, k);                       // 向右递归pushup(u);                      // 上传信息
}

模板代码

// Problem: P3369 【模板】普通平衡树
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P3369
// Memory Limit: 128 MB
// Time Limit: 1000 ms
// 
// Powered by CP Editor (https://cpeditor.org)#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
// #define int long longconst int N = 1e5 + 9;
const int INF = 0x3f3f3f3f;struct BST
{int l, r;       // 左右子树节点int val, key;   // val: 这一个节点上存储的权值  key: 随机数以满足堆的性质int cnt, size;  // 节点权值出现次数的计数器和这个树的大小
}tr[N];
int root, idx;        // root表示这个树的根的下标,idx记录着当前的下标void pushup(int u)    // 上传信息
{tr[u].size = tr[tr[u].l].size + tr[tr[u].r].size + tr[u].cnt;
}int new_node(int k)                    // 创建新节点
{tr[++ idx].val = k;             // 将权值修改tr[idx].key = rand();           // 给key赋予一个随机数tr[idx].size = tr[idx].cnt = 1; // 默认的子树的大小以及出现的次数肯定为1return idx;                     // 返回用过的下标(以后会用)
}void build()
{new_node(-INF), new_node(INF);  // 添加两个哨兵以防止越界REroot = 1, tr[1].r = 2;          // 让根节点为1,右子树节点为2pushup(root);                   // 上传更新节点大小
}void zig(int &u)                            // 右旋
{int v = tr[u].l;                        // v是节点u的左子节点tr[u].l = tr[v].r, tr[v].r = u, u = v;  // 依次更新节点pushup(tr[u].r), pushup(u);             // 上传标记
}void zag(int &u)                            // 左旋
{int v = tr[u].r;                        // v是节点u的右子节点tr[u].r = tr[v].l, tr[v].l = u, u = v;  // 依次更新节点pushup(tr[u].l), pushup(u);             // 上传标记
}void insert(int &u, int k)
{if (u == 0) u = new_node(k);            // 如果节点为0,则直接新建一个else{if (tr[u].val == k) tr[u].cnt ++;   // 如果相等,则在计数器上加一个else{if (tr[u].val > k){insert(tr[u].l, k);     // 向左递归if (tr[tr[u].l].key > tr[u].key) zig(u); // 如果不满足堆的性质,右旋}else{insert(tr[u].r, k);     // 向右递归if (tr[tr[u].r].key < tr[u].key) zag(u); // 如果不满足堆的性质,左旋}}}pushup(u);                              // 上传信息
}void del(int &u, int k)
{if (u == 0) return ;                // 如果节点为0,直接returnif (tr[u].val == k)                 // 如果权值相等,分三类讨论{if (tr[u].cnt > 1) tr[u].cnt --;        // 计数器大于1时,直接剪掉else {if (tr[u].l || tr[u].r)             // 不是叶子节点时{if (!tr[u].r || tr[tr[u].l].key > tr[tr[u].r].key) // 有右子树或者左子树的key大于右子树的key{zig(u);             // 右旋del(tr[u].r, k);    // 向右递归}else {zag(u);             // 左旋del(tr[u].l, k);    // 否则向左递归}}else u = 0;         // 是叶子节点直接改为0}}else if (tr[u].val > k) del(tr[u].l, k);    // 向左递归else del(tr[u].r, k);                       // 向右递归pushup(u);                      // 上传信息
}int get_rank_by_val(int u, int k)
{if (u == 0) return 0;       // 如果不存在这个节点,则直接返回0if (tr[u].val == k) return tr[tr[u].l].size + 1;        // 如果节点权值等于k,返回这个排名的大小+1if (tr[u].val > k) return get_rank_by_val(tr[u].l, k);  // 如果节点权值大于k,则向左侧递归查找return tr[tr[u].l].size + tr[u].cnt + get_rank_by_val(tr[u].r, k);  // 否则应该向右递归,并且加上左子树和节点的大小之和
}int get_val_by_rank(int u, int rank)
{if (u == 0) return INF;     // 如果不存在这个节点,说明超出了最大值,返回INFif (tr[tr[u].l].size >= rank) return get_val_by_rank(tr[u].l, rank);    // 如果左子树的大小大于rank值,则说明权值在左子树里面,向左递归if (tr[tr[u].l].size + tr[u].cnt >= rank) return tr[u].val;             // 如果左子树的大小加上当前节点数大于rank值,收到前面条件的限制,说明val值为答案return get_val_by_rank(tr[u].r, rank - tr[tr[u].l].size - tr[u].cnt);   // 否则向右递归,并且向右递归时的rank值是相对于右子树的
}int get_prev(int u, int k)            // 找前驱
{if (u == 0) return -INF;        // 如果节点不存在,则直接返回-INFif (tr[u].val >= k) return get_prev(tr[u].l, k);    // k小于当前权值,向左递归return max(get_prev(tr[u].r, k), tr[u].val);        // 否则在两者之中取最大值
}int get_next(int u, int k)            // 找后驱
{if (u == 0) return INF;         // 如果节点不存在,则直接返回INFif (tr[u].val <= k) return get_next(tr[u].r, k);    // k大于当前权值,向右递归return min(get_next(tr[u].l, k), tr[u].val);        // 否则在两者之中取最小值
}signed main()
{// ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);build();int n; cin >> n;while (n --){int op, k; cin >> op >> k;if (op == 1) insert(root, k);else if (op == 2) del(root, k);else if (op == 3){insert(root, k);cout << get_rank_by_val(root, k) - 1<< '\n';del(root, k);}else if (op == 4) cout << get_val_by_rank(root, k + 1) << '\n';else if (op == 5) cout << get_prev(root, k) << '\n';else cout << get_next(root, k) << '\n';}return 0;
}

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

10.20 每日总结(今天继续软考)

今天学习软考。学习时长两小时。 主要学习了下午题,下午的第二题基本上会出,E-R图和关系模式: 然后根据ER图生成关系

node-red使用echart

node-red使用echart 官网下载echart.js Apache ECharts 下载页面 查找Node-red的settings.js文件 方法一 登陆服务器,启动node-red。 $ node-red 3 Nov 20:22:38 - [info] Welcome to Node-RED ===================3 Nov 20:22:38 - [info] Node-RED version: v4.0.3 3 Nov 20…

什么是无线漫游?无线漫游的工作原理是什么?

什么是无线漫游无线漫游是指在相同SSID(Service Set Identifier,服务集标识)的无线网络中,确保无线终端STA(如手机、电脑等终端设备)在移动过程中能够保持不间断网络连接的技术。因为在无线局域网中,每个AP(Access Point,无线访问接入点)设备的信号覆盖范围都有限,无线…

【CoCollider】让系统和应用适配如此简单

在各平台应用开发过程中,随着业务的功能增加,不免会涉及到非公开的 API 依赖,针对某些应用或厂商系统的适配,每个版本都需要投入精力去排查,CoCollider 可以让我们的适配效率从几个星期提升到几小时即可完成。在各平台应用开发过程中,随着业务的功能增加,不免会涉及到非…

深入学习软件组件认证的三个关键

人工智能软件,特别是深学习组件,是目前实现自主汽车等自主系统的最先进和经济上可行的解决方案。然而,DL算法的性质及其当前的实现与汽车、卫星和火车等安全关键系统中严格的软件开发过程不一致。 传统的安全相关软件采用自上而下的方法,对组件进行分解,并相应地传播安全要求,直…

IDEA中通义灵码的使用技巧

大家好,我是 V 哥。在日常写代码的过程中,通过 AI 工具辅助开发已是当下程序员惯用的方式,V 哥在使用了众多的 AI 工具后,多数情况下,选择通义灵码来辅助开发,尤其是解释代码和生成单元测试功能甚是好用,听 V 哥来详细聊聊。 通义灵码安装 打开你的 IDEA 开发工具,在菜…

10.14 每日总结

今天继续学习软考。 代码时长1小时,学习时长两小时。 软考内容学习数据结构:时间复杂度: 递归的次数 X 每次递归的时间复杂度

【TEC047】基于Xilinx XCZU47DR RFSOC的射频收发核心处理模块

​TEC047是一款基于Xilinx的Zynq UltraSacle+ RFSoC Gen3系列FPGA XCZU47DR的射频收发核心模块,该模块将XCZU47DR芯片及其最小系统集成在了一个80*115mm的核心板上,可以作为一个紧凑的核心,进行功能的扩展,能够快速的搭建起一个多通道射频收发的信号处理平台,缩短用户的产…

C# 实现拖拽功能

很多时候,为了方便用户操作,需要增加拖拽功能。实现起来其实很简单,按照下面的步骤,你也能成功!快去试试吧。 一、 属性设置为True 二、共需要实现两个方法。 代码如下:private void FrmReplaceTools_DragDrop(object sender, DragEventArgs e){string[] s = (string[])…

设计登陆验证码的逻辑

今日面试某信息的场景题提到了下这个问题,没太搞懂面试官具体想让我说的就是两个接口的设计,一个生成验证码,一个校验验证码,和其具体的设计方案,没听懂当时的意思,有的小懵逼,因此回头总结回顾一下,日常开发中对这些小细节还是要注重一下,不能因为场景逻辑简单就不去…

使用最新版的wvp和ZLMediaKit搭建Gb28181测试服务器

目录说明安装1.安装nodejs简介安装步骤2.安装java环境3.安装mysql安装修改密码4.安装redis5.安装编译器6.安装cmake7.安装依赖库8.编译ZLMediaKit9.编译wvp-GB28181-pro配置1.ZLMediaKit配置2.wvp-GB28181-pro配置2.1.配置ZLMediaKit连接信息2.2.28181服务器的配置2.3.配置web服…

在昇腾Ascend 910B上运行Qwen2.5推理

使用 GPUStack 在昇腾 Ascend 910B 上运行 Qwen2.5 全系列的推理性能表现目前在国产 AI 芯片,例如昇腾 NPU 上运行大模型是一项广泛且迫切的需求,然而当前的生态还远未成熟。从底层芯片的算力性能、计算架构的算子优化,到上层推理框架对各种模型的支持及推理加速,仍有很多需…