【高阶数据结构】二叉树搜索树 {概念;实现:核心结构,增删查,默认成员函数;应用:K模型和KV模型;性能分析;相关练习}

news2025/1/20 4:42:27

二叉搜索树

一、二叉搜索树的概念

在这里插入图片描述

二叉搜索树又称二叉排序树,它可以是一棵空树,若果不为空则满足以下性质:

  1. 若它的左子树不为空,则左子树上所有节点的值都小于根节点的值
  2. 若它的右子树不为空,则右子树上所有节点的值都大于根节点的值
  3. 它的左右子树也分别为二叉搜索树
  4. 二叉搜索树中不允许出现相同的键值

提示:二叉搜索树的附加功能是排序和去重。对于一个二叉搜索树,按照左子树-根节点-右子树的顺序进行中序遍历,得到的序列就是有序的。


二、二叉树搜索树的实现

2.1 核心结构

template <class K>
struct BSTreeNode{ //搜索二叉树的节点
  typedef BSTreeNode<K> Node;
  Node *_left;
  Node *_right;
  K _key;

  BSTreeNode(const K &key = K())
    :_left(nullptr), //必须将指针初始化为nullptr,防止出现野指针问题。
    _right(nullptr),
    _key(key)
  {}
}; 

template <class K>
class BSTree{ //搜索二叉树
  typedef BSTreeNode<K> Node;
  Node *_root = nullptr;
    
public:
  BSTree() = default; //C++11的用法:使用了default关键字,表示使用编译器自动生成的默认构造函数
    
  void InOrder(){  //中序遍历多套一层是因为递归调用需要传递根节点指针,而根节点指针_root是private权限,在类外不能访问。
    _InOrder(_root);  
    cout << endl;
  }
    
  //......
};
template <class K>
  void BSTree<K>::_InOrder(Node *root){ //中序遍历
    if(root == nullptr) return;
    _InOrder(root->_left);
    cout << root->_key << " ";
    _InOrder(root->_right);
  }

2.2 查找

  1. 从根开始比较,如果key比根大则到右树去找;如果key比根小则到左树去找;
  2. 最多查找高度次。如果走到空节点还未找到,则说明这个键值不存在。
//迭代法查找:
template <class K>
   bool BSTree<K>::Find(const K &key){
    //如果是空树,直接返回false;
    if(_root == nullptr) return false;
       
    Node *cur = _root;
    while(cur != nullptr)
    {
      if(key > cur->_key) //如果键值大,就到右树去找
      {
        cur = cur->_right;
      }
      else if(key < cur->_key) //如果键值小,就到左树去找
      {
        cur = cur->_left;
      }
      else{
        //找到返回true
        return true;
      }
    }
    //如果走到空节点还未找到,则说明这个键值不存在,返回false
    return false;
  }

//递归法查找:
template <class K>
  bool BSTree<K>::_rFind(Node *root, const K &key){
    if(root == nullptr) return false; //如果是空树或者走到空节点还未找到,返回false
    if(key > root->_key) return _rFind(root->_right, key); //如果键值大,就到右树去找
    else if(key < root->_key) return _rFind(root->_left, key); //如果键值小,就到左树去找
    else return true;//找到返回true
  }

2.3 插入

插入的具体过程如下:

  1. 树为空,则直接新增节点,赋值给root指针
  2. 树不为空,按二叉搜索树性质查找插入位置,插入新节点:
  3. 如果找到相同的键值,则不进行插入。
  4. 直到找到合适的空位置,才能进行插入操作。
//迭代法插入:
template <class K>
bool BSTree<K>::Insert(const K &key){
    //树为空,直接进行插入:
    if(_root == nullptr)
    {
      _root = new Node(key);
      return true;
    }
    
    //树不为空,按二叉搜索树性质查找插入位置,插入新节点:
    Node *cur = _root;
    Node *parent = _root;
    while(cur != nullptr)
    {
      if(key > cur->_key) //如果键值大,就到右树去找
      {
        parent = cur; //移动cur指针前记录父节点指针parent,便于下一步插入操作的连接。
        cur = cur->_right;
      }
      else if(key < cur->_key) //如果键值小,就到左树去找
      {
        parent = cur;
        cur = cur->_left;
      }
      else{
        return false; //如果找到相同的键值,则插入失败。
      }
    }
    
    //直到找到合适的空位置,才能进行插入操作:
    if(key > parent->_key) //判断该位置是父节点的左节点还是右节点
    {
      parent->_right = new Node(key);
    }
    else{
      parent->_left = new Node(key);
    }
    return true;
}

