[数据结构进阶 C++] 二叉搜索树(BinarySearchTree)的模拟实现

在这里插入图片描述

文章目录

  • 1、二叉搜索树
    • 1.1 二叉搜索数的概念
    • 1.2 二叉搜索树的操作
      • 1.2.1 二叉搜索树的查找
      • 1.2.2 二叉搜索树的插入
      • 1.2.3 二叉搜索树的删除
  • 2、二叉搜索树的应用
    • 2.1 K模型
    • 2.2 KV模型
  • 3、二叉搜索树的性能分析
  • 4、K模型与KV模型完整代码
    • 4.1 二叉搜索树的模拟实现(K模型)
    • 4.2 KV模型的模拟实现

1、二叉搜索树

1.1 二叉搜索数的概念

二叉搜索树又称二叉排序树,它或者是一棵空树,或者是具有以下性质的二叉树:

  • 若它的左子树不为空,则左子树上所有节点的值都小于根节点的值
  • 若它的右子树不为空,则右子树上所有节点的值都大于根节点的值
  • 它的左右子树也分别为二叉搜索树
    我们先给出两个示例:
    在这里插入图片描述
    此二叉树就不是搜索二叉树,根据性质来看,每颗子树也是二叉搜索树,红圈圈起来的地方显然是违背了这个性质。
    在这里插入图片描述
    此搜索二叉树按性质来看是完全满足的,因此此二叉树就是二叉搜索树。

1.2 二叉搜索树的操作

二叉搜索树的操作包含,增删查,下面我们就来分别模拟实现这些接口。

1.2.1 二叉搜索树的查找

思路:
a、从根开始比较,查找,比根大则往右边走查找,比根小则往左边走查找。
b、最多查找高度次,走到到空,还没找到,这个值不存在。
查找比较好理解,这里就不画图了,直接开始写代码。
1.查找的非递归方法:

bool Find(const K& key)
{Node* root = _root;while (root){if (key > root->_key)root = root->_right;else if (key < root->_key)root = root->_left;elsereturn true;}return false;
}

2.查找的递归方法:

bool FindR(const K& key)
{return _FindR(_root, key);
}bool _FindR(Node* root, const K& key)
{if (nullptr == root)return false;if (key > root->_key)return _FindR(root->_right, key);else if (key < root->_key)return _FindR(root->_left, key);elsereturn true;
}

在递归查找中,我们用户在使用的时候只需要填入要查找的数字,但是我们的查找接口需要两个参数,开始节点与查找数值,因此我们在 FindR 下再封装一层 _FindR 就可以实现了。

1.2.2 二叉搜索树的插入

二叉搜索树的插入思路:
a. 树为空,则直接新增节点,赋值给root指针
b. 树不空,按二叉搜索树性质查找插入位置,插入新节点
我们先来以图例演示一遍过程:
对下面这棵树插入一个值为11的节点
在这里插入图片描述

这里是成功插入的情况,具体过程是上面的图例,我们在梳理一遍:
1、首先,我们用两个结点指针 parent和cur ,cur不断寻找正确插入位置,parent不断记下来cur的父节点指针;
2、当 cur 找到正确位置之后,先 new 一个节点对象给 cur,此时 new 出来的对象只是给了 cur 这个指针变量,并没有链接起来;
3、判断要插入的位置是 parent 的左孩子还是右孩子,再将其链接到正确位置插入就算完成了。
失败情况:
因为是二叉搜索树,节点左子树的值全小于节点的值,右子树的值全大于节点的值,不存在相等的情况,因此当找到一个节点的值与要插入的值相等时,就是插入失败,此时返回false。
根据上面的图示以及过程的梳理我们来写非递归版本的代码:

bool Insert(const K& key)
{if (nullptr == _root){_root = new Node(key);return true;}Node* parent = nullptr;Node* cur = _root;while (cur){if (key > cur->_key){parnet = cur;cur = cur->_right;}else if (key < cur->_key){parent = cur;cur = cur->_left;}else{return false;}}cur = new Node(key);if (key > parent->_key){parent->_right = cur;}else{parent->_left = cur;}return true;
}

insert接口我们只提供非递归版本的。

1.2.3 二叉搜索树的删除

