二叉搜索树、AVL树、红黑树

在这里插入图片描述
为者常成,行者常至

文章目录

  • 二叉搜索树
    • 节点
    • 查找
    • 插入
    • 重头戏——删除
      • 叶子节点
      • 只有一个子节点
      • 有两个子节点
    • 分析
  • 平衡二叉搜索树
    • 右单旋
    • 左右双旋
    • 插入的四种情况
      • 左左
      • 右右
      • 左右
      • 右左
      • 插入操作
    • 小结
  • 红黑树

二叉搜索树

二叉搜索树就是在二叉树的基础上增加一些规则,使该结构具备某些规则方便查找,提升查找效率。

规则:

  1. 右子节点都比父节点大
  2. 左子节点都比父节点小

image-20240406163804365

节点

标准二叉树的节点定义,一个值,两个指针。

	template<class K>
	struct TreeNode
	{
		TreeNode(const K& key)
			:_key(key)
			,_left(nullptr)
			,_right(nullptr)
		{}

		K _key;
		TreeNode* _left;
		TreeNode* _right;
	};

查找

二叉搜索树从名字就可以看出搜索就是它的特色,如上图,例如要查找值为16的节点。

image-20240406184953235

首先在根节点进行比较,查找的值16小于根节点17,因此往根节点左边找,然后到值为12的节点,发现查找值16大于该节点值12,因此往右边找,到了节点15,又发现查找值比15大,再往右边走,到了值为16的节点,查找到了然后返回。

如果一直查找到空还没有匹配的值,那么就认为没有这个值,没有查找到。

// 根节点指针 _root 是指向根节点的指针 
bool Find(const K& key)
{
    Node* cur = _root;
    while (cur)
    {
        if (cur->_key > key) cur = cur->_left;
        else if (cur->_key < key) cur = cur->_right;
        else return true;
    }
    return false;
}

插入

插入操作和查找操作差不多,插入也需要找到一个合适的位置,然后链接即可。

image-20240406190124340

如上,插入一个值为16的节点,需要从根节点向下逐层比较,插入值小于节点值就往左走,反之往右走,直到合适位置。

合适位置的判断一般有两种方法:

  1. 用前后指针,cur和prev指针,当cur走到null的时候,prev就是链接节点,只需要比较插入值与prev值的大小来确定插入的位置。
  2. 边走边判断,如果当前位置的值比插入值小并且左边为空,那么左边就是插入位置,反之右边也是一样。
bool InSert(const K& key)
{
    Node* node = new Node(key); // new一个新增节点
    if (_root == nullptr)  // 链表为空,直接动_root
    {
        _root = node;
        return true;
    }
    // 不为空,查找合适插入位置
    //前后指针,大于往右,小于往左
    Node* prev = nullptr;
    Node* cur = _root;
    while (cur)
    {
        if (cur->_key > key) 
        {
            prev = cur;
            cur = cur->_left;
        }
        else if (cur->_key < key)
        {
            prev = cur;
            cur = cur->_right;
        }
        else //如果找到值相等已经存在的节点,默认插入成功
        {
            delete node; //记住释放刚才new的节点,不然内存泄漏
            return true;
        }
    }
    //到这里就已经找到了合适位置,prev指向的节点就是链入节点
    if (prev->_key > key) prev->_left = node;
    else prev->_right = node;
    return true;
}

重头戏——删除

查找和插入都是开胃菜,删除才是主食,删除分为3种情况

  1. 删除节点为叶子结点,即左右孩子都为nullptr
  2. 删除节点有一个孩子
  3. 删除节点有两个孩子

叶子节点

image-20240406192457332

叶子结点的删除最为简单,无需考虑其他情况直接将它的父节点链接它的指针指向nullptr就可以了,然后delete该节点。

if (cur->_left == nullptr && cur->_right == nullptr) //叶子结点
{
    if (prev == nullptr) _root = nullptr; // 如果prev为空代表删除的节点为根节点
    else if (prev->_left == cur) prev->_left = nullptr;
    else prev->_right = nullptr;
    delete cur; 
    cur = nullptr;
}

只有一个子节点

在这里插入图片描述

当删除节点只有一个子节点,那么就只需要将删除节点的父节点指向它的指针移动到指向它的唯一孩子上即可(无论左右孩子)

