c++ 凸包 分治算法_三维凸包

缘起

众所周知,二维凸包可以使用 Graham 扫描 内解决.

所以本文来学习一下三维空间中凸包的一种直观算法——增量算法(increment algorithm)

分析

有一条叫 Willy 的苹果虫一直快乐的居住在一个苹果中,直到有一天有一只仓鼠想吃这个苹果,Willy 自然不想被仓鼠
吃掉,所以 Willy 要开始逃亡!
为了简化问题,我们将苹果视作一个3维的凸多面体,我们将给出这个凸多面体的所有顶点的数据,以及一系列的 Willy 
可能位于的位置坐标. 请你设计一个程序计算 Willy 最少需要爬行的距离才能爬到苹果的表面. 

【输入】
多样例. 每个测试样例开始于一个 n, 4≤n≤1,000, 表示该凸多面体的顶点个数. 然后 n 行,每行三个整数
x y z , |x|,|y|,|z| 皆 <= 10000, 整个苹果是这 n 个点的三维凸包. 这里保证输入不会出现四点共面的情况.
然后一个 q, 1≤q≤100,000, 表示q次询问,然后 q 行,每行三个整数 x y z ,  |x|, |y|, |z| <= 10000
输入保证 Willy 一定在苹果内部. 输入以 n = 0 为结束,并且你不需要处理 n = 0 这组数据

【输出】
对每次询问,输出答案. 精确到四位小数. 

【样例输入】
6
0 0 0
100 0 0
0 100 0
0 0 100
20 20 20
30 20 10
4
1 1 1
30 30 35
7 8 9
90 2 2
0

【样例输出】
1.0000
2.8868
7.0000
2.0000

【限制】
20s 64MB

因为保证了没有四点共面,所以该凸多面体的每个面都恰好是一个三角形.  本题的思路是显然的——首先计算出三维凸包,然后计算虫子到凸包的各个三角面的距离,然后这些距离取最小就是答案. 计算点到面的距离是很简单的. 只需要使用平行六面体的体积除以平行四边形底面的面积即可. 前者通过混合积即可得到. 后者通过叉积即可得到.  所以本题的关键是计算三维凸包.

首先,我们知道计算二维凸包的算法是 Graham 扫描. 它其实本质上讲是一种增量算法.  什么是增量算法?  这涉及到一些算法的基本思想.  这里清晰的阐述一下.

首先,我们知道 分治 就是将大问题不断的分解为(规模不等的)小的问题. 然后自底向上(bottom up)将小问题的答案合并为大问题的答案. 这种思想运用在 dp 和 递归中. 而增量法本质上是第一数学归纳法. 即假设问题规模为 n - 1 的时候已经计算得到了答案,现在要解决问题规模为 n.  增量算法是自顶向下的(top-down). 打个不恰当的比方,增量算法有点像贪心.

分治的复杂度公式为

而增量的复杂度公式为

根据上面的学习,我们就不难理解为什么说 Graham 扫描是一种增量算法了.

那么放到三维,怎么使用增量算法求三维凸包呢?  其实和 Graham 扫描是一样的. 就是伊始选定四个不共面的点组成初始的四面体,这是待求解的凸包的初始状态. 然后不断的,一个一个的往点集中加入点,与此过程中不断的修改(或者说维护)凸包 (下面简记 CH Convex Hull) 的样子. 直至成功加入最后的点,则凸包就构建完毕了.  那么加入点的过程中,自然会遇到两种情况

  1. 加入的点在 CH 内部,那么这种点显然是不需要考虑的. 直接continue考察下一个点就行了.
  2. 加入的点不在 CH 内部,那么这种点是要考虑的.  还是画图举例比较好. 假定当前 CH 就是一个四面体 ABCD. 然后我们考虑加入一个点 P,下面考虑这种加入导致  CH 的变化.
fddd89fabf3dd58a6656664c614614ec.png