//递归法插入:
/*传引用的好处:root是父节点内_left,_right指针的引用(对于空树就是_root的引用),修改root就是修改父节点的_left,_right指针。所以我们不需要再记录父节点的指针,也不需要再判断该位置是父节点的左节点还是右节点。*/
template <class K>
  bool BSTree<K>::_rInsert(Node* &root, const K &key){
    //如果树为空或者找到了合适的空位置,进行插入操作:
    if(root == nullptr)
    {
      root = new Node(key);
      return true;
    }
      
    if(key > root->_key)  //如果键值大,就到右树去插入
        return _rInsert(root->_right, key);
    else if(key < root->_key)  //如果键值小,就到左树去插入
        return _rInsert(root->_left, key);
    else  //如果找到相同的键值,则插入失败。
        return false;
  }

2.4 删除

首先查找元素是否在二叉搜索树中,如果不存在,则返回, 否则要删除的结点可能分下面四种情况:

  1. 要删除的结点是叶节点
  2. 要删除的结点只有左结点
  3. 要删除的结点只有右结点
  4. 要删除的结点有左、右结点

看起来待删除节点有4中情况,实际情况1可以与情况2或者3合并起来(使父节点指向nullptr),因此真正的删除过程如下:

  1. 使父结点指向被删除节点的左结点;然后删除该结点;–直接删除
  2. 使父结点指向被删除节点的右结点;然后删除该结点;–直接删除
  3. 找到被删除节点左树的最大值(最右)或右树的最小值(最左);将其key值与被删除节点的key值交换,保证搜索树的结构;最后删除该节点(最大或最小节点)–替换法删除
//迭代法删除:
template <class K>
  bool BSTree<K>::Erase(const K &key){
    Node *cur = _root;
    Node *parent = _root; 
    while(cur != nullptr)
    {
      if(key > cur->_key)
      {
        parent = cur; //移动cur指针前记录父节点指针parent,便于下一步删除操作的连接。
        cur = cur->_right;
      }
      else if(key < cur->_key)
      {
        parent = cur;
        cur = cur->_left;
      }
      else{
        //情况1,2,3 直接删除
        if(cur->_left == nullptr || cur->_right == nullptr)
        {
          DelNode(parent, cur);
        }
        else{
          //情况4 替换法删除
          Node *lmaxp = cur; //如果cur->left就是左树的最大值,lmaxp应该指向cur
          Node *lmax = cur->_left; //lmax找左树的最大值,即左树的最右节点。
          while(lmax->_right != nullptr) 
          {
            lmaxp = lmax; //移动lmax指针前记录父节点指针lmaxp,便于下一步删除操作的连接。
            lmax = lmax->_right;
          } 
          swap(cur->_key, lmax->_key); //将其key值与被删除节点的key值交换,保证搜索树的结构
        
          //最后删除该节点。
          //注意:lmax指向最右节点,但该节点可能有左树;即使是叶节点,删除后也要接nullptr;
          //因此要调用DelNode删除前进行连接。替换法删除实际是将问题转化为情况1或2或3
          DelNode(lmaxp, lmax); 
        }
        return true; //完成删除马上返回
      }
    }
     //如果是空树,或者找不到要删除的元素,删除失败。
     return false; 
  }

//直接删除节点
template <class K>
  void BSTree<K>::DelNode(Node *parent, Node *cur){
    if(cur->_left == nullptr) //要删除的结点只有右结点(或者是叶节点);使其父结点指向其右结点(叶节点指向空);
    {
      if(cur == _root) //如果要删除的是根节点,需要特殊处理。
      {
        _root = cur->_right;
      }
      else if(parent->_left == cur) //判断要删除的结点是父节点的左节点还是右节点
      {
        parent->_left = cur->_right;
      }
      else{
        parent->_right = cur->_right;
      }
    }
    else if(cur->_right == nullptr) //要删除的结点只有左结点;使父结点指向其左结点;
    {
      if(cur == _root)
      {
        _root = cur->_left;
      }
      else if(parent->_left == cur)
      {
        parent->_left = cur->_left;
      }
      else{
        parent->_right = cur->_left;
      }
    }
    //最后释放删除节点
    delete cur;
  }

