数据结构:线索二叉树

线索二叉树

通过前面对二叉树的学习,了解到二叉树本身是一种非线性结构,采用任何一种遍历二叉树的方法,都可以得到树中所有结点的一个线性序列。在这个序列中,除第一个结点外,每个结点都有自己的直接前趋;除最后一个结点外,每个结点都有一个直接后继。

在这里插入图片描述

例如,上图采用先序遍历的方法得到的结点序列为:1 2 4 5 3 6 7,在这个序列中,结点 2 的直接前趋结点为 1,直接后继结点为 4

什么是线索二叉树

如果算法中多次涉及到对二叉树的遍历,普通的二叉树就需要使用栈结构做重复性的操作。

线索二叉树不需要如此,在遍历的同时,使用二叉树中空闲的内存空间记录某些结点的前趋和后继元素的位置(不是全部)。这样在算法后期需要遍历二叉树时,就可以利用保存的结点信息,提高了遍历的效率。使用这种方法构建的二叉树,即为“线索二叉树”。

线索二叉树的结点结构

如果在二叉树中想保存每个结点前趋和后继所在的位置信息,最直接的想法就是改变结点的结构,即添加两个指针域,分别指向该结点的前趋和后继。

但是这种方式会降低树存储结构的存储密度。而对于二叉树来讲,其本身还有很多未利用的空间。

存储密度指的是数据本身所占的存储空间和整个结点结构所占的存储量之比。

每一棵二叉树上,很多结点都含有未使用的指向NULL的指针域。除了度为2的结点,度为 1 的结点,有一个空的指针域;叶子结点两个指针域都为NULL

规律:在有 n 个结点的二叉链表中必定存在 n+1 个空指针域。

线索二叉树实际上就是使用这些空指针域来存储结点之间前趋和后继关系的一种特殊的二叉树。

线索二叉树中,如果结点有左子树,则 lchild 指针域指向左孩子,否则 lchild 指针域指向该结点的直接前趋;同样,如果结点有右子树,则 rchild 指针域指向右孩子,否则 rchild 指针域指向该结点的直接后继。

为了避免指针域指向的结点的意义混淆,需要改变结点本身的结构,增加两个标志域,如下图2所示。线索二叉树中的结点结构

图2

在这里插入图片描述

LTagRTag 为标志域。实际上就是两个布尔类型的变量:

  • LTag 值为 0 时,表示 lchild 指针域指向的是该结点的左孩子;为 1 时,表示指向的是该结点的直接前趋结点;
  • RTag 值为 0 时,表示 rchild 指针域指向的是该结点的右孩子;为 1 时,表示指向的是该结点的直接后继结点。

结点结构代码实现:

#define TElemType int//宏定义,结点中数据域的类型
//枚举,Link为0,Thread为1
typedef enum PointerTag{
    Link,
    Thread
}PointerTag;
//结点结构构造
typedef struct BiThrNode{
    TElemType data;//数据域struct BiThrNode* lchild,*rchild;//左孩子,右孩子指针域
    PointerTag Ltag,Rtag;//标志域,枚举类型
}BiThrNode,*BiThrTree;

表示二叉树时,使用上图所示的结点结构构成的二叉链表,被称为线索链表;构建的二叉树称为线索二叉树

线索链表中的“线索”,指的是链表中指向结点前趋和后继的指针。二叉树经过某种遍历方法转化为线索二叉树的过程称为线索化。

对二叉树进行线索化

将二叉树转化为线索二叉树,实质上是在遍历二叉树的过程中,将二叉链表中的空指针改为指向直接前趋或者直接后继的线索。

线索化的过程即为在遍历的过程中修改空指针的过程。

在遍历过程中,如果当前结点没有左孩子,需要将该结点的 lchild 指针指向遍历过程中的前一个结点,所以在遍历过程中,设置一个指针(名为 pre ),时刻指向当前访问结点的前一个结点。代码实现(拿中序遍历为例):