那么,显然,我们要做的,就是删除 BCD 这个三角面,然后新增 PCD、PDB、PBC 三个三角面, 就像下图这样

4b6001b0acf5901f06d8f6002126e906.png

即凸包从原先的四面体变成了现在的六面体.

那么我们想想看,为什么要删除 BCD, 而不需要删除 ABC、ADC、ADB 三个面呢?  原因很简单,因为如果你想象一下 P 处放一个单点光源,那么BCD 面将被照耀(白天),但是 ABC、ADC、ADB 三个面将处于一片漆黑,因为不能被照到.

所以我们必须想办法刻画能被照耀这件事情.

自然的,聪明如你想到了外法向量这个有力工具. 强调一遍,这是自然的,因为外法向量本身就是曲面定向的工具.

2861c3f968a0083c4c095a56a8654e5f.png

如上图所示,n1是三角面BCD 的外法向量、n2 是三角面ADC的外法向量、n3是三角面ADB的外法向量、n4是三角面ABC的外法向量. P的加入之所以引发了 BCD 面被删除,而 ADB、ADC、ABC 三个三角面不需要被删除的根本原因在于 P 在 BCD 的侧刚好是 n1 指向的方向.  用数学的话来讲就是

而 P 在另外三个面的侧和其相应的外法向量是反向的. 注意,CH 的所有面中,只要有一个面出现了 BCD 这种情况,我们立马就可以肯定 P 一定在 CH 的外部.

于是,我们就知道了,维护一个三角面的外法向是极为重要的.

所以,你可以开一个四维数组,来保存每个三角面的外法向量. 但是这种存储并不够优美和内蕴(implicit). 更为内蕴的做法是——用混合积.   (1)式的充要条件其实是

因此,我们只需要记录每个三角面的三个顶点的顺序即可. 例如对于三角面 BCD, 它的顺序是 D --> B --> C, 也就是,我们如果用一个数据结构 Surface 来刻画三角面的话,就长下面的样子

class Surface 
{
    int a, b, c, flag; 
    Surface(int a, int b, int c, int flag = 0):a(a), b(b), c(c){}
}

则 a 就代表 D 点,b 就代表 B 点, c 就代表 C 点(因为这样的话, 就是三角面 BCD 的外法向量),flag = 0 或者1,因为 CH 在整个凸包构建过程中不断地变化,所以有一些面会曾经被加入,后来又被删除(例如 BCD 面),所以 flag = 1 表示该面用于构成当前 CH, flag = 0 表示该面不用于构成当前 CH. 然后 P 在该面的外部(也就是(1))的充要条件就化为如下混合积大于0

于是,我们就很清楚 CH 的整个构建过程了,就是每次加入 P 之后,遍历检查每个当前参与构建 CH 的面 S(即flag = 0 的Surface),然后检查 (2) 是否成立,如果成立的话,就表明S需要从 CH 中移除(即置 S.flag = 0). 但是,要注意,P的引入可能不止引起一个面被移除,所以我们还需要去考虑 P 会不会引起和 S 相邻的三角面的移除.  还是画图说明方便:

26a6abe5b5765a479f2e1cc8dd36e589.png

上图中 P 的引入已经导致了 abc 三角面的移除,但是 P 的引入可能不仅仅导致 abc 面的移除,还可能导致 abd、bce、acf 这三个和 abc 接壤的三角面的移除. 所以我们还需要考察 p 和 这三个三角面之间是否满足 (2) ,如果满足,就要移除,并且再去考察接壤的三角面是否需要被移除. 例如 上图中,如果我们发现 abd 也要被移除的话,我们就会再去考察和 abd 接壤的诸如 bde 这个三角面是否需要被移除. 如果发现不需要移除了(即(2)不被满足),则我们就要新建一张三角面,这张三角面将参与到 CH 的构建中(即 CH 的样子发生了变化). 就好比图1的例子,我们发现  ABC、ADC、ADB 三张三角面不需要被移除,所以就新增了 PDC、PDB、PCB 三张三角面.