//递归法删除:
/*传引用的好处:root是父节点内_left,_right指针的引用,修改root就是修改父节点的_left,_right指针。所以我们不需要再记录父节点的指针,也不需要再判断该位置是父节点的左节点还是右节点。*/
template <class K>
  bool BSTree<K>::_rErase(Node* &root, const K &key){
    //如果是空树,或者找不到要删除的元素,删除失败。
    if(root == nullptr) return false;
      
    if(key > root->_key) 
        _rErase(root->_right, key);
    else if(key < root->_key) 
        _rErase(root->_left, key);
    else{
      Node *del = root; //记录要删除的节点
      //情况1,2,3 直接删除
      if(root->_left == nullptr)
      {
        root = root->_right;
      }
      else if(root->_right == nullptr)
      {
        root = root->_left; 
      }
      else{
        //情况4 替换法删除 
        Node *rmin = root->_right; //rmin找右树的最小值,即右树的最左节点。
        while(rmin->_left!=nullptr)
        {
          rmin = rmin->_left;
        }
        swap(rmin->_key, root->_key); //将其key值与被删除节点的key值交换,保证搜索树的结构
        
        //最后删除该节点。
        //注意:rmin指向最左节点,但该节点可能有右树;即使是叶节点,删除后也要接nullptr;
        //因此要递归调用_rErase删除前进行连接。替换法删除实际是将问题转化为情况1或2或3
        //交换后,已经不能从根节点开始找key了(key的位置已经不符合搜索树结构);
        //应该从右子树的根节点开始找(key的位置在右子树中仍符合搜索树结构);
        return _rErase(root->_right, key);
      }
      //释放节点
      delete del;
      return true;
    }
  }

2.5 默认成员函数

template <class K>
class BSTree{
  typedef BSTreeNode<K> Node;
  Node *_root = nullptr;
public:
  BSTree() = default; //使用编译器自动生成的默认构造函数

  //多套一层是因为递归调用需要传递根节点指针,而根节点指针_root是private权限,在类外不能访问。析构同理。
  BSTree(const BSTree<K> &bst){
    _root = _Copy(bst._root);
  }

  BSTree<K>& operator=(BSTree<K> bst){ //复用拷贝构造
    swap(bst._root, _root);
    return *this;
  }
  
  ~BSTree(){
    _Destroy(_root);
  }
  
  //......
};

拷贝构造

  template <class K>
  Node* BSTree<K>::_Copy(Node *root){
    if(root == nullptr) return nullptr;
    Node *copyroot = new Node(root->_key); //前序遍历拷贝,保证两棵树的结构顺序完全相同
    copyroot->_left = _Copy(root->_left);
    copyroot->_right = _Copy(root->_right);
    return copyroot;
  }

析构

  template <class K>
  void BSTree<K>::_Destroy(Node *root){
    if(root == nullptr) return;
    _Destroy(root->_left); //析构必须采用后序遍历
    _Destroy(root->_right);
    delete root;
  } 

三、二叉搜索树的应用

3.1 K模型

K模型:K模型即只有key作为关键码,结构中只需要存储Key即可,关键码即为需要搜索到的值。

比如:给一个单词word,判断该单词是否拼写正确,具体方式如下:(示例代码:wordchecker.cc)

  1. 以词库中所有单词集合中的每个单词作为key,构建一棵二叉搜索树
  2. 在二叉搜索树中检索该单词是否存在,存在则拼写正确,不存在则拼写错误。
//wordchecker.cc
#include "BSTree_K.hpp"
#include <iostream>
#include <string>
using namespace std;

int main(){
  string tmp[] = {"search", "word", "vector", "string", "dictionary", "list", "binary"};
  BSTree<string> lib;
  for(string &e : tmp)
  {
    lib.Insert(e);
  }
  lib.InOrder();
  string input;
  while(cin >> input)
  {
    bool ret = lib.Find(input);
    if(ret)
    {
      cout << "拼写正确!" << endl;
    }
    else{
      cout << "拼写错误!" << endl;
    }
  }
  return 0;
}

3.2 KV模型

KV模型:每一个关键码key,都有与之对应的值Value,即<Key, Value>的键值对。

KV模型中key为关键码,二叉搜索树构建过程中的比较仍以key值为准。value只是一个对应值,附加值。

该种方式在现实生活中非常常见:

  • 比如英汉词典就是英文与中文的对应关系,通过英文可以快速找到与其对应的中文,英文单词与其对应的中文<word, chinese>就构成一种键值对;(示例代码:dictionary.cc)
  • 再比如统计单词次数,统计成功后,给定单词就可快速找到其出现的次数,单词与其出现次数就是<word, count>就构成一种键值对。(示例代码:wordcounter.cc)