if ((cur->_left == nullptr && cur->_right) || (cur->_left && cur->_right == nullptr)) // 单孩子节点
{
    if (prev == nullptr) //删除节点为根节点
    {
        if (cur->_left) _root = cur->_left;
        else _root = cur->_right;
    }
    else if (prev->_left == cur) //父节点的左边指向删除节点
    {
        if (cur->_left) prev->_left = cur->_left;
        else prev->_left = cur->_right;
    }
    else //父节点的右边指向删除节点
    {
        if (cur->_left) prev->_right = cur->_left;
        else prev->_right = cur->_right;
    }
    delete cur;
    cur = nullptr;
}

有两个子节点

image-20240406193526989

当删除有两个孩子的节点时,不能直接在该节点上动手脚,需要移花接木一下,有种方法进行移花接木

  1. 找左子树最大节点替换
  2. 找右子树最小节点替换

在找到合适的替死鬼之后,就可以直接删除替死鬼节点了,因为无论是左子树最大节点还是右子树最小节点再替换后都不会打乱二叉搜索树的特性,且这两个节点必定是叶子结点或者只有一个子节点,此时删除两个子节点的操作就转移为删除叶子结点或者单字节点。

//三指针,当cur为空时,sprev刚好指向替死鬼节点
// spprev指向sprev的父节点
Node* scur = cur->_right;  // 找右子树最小节点
Node* sprev = cur;
Node* spprev = prev;
while (scur)
{
    spprev = sprev;
    sprev = scur;
    scur = scur->_left;
}
cur->_key = sprev->_key; //替换
if (spprev->_left == sprev) spprev->_left = sprev->_right;
else spprev->_right = sprev->_right;
delete sprev;

分析

二叉搜索树的约束条件非常简单,因此查找的性能不稳定,当插入较为随机时查找的效率可以达到O(logn),但是当插入不够随机时,例如当插入序列为顺序或者逆序,就会变成歪脖子树,查找效率降低为O(n)

image-20240406195748555

平衡二叉搜索树

因为二叉搜索树可能会退化为歪脖子树,导致查找效率降低为O(n),因此平衡二叉搜索树就是来解决这个问题。平衡二叉搜索树简称AVL树,AVL树就是在二叉搜索树的基础上增加一个条件

任何节点的左右子树高度差值不能大于1

通过这个条件来保证“相对平衡”状态,一般用平衡因子来表示这个条件。

image-20240407123212453

如下图,开始树的每个节点左右子树高度相差都不大于1,且维持了二叉搜索树的特性,因此这个树是一个AVL树。

但是当在新增一个值为11的节点时,那么插入这个节点的祖先节点的树高都会受到影响,因此值为18和值为22的节点左右子树的高度差为2,打破AVL树的特性。

当插入节点时,树的AVL特性被打破就需要做一些处理,让这棵树又变为AVL树,这个处理方式分4种,单旋和双旋

  1. 左单旋
  2. 右单旋
  3. 左单旋 + 右单旋
  4. 右单旋 + 左单旋

右单旋

image-20240407130709296

如上,当插入值为11的节点时,AVL树的相对平衡被打破,执行右单旋,k2节点的左接收k1节点的右子节点,然后降低高度成为k1的右子节点。

左单旋情况类似

左右双旋

image-20240407131447483

当插入值为15时,打破AVL树相对平衡状态,执行左右双旋操作,先对k3节点的左子节点k1执行左单旋,然后再对k3执行右单旋,大功告成。

右左单旋情况类似

插入的四种情况

四种旋转对应着插入的4种情况(四种情况都是破坏了AVL树"相对平衡"的情况,没有破坏则不需要旋转)

  1. 插入左子树的左边——左左(右单旋)
  2. 插入左子树的右边——左右(左右双旋)
  3. 插入右子树的左边——右左(右左双旋)
  4. 插入右子树的右边——右右(左单旋)

左左

在这里插入图片描述

插入该节点的左子树的左边,简称左左,执行右单旋。最近受影响节点接收左子树的右子节点,并成为左子树的右子节点,降低高度。

最近受影响节点即为从插入节点开始,依次更新它的祖先节点,当第一个被打破“相对平衡”状态的祖先节点

void RotateR(Node* parent)
{
    Node* subL = parent->_left; 
    Node* subLR = subL->_right; 
    parent->_left = subLR;
    if (subLR) subLR->_parent = parent;
    
    Node* ppnode = parent->_parent;
    subL->_right = parent;
    parent->_parent = subL;
    if (parent == _root)
    {
        _root = subL;
        _root->_parent = nullptr;
    }
    else
    {
        if (ppnode->_left == parent) ppnode->_left = subL; 
        else ppnode->_right = subL;
        
        subL->_parent = ppnode;
    }
    subL->_bf = parent->_bf = 0;
}