//中序对二叉树进行线索化
void InThreading(BiThrTree p){
//如果当前结点存在
if (p) {
        InThreading(p->lchild);//递归当前结点的左子树,进行线索化//如果当前结点没有左孩子,左标志位设为1,左指针域指向上一结点 pre
if (!p->lchild) {
            p->Ltag=Thread;
            p->lchild=pre;
        }
//如果 pre 没有右孩子,右标志位设为 1,右指针域指向当前结点。
if (!pre->rchild) {
            pre->Rtag=Thread;
            pre->rchild=p;
        }
        pre=p;//线索化完左子树后,让pre指针指向当前结点
        InThreading(p->rchild);//递归右子树进行线索化
    }
}

注意:中序对二叉树进行线索化的过程中,在两个递归函数中间的运行程序,和之前介绍的中序遍历二叉树的输出函数的作用是相同的。

将中间函数移动到两个递归函数之前,就变成了前序对二叉树进行线索化的过程;后序线索化同样如此。

线索二叉树遍历

3 中是一个按照中序遍历建立的线索二叉树。其中,实线表示指针,指向的是左孩子或者右孩子。虚线表示线索,指向的是该结点的直接前趋或者直接后继。

在这里插入图片描述

图 3 线索二叉树

使用线索二叉树时,会经常遇到一个问题,如图 3 中,结点 b b b的直接后继直接通过指针域获得,为结点 ∗ * ;而由于结点 ∗ * 的度为2 ,无法利用指针域指向后继结点,整个链表断掉了。当在遍历过程,遇到这种问题是解决的办法就是:寻找先序、中序、后序遍历的规律,找到下一个结点。

在先序遍历过程中,如果结点因为有右孩子导致无法找到其后继结点,如果结点有左孩子,则后继结点是其左孩子;否则,就一定是右孩子。拿图 3 举例,结点 + 的后继结点是其左孩子结点 a ,如果结点 a 不存在的话,就是结点 *

在中序遍历过程中,结点的后继是遍历其右子树时访问的第一个结点,也就是右子树中位于最左下的结点。例如图 3 中结点 * ,后继结点为结点 c ,是其右子树中位于最左边的结点。反之,结点的前趋是左子树最后访问的那个结点。

后序遍历中找后继结点需要分为 3 种情况:

  1. 如果该结点是二叉树的根,后继结点为空;
  2. 如果该结点是父结点的右孩子(或者是左孩子,但是父结点没有右孩子),后继结点是父结点;
  3. 如果该结点是父结点的左孩子,且父结点有右子树,后继结点为父结点的右子树在后序遍历列出的第一个结点。

使用后序遍历建立的线索二叉树,在真正使用过程中遇到链表的断点时,需要访问父结点,所以在初步建立二叉树时,宜采用三叉链表做存储结构。

遍历线索二叉树非递归代码实现:

//中序遍历线索二叉树
void InOrderThraverse_Thr(BiThrTree p)
{
    while(p)
    {
//一直找左孩子,最后一个为中序序列中排第一的
        while(p->Ltag == Link){
            p = p->lchild;
        }
        cout << p->data;//操作结点数据//当结点右标志位为1时,直接找到其后继结点
        while(p->Rtag == Thread && p->rchild !=NULL){
            p = p->rchild;
            cout <<  p->data;
        }
//否则,按照中序遍历的规律,找其右子树中最左下的结点,也就是继续循环遍历
        p = p->rchild;
    }
}

整节完整代码

#include "iostream"
using namespace std;
#define TElemType char//宏定义,结点中数据域的类型
//枚举,Link为0,Thread为1
typedef enum {
    Link,
    Thread
}PointerTag;
//结点结构构造
typedef struct BiThrNode {
    TElemType data;//数据域
    struct BiThrNode* lchild, *rchild;//左孩子,右孩子指针域
    PointerTag Ltag, Rtag;//标志域,枚举类型
}BiThrNode, *BiThrTree;

BiThrTree pre = NULL;