所以我们发现了,本质上,移除+新增的过程可以使用一个 dfs 来进行. 递归的出口发生三角面的新增.

于是,就可以来切代码了.

//#define LOCAL
#pragma GCC optimize(2)
#pragma G++ optimize(2)
#pragma warning(disable:4996)
#pragma comment(linker, "/STACK:1024000000,1024000000")
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
//#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
using namespace std;
//#define int unsigned long long
//#define int long long
#define re register int
#define ci const int
#define ui unsigned int 
typedef pair<int, int> P;
#define FE(cur) for(re h = head[cur], to; ~h; h = g[h].nxt)
#define ilv inline void
#define ili inline int
#define ilc inline char
#define ild inline double
#define ilp inline P
#define LEN(cur) (hjt[cur].r - hjt[cur].l)
#define MID(cur) (hjt[cur].l + hjt[cur].r >> 1)
#define SQUARE(x) ((x) * (x))
typedef vector<int>::iterator vit;
typedef set<int>::iterator sit;
typedef map<int, int>::iterator mit;
const int inf = ~0u >> 1;
//const int inf = 0x3f3f3f3f;
const double PI = acos(-1.0), eps = 1e-8;
namespace fastio
{
    const int BUF = 1 <21;
    char fr[BUF], fw[BUF], * pr1 = fr, * pr2 = fr; int pw;
    ilc gc() { return pr1 == pr2 && (pr2 = (pr1 = fr) + fread(fr, 1, BUF, stdin), *pr2 = 0, pr1 == pr2) ? EOF : *pr1++; }
    ilv flush() { fwrite(fw, 1, pw, stdout); pw = 0; }
    ilv pc(char c) { if (pw >= BUF) flush(); fw[pw++] = c; }
    ili read(int& x){
        x = 0; int f = 1; char c = gc(); if (!~c) return EOF;
        while (!isdigit(c)) { if (c == '-') f = -1; c = gc(); }
        while (isdigit(c)) x = (x <3) + (x <1) + (c ^ 48), c = gc();
        x *= f; return 1;
    }
    ili read(double& x){
        int xx = 0; double f = 1.0, fraction = 1.0; char c = gc(); if (!~c) return EOF;
        while (!isdigit(c)) { if (c == '-') f = -1.0; c = gc(); }
        while (isdigit(c)) { xx = (xx <3) + (xx <1) + (c ^ 48), c = gc(); }
        x = xx;
        if (c ^ '.') { x = f * xx; return 1; }
        c = gc();
        while (isdigit(c)) x += (c ^ 48) * (fraction /= 10), c = gc();
        x *= f; return 1;
    }
    ilv write(int x) { if (x 0) pc('-'), x = -x; if (x > 9) write(x / 10); pc(x % 10 + 48); }
    ilv writeln(int x) { write(x); pc(10); }
    ili read(char* x){
        char c = gc(); if (!~c) return EOF;
        while (!isalpha(c) && !isdigit(c)) c = gc();
        while (isalpha(c) || isdigit(c)) *x++ = c, c = gc();
        *x = 0; return 1;
    }
    ili readln(char* x){
        char c = gc(); if (!~c) return EOF;
        while (c == 10) c = gc();
        while (c >= 32 && c <= 126) *x++ = c, c = gc();
        *x = 0; return 1;
    }
    ilv write(char* x) { while (*x) pc(*x++); }
    ilv write(const char* x) { while (*x) pc(*x++); }
    ilv writeln(char* x) { write(x); pc(10); }
    ilv writeln(const char* x) { write(x); pc(10); }
    ilv write(char c) { pc(c); }
    ilv writeln(char c) { write(c); pc(10); }
} using namespace fastio;
#define cp const Point
#define cs const Surface
ci maxn = 1005;