右右

image-20240407133458938

插入该节点的右子树的右边,简称右右,执行左单旋。最近受影响节点接收它右子树的左子节点,并成为右子树的左子节点,降低高度。

void RotateL(Node* parent)
{
    Node* subR = parent->_right;
    Node* subRL = subR->_left;
    parent->_right = subRL;
    if (subRL) subRL->_parent = parent;
        
    Node* ppnode = parent->_parent;
    subR->_left = parent;
    parent->_parent = subR;

    if (ppnode == nullptr)
    {
        _root = subR;
        _root->_parent = nullptr;
    }
    else
    {
        if (ppnode->_left == parent)
        {
            ppnode->_left = subR;
        }
        else
        {
            ppnode->_right = subR;
        }
        subR->_parent = ppnode;
    }

    parent->_bf = subR->_bf = 0;
}

左右

image-20240407134500420

插入左子树的右子节点里,这种情况单靠一种单旋是没办法解决问题的,因此需要借助两次单旋来处理。首先将最近受影响节点的左子节点进行左单旋,旋转之后的结果就和右单旋的插入情况一样,然后进行右单旋,降低高度。

void RotateLR(Node* parent)
{
    Node* subL = parent->_left;
    Node* subLR = subL->_right;
    int bf = subLR->_bf;

    RotateL(parent->_left);
    RotateR(parent);

    if (bf == 1)
    {
        subL->_bf = -1;
    }
    else if (bf == -1)
    {
        parent->_bf = 1;
    }
}

右左

image-20240407140620012

插入右子树的左子节点里,这种情况单靠一种单旋也是没办法解决问题的,因此需要借助两次单旋来处理。首先将最近受影响节点的右子节点进行右单旋,旋转之后的结果就和左单旋的插入情况一样,然后进行左单旋,降低高度。

void RotateRL(Node* parent)
{
    Node* subR = parent->_right;
    Node* subRL = subR->_left;
    int bf = subRL->_bf;

    RotateR(parent->_right);
    RotateL(parent);

    if (bf == 1)
    {
        parent->_bf = -1;
    }
    else if (bf == -1)
    {
        subR->_bf = 1;
    }
}

插入操作

综合四种旋转和四种插入情况,即可得插入操作

bool InSert(const K& key, const V& val)
{
    Node* node = new Node(key, val);
    if (_root == nullptr)
    {
        _root = node;
        return true;
    }
    Node* prev = nullptr;
    Node* cur = _root;
    while (cur)
    {
        if (cur->_key > key)
        {
            prev = cur;
            cur = cur->_left;
        }
        else if (cur->_key < key)
        {
            prev = cur;
            cur = cur->_right;
        }
        else
        {
            delete node;
            return true;
        }
    }
    if (prev->_key > key)
    {
        prev->_left = node;
    }
    else
    {
        prev->_right = node;
    }
    node->_parent = prev;
    upDate_In(node, prev);
    return true;
}

void upDate_In(Node* cur, Node* parent)
{
    while (parent)
    {
        if (parent->_left == cur) parent->_bf--;
        else parent->_bf++;
        if (parent->_bf == 0) return;
        if (parent->_bf == -2 && cur->_bf == -1)
        {
            RotateR(parent);
            return;
        }
        else if (parent->_bf == -2 && cur->_bf == 1)
        {
            RotateLR(parent);
            return;
        }
        else if (parent->_bf == 2 && cur->_bf == 1)
        {
            RotateL(parent);
            return;
        }
        else if(parent->_bf == 2 && cur->_bf == -1)
        {
            RotateRL(parent);
            return;
        }
        else
        {
            cur = parent;
            parent = parent->_parent;
        }
    }
}

小结

image-20240407172740558

虽说平衡二叉搜索树的查找效率为O(logn),治好了二叉搜索树的歪脖子病,但是其过分严格的限制左右子树的高度差不能大于1,导致再插入和删除数据时会多次进行旋转,影响性能。

红黑树

红黑树相较于AVL树,它没有那么严格的平衡要求,因此不会进行大量频繁的旋转,而且相较于搜索二叉树,他又有自己的约束条件来维持相对平衡状态,因此红黑树通常是个不错的选择,map和set容器都是以红黑树作为底层容器。

  1. 节点是红色或黑色。
  2. 根是黑色。
  3. 所有叶子都是黑色(叶子是NIL节点)。
  4. 不存在两个相邻的红色节点。
  5. 从任一节点到其每个叶子的所有简单路径都包含相同数目的黑色节点。