//采用前序初始化二叉树
//中序和后序只需改变赋值语句的位置即可
void CreateTree(BiThrTree * tree) {
    char data;
    cin >> data;
    if (data != '#') {
        if (!((*tree) = (BiThrNode*)malloc(sizeof(BiThrNode)))) {
            cout << "申请结点空间失败";
            return;
        }
        else {
            (*tree)->data = data;//采用前序遍历方式初始化二叉树
            (*tree)->Ltag = Link;
            (*tree)->Rtag = Link;
            CreateTree(&((*tree)->lchild));//初始化左子树
            CreateTree(&((*tree)->rchild));//初始化右子树
        }
    }
    else {
        *tree = NULL;
    }
}
//中序对二叉树进行线索化
void InThreading(BiThrTree p) {
    //如果当前结点存在
    if (p) {
        InThreading(p->lchild);//递归当前结点的左子树,进行线索化
        //如果当前结点没有左孩子,左标志位设为1,左指针域指向上一结点 pre
        if (!p->lchild) {
            p->Ltag = Thread;
            p->lchild = pre;
        }
        //如果 pre 没有右孩子,右标志位设为 1,右指针域指向当前结点。
        if (pre && !pre->rchild) {
            pre->Rtag = Thread;
            pre->rchild = p;
        }
        pre = p;//pre指向当前结点
        InThreading(p->rchild);//递归右子树进行线索化
    }
}
//中序遍历线索二叉树
void InOrderThraverse_Thr(BiThrTree p)
{
    while (p)
    {
        //一直找左孩子,最后一个为中序序列中排第一的
        while (p->Ltag == Link) {
            p = p->lchild;
        }
        cout << p->data;
        //当结点右标志位为1时,直接找到其后继结点
        while (p->Rtag == Thread && p->rchild != NULL)
        {
            p = p->rchild;
            cout << p->data;
        }
        //否则,按照中序遍历的规律,找其右子树中最左下的结点,也就是继续循环遍历
        p = p->rchild;
    }
}

int main() {
    BiThrTree t;
    cout << "输入前序二叉树:" << '\n';
    CreateTree(&t);
    InThreading(t);
    cout << "输出中序序列:" << '\n';
    InOrderThraverse_Thr(t);
    return 0;
}

运行结果

输入前序二叉树:
124###35##6##
输出中序序列:
4 2 1 5 3 6

通过前一节对线索二叉树的学习,其中,在遍历使用中序序列创建的线索二叉树时,对于其中的每个结点,即使没有线索的帮助下,也可以通过中序遍历的规律找到直接前趋和直接后继结点的位置。

也就是说,建立的线索二叉链表可以从两个方向对结点进行中序遍历。通过前一节的学习,线索二叉链表可以从第一个结点往后逐个遍历。但是起初由于没有记录中序序列中最后一个结点的位置,所以不能实现从最后一个结点往前逐个遍历。

双向线索链表的作用就是可以让线索二叉树从两个方向实现遍历。

双向线索二叉树的实现过程

在线索二叉树的基础上,额外添加一个结点。此结点的作用类似于链表中的头指针,数据域不起作用,只利用两个指针域(由于都是指针,标志域都为 0 )。

左指针域指向二叉树的树根,确保可以正方向对二叉树进行遍历;同时,右指针指向线索二叉树形成的线性序列中的最后一个结点。

这样,二叉树中的线索链表就变成了双向线索链表,既可以从第一个结点通过不断地找后继结点进行遍历,也可以从最后一个结点通过不断找前趋结点进行遍历。

在这里插入图片描述

图4 双向线索二叉链表

代码实现:

//建立双向线索链表
void InOrderThread_Head(BiThrTree *h, BiThrTree t)
{
//初始化头结点
    (*h) = (BiThrTree)malloc(sizeof(BiThrNode));
    if((*h) == NULL){
        cout << "申请内存失败";
        return ;
    }
    (*h)->rchild = *h;
    (*h)->Rtag = Link;
//如果树本身是空树
if(!t){
        (*h)->lchild = *h;
        (*h)->Ltag = Link;
    }
    else{
        pre = *h;//pre指向头结点
        (*h)->lchild = t;//头结点左孩子设为树根结点
        (*h)->Ltag = Link;
        InThreading(t);//线索化二叉树,pre结点作为全局变量,线索化结束后,pre结点指向中序序列中最后一个结点
        pre->rchild = *h;
        pre->Rtag = Thread;
        (*h)->rchild = pre;
    }
}

双向线索二叉树的遍历

双向线索二叉树遍历时,如果正向遍历,就从树的根结点开始。整个遍历过程结束的标志是:当从头结点出发,遍历回头结点时,表示遍历结束。