将二叉搜索树改造成KV模型:

//将二叉搜索树改造成KV模型
//只有少量改动,不变的内容省略不写。
#include <iostream>
using namespace std;

template <class K, class V>
struct BSTreeNode{
  typedef BSTreeNode<K, V> Node;
  Node *_left;
  Node *_right;
  K _key;
  V _val; //加了一个value

  BSTreeNode(const K &key = K(), const V &val = V())
    :_left(nullptr),
    _right(nullptr),
    _key(key),
    _val(val)
  {}
}; 

template <class K, class V>
class BSTree{
  typedef BSTreeNode<K, V> Node;
  Node *_root = nullptr;
    
public:
  bool Insert(const K &key, const V &val);
  
  Node* Find(const K &key);

  //......
  
private:
  Node* _Copy(Node *root);
     
  //......
};

template <class K, class V>
bool BSTree<K,V>::Insert(const K &key, const V &val){
    if(_root == nullptr)
    {
      _root = new Node(key, val); //插入时要初始化value
      return true;
    }
    //......
    if(key > parent->_key)
    {
      parent->_right = new Node(key, val);
    }
    else{
      parent->_left = new Node(key, val);
    }
    return true;
}
  
template <class K, class V>
    BSTreeNode<K,V>* BSTree<K,V>::Find(const K &key){
    if(_root == nullptr) return nullptr;
    Node *cur = _root;
    while(cur != nullptr)
    {
      //......
      else{
        return cur; //找到返回节点指针,便于查看和修改value
      }
    }
    return nullptr;
  }
  
template <class K, class V>
  BSTreeNode<K,V>* BSTree<K,V>::_Copy(Node *root){
    if(root == nullptr) return nullptr;
    Node *copyroot = new Node(root->_key, root->_val);  //拷贝也要复制value
    //......
  }

template <class K, class V>
  void BSTree<K,V>::_InOrder(Node *root){
    if(root == nullptr) return;
    _InOrder(root->_left);
    cout << root->_key << "-->" << root->_val << endl;
    _InOrder(root->_right);
  }

KV模型的应用示例:

//dictionary.cc
#include "BSTree_KV.hpp"
#include <iostream>
#include <string>
using namespace std;

int main(){
  BSTree<string, string> dct;
  dct.Insert("buffer", "缓冲器");
  dct.Insert("error", "错误");
  dct.Insert("derive", "继承自,来自");
  dct.Insert("soldier", "士兵");
  dct.Insert("column", "列");
  dct.Insert("row", "行");

  dct.InOrder();

  string input;
  while(cin >> input){
    BSTreeNode<string,string> *ret = dct.Find(input);
    if(ret != nullptr)
    {
      cout << ret->_val << endl;
    }
    else{
      cout << "词库中无此单词" << endl;
    }
  }
}

//wordcounter.cc
int main(){
  string tmp[] = {"orange", "orange","orange","strawberry", "pear","apple", "apple","apple","apple","peach","peach"};
  BSTree<string, int> counter;
  for(string &e : tmp)
  {
    BSTreeNode<string, int> *ret = counter.Find(e);
    if(ret)
    {
      ++ret->_val;
    }
    else{
      counter.Insert(e, 1);
    }
  }
  counter.InOrder();
  return 0;
}

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

插入和删除操作都必须先查找,查找效率代表了二叉搜索树中各个操作的性能。

对有n个结点的二叉搜索树,若每个元素查找的概率相等,则二叉搜索树平均查找次数是结点在二叉搜索树的深度的函数,即结点越深,则比较次数越多。时间复杂度:O(h),h是树的深度。

但对于同一个关键码集合,如果各关键码插入的次序不同,可能得到不同结构的二叉搜索树:

在这里插入图片描述

最优情况下,二叉搜索树为完全二叉树(或者接近完全二叉树),其平均比较次数为:log_2 N
最差情况下,二叉搜索树退化为单支树(或者类似单支),其平均比较次数为:N

问题:如果退化成单支树,二叉搜索树的性能就失去了。那能否进行改进,不论按照什么次序插入关键码,二叉搜索树的性能都能达到最优?那么我们后续章节学习的AVL树和红黑树就可以上场了。


五、相关练习