image-20240407212326038

更多红黑树介绍直接跳转http://t.csdnimg.cn/CUgRR,文章里有红黑树的插入操作的详解,这里直接给出插入代码

enum Color  //枚举颜色
{
    RED,
    BLACK
};

template <class K, class V>
struct RB_Node
{
    typedef std::pair<K, V> Pair;
    typedef RB_Node<K, V> Node;
    RB_Node(const Pair& node)
        :_val(node)
        ,_col(RED)
        ,_left(nullptr)
        ,_right(nullptr)
        ,_parent(nullptr)
	{}

    Pair _val;
    Color _col;
    Node* _left;
    Node* _right;
    Node* _parent;
};

void InSert(const K& key, const V& val)
{
    if (_root == nullptr)
    {
        _root = new Node({ key, val });
        _root->_col = BLACK;
        return;
    }

    Node* cur = _root;
    Node* prev = nullptr;
    while (cur)
    {
        if (key > cur->_val.first)
        {
            prev = cur;
            cur = cur->_right;
        }
        else if (key < cur->_val.first)
        {
            prev = cur;
            cur = cur->_left;
        }
        else return;
    }

    Node* node = new Node({ key, val });
    if (prev->_val.first > key)
    {
        prev->_left = node;
    }
    else
    {
        prev->_right = node;
    }
    node->_parent = prev;
    if (prev->_col == RED)
    {
        UpDate(prev, node);
    }
}

void UpDate(Node* parent, Node* cur)
{
    while (parent && parent->_col == RED)
    {
        Node* grandparent = parent->_parent;
        Node* uncle = nullptr;
        if (grandparent->_left == parent) uncle = grandparent->_right;
        else uncle = grandparent->_left;

        if (!uncle || uncle->_col == BLACK)
        {
            if (grandparent->_left == parent && parent->_left == cur)
            {
                RevoleR(grandparent);
                parent->_col = BLACK;
                grandparent->_col = RED;
            }
            else if (grandparent->_right == parent && parent->_right == cur)
            {
                RevoleL(grandparent);
                parent->_col = BLACK;
                grandparent->_col = RED;
            }
            else if (grandparent->_left == parent && parent->_right == cur)
            {
                RevoleLR(grandparent);
                grandparent->_col = RED;
                cur->_col = BLACK;
            }
            else if (grandparent->_right == parent && parent->_left == cur)
            {
                RevoleRL(grandparent);
                grandparent->_col = RED;
                cur->_col = BLACK;
            }
            else assert(0);
            if (grandparent->_parent == nullptr) grandparent->_col = BLACK;
            return;
        }
        else if (uncle && uncle->_col == RED)
        {
            parent->_col = BLACK;
            uncle->_col = BLACK;
            grandparent->_col = RED;
            parent = grandparent->_parent;
            cur = grandparent;
        }
    }
    if (parent == nullptr)
    {
        cur->_col = BLACK;
    }
}

void RevoleLR(Node* grandparent)
{
    RevoleL(grandparent->_left);
    RevoleR(grandparent);
}
void RevoleRL(Node* grandparent)
{
    RevoleR(grandparent->_right);
    RevoleL(grandparent);
}
void RevoleL(Node* grandparent)
{
    Node* subR = grandparent->_right;
    Node* subRL = subR->_left;
    Node* ggrand = grandparent->_parent;

    grandparent->_right = subRL;
    if (subRL) subRL->_parent = grandparent;
    subR->_left = grandparent;
    grandparent->_parent = subR;

    if (ggrand)
    {
        if (ggrand->_left == grandparent)
            ggrand->_left = subR;
        else
            ggrand->_right = subR;
    }
    else
    {
        _root = subR;
    }
    subR->_parent = ggrand;
}
void RevoleR(Node* grandparent)
{
    Node* subL = grandparent->_left;
    Node* subLR = subL->_right;
    Node* ggrand = grandparent->_parent;

    grandparent->_left = subLR;
    if (subLR) subLR->_parent = grandparent;
    subL->_right = grandparent;
    grandparent->_parent = subL;

    if (ggrand)
    {
        if (ggrand->_left == grandparent)
            ggrand->_left = subL;
        else
            ggrand->_right = subL;
    }
    else
    {
        _root = subL;
    }
    subL->_parent = ggrand;
}
private:
Node* _root;
};

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

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