//中序正向遍历双向线索二叉树
void InOrderThraverse_Thr(BiThrTree h)
{
    BiThrTree p;
    p = h->lchild;//p指向根结点
while(p != h)
    {
        while(p->Ltag == Link)//当ltag = 0时循环到中序序列的第一个结点
        {
            p = p->lchild;
        }
				cout << p->data;//显示结点数据,可以更改为其他对结点的操作
while(p->Rtag == Thread && p->rchild != h)
        {
            p = p->rchild;
						cout << p->data;
        }

        p = p->rchild;//p进入其右子树
    }
}

逆向遍历线索二叉树的过程即从头结点的右指针指向的结点出发,逐个寻找直接前趋结点,结束标志同正向遍历一样:

//中序逆方向遍历线索二叉树
void InOrderThraverse_Thr(BiThrTree h){
    BiThrTree p;
    p=h->rchild;
    while (p!=h) {
        while (p->Rtag==Link) {
            p=p->rchild;
        }
        cout << p->data;
//如果lchild为线索,直接使用,输出
while (p->Ltag==Thread && p->lchild !=h) {
            p=p->lchild;
            cout << p->data;
        }
        p=p->lchild;
    }
}

完整代码实现

#include "iostream"
using namespace std;
#define TElemType char//宏定义,结点中数据域的类型
//枚举,Link为0,Thread为1
typedef enum {
    Link,
    Thread
}PointerTag;
//结点结构构造
typedef struct BiThrNode {
    TElemType data;//数据域
    struct BiThrNode* lchild, *rchild;//左孩子,右孩子指针域
    PointerTag Ltag, Rtag;//标志域,枚举类型
}BiThrNode, *BiThrTree;

BiThrTree pre = NULL;

//采用前序初始化二叉树
//中序和后序只需改变赋值语句的位置即可
void CreateTree(BiThrTree * tree) {
    char data;
    cin >> data;
    if (data != '#') {
        if (!((*tree) = (BiThrNode*)malloc(sizeof(BiThrNode)))) {
            cout << "申请结点空间失败";
            return;
        }
        else {
            (*tree)->data = data;//采用前序遍历方式初始化二叉树
            (*tree)->Ltag = Link;
            (*tree)->Rtag = Link;
            CreateTree(&((*tree)->lchild));//初始化左子树
            CreateTree(&((*tree)->rchild));//初始化右子树
        }
    }
    else {
        *tree = NULL;
    }
}
//中序对二叉树进行线索化
void InThreading(BiThrTree p) {
    //如果当前结点存在
    if (p) {
        InThreading(p->lchild);//递归当前结点的左子树,进行线索化
        //如果当前结点没有左孩子,左标志位设为1,左指针域指向上一结点 pre
        if (!p->lchild) {
            p->Ltag = Thread;
            p->lchild = pre;
        }
        //如果 pre 没有右孩子,右标志位设为 1,右指针域指向当前结点。
        if (pre && !pre->rchild) {
            pre->Rtag = Thread;
            pre->rchild = p;
        }
        pre = p;//pre指向当前结点
        InThreading(p->rchild);//递归右子树进行线索化
    }
}
//建立双向线索链表
void InOrderThread_Head(BiThrTree *h, BiThrTree t)
{
    //初始化头结点
    (*h) = (BiThrTree)malloc(sizeof(BiThrNode));
    if ((*h) == NULL) {
        cout << "申请内存失败";
        return;
    }
    (*h)->rchild = *h;
    (*h)->Rtag = Link;
    //如果树本身是空树
    if (!t) {
        (*h)->lchild = *h;
        (*h)->Ltag = Link;
    }
    else {
        pre = *h;//pre指向头结点
        (*h)->lchild = t;//头结点左孩子设为树根结点
        (*h)->Ltag = Link;
        InThreading(t);//线索化二叉树,pre结点作为全局变量,线索化结束后,pre结点指向中序序列中最后一个结点
        pre->rchild = *h;
        pre->Rtag = Thread;
        (*h)->rchild = pre;
    }
}
//中序正向遍历双向线索二叉树
void InOrderThraverse_Thr(BiThrTree h)
{
    BiThrTree p;
    p = h->lchild;           //p指向根结点
    while (p != h)
    {
        while (p->Ltag == Link)   //当ltag = 0时循环到中序序列的第一个结点
        {
            p = p->lchild;
        }
        cout << p->data;
        while (p->Rtag == Thread && p->rchild != h)
        {
            p = p->rchild;
            cout << p->data;
        }

        p = p->rchild;           //p进入其右子树
    }
}
int main() {
    BiThrTree t;
    BiThrTree h;
    cout << "输入前序二叉树:" << '\n';
    CreateTree(&t);
    InOrderThread_Head(&h, t);
    cout << "输出中序二叉树:" << '\n';
    InOrderThraverse_Thr(h);
    return 0;
}