这些题目更适合使用C++完成,难度也更大一些

  1. 二叉树创建字符串。OJ链接
  2. 二叉树的分层遍历1。OJ链接
  3. 二叉树的分层遍历2。OJ链接
  4. 给定一个二叉树, 找到该树中两个指定节点的最近公共祖先 。OJ链接
  5. 二叉树搜索树转换成排序双向链表。OJ链接
  6. 根据一棵树的前序遍历与中序遍历构造二叉树。 OJ链接
  7. 根据一棵树的中序遍历与后序遍历构造二叉树。OJ链接
  8. 二叉树的前序遍历,非递归迭代实现 。OJ链接
  9. 二叉树中序遍历 ,非递归迭代实现。OJ链接
  10. 二叉树的后序遍历 ,非递归迭代实现。OJ链接

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

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

相关文章

Cadence软件屏幕显示问题

问题 就是今天打开Cadence软件想导出网表看一下&#xff0c;发现没有显示确定按钮什么的&#xff0c;那个窗口也是无语&#xff0c;不能移动&#xff0c;缩放也只能左右缩放&#xff0c;还不能缩小什么的&#xff0c;真的醉了&#xff0c;后面就是调整窗口的分辨率。 因为我最…

windwos系统如何创建typecho个人博客并通过内网穿透实现无公网IP访问

文章目录 前言1. 环境安装2.安装Typecho3.安装cpolar内网穿透4. 固定公网地址5.配置Typecho 前言 Typecho是一款PHP语言编写的开源博客程序&#xff0c;它是一个轻量级的内容管理系统&#xff0c;专注于博客领域。支持多用户、多站点、多语言等功能&#xff0c;可以满足不同用…

Win10怎么关闭自动更新?简单4招为你解决烦恼!

“买了一部win10的电脑&#xff0c;每次电脑自动更新都会导致我一些文件丢失或者系统错误。怎么才能关闭win10自动更新的功能呢&#xff1f;” Win10自动更新有时候会很影响我们使用电脑。在目前电脑用户中&#xff0c;使用win10系统的用户占大多数。因此很多朋友都会反映win10…

LLM(大语言模型)解码时是怎么生成文本的?

Part1配置及参数 transformers4.28.1 源码地址&#xff1a;transformers/configuration_utils.py at v4.28.1 huggingface/transformers (github.com) 文档地址&#xff1a;Generation (huggingface.co) 对于生成任务而言&#xff1a;text-decoder, text-to-text, speech-…

华为质量管理:从产品质量到用户体验,Kano模型成为新方向

目录 前言 华为质量管理的四个阶段 基于 IPD 如何做质量管理呢&#xff1f; CSDN相关课程 作者简介 前言 今天继续来谈谈华为流程体系中的质量管理过程。 通常来说质量具体是指产品的质量&#xff0c;也就是产品的使用价值及其属性。 产品再细分的话可以分为三个层次&a…

沃尔玛、亚马逊、ozon卖家必看:如何为旺季做准备?

近二十年来&#xff0c;得益于国家外贸政策的大力扶持&#xff0c;再加上近几年国家对跨境电商行业发展的高度重视&#xff0c;国货出海机会明显增多。 在政策利好的情况下&#xff0c;生产制造业的蓬勃发展等各种有利的局面&#xff0c;可谓是天时地利人和&#xff0c;那么在…

JetBrains 2023.2全新发布!IDEA、PyCharm等支持AI辅助

日前JetBrains官方正式宣布旗下IDE系列今年第二个重要版本——v2023.2全新发布&#xff0c;涵盖了 IntelliJ IDEA、PyCharm、WebStorm等一众知名产品&#xff0c;接下来我们一起详细了解一下他们的更新重点吧~ IntelliJ IDEA v2023.2——引入AI辅助开发 IntelliJ IDEA 2023.2…

java-CyclicBarrier、CountDownLatch、Semaphore 的用法以及 volatile 关键字的作用

CyclicBarrier、CountDownLatch、Semaphore 的用法 1. CountDownLatch&#xff08;线程计数器 &#xff09; CountDownLatch 类位于 java.util.concurrent 包下&#xff0c;利用它可以实现类似计数器的功能。比如有一个任务 A&#xff0c;它要等待其他 4 个任务执行完毕之后才…

powerJob报错以及解决办法集锦

1. 本地测试成功新建任务并运行成功&#xff0c;但是部署到服务器时新建任务只要 “参数”有中文就无法报错 前台报错信息&#xff1a; ERROR&#xff1a;JpaSystemException: could not execute statement; nested exception is org.hibernate.exception.GenericJDBCException…