首先查找元素是否在二叉搜索树中,如果不存在,则返回, 否则要删除的结点可能分下面四种情
况:
a. 要删除的结点无孩子结点
b. 要删除的结点只有左孩子结点
c. 要删除的结点只有右孩子结点
d. 要删除的结点有左、右孩子结点

看起来有待删除节点有4中情况,实际情况a可以与情况b或者c合并起来,因此真正的删除过程
如下:
情况b:删除该结点且使被删除节点的双亲结点指向被删除节点的左孩子结点–直接删除
情况c:删除该结点且使被删除节点的双亲结点指向被删除结点的右孩子结点–直接删除
情况d:在它的右子树中寻找中序下的第一个结点(关键码最小),用它的值填补到被删除节点
中,再来处理该结点的删除问题–替换法删除
1、删除节点没有左孩子

在这里插入图片描述

// 1、左为空
if (cur->_left == nullptr)
{if (cur == _root){_root = cur->_right;}if (parent->_left == cur){parent->_left = cur->_right;}else{parent->_right = cur->_right;}
}

2、删除节点没有右孩子
在这里插入图片描述

// 2、右为空
else if (cur->_right == nullptr)
{if (cur == _root){_root = cur->_left;}		if (parent->_left == cur){parent->_left = cur->_left}else{parent->_right = cur->_left;}
}

3、删除节点左右孩子都存在
在这里插入图片描述

// 3、左右都不为空
else
{Node* parent = cur;Node* subLeft = cur->_right;while (subLeft->_left){parent = subLeft;subLeft = subLeft->_left;}swap(cur->_key, subLeft->_key);if (subLeft == parent->_left)parent->_left = subLeft->_right;elseparent->_right = subLeft->_right;delete(subLeft);
}

整个删除接口合在一起的代码:

bool Erase(const K& key)
{Node* parent = nullptr;Node* cur = _root;while (cur){if (key > cur->_key){parnet = cur;cur = cur->_right}else if (key < cur->_key){parent = cur;cur = cur->_left;}else{// 1、左为空if (cur->_left == nullptr){if (cur == _root){_root = cur->_right;}if (parent->_left == cur){parent->_left = cur->_right;}else{parent->_right = cur->_right;}delete(cur);}// 2、右为空else if (cur->_right == nullptr){if (cur == _root){_root = cur->_left;}		if (parent->_left == cur){parent->_left = cur->_left}else{parent->_right = cur->_left;}delete(cur);}// 3、左右都不为空else{Node* parent = cur;Node* subLeft = cur->_right;while (subLeft->_left){parent = subLeft;subLeft = subLeft->_left;}swap(cur->_key, subLeft->_key);if (subLeft == parent->_left)parent->_left = subLeft->_right;elseparent->_right = subLeft->_right;delete(subLeft);}}}	
}

递归版本:
递归版本与非递归版本类似, 非递归版本中使用while循环来找key位置,递归就是不断调用自身来找key位置当找到后依然分三种情况来分析:左为空、右为空、左右都不为空。
递归版本中用引用来接受传来的root,因此不用考虑链接问题也就不存在判断删除位置是父节点的左还是右,直接修改root即可。
1、左为空/左右都为空:先记下要删除的节点,再修改root,最后释放删除的节点。 Node* del = root; root = root->_right; delete(del);
2、右为空:先记下要删除的节点,再修改root,最后释放删除的节点。Node* del = root; root = root->_left; delete(del);
3、左右都不为空:先找到右子树的最左节点(最小值),交换 root->_key与subLeft->_key, 右子树的最左节点一定是叶子节点,因此删除subLeft直接再去调用函数自身即可,即再来一次左右都为空(左为空)的删除。

bool EraseR(const K& key)
{return _EraseR(_root, key);
}
bool _EraseR(Node*& root, const K& key)
{if (root == nullptr)return false;if (root->_key < key){_EraseR(root->_right, key);}else if (root->_key > key){_EraseR(root->_left, key);}else{if (root->_left == nullptr){Node* del = root;root = root->_right;delete(del);// 必须释放,不释放会导致内存泄漏return true;}else if (root->_right == nullptr){Node* del = root;root = root->_left;delete(del);return true;}else{Node* subLeft = root->_right;while (subLeft->_left){subLeft = subLeft->_left;}swap(root->_key, subLeft->_key);return _EraseR(root->_right, key);}}
}

2、二叉搜索树的应用

2.1 K模型

K模型:K模型即只有key作为关键码,结构中只需要存储Key即可,关键码即为需要搜索到的值。
比如:给一个单词word,判断该单词是否拼写正确,具体方式如下:

  • 以词库中所有单词集合中的每个单词作为key,构建一棵二叉搜索树
  • 在二叉搜索树中检索该单词是否存在,存在则拼写正确,不存在则拼写错误。

2.2 KV模型

KV模型:每一个关键码key,都有与之对应的值Value,即<Key, Value>的键值对。 该种方式在现实生活中非常常见:
● 比如英汉词典就是英文与中文的对应关系,通过英文可以快速找到与其对应的中文,英文单词与其对应的中文<word, chinese>就构成一种键值对;
● 再比如统计单词次数,统计成功后,给定单词就可快速找到其出现的次数,单词与其出现次数就是<word, count>就构成一种键值对。

3、二叉搜索树的性能分析

插入和删除操作都必须先查找,查找效率代表了二叉搜索树中各个操作的性能。
对有n个结点的二叉搜索树,若每个元素查找的概率相等,则二叉搜索树平均查找长度是结点在二叉搜索树的深度的函数,即结点越深,则比较次数越多。
但对于同一个关键码集合,如果各关键码插入的次序不同,可能得到不同结构的二叉搜索树:
在这里插入图片描述
最优情况下,二叉搜索树为完全二叉树(或者接近完全二叉树),其平均比较次数为:log_2 N
最差情况下,二叉搜索树退化为单支树(或者类似单支),其平均比较次数为:N

最差情况是退化为单支树,性能就变得很差了,因为后续我们将改进搜索二叉树的插入与删除,来实现平衡二叉搜索树,即AVL树与红黑树(RB树)。

4、K模型与KV模型完整代码

4.1 二叉搜索树的模拟实现(K模型)

namespace key
{template <class K>struct BSTreeNode{BSTreeNode* _left;BSTreeNode* _right;K _key;BSTreeNode(const K& key):_left(nullptr), _right(nullptr), _key(key){}};template <class K>class BSTree{typedef BSTreeNode<K> Node;public:bool Insert(const K& key){if (_root == nullptr){_root = new Node(key);return true;}Node* parent = nullptr;Node* cur = _root;while (cur){if (cur->_key < key){parent = cur;cur = cur->_right;}else if (cur->_key > key){parent = cur;cur = cur->_left;}else{return false;}}cur = new Node(key);if (parent->_key < key){parent->_right = cur;}else{parent->_left = cur;}return true;}bool Find(const K& key){Node* cur = _root;while (cur){if (cur->_key < key){cur = cur->_right;}else if (cur->_key > key){cur = cur->_left;}else{return true;}}return true;}bool Erase(const K& key){Node* parent = nullptr;Node* cur = _root;while (cur){if (cur->_key < key){parent = cur;cur = cur->_right;}else if (cur->_key > key){parent = cur;cur = cur->_left;}else{// 删除分情况讨论:// 1. 左为空(左右都为空);2. 右为空;3.左右都不为空if (cur->_left == nullptr)// 左为空{if (cur == _root)// 根节点是删除的节点情况_root = cur->_right;else{if (cur == parent->_left){parent->_left = cur->_right;}else{parent->_right = cur->_right;}}delete(cur);}else if (cur->_right == nullptr)// 右为空{if (cur == _root)// 根节点是删除的节点情况_root = cur->_left;else{if (cur == parent->_left){parent->_left = cur->_left;}else{parent->_right = cur->_left;}}delete(cur);}else// 左右都不为空{// 替换法,找左子树最大 / 右子树最小来替换curNode* parent = cur;Node* subLeft = cur->_right;while (subLeft->_left){parent = subLeft;subLeft = subLeft->_left;}swap(cur->_key, subLeft->_key);if (subLeft == parent->_left)parent->_left = subLeft->_right;else// 处理删除根节点的情况parent->_right = subLeft->_right;delete(subLeft);}return true;}}return false;}void InOrder(){_InOrder(_root);cout << endl;}//递归bool InsertR(const K& key){return _InsertR(_root, key);}bool FindR(const K& key){return _FindR(_root, key);}bool EraseR(const K& key){return _EraseR(_root, key);}// C++11BSTree() = default; // 强制编译器生成默认构造BSTree(const BSTree<K>& t){_root = Copy(t._root);}// 赋值重载,现代写法BSTree<K>& operator=(BSTree<K> t){swap(_root, t._root);return *this;}~BSTree(){Destroy(_root);}private:Node* Copy(Node* root){if (root == nullptr)return nullptr;Node* newRoot = new Node(root->_key);newRoot->_left = Copy(root->_left);newRoot->_right = Copy(root->_right);return newRoot;}void Destroy(Node*& root){if (root == nullptr)return;Destroy(root->_left);Destroy(root->_right);delete root;root = nullptr;}bool _InsertR(Node*& root, const K& key){if (root == nullptr){root = new Node(key);return true;}if (root->_key < key){_InsertR(root->_right, key);}else if (root->_key > key){_InsertR(root->_left, key);}else{return false;}}bool _FindR(Node* root, const K& key){if (root == nullptr){return false;}if (root->_key < key){_FindR(root->_right, key);}else if (root->_key > key){_FindR(root->_left, key);}else{return true;}}bool _EraseR(Node*& root, const K& key){if (root == nullptr)return false;if (root->_key < key){_EraseR(root->_right, key);}else if (root->_key > key){_EraseR(root->_left, key);}else{if (root->_left == nullptr){Node* del = root;root = root->_right;delete(del);// 必须释放,不释放会导致内存泄漏return true;}else if (root->_right == nullptr){Node* del = root;root = root->_left;delete(del);return true;}else{Node* subLeft = root->_right;while (subLeft->_left){subLeft = subLeft->_left;}swap(root->_key, subLeft->_key);return _EraseR(root->_right, key);}}}void _InOrder(Node* root){if (root == nullptr)return;_InOrder(root->_left);cout << root->_key << " ";_InOrder(root->_right);}private:Node* _root = nullptr;};
};

4.2 KV模型的模拟实现

namespace kv
{template <class K, class V>struct BSTreeNode{BSTreeNode<K, V>* _left;BSTreeNode<K, V>* _right;K _key;V _val;BSTreeNode(const K& key, const V& val):_left(nullptr), _right(nullptr), _key(key), _val(val){}};template <class K, class V>class BSTree{typedef BSTreeNode<K, V> Node;public:bool Insert(const K& key, const V& val){if (_root == nullptr){_root = new Node(key, val);return true;}Node* parent = nullptr;Node* cur = _root;while (cur){if (cur->_key < key){parent = cur;cur = cur->_right;}else if (cur->_key > key){parent = cur;cur = cur->_left;}else{return false;}}cur = new Node(key, val);if (parent->_key < key){parent->_right = cur;}else{parent->_left = cur;}return true;}Node* Find(const K& key){Node* cur = _root;while (cur){if (cur->_key < key){cur = cur->_right;}else if (cur->_key > key){cur = cur->_left;}else{return cur;}}return nullptr;}bool Erase(const K& key){Node* parent = nullptr;Node* cur = _root;while (cur){if (cur->_key < key){parent = cur;cur = cur->_right;}else if (cur->_key > key){parent = cur;cur = cur->_left;}else{// 删除分情况讨论:// 1. 左为空(左右都为空);2. 右为空;3.左右都不为空if (cur->_left == nullptr)// 左为空{if (cur == _root)// 根节点是删除的节点情况_root = cur->_right;else{if (cur == parent->_left){parent->_left = cur->_right;}else{parent->_right = cur->_right;}}delete(cur);}else if (cur->_right == nullptr)// 右为空{if (cur == _root)// 根节点是删除的节点情况_root = cur->_left;else{if (cur == parent->_left){parent->_left = cur->_left;}else{parent->_right = cur->_left;}}delete(cur);}else// 左右都不为空{// 替换法,找左子树最大 / 右子树最小来替换curNode* parent = cur;Node* subLeft = cur->_right;while (subLeft->_left){parent = subLeft;subLeft = subLeft->_left;}swap(cur->_key, subLeft->_key);if (subLeft == parent->_left)parent->_left = subLeft->_right;else// 处理删除根节点的情况parent->_right = subLeft->_right;delete(subLeft);}return true;}}return false;}void InOrder(){_InOrder(_root);cout << endl;}private:void _InOrder(Node* root){if (root == nullptr)return;_InOrder(root->_left);cout << root->_key << " : " << root->_val << endl;_InOrder(root->_right);}private:Node* _root = nullptr;};
};

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

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

相关文章

【Java】编写一个简单的Servlet程序

Java Servlet 是运行在 Web 服务器或应用服务器上的程序&#xff0c;它是作为来自 Web 浏览器或其他 HTTP 客户端的请求和 HTTP 服务器上的数据库或应用程序之间的中间层。 使用 Servlet&#xff0c;可以收集来自网页表单的用户输入&#xff0c;呈现来自数据库或者其他源的记录…

求交错序列前N项和 C语言xdoj149

题目描述&#xff1a;编写程序&#xff0c;计算交错序列1-2/33/5-4/75/9-6/11…的前N项之和。 输入格式&#xff1a;输入一个正整数 输出格式&#xff1a;输出计算结果&#xff0c;结果保留三位小数 示例&#xff1a; 输入&#xff1a;5 输出&#xff1a;0.917 #include <st…

基于深度学习的森林火焰烟雾检测系统(含UI界面,yolov8、Python代码,数据集)

项目介绍 项目中所用到的算法模型和数据集等信息如下&#xff1a; 算法模型&#xff1a;     yolov8 yolov8主要包含以下几种创新&#xff1a;         1. 添加注意力机制&#xff08;SE、CBAM等&#xff09;         2. 修改可变形卷积&#xff08;DySnake-主干c…

二分查找法详解(6种变形)

前言 在之前的博客中&#xff0c;我给大家介绍了最基础的二分查找法&#xff08;没学的话点我点我&#xff01;&#xff09; 今天我将带大家学习二分法的六种变形如何使用&#xff0c;小伙伴们&#xff0c;快来开始今天的学习吧&#xff01; 文章目录 1&#xff0c;查找第一个…

Ubuntu 常用命令之 du 命令用法介绍

&#x1f4d1;Linux/Ubuntu 常用命令归类整理 Ubuntu系统下的du命令是一个用来估计和显示文件和目录所占用的磁盘空间的命令。du是“disk usage”的缩写&#xff0c;这个命令可以帮助用户了解磁盘被哪些文件和目录使用。 du命令的常见参数有 -a&#xff1a;列出所有文件和目…

Python实验报告十一、自定义类模拟三维向量及其运算

一、实验目的&#xff1a; 1、了解如何定义一个类。 2、了解如何定义类的私有数据成员和成员方法。 3、了解如何使用自定义类实例化对象。 二、实验内容&#xff1a; 定义一个三维向量类&#xff0c;并定义相应的特殊方法实现两个该类对象之间的加、减运算&#xff08;要…

【数据结构和算法】最大连续1的个数 III

其他系列文章导航 Java基础合集数据结构与算法合集 设计模式合集 多线程合集 分布式合集 ES合集 文章目录 其他系列文章导航 文章目录 前言 一、题目描述 二、题解 2.1 方法一&#xff1a;滑动窗口 2.2 滑动窗口解题模板 三、代码 3.1 方法一&#xff1a;滑动窗口 四、…

Echarts 仪表盘实现平均值和实时值

const gaugeData [{value: 20,name: 互动发起率实时值,title: {offsetCenter: [-25%, 10%]},detail: {offsetCenter: [-25%, 18%]}},{value: 40,name: 互动发起平均值,title: {offsetCenter: [25%, 10%]},detail: {offsetCenter: [25%, 18%]}},// {// value: 60,// name: …

Java_集合进阶Map实现类

一、Map集合 已经学习了Map集合的常用方法&#xff0c;以及遍历方式。 下面学习的是Map接口下面的是三个实现类HashMap、LinkedHashMap、TreeMap。实际上这三个实现类并没有什么特有方法需要我们学习&#xff0c;它们的方法就是前面学习Map的方法。这里我们主要学习它们的底层…

机器学习——分类评价指标

【说明】文章内容来自《机器学习——基于sklearn》&#xff0c;用于学习记录。若有争议联系删除。 1、评价指标 对于模型的评价往往会使用损失函数和评价指标&#xff0c;两者的本质是一致的。一般情况下&#xff0c;损失函数应用于训练过程&#xff0c;而评价指标应用于测试过…

深入浅出堆排序: 高效算法背后的原理与性能

&#x1f3ac; 鸽芷咕&#xff1a;个人主页 &#x1f525; 个人专栏: 《linux深造日志》 《高效算法》 ⛺️生活的理想&#xff0c;就是为了理想的生活! &#x1f4cb; 前言 &#x1f308;堆排序一个基于二叉堆数据结构的排序算法&#xff0c;其稳定性和排序效率在八大排序中也…

maven学习和maven聚合工程搭建

1.学习maven maven的概念 项目管理工具 &#xff0c;对jar进行依赖管理&#xff0c;编译&#xff0c;打包&#xff0c;单元测试&#xff0c;安装&#xff0c;部署&#xff0c;贯穿整个项目 为什么要学maven 要解决的问题&#xff1a; 不同的开发工具开发出来的项目目录结构…

计算机基础:网络基础

目录 一.网线制作 1.制作所需要工具 网线制作标准 ​编辑 2.水晶头使用 3.网线钳使用 4.视频教学 二.集线器、交换机介绍 1.OSI七层模型 2.TCP/IP四层参考模型 3.集线器、交换机。路由器介绍 集线器 交换机 路由器 区别 三.路由器的配置 1.路由器设置 说明书 设…

IDEA版SSM入门到实战(Maven+MyBatis+Spring+SpringMVC) -Spring的AOP前奏

第一章 AOP前奏 1.1 代理模式 代理模式&#xff1a;我们需要做一件事情&#xff0c;又不期望自己亲力亲为&#xff0c;此时&#xff0c;可以找一个代理【中介】 我们【目标对象】与中介【代理对象】不能相互转换&#xff0c;因为是“兄弟”关系 1.2 为什么需要代理【程序中…

【大模型】快速体验百度智能云千帆AppBuilder搭建知识库与小助手

文章目录 前言千帆AppBuilder什么是千帆AppBuilderAppBuilder能做什么 体验千帆AppBuilderJava知识库高考作文小助手 总结 前言 前天&#xff0c;在【百度智能云智算大会】上&#xff0c;百度智能云千帆AppBuilder正式开放服务。这是一个AI原生应用开发工作台&#xff0c;可以…

技术分享-Jenkins

持续集成及Jenkins介绍 软件开发生命周期叫SDLC&#xff08;Software Development Life Cycle&#xff09;&#xff0c;集合了计划、开发、测试、部署过程。 在平常的开发过程中&#xff0c; 需要频繁地&#xff08;一天多次&#xff09;将代码集成到主干&#xff0c;这个叫持…

实力强的大模型都有哪些超能力?

实力强的大模型都有哪些超能力&#xff1f; 前几日&#xff0c;人工智能研究公司OpenAI CEO山姆奥特曼&#xff08;Sam Altman&#xff09;在谈及人工智能这项技术的潜力以及人们对它的担忧时&#xff0c;曾表示“AI发展速度快得吓人&#xff0c;就像停不下来的龙卷风。”可见&…

软件测试:最强面试题整理出炉附答案,一点点小总结,建议收藏

一、Web自动化测试 1.Selenium中hidden或者是display &#xff1d; none的元素是否可以定位到&#xff1f; 不能,可以写JavaScript将标签中的hidden先改为0&#xff0c;再定位元素 2.Selenium中如何保证操作元素的成功率&#xff1f;也就是说如何保证我点击的元素一定是可以点…

CMU\谷歌等最新研究综述:面向通用机器人的基础模型

构建能够在任何环境中无缝操作、使用各种技能处理不同物体和完成多样化任务的通用机器人&#xff0c;一直是人工智能领域的长期目标。然而&#xff0c;不幸的是&#xff0c;大多数现有的机器人系统受到限制——它们被设计用于特定任务、在特定数据集上进行训练&#xff0c;并在…

deCasteljau 递推

递推函数 P i r ( t ) ( 1 − t ) P i r − 1 ( t ) t P i 1 r − 1 ( t ) &#xff0c; \begin{equation} \bm{P}_{i}^r (t) (1-t) \bm{P}_{i}^{r-1} (t) t \bm{P}_{i1}^{r-1} (t)&#xff0c; \end{equation} Pir​(t)(1−t)Pir−1​(t)tPi1r−1​(t)&#xff0c;​​ …