运行结果:

输入前序二叉树:
124###35##6##
输出中序序列:
4 2 1 5 3 6

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

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

相关文章

使用Python创建快速创建剪映草稿轨道,自动生成视频

使用Python创建快速创建剪映草稿轨道&#xff0c;自动生成视频 一、实现原理 实现原理 : JianYingPro 项目文件是 json 的形式存储的&#xff0c;只需要创建draft_content.json,draft_mate_info.json 打开软件后会自动补全。 作用&#xff1a;快速生成草稿&#xff0c;可以完…

formatter的用法,深拷贝, Object.assign 方法实战。

1. :formatter的用法 :formatter 接受一个函数作为参数&#xff0c;这个函数有三个参数&#xff1a;row&#xff0c;column 和 cellValue。row 是当前行的数据&#xff0c;column 是当前列的数据&#xff0c;cellValue 是当前单元格的值。 <el-table-column prop"SYS…

2021年全国硕士研究生入学统一考试管理类专业学位联考写作试题——解析版

四、写作&#xff1a;第56~57小题&#xff0c;共65分。其中论证有效性分析30分&#xff0c;论说文35分。请答在答题纸相应的位置上。 56.论证有效性分析&#xff1a;分析下述论证中存在的缺陷与漏洞&#xff0c;选择若干要点&#xff0c;写一篇600字左右的文章&#xff0c;对该…

STM32 CAN通讯实验程序

目录 STM32 CAN通讯实验 CAN硬件原理图 CAN外设原理图 TJA1050T硬件描述 实验线路图 回环实验 CAN头文件配置 CAN_GPIO_Config初始化 CAN初始化结构体 CAN筛选器结构体 接收中断优先级配置 接收中断函数 main文件 实验现象 补充 STM32 CAN通讯实验 CAN硬件原理图…

MyBatis查询数据库

1.MyBatis 是什么&#xff1f; MyBatis 是⼀款优秀的持久层框架&#xff0c;它⽀持⾃定义 SQL、存储过程以及⾼级映射。MyBatis 去除了几乎所有的 JDBC 代码以及设置参数和获取结果集的⼯作。MyBatis 可以通过简单的 XML 或注解来配置 和映射原始类型、接⼝和 Java POJO&#…

CMake:为Eigen库使能向量化

CMake:为Eigen库使能向量化 导言构建Eigen项目结构CMakeLists.txt相关源码 导言 本篇开始将涉及检测外部库相关的内容&#xff0c;期间会穿插着一些其他的内容。为了能够使得系统在系统中运行Eigen库&#xff0c;我们首先需要在系统中配置好Eigen库。然后介绍与Eigen库相关的C…

【C++】STL——vector的模拟实现、常用构造函数、迭代器、运算符重载、扩容函数、增删查改

文章目录 1.模拟实现vector1.1构造函数1.2迭代器1.3运算符重载1.4扩容函数1.5增删查改 1.模拟实现vector vector使用文章 1.1构造函数 析构函数 在C中&#xff0c;vector是一个动态数组容器&#xff0c;可以根据需要自动调整大小。vector类提供了几个不同的构造函数来创建和初…

java 版本企业招标投标管理系统源码,多个行业+及时准确+全程电子化

&#xfeff; 项目说明 随着公司的快速发展&#xff0c;企业人员和经营规模不断壮大&#xff0c;公司对内部招采管理的提升提出了更高的要求。在企业里建立一个公平、公开、公正的采购环境&#xff0c;最大限度控制采购成本至关重要。符合国家电子招投标法律法规及相关规范&am…

Electron 系统通知 Notification 实践指南

系统通知是桌面应用的常见功能&#xff0c;用于给用户发送提醒&#xff08;刷下存在感 &#x1f642;&#xff09;&#xff0c;还能帮定点击事件以便后续的操作。 Electron 自带通知模块&#xff0c;下方代码是一个简单的示例 const { Notification } require(electron)cons…