MySQL 小数类型介绍

文章目录 前言1. 浮点类型1.1 数值精度说明1.2 整数超出范围1.3 小数超出范围1.4 精度误差说明 2. 定点类型2.1 数值精度说明2.2 整数超出范围2.3 小数超出范围 总结 前言 对于保证精度的数字&#xff0c;MySQL 也有对应的小数类型&#xff0c;下图是 MySQL 中小数类型概览。 …

Unity 之利用 localEulerAngle与EulerAngle 控制物体旋转

文章目录 概念讲解localEulerAngle与EulerAngle的区别 概念讲解 欧拉角&#xff08;Euler Angles&#xff09;是一种常用于描述物体在三维空间中旋转的方法。它使用三个角度来表示旋转&#xff0c;分别绕物体的三个坐标轴&#xff08;通常是X、Y和Z轴&#xff09;进行旋转。这…

cvat 安装部署

官网地址&#xff1a; https://github.com/opencv/cvat/tree/masterhttps://github.com/opencv/cvat/tree/master 1.从官网上下载源码地址。 2.配置环境变量 vim /etc/profile 或者执行&#xff1a; export CVAT_HOSTyour-ip-address 3.执行命令 docker-compose up -d …

视频中的声音怎么提取出来?这样做提取出来很简单

提取视频中的声音可以有多种用途。例如&#xff0c;我们可能希望从视频中提取音乐或音效&#xff0c;以在其他项目中使用。或者&#xff0c;可能需要将视频中的对话转录为文本&#xff0c;以便更轻松地编辑和共享内容。无论目的是什么&#xff0c;提取视频中的声音都可以帮助我…

Dwg如何转dxf格式?分享三种转换小技巧

DWG格式是CAD的原生格式&#xff0c;但是DXF文件格式是CAD支持的通用格式。将DWG文件转换为DXF文件可以使其他软件能够读取和编辑这些文件&#xff0c;因为大多数CAD软件都支持DXF格式。此外&#xff0c;DXF文件比DWG文件更小&#xff0c;在传输和存储时更方便。因此&#xff0…

“摧毁我们的文明”?推特博主马斯克批评TikTok:我决定不再使用

根据8月25日的消息&#xff0c;推特博主DogeDesigner发表了关于TikTok的批评言论&#xff0c;声称这个应用正在“摧毁我们的文明”。 他在配图中展示了两种形象&#xff0c;左边的形象代表着各方面的专家知识&#xff0c;如天文学、数学、物理学、哲学和生活等&#xff0c;但似…

从LeakCanary看内存快照生成

前面我们已经完成了生命周期监控并且可以通过ReferenceQueue和WeakHashMap的比较确定哪些对象发生泄漏了&#xff0c;那么接下来需要考虑的就是如何确定这个对象是被谁持有导致泄漏的呢&#xff1f; 从内存泄漏一文中可知&#xff0c;当我们使用Android Studio或MAT分析内存泄…

vue3范围选择组件封装

个人项目地址&#xff1a; SubTopH前端开发个人站 &#xff08;自己开发的前端功能和UI组件&#xff0c;一些有趣的小功能&#xff0c;感兴趣的伙伴可以访问&#xff0c;欢迎提出更好的想法&#xff0c;私信沟通&#xff0c;网站属于静态页面&#xff09; SubTopH前端开发个人站…

测试框架pytest教程(10)自定义命令行-pytest_addoption

pytest_addoption pytest_addoption是pytest插件系统中的一个钩子函数&#xff0c;用于向pytest添加自定义命令行选项。 在pytest中&#xff0c;可以使用命令行选项来控制测试的行为和配置。pytest_addoption钩子函数允许您在运行pytest时添加自定义的命令行选项&#xff0c;…

四信智能充电桩解决方案

新能源汽车是信息技术与制造体系的全面融合&#xff0c;是产业发展的大势所趋&#xff0c;也是新动能的重要支点&#xff0c;而推进充电基础设施建设则是实现我国从汽车大国迈向汽车强国必由之路战略举措的有力保障。 据国际能源署测算&#xff0c;2030年全球私人充电桩保有量预…

数组和指针的练习解析(4)

题目&#xff1a; int main() { int aa[2][5] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; int *ptr1 (int *)(&aa 1); int *ptr2 (int *)(*(aa 1)); printf( "%d,%d", *(ptr1 - 1), *(ptr2 - 1)); return 0; } 思路分析&#xff1a; int *ptr1 (int *)(&…