ili dbcmp(double x){
    if (fabs(x)     {
        return 0;
    }
    return x > eps ? 1 : -1;
}

ilv mmin(double& a, double b){
    if (a > b)
    {
        a = b;
    }
}

struct CH3D // 三维凸包
{
    struct Point // 点
    {
        double x, y, z;
        Point(double x = 0, double y = 0, double z = 0) :x(x), y(y), z(z) {}

        Point operator + (cp& o) const
        {
            return Point(x + o.x, y + o.y, z + o.z);
        }

        Point operator - (cp& o) const
        {
            return Point(x - o.x, y - o.y, z - o.z);
        }

        double operator * (cp& o) const
        {
            return x * o.x + y * o.y + z * o.z;
        }

        Point operator / (cp& o) const  
        {
            return Point(y * o.z - z * o.y, z * o.x - x * o.z, x * o.y - y * o.x);
        }

        double magnitude() const{
            return sqrt(SQUARE(x) + SQUARE(y) + SQUARE(z));
        }

        Point scalar(double lambda) const {
            return Point(x * lambda, y * lambda, z * lambda);
        }

    } ps[maxn];
    int n;

    struct Surface
    {
        int a, b, c, flag;
        Surface(int a = 0, int b = 0, int c = 0) :a(a), b(b), c(c) { flag = 1; }
    } surfaces[maxn <3];
    int num; 

    int g[maxn][maxn]; 

    ild area(cp& a, cp& b, cp& c) {
        return ((b - a) / (c - a)).magnitude() / 2.0;
    }

    ild area(cs& sur) {
        return area(ps[sur.a], ps[sur.b], ps[sur.c]);
    }

    ild area() // 三维凸包的表面积{
        double ans = 0;
        if (n == 3)
        {
            return area(ps[0], ps[1], ps[2]);
        }
        for (re i = 0; i         {
            ans += area(ps[surfaces[i].a], ps[surfaces[i].b], ps[surfaces[i].c]);
        }
        return ans;
    }

    ild volume(cp& a, cp& b, cp& c, cp& d) {
        return ((b - a) / (c - a)) * (d - a) / 6.0;
    }

    ild volume(cp& p, cs& sur) {
        return volume(ps[sur.a], ps[sur.b], ps[sur.c], p);
    }

    ild volume() // 三维凸包的体积{
        double ans = 0;
        Point o;
        for (re i = 0; i         {
            ans += volume(o, ps[surfaces[i].a], ps[surfaces[i].b], ps[surfaces[i].c]);
        }
        return ans;
    }

    ili ck(cp& p, cs& sur) {
        return dbcmp(volume(ps[sur.a], ps[sur.b], ps[sur.c], p)) == 1;
    }

    ili onsurface(cp& p, cs& sur) {
        return !dbcmp(volume(ps[sur.a], ps[sur.b], ps[sur.c], p));
    }

    ili same(cs& s, cs& t) {
        return onsurface(ps[s.a], t) && onsurface(ps[s.b], t) && onsurface(ps[s.c], t);
    }

    Point gg() // 计算三维凸包的重心{
        Point ans, o = ps[0];
        double v = 0, t;
        for (re i = 0; i         {
            if (surfaces[i].flag)
            {
                cp& a = ps[surfaces[i].a];
                cp& b = ps[surfaces[i].b];
                cp& c = ps[surfaces[i].c];
                t = volume(o, surfaces[i]); 
                ans = ans + (a + b + c + o).scalar(t / 4); 
                v += t;
            }
        }
        return ans.scalar(1 / v);
    }

    ili countSurface() // 返回三维凸包有多少张表面{
        int ans = 0;
        for (re i = 0, ok; i         {
            ok = 1;
            for (re j = 0; j             {
                if (same(surfaces[i], surfaces[j])) 
                {
                    ok = 0;
                    break;
                }
            }
            ans += ok;
        }
        return ans;
    }

    ili prework(){
        if (n 4)
        {
            return 0;
        }
        int ok = 1;
        for (re i = 1; i         {
            if (dbcmp((ps[0] - ps[i]).magnitude()))
            {
                swap(ps[1], ps[i]);
                ok = 0;
                break;
            }
        }
        if (ok) 
        {
            return 0;
        }
        ok = 1; 
        for (re i = 2; i         {
            if (dbcmp(area(ps[0], ps[1], ps[i]))) 
            {
                swap(ps[2], ps[i]);
                ok = 0;
                break;
            }
        }
        if (ok)
        {
            return 0;
        }
        ok = 1;
        for (re i = 3; i         {
            if (dbcmp(volume(ps[0], ps[1], ps[2], ps[i])))
            {
                swap(ps[3], ps[i]);
                ok = 0;
                break;
            }
        }
        return !ok;
    }

    ilv construct() // 构造三维凸包{
        num = 0;
        if (!prework()) 
        {
            return; 
        }
        Surface add; 
        for (re i = 0; i 4; i++)
        {
            Surface add((i + 1) % 4, (i + 2) % 4, (i + 3) % 4);
            if (ck(ps[i], add)) 
            {
                swap(add.b, add.c);
            }
            g[add.a][add.b] = g[add.b][add.c] = g[add.c][add.a] = num; 
            surfaces[num++] = add; 
        }
        for (re i = 4; i         {
            for (re j = 0; j             {
                if (surfaces[j].flag && ck(ps[i], surfaces[j])) 
                {
                    dfs(i, j); 
                    break;
                }
            }
        }
        int tmp = num; num = 0;
        for (re i = 0; i         {
            if (surfaces[i].flag)
            {
                surfaces[num++] = surfaces[i];
            }
        }
    }

    void dfs(int p, int j) {
        surfaces[j].flag = 0;
        rmv(p, surfaces[j].b, surfaces[j].a);
        rmv(p, surfaces[j].c, surfaces[j].b);
        rmv(p, surfaces[j].a, surfaces[j].c);
    }

    void rmv(int p, int a, int b) {
        int f = g[a][b]; 
        if (surfaces[f].flag)
        {
            if (ck(ps[p], surfaces[f])) 
            {
                dfs(p, f); 
            }
            else 
            {
                Surface add(b, a, p);
                g[b][a] = g[a][p] = g[p][b] = num;
                surfaces[num++] = add;
            }
        }
    }

    ild dis(cp& p) // 计算p到三维凸包的每个三角面的距离中的最小值{
        double ans = inf;
        for (re i = 0; i         {
            mmin(ans, dis(p, surfaces[i]));
        }
        return ans;
    }

    ild dis(cp& p, cs& sur) {
        return fabs(volume(ps[sur.a], ps[sur.b], ps[sur.c], p)) / area(sur) * 3.0;
    }

}ch;

signed main(){
#ifdef LOCAL
    FILE* ALLIN = freopen("d:\\data.in", "r", stdin);
    //  freopen("d:\\my.out", "w", stdout);
#endif
    while (read(ch.n), ch.n)
    {
        for (re i = 0; i         ch.construct();
        int q; read(q);
        CH3D::Point tp;
        while (q--)
        {
            read(tp.x), read(tp.y), read(tp.z);
            printf("%.4lf\n", ch.dis(tp));
        }
    }
    flush();
#ifdef LOCAL
    fclose(ALLIN);
#endif
    return 0;
}

ac情况

Accepted 5678ms 7252kB C++

最后指出的是,代码中封装了三维凸包的体积、表面积、面的个数等等有用函数,都经过题目检验,都是正确无误的, 大可放心使用.

39ace77a7f82ee2f6d7d43d6a598c11a.gif

温馨提示

如果你喜欢本文,请分享到朋友圈,想要获得更多信息,请关注ACM算法日常

2c1b10421d142c868a05fd7606689e61.png

点赞的时候,请宠溺一点

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

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

相关文章

深度分析Java的ClassLoader机制(源码级别)

转载自 深度分析Java的ClassLoader机制&#xff08;源码级别&#xff09;Java中的所有类&#xff0c;必须被装载到jvm中才能运行&#xff0c;这个装载工作是由jvm中的类装载器完成的&#xff0c;类装载器所做的工作实质是把类文件从硬盘读取到内存中&#xff0c;JVM在加载类的时…

.NET跨平台之Sake和KoreBuild

最近在了解Asp.net Core 1.0&#xff08;也可称为Asp.net5\Asp.net vNext)的跨平台&#xff0c;发现了两个新的新东西&#xff1a;Sake和KoreBuild&#xff08;或者已经出了很久&#xff09;。 通过国内某度查询资料大部分都是复制黏贴来的&#xff0c;几乎没有详细的介绍。 只…

Ubuntu系统安装准备

前言 最初学习程序开发时&#xff0c;大多数都会教linux系统的基础用法&#xff0c;但是很少以linux为操作系统开发程序&#xff0c;因为windows系统的简单便利为人的提供优秀的操作基础&#xff0c;成为首选系统。 突然心血来找想使用linux操作系统作基础进行程序代码开发。 …

(转)Kafka 消费者 Java 实现

转自&#xff1a; Kafka 消费者 Java 实现 - 简书应用程序使用 KafkaConsumer向 Kafka 订阅 Topic 接收消息&#xff0c;首先理解 Kafka 中消费者&#xff08;consumer&#xff09;和消费者组&#xff08;consumer group...https://www.jianshu.com/p/1f9e18e926f6据原文作者&…

docker export_docker使用简介

一 docker服务端和客户端declare -x DOCKER_HOST"192.168.2.162export DOCKER_HOST192.168.2.162docker -H tcp://192.168.2.162:2375 images二 docker daemon配置cat /etc/sysconfig/dockerOPTIONS--selinux-enabled --log-driverjournald --signature-verificationfalse…

深度解析单例与序列化之间的爱恨情仇

转载自 深度解析单例与序列化之间的爱恨情仇本文将通过实例阅读Java源码的方式介绍序列化是如何破坏单例模式的&#xff0c;以及如何避免序列化对单例的破坏。单例模式&#xff0c;是设计模式中最简单的一种。通过单例模式可以保证系统中一个类只有一个实例而且该实例易于外界访…

Windows 10中国定制版完工!更专业

去年底&#xff0c;微软与中国电子科技网络信息安全有限公司成立了一个合资公司&#xff0c;该公司的主要任务是为中国政府和企业定制 Windows 10 系统。 现在&#xff0c;微软大中华区董事长兼CEO贺乐赋在接受财新网记者采访时表示&#xff0c;微软与中国电子科技集团公司&…

Spring [CVE-2022-22965]漏洞处理

问题描述 近期spring官方公布了漏洞 - [CVE-2022-22965] 参考地址&#xff1a; https://tanzu.vmware.com/security/cve-2022-22965 参考issues提到的问题答案开发人员回应&#xff1a; 可能是由于Springframework 3.x 早于 JDK9发布&#xff0c;甚至发布时还未完整的支持JDK…

kafak消费者从头开始消费(消费者组)

【README】 本文主要用于描述 kafka 消费者如何从头开始消费&#xff1b; 【1】从头开始消费 1&#xff09;从头开始消费&#xff0c;需要满足两个条件&#xff0c; 如下&#xff1a; 条件1&#xff0c; 使用一个全新的消费者组id&#xff1b;条件2&#xff0c;指定 auto.of…

jenkins安装与配置windows_Windows下Scoop安装、配置与使用

Scoop简介Scoop是Windows的命令行安装程序&#xff0c;是一个强大的包管理工具。可以在github上找到其项目的相关信息&#xff0c;项目网址。安装的起因&#xff1a;在平常生活中如果要安装像gcc、git等一些需要手动配置相关参数的工具&#xff0c;需要先去官网下载安装程序&am…

对于线程安全的集合类(例如Vector)的任何操作是不是都能保证线程安全

转载自 对于线程安全的集合类&#xff08;例如Vector&#xff09;的任何操作是不是都能保证线程安全之前在公众号中问了这个问题&#xff1a;对于线程安全的集合类&#xff08;例如Vector&#xff09;的任何操作是不是都能保证线程安全&#xff1f; 三天之内收到120回复&#x…

ASP.NET Core 1.0中的管道-中间件模式

ASP.NET Core 1.0借鉴了Katana项目的管道设计(Pipeline)。日志记录、用户认证、MVC等模块都以中间件(Middleware)的方式注册在管道中。显而易见这样的设计非常松耦合并且非常灵活&#xff0c;你可以自己定义任意功能的Middleware注册在管道中。这一设计非常适用于“请求-响应”…

怎么样安装Ubuntu系统,一文告诉你

前言 额滴神呐/(ㄒoㄒ)/~~&#xff0c;用惯了windows开发&#xff0c;初上手Linux桌面开发真的是举步维艰&#xff08;内心ps&#xff1a;谁让你立这个标题的&#xff0c;现在后悔了吧… 你自己想办法 怎么把这个标题栏目圆过去&#xff09; 经过跟内心戏反复的都在&#xff0…

(转) SpringBoot接入两套kafka集群

转自&#xff1a; SpringBoot接入两套kafka集群 - 风小雅 - 博客园引入依赖 compile org.springframework.kafka:spring-kafka 第一套kafka配置 package myapp.kafka; importhttps://www.cnblogs.com/ylty/p/13673357.html 引入依赖 compile org.springframework.kafka:spring…

idea tomcat部署web项目_项目开发之部署帆软到Tomcat服务一

书接上回上一篇文章介绍了两种图表取数的方式&#xff0c;新增数据库查询和通过存储过程取数&#xff0c;其他的内置数据集&#xff0c;文件数据集和关联数据集等方式暂时还没有用到&#xff0c;先暂时不介绍了&#xff0c;等之后用到了或者等小编有时间试过之后再来做个简单的…

C#工业物联网和集成系统解决方案的技术路线

前言 2000年以后&#xff0c;互联网在中国的大地上如火如荼的发展&#xff0c;在这个行业竞争中比的是加速度。我清晰的记得《世界是平的》中有这样一段话&#xff1a;在非洲&#xff0c;羚羊每天早上醒来时&#xff0c;它知道自己必须跑得比最快的狮子还快&#xff0c;否则就会…

转:Kafka事务使用和编程示例/实例

Kafka事务使用和编程示例/实例_JobShow裁员加班实况-微信小程序-CSDN博客一、概述​ Kafka事务特性是指一系列的生产者生产消息和消费者提交偏移量的操作在一个事务中&#xff0c;或者说是一个原子操作&#xff0c;生产消息和提交偏移量同时成功或者失败。注意&#xff1a;kafk…

[初级]Java中的switch对整型、字符型、字符串的具体实现细节

转载自 [初级]Java中的switch对整型、字符型、字符串的具体实现细节Java 7中&#xff0c;switch的参数可以是String类型了&#xff0c;这对我们来说是一个很方便的改进。到目前为止switch支持这样几种数据类型&#xff1a;byteshort int char String 。但是&#xff0c;作为一个…

SpringBoot-Cache整合redis

前言 SpringBoot的众多Starter有两个很重要的缓存Starter&#xff0c;其中一个是我们经常用到的Redis&#xff08;spring-boot-starter-data-redis&#xff09;还有一个是 spring-boot-starter-cache。 今天主要是简单介绍一个如何整合这两个组件&#xff0c;达到相互合作的关系…

C#跨平台物联网通讯框架ServerSuperIO(SSIO)

一.SSIO的特点 轻型高性能通信框架&#xff0c;适用于多种应用场&#xff0c;轮询模式、自控模式、并发模式和单例模式。设备驱动、IO通道、控制模式场景协调统一。设备驱动内轩命令驱动器、命令缓存器、自定义参数和实时数据元素。框架平台支持按设备命令优先级别进行调度&…