【C#教程】零基础从入门到精通

今天给大家分享一套零基础从入门到精通&#xff1a;.NetCore/C#视频教程&#xff1b;这是2022年最新整理的、590G的开发教程资料。课程涵盖了.Net各方面的知识&#xff0c;跟着这个教程学习&#xff0c;就足够了。 课程分类 1、C#从基础到精通教程&#xff1b; 2、Winform从…

Framework开发的热度从未下降~

近几日&#xff0c;统计局也发布了就业相关数据&#xff0c;全国失业青年达600多万&#xff0c;面对此数据&#xff0c;我们能想到的是实际的失业人数肯定会比公布的数据要多很多&#xff0c;尤其是表示 “一周工作一小时以上” 也纳入了就业范围。 反观有一批Android开发在如此…

【数据挖掘torch】 基于LSTM电力系统负荷预测分析(Python代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

解决 MyBatis-Plus + PostgreSQL 中的 org.postgresql.util.PSQLException 异常

&#x1f337;&#x1f341; 博主猫头虎 带您 Go to New World.✨&#x1f341; &#x1f984; 博客首页——猫头虎的博客&#x1f390; &#x1f433;《面试题大全专栏》 文章图文并茂&#x1f995;生动形象&#x1f996;简单易学&#xff01;欢迎大家来踩踩~&#x1f33a; &a…

用C语言构建一个手写数字识别神经网络

(原理和程序基本框架请参见前一篇 "用C语言构建了一个简单的神经网路") &#xff11;&#xff0e;准备训练和测试数据集 从http://yann.lecun.com/exdb/mnist/下载手写数字训练数据集, 包括图像数据train-images-idx3-ubyte.gz 和标签数据 train-labels-idx1-ubyte.…

Mac 安装配置adb命令环境(详细步骤)

一、注意&#xff1a;前提要安装java环境。 因为android sdk里边开发的一些包都是依赖java语言的&#xff0c;所以&#xff0c;首先要确保已经配置了java环境。 二、在Mac下配置android adb命令环境&#xff0c;配置方式如下&#xff1a; 1、下载并安装IDE &#xff08;andr…

Django的生命周期流程图(补充)、路由层urls.py文件、无名分组和有名分组、反向解析(无名反向解析、有名反向解析)、路由分发、伪静态

一、orm的增删改查方法&#xff08;补充&#xff09; 1. 查询resmodels.表名(类名).objects.all()[0]resmodels.表名(类名).objects.filter(usernameusername, passwordpassword).all()res models.表名(类名).objects.first() # 判断&#xff0c;判断数据是否有# res如果查询…

UE4 unlua学习笔记

将这三个插件放入Plugins内并重新编译 创建一个BlueprintLibrary&#xff0c;声明一个全局函数 在这里声明路径 点击Create Lua Template 在Content的Script即可生成对应的lua文件打开它&#xff01; 显示以上lua代码 打印Hello Unlua 创建该UI&#xff0c;就会在创建UI的Con…

第5章 最佳实践

过去的错误 不要怪罪JavaScript 游览器遇到不合法的html会想尽办法将他展现出来游览器遇到不合法的js将拒绝执行它们并报错写js要保障自己代码的健壮性 质疑一切 写js功能前一定要考虑这个功能的合理性&#xff0c;避免造成不可预见的后果写js功能前一定要考虑用户的游览器…

企业级IT应用运维监控层次架构设计

企业基本都有自己的IT系统&#xff0c;而每个IT系统都有自己的监控系统。 企业级的IT应用监控架构是一种综合性的解决方案&#xff0c;涉及到很多层级和相应的工具。随着企业IT系统的规模和复杂程度的不断增加&#xff0c;监控和管理系统也面临着越来越大的挑战。 大家有时在…

视频监控汇聚平台EasyCVR告警消息生成后,合成录像显示不了是什么原因?

智能视频监控平台TSINGSEE青犀视频EasyCVR具备视频融合汇聚能力&#xff0c;作为安防视频监控综合管理平台&#xff0c;它支持多协议接入、多格式视频流分发&#xff0c;可支持的主流标准协议有国标GB28181、RTSP/Onvif、RTMP等&#xff0c;以及支持厂家私有协议与SDK接入&…