相关文章

【LeetCode】排序数组——不一样的方式实现快排

目录 题目链接 颜色分类 算法原理 代码实现 排序数组 算法原理 代码实现 最小的k个数 算法原理 代码实现 题目链接 LeetCode链接&#xff1a;75. 颜色分类 - 力扣&#xff08;LeetCode&#xff09; LeetCode链接&#xff1a;912. 排序数组 - 力扣&#xff08;L…

【三十七】【算法分析与设计】STL 练习,凌波微步,栈和排序,吐泡泡,[HNOI2003]操作系统,优先队列自定义类型

凌波微步 链接&#xff1a;登录—专业IT笔试面试备考平台_牛客网 来源&#xff1a;牛客网 时间限制&#xff1a;C/C 1 秒&#xff0c;其他语言 2 秒 空间限制&#xff1a;C/C 32768K&#xff0c;其他语言 65536K 64bit IO Format: %lld 题目描述 小 Z 的体型实在是太胖了&…

【论文复现|智能算法改进】改进猎人猎物优化算法在WSN覆盖中的应用

目录 1.算法原理2.改进点3.结果展示4.参考文献 1.算法原理 【智能算法】猎人猎物算法&#xff08;HPO&#xff09;原理及实现 【智能算法应用】猎人猎物优化算法&#xff08;HPO&#xff09;在WSN覆盖中的应用 2.改进点 差分进化 自适应α变异 全局最优引导的动态反向学…

中仕公考:2024年成人高考大专能考事业编吗?

关于2024年成人高考大专学历是否具备报考事业单位编制的资格&#xff0c;相关规定明确地指出&#xff0c;该学历符合国家认证标准&#xff0c;并可在学信网进行验证。持有成人高考大专学历的考生&#xff0c;在满足其他职位需求的条件下&#xff0c;是有资格参加事业编考试的。…

VIM支持C/C++/Verilog/SystemVerilog配置并支持Win/Linux环境的配置

作为一个芯片公司打杂人口&#xff0c;同时兼数字IC和软件&#xff0c;往往需要一个皮实耐打上天入地的编辑器… 一、先附上github路径&#xff0c;方便取走 git clone gitgithub.com:qqqw4549/vim_config_c_verilog.git 二、效果展示 支持ctrl]函数/模块跳转&#xff0c;支持…

书生·浦语大模型实战营之茴香豆:搭建你的 RAG 智能助理

书生浦语大模型实战营之茴香豆&#xff1a;搭建你的 RAG 智能助理 RAG&#xff08;Retrieval Augmented Generation&#xff09;技术&#xff0c;通过检索与用户输入相关的信息&#xff0c;并结合外部知识库来生成更准确、更丰富的回答。解决 LLMs 在处理知识密集型任务时可能遇…

学习CSS Flexbox 玩flexboxfroggy flexboxfroggy1-24关详解

欢迎来到Flexbox Froggy&#xff0c;这是一个通过编写CSS代码来帮助Froggy和朋友的游戏! justify-content 和 align-items 是两个用于控制 CSS Flexbox 布局的属性。 justify-content&#xff1a;该属性用于控制 Flexbox 容器中子项目在主轴&#xff08;水平方向&#xff09;…

C++算法 —— 位运算

一、基本的位运算操作 1.基础位运算操作符 << : 二进制位整体左移 >> : 二进制位整体右移 ~ : 按位取反 & &#xff1a; 按位与 | &#xff1a; 按位或 ^ : 按位异或 &#xff08;无进位相加&#xff09; 2.给一个数n&#xff0c;确定它的二进制表示中第…

聚类算法 | Kmeans:肘方法、Kmeans++、轮廓系数 | DBSCAN

目录 一. 聚类算法划分方式1. 划分式2. 层次式3. 基于密度4. 基于网络5. 基于模型 二. K-means算法1. K-means算法公式表达2. K-means算法流程3. Kmeans算法缺点3.1 肘方法3.2 k-means 算法3.2.1 k-means|| 算法 3.3 Mini Batch K-Means 算法 4. 聚类评估 三. DBSCAN算法1. DBS…

秋招学习数据库LeetCode刷题

数据库基本知识以前学过次数较多&#xff0c;今天看完一遍后都是可以理解的。直接刷Leetcode题吧 牛客上题库刷基础&#xff0c;Leetcode刷 写语句题(争取坚持每日2个sql语句题) 牛客&#xff1a;https://www.nowcoder.com/exam/intelligent?questionJobId10&tagId21015 L…

C#速览入门

C# & .NET C# 程序在 .NET 上运行&#xff0c;而 .NET 是名为公共语言运行时 (CLR) 的虚执行系统和一组类库。 CLR 是 Microsoft 对公共语言基础结构 (CLI) 国际标准的实现。 CLI 是创建执行和开发环境的基础&#xff0c;语言和库可以在其中无缝地协同工作。 用 C# 编写的…

私域必备神器来袭!朋友圈转发一键搞定!

在私域运营中&#xff0c;朋友圈的转发和管理是至关重要的环节。为了能够更好的管理和运营多个微信号的朋友圈&#xff0c;很多人都开始使用微信管理系统来辅助自己开展管理和运营工作。 接下来&#xff0c;就一起来看看微信管理系统的朋友圈管理功能吧&#xff01; 首先&…

π162U31 增强ESD功能,3.0kVrms隔离电压150kbps 六通道数字隔离器 可提高工业应用系统性能

π162U31产品概述&#xff1a; π162U31是一款数字隔离器&#xff0c;π162U31数字隔离器具有出色的性能特 征和可靠性&#xff0c;整体性能优于光耦和基于其他原理的数字隔离器 产品。 智能分压技术(iDivider技术)利用电容分压原理&#xff0c; 在不需要调制和解调的情况下&a…

【测试开发学习历程】python流程控制

前言&#xff1a;写到这里也许自己真的有些疲惫了&#xff0c;但是人生不就是像西西弗斯推石上山一样的枯燥乏味吗&#xff1f; 在python当中的流程控制主要包含以下两部分的内容&#xff1a; 条件判断 循环 1 条件判断 条件判断用if语句实现&#xff0c;if语句的几种格式…

【研发管理】产品经理知识体系-数字化战略

导读: 数字化战略对于企业的长期发展具有重要意义。实施数字化战略需要企业从多个方面进行数字化转型和优化&#xff0c;以提高效率和创新能力&#xff0c;并实现长期竞争力和增长。 目录 1、定义 2、数字化战略必要性 3、数字战略框架 4、数字化转型对产品和服务设计的影响…

京东云轻量云主机8核16G配置租用价格1198元1年、4688元三年

京东云轻量云主机8核16G服务器租用优惠价格1198元1年、4688元三年&#xff0c;配置为8C16G-270G SSD系统盘-5M带宽-500G月流量&#xff0c;华北-北京地域。京东云8核16G服务器活动页面 yunfuwuqiba.com/go/jd 活动链接打开如下图&#xff1a; 京东云8核16G服务器优惠价格 京东云…

截稿倒计时 CCF-B 推荐会议EGSR’24论文摘要4月9日提交

会议之眼 快讯 第35届EGSR 2024 (Eurographics Symposium on Rendering)即欧洲图形学渲染专题讨论会将于 2024 年 7月3日-5日在英国伦敦帝国理工学院举行&#xff01;在EGSR之前&#xff0c;将有一个全新的联合研讨会&#xff0c;即材料外观建模&#xff08;MAM&#xff09;和…

非机构化解析【包含PDF、word、PPT】

此项目是针对PDF、docx、doc、PPT四种非结构化数据进行解析&#xff0c;识别里面的文本和图片。 代码结构 ├── Dockerfile ├── requirements ├── resluts ├── test_data │ ├── 20151202033304658.pdf │ ├── 2020_World_Energy_Data.pdf │ ├── …

JVM字节码与类的加载——class文件结构

文章目录 1、概述1.1、class文件的跨平台性1.2、编译器分类1.3、透过字节码指令看代码细节 2、虚拟机的基石&#xff1a;class文件2.1、字节码指令2.2、解读字节码方式 3、class文件结构3.1、魔数&#xff1a;class文件的标识3.2、class文件版本号3.3、常量池&#xff1a;存放所…

【MATLAB源码-第181期】基于matlab的32QAM调制解调系统频偏估计及补偿算法仿真,对比补偿前后的星座图误码率。

操作环境&#xff1a; MATLAB 2022a 1、算法描述 在通信系统中&#xff0c;频率偏移是一种常见的问题&#xff0c;它会导致接收到的信号频率与发送信号的频率不完全匹配&#xff0c;进而影响通信质量。在调制技术中&#xff0c;QPSK&#xff08;Quadrature Phase Shift Keyi…