当前位置: 首页 > news >正文

数据结构——二叉搜索树(附带C++实现版本)

文章目录

  • 二叉搜索树
    • 概念
  • 二叉树的实际应用
  • 二叉树模拟实现
    • 存储结构
    • 二叉搜索树构成
    • 二叉搜索树的查找
    • 插入操作
    • 中序遍历
    • 二叉树的删除
      • 循环(利用左子树最右节点)
      • 递归(利用右子树根节点)
    • 二叉树拷贝
    • 二叉树资源的销毁
  • 二叉树实现完整代码
  • 总结

二叉搜索树

概念

二叉搜索树又叫二叉排序树,二叉搜索树也是一种树形结构。
它是一课满足以下性质的搜索树:

  • 若它的左子树不为空,则左子树上所有节点的值都小于根节点的值
  • 若它的右子树不为空,则右子树上所有节点的值都大于根节点的值
  • 它的左右子树也分别是二叉搜索树

注意,二叉搜索树对于值相同的节点只能存储一个

在这里插入图片描述

优点:这样的结构能够平均用logn的时间复杂度找到我们想要的值,但是其最坏时间复杂度是O(n), 这是由于搜索二叉树不一定是平衡的,如下图所示:
在这里插入图片描述
这个结构也满足二叉搜索树的性质,但是其查找所需要的复杂度是O(n)

二叉树的实际应用

  1. K模型:K模型只以key为关键码,结构中只需要存储K即可,关键码即为需要搜索道的值。

例如: 给一个单词word,判断该单词是否拼写正确,具体方法如下:

  • 以词库中的所有单词集合中的每一个单词作为key,构建一颗二叉搜索树。
  • 在二叉搜索树中检索该单词是否存在,存在则拼写正确,不存在则拼写错误
  • KV模型: 每一个关键码key,都有与之对应的值Value,即<Key, Value>的键值对。该种方 式在现实生活中非常常见:
  • 比如英汉词典就是英文与中文的对应关系,通过英文可以快速找到与其对应的中文,英文单词与其对应的中文<word, chinese>就构成一种键值对
  • 再比如统计单词次数,统计成功后,给定单词就可快速找到其出现的次数,单词与其出 现次数就是<word, count>就构成一种键值对

二叉树模拟实现

在这里为大家以kv模型为例模拟实现二叉搜索树,只要稍作修改即可变成k模型。

存储结构

首先,二叉搜索树中存储的是节点,所以我们需要定义一个表示节点的结构体,如下:

	template<typename K, typename V>struct BSTree_node{K _key = K();V _value = V();BSTree_node* _left = nullptr;BSTree_node* _right = nullptr;BSTree_node() {}BSTree_node(const K& key, const V& value):_key(key), _value(value){}};

K模型和K,V模型差别就在k,v模型里面还存储了键所对应的值的内容,而k模型没有

二叉搜索树构成

对于二叉搜索树来说,我们想要找到其中的全部成员,和二叉树一样,我们只需要存储它的根节点即可。

template<typename K, typename V>
class BSTree
{//typedef减少代码长度typedef BSTree_node<K, V> node;
private:node* _root = nullptr;

二叉搜索树的查找

由于二叉搜索树的性质,想要从中查找就很简单了。
a. 从根开始比较查找,比根大往右走,比根小往左走。
b. 最多查找高度次,如果走到空还没找到,则这个值不存在

//循环操作
node* find(const K& key)
{//find不需要查找值,只需要查找键node* cur = _root;while (cur){if (cur->_key > key) cur = cur->_left;else if (cur->_key < key) cur = cur->_right;else return cur;}return nullptr;
}
//递归的方式
//由于递归需要传递this指针来找到根节点,而方法不能在外面使用this指针,因此我们需要写一个子函数来完成任务
private:node* _findR(node* root, const K& key){if (!root) return nullptr;if (root->_key < key) return _findR(root->_right, key);else if (root->_key > key) return _findR(root->_left, key);else return root;}
public:node* findR(const K& key){return _findR(_root,key);}

插入操作

插入操作同样两个要点:
a. 如果树为空,则直接新增节点,赋值给root指针
b. 树不空,按二叉搜索树的位置不断进行比较找到插入位置,如果找到相同值的节点则插入失败,否则插入新节点
同样提供递归和循环两种方法:

//循环
//对于循环,由于当找到空节点的时候并不知道其在其双亲的左侧还是右侧,所以每次变换时需要记录其是在左侧还是右侧//成功插入返回true,插入失败返回falsebool insert(const K& key, const V& value){node* tmp = new node(key, value);if (!_root){_root = tmp;return true;}//需要存储parent,还要存储所在的方向node* parent = nullptr;node* cur = _root;bool is_right = false;while (cur){if (cur->_key < key) {is_right = true;parent = cur;cur = cur->_right;}else if (cur->_key > key) {is_right = false;parent = cur;cur = cur->_left;}else return false;}//出循环时,cur已经指向空指针//如果直接对cur赋值,是在对该拷贝赋值,并没有修改其双亲的指向if (is_right) parent->_right = tmp;else parent->_left = tmp;return true;}//递归//同样,我们需要一个子函数来传递this指针private://这里使用引用是为了解决循环中无法知道新节点在其双亲的左边还是右边的问题bool _insertR(node*& root, const K& key, const V& value)
{//引用解决了找不到父亲的问题if (!root){//由于使用的是引用,这里其实是在修改双亲的指向root = new node(key, value);return true;}if (root->_key < key) return _insertR(root->_right, key, value);else if (root->_key > key) return _insertR(root->_left, key, value);else return false;
}		

中序遍历

由于二叉树的特殊性质,其中序遍历一定是有序的,因此我们可以写一个inorder函数输出存储结果用于检验我们操作是否正确实现

private:void _inorder(node* root){if (!root) return;_inorder(root->_left);std::cout << root->_key << ":" << root->_value << std::endl;_inorder(root->_right);	}
public:void inorder(){_inorder(_root);std::cout << std::endl;}

二叉树的删除

  1. 首先查找元素是否在二叉树中,如果不存在,则返回
  2. 如果存在,则删除节点还需要分以下几种情况:
  • 要删除的节点无左孩子
  • 要删除的节点只有左孩子节点
  • 要删除的节点只有右孩子节点
  • 要删除的节点有左,右孩子节点

在实际删除过程中,可以将情况1和情况2或情况3合并起来,删除过程如下:

  • 情况1: 删除该节点且使被删除节点的双亲节点指向被删除节点的左孩子——直接删除
    情况2: 删除该节点且使被删除节点的双亲指向被删除节点的右孩子
    情况3: 寻找和节点的值最相近的节点(左子树最右节点或者该节点右子树的根节点),用它的值填补道被删除节点,然后再来处理该节点的删除问题。

节点的删除操作实现是二叉搜索树中最困难的一个,由于其的细节很多,这里同样还是给出递归和循环各一种方法。

循环(利用左子树最右节点)

对于循环,博主选择了和左子树的最右节点进行交换的方法,这是由于只要找到了左子树的最右节点,和此时的根节点交换,然后就只需要进行左右都没孩子的删除操作即可。
但是,和插入有着同样的问题,我们在查找待删除节点的时候并不知道它在左侧还是右侧,所以我们仍然要保存其双亲节点的位置,但是,还有例外。
先看情况一和情况二
在这里插入图片描述
对于这种情况来说,如果要删除的是根节点,那么其双亲节点的指针就是nullptr,如果不加以判断,就会出错,当待删除节点为根节点时,我们直接将树的_root节点改成_root->left/_root->right即可
情况三:
对于情况三来说,虽然我们需要找的是左子树的最右节点,但是一定不要认为左子树最右节点一定在其双亲的右边,有一种情况是例外的。如下:
在这里插入图片描述
如果此时要删除的是根节点,那么其左子树的最右节点就在其双亲的左边,所以我们仍然需要一个标记来判断该节点是在双亲的左边还是右边。

对于二叉树的删除操作来说,循环需要考虑的细节较多,递归虽然也有细节,但是相对更简单一些,但是循环的好处就是不会爆栈,因此在数据量非常大的时候还是使用循环更合适。
循环模拟代码如下:

bool erase(const K& key)
{//可以分为两种大情况//无子节点,有一个子节点 -> 采用托孤处理//托孤要注意删根节点的情况//两个子节点都有 -> 将其与左树最大节点或者右数最小节点相交换,然后删除//第一步先找到要删除的值的位置node* parent = nullptr;node* cur = _root;//保存节点位于其双亲的位置bool is_right = false;while (cur){if (cur->_key < key){is_right = true;parent = cur;cur = cur->_right;}else if (cur->_key > key){is_right = false;parent = cur;cur = cur->_left;}else break;}if (!cur) return false;node* del = cur;//这里需要找到父节点的本质原因是引用不能改变指向if (!cur->_left){//特殊情况,删除的是根节点if (!parent) _root = _root->_right;else{if (is_right) parent->_right = cur->_right;else parent->_left = cur->_right;}}else if (!cur->_right){//同理if (!parent) _root = _root->_left;else{if (is_right) parent->_right = cur->_left;else parent->_left = cur->_left;}}else{//左右两边都不为空//这里循环找左边最大更方便node* leftMax = cur->_left;//bool is_up = true;//出现 特殊情况的本质是下面这个循环没有生效while (leftMax->_right){parent = leftMax;leftMax = leftMax->_right;//is_up = false;}std::swap(leftMax->_key, cur->_key);//如果左子树最大节点就是初始的leftMax,则将待删除节点的左指向leftMax的左//if(is_up)if (leftMax == cur->_left) cur->_left = leftMax->_left;else parent->_right = leftMax->_left;//转换待释放的节点del = leftMax;}delete del;return true;
}

递归(利用右子树根节点)

对于递归来说,如果我们选择右子树的根节点进行操作,整个删除过程就可以变成子问题解决。

首先,由于递归可以使用引用作为参数,我们不需要纠结双亲以及其位于双亲左还是右的问题,因此对于循环中情况1,2删除根节点的问题就不需要考虑了
另外,对于情况3来说,选择右子树的根节点也使得情况简单了许多,因为将右子树节点与待删除节点的值交换后,就变成了删除其右子树的根的子问题,完美符合递归的逻辑,直到根只有一个孩子的时侯就变成情况1了,不需要考虑其他特殊情况。
代码如下:

private:bool _erase(node*& root, const K& key)
{if (!root) return false;if (root->_key < key) return _erase(root->_right, key);else if (root->_key > key) return _erase(root->_left, key);else{//用引用就不需要考虑父亲指向的问题了if (!root->_left) {root = root->_right;return true;}else if (!root->_right){root = root->_left;return true;}else{swap(root->_key, root->_right->_key);return _erase(root->_right, key);}}
}
public:bool eraseR(const K& key){_erase(_root, key);}

二叉树拷贝

二叉树的拷贝构造也可以利用递归的性质来实现,先拷贝根节点,然后拷贝左子树,最后拷贝右子树,拷贝左子树和右子树的逻辑与主逻辑相同。
代码:

private:node* _copy(node*& root, node* copy){if (!copy) return nullptr;root = new node(copy->_key, copy->_value);root->_left = _copy(root->_left, copy->_left);root->_right = _copy(root->_right, copy->_right);return root;}
public:BSTree(const BSTree& t){_copy(_root, t._root);}

二叉树资源的销毁

由于二叉树的节点都是new出来的节点,所以我们在结束使用时也需要释放资源,否则就会导致内存泄漏的问题,对于释放资源,我们可以放在析构函数解决,运用递归后序遍历的思路,先释放左子树的资源,然后释放右子树的资源,最后释放根节点的资源,代码如下:

private:void _destroy(node* root){if (!root) return;_destroy(root->_left);_destroy(root->_right);delete root;}public:~BSTree(){_destroy(_root);}

二叉树实现完整代码

#pragma once
#include<iostream>
#include<algorithm>
#include<string>
using namespace std;namespace key_value
{template<typename K, typename V>struct BSTree_node{K _key = K();V _value = V();BSTree_node* _left = nullptr;BSTree_node* _right = nullptr;BSTree_node() {}BSTree_node(const K& key, const V& value):_key(key), _value(value){}};template<typename K, typename V>class BSTree{typedef BSTree_node<K, V> node;private:node* _root = nullptr;void _inorder(node* root){if (!root) return;_inorder(root->_left);std::cout << root->_key << ":" << root->_value << std::endl;_inorder(root->_right);	}bool _insertR(node*& root, const K& key, const V& value){//引用解决了找不到父亲的问题if (!root){root = new node(key, value);return true;}if (root->_key < key) return _insertR(root->_right, key, value);else if (root->_key > key) return _insertR(root->_left, key, value);else return false;}node* _findR(node* root, const K& key){if (!root) return nullptr;if (root->_key < key) return _findR(root->_right, key);else if (root->_key > key) return _findR(root->_left, key);else return root;}//同理,这里要修改的不是局部变量,而是上一个指针的指向,所以要使用引用bool _erase(node*& root, const K& key){if (!root) return false;if (root->_key < key) return _erase(root->_right, key);else if (root->_key > key) return _erase(root->_left, key);else{//用引用就不需要考虑父亲指向的问题了if (!root->_left) {root = root->_right;return true;}else if (!root->_right){root = root->_left;return true;}else{swap(root->_key, root->_right->_key);return _erase(root->_right, key);}}}void _destroy(node* root){if (!root) return;_destroy(root->_left);_destroy(root->_right);delete root;}node* _copy(node*& root, node* copy){if (!copy) return nullptr;root = new node(copy->_key, copy->_value);root->_left = _copy(root->_left, copy->_left);root->_right = _copy(root->_right, copy->_right);return root;}public:BSTree() {}//循环///插入,如果已经存在就不用插入bool insert(const K& key, const V& value){node* tmp = new node(key, value);if (!_root){_root = tmp;return true;}//需要存储parent,还要存储所在的方向node* parent = nullptr;node* cur = _root;bool is_right = false;while (cur){if (cur->_key < key) {is_right = true;parent = cur;cur = cur->_right;}else if (cur->_key > key) {is_right = false;parent = cur;cur = cur->_left;}else return false;}//出循环时,cur已经指向空指针if (is_right) parent->_right = tmp;else parent->_left = tmp;return true;}node* find(const K& key){//find不需要查找值,只需要查找键node* cur = _root;while (cur){if (cur->_key > key) cur = cur->_left;else if (cur->_key < key) cur = cur->_right;else return cur;}return nullptr;}bool erase(const K& key){//可以分为两种大情况//无子节点,有一个子节点 -> 采用托孤处理//托孤要注意删根节点的情况//两个子节点都有 -> 将其与左树最大节点或者右数最小节点相交换,然后删除//第一步先找到要删除的值的位置node* parent = nullptr;node* cur = _root;//保存节点位于其双亲的位置bool is_right = false;while (cur){if (cur->_key < key){is_right = true;parent = cur;cur = cur->_right;}else if (cur->_key > key){is_right = false;parent = cur;cur = cur->_left;}else break;}if (!cur) return false;node* del = cur;//这里需要找到父节点的本质原因是引用不能改变指向if (!cur->_left){//特殊情况,删除的是根节点if (!parent) _root = _root->_right;else{if (is_right) parent->_right = cur->_right;else parent->_left = cur->_right;}}else if (!cur->_right){//同理if (!parent) _root = _root->_left;else{if (is_right) parent->_right = cur->_left;else parent->_left = cur->_left;}}else{//左右两边都不为空//这里循环找左边最大更方便node* leftMax = cur->_left;//bool is_up = true;//出现 特殊情况的本质是下面这个循环没有生效while (leftMax->_right){parent = leftMax;leftMax = leftMax->_right;//is_up = false;}std::swap(leftMax->_key, cur->_key);//如果左子树最大节点就是初始的leftMax,则将待删除节点的左指向leftMax的左//if(is_up)if (leftMax == cur->_left) cur->_left = leftMax->_left;else parent->_right = leftMax->_left;//转换待释放的节点del = leftMax;}delete del;return true;}//递归///中序遍历void inorder(){_inorder(_root);std::cout << std::endl;}bool insertR(const K& key, const V& value){return _insertR(_root, key, value);}node* findR(const K& key) { return _findR(_root, key); }bool eraseR(const K& key){_erase(_root, key);}~BSTree(){_destroy(_root);}BSTree(const BSTree& t){_copy(_root,t._root);}};//测试用例1void test_BSTree(){int a[] = { 8,3,1,10,6,4,7,14,13 };BSTree<int, int> bst;for (auto e : a) bst.insert(e,e);std::cout << bst.find(8)->_key << std::endl;std::cout << bst.find(13)->_key << std::endl;//std::cout << bst.find(18)->_key << std::endl;BSTree<int, int> t1(bst);t1.inorder();bst.erase(4);bst.inorder();bst.erase(6);bst.inorder();bst.erase(7);bst.inorder();bst.erase(3);bst.inorder();for (auto e : a){bst.erase(e);}bst.inorder();}//测试用例2void TestBSTree(){BSTree<string, string> dict;dict.insertR("insert", "插入");dict.insertR("erase", "删除");dict.insertR("left", "左边");dict.insertR("string", "字符串");string str;while (cin >> str){auto ret = dict.findR(str);if (ret){cout << str << ":" << ret->_value << endl;}else{cout << "单词拼写错误" << endl;}}string strs[] = { "苹果", "西瓜", "苹果", "樱桃", "苹果", "樱桃", "苹果", "樱桃", "苹果" };// 统计水果出现的次BSTree<string, int> countTree;for (auto str : strs){auto ret = countTree.findR(str);if (ret == nullptr){countTree.insertR(str, 1);}else{ret->_value++;}}BSTree<string, int> t1(countTree);countTree.inorder();t1.inorder();}
}

总结

前面提到了对于二叉查找树来说,

  • 最好情况下二叉树为完全二叉树(或接近完全二叉树),其平均比较次数为: l o g 2 N log_2 N log2N
  • 最坏情况下,二叉搜索树有可能退化成单支树(或类似单支),其平均比较次数为: N 2 \frac{N}{2} 2N

那么就有一个问题了,如果退化成单支树,二叉搜索树的性能就消失了,那么是否能够改进,不论按什么次数插入关键码,二叉搜索树的性能都能达到最优呢?
那么就需要用到AVL树和红黑树了,这两种树都是特殊的搜索二叉树,但是底层相对于普通的二叉搜索树又复杂了许多,加入了翻转二叉树等操作来达到最有优效率!
STL中的mapset底层就是用红黑树实现的,其做到了查找最坏时间复杂度为 O ( l o g 2 N ) O(log_2 N) O(log2N),这样大家就感受到红黑树的强大了把!

对于AVL树和红黑树的知识,博主会在之后的博客中讲解,大家敬请期待!


以上就是二叉树实现的增删查改的相关知识内容,完整代码以及其作用和性能分析的总结了,希望大家看完能够有所收获!如果对博主的内容有疑惑或者博主内容有误的话,欢迎评论区指出!

相关文章:

数据结构——二叉搜索树(附带C++实现版本)

文章目录 二叉搜索树概念 二叉树的实际应用二叉树模拟实现存储结构二叉搜索树构成二叉搜索树的查找插入操作中序遍历二叉树的删除循环(利用左子树最右节点&#xff09;递归(利用右子树根节点) 二叉树拷贝二叉树资源的销毁 二叉树实现完整代码总结 二叉搜索树 概念 二叉搜索树…...

C++(3)C++对C的扩展Extension

类型增强 1、类型更加严格 不初始化&#xff0c;无法通过编译&#xff1b;C不初始化&#xff0c;则随机赋值 #include <iostream> #include <stdlib.h>int main() {const int a 100; //真正的const,无法修改 // int *p &a; 报错const int *p…...

在vscode(idea)使用GitHub账号、Copilot异常

在idea使用GitHub账号、Copilot异常 登录GitHub显示 Invalid authentication data.Connection refused: connect或者副驾驶显示 Failed to initiate the GitHub login process. Please try again.一般网上的方法推荐使用token登录&#xff0c;或者降级副驾驶 经过研究&#x…...

新的后端渲染:服务器驱动UI

通过API发送UI是一种彻底的新方法&#xff0c;将改变传统的UI开发。 一项正在改变我们对用户界面 (UI) 的看法的技术是通过 API 发送 UI&#xff0c;也称为服务器驱动UI。这种方法提供了新水平的活力和灵活性&#xff0c;正在改变 UI 开发的传统范例。 服务器驱动 UI 不仅仅是…...

Postman如何做接口自动化测试?

前言 什么是自动化测试 把人对软件的测试行为转化为由机器执行测试行为的一种实践。 例如GUI自动化测试&#xff0c;模拟人去操作软件界面&#xff0c;把人从简单重复的劳动中解放出来。 本质是用代码去测试另一段代码&#xff0c;属于一种软件开发工作&#xff0c;已经开发完…...

excel文本函数篇2

本期主要介绍LEN、FIND、SEARCH以及后面加B的情况&#xff1a; &#xff08;1&#xff09;后缀没有B&#xff1a;一个字节代表一个中文字符 &#xff08;2&#xff09;后缀有B&#xff1a;两个字节代表一个中文字符 1、LEN(text)&#xff1a;返回文本字符串中的字符个数 2、…...

【MyBatis】动态SQL > 重点:${...}和#{...}与resultMap和resultType的区别

目录 一、MyBatis动态sql 1.1 动态sql的作用 1.2 动态sql作用论证 1.2.1 条件判断&#xff1a;<if> 1.2.2 循环迭代&#xff1a;<foreach> 1.2.3 SQL片段重用 1.2.4 动态条件组合&#xff1a;<choose><when><otherwise> 1.2.5 <where…...

什么是BEM命名规范?为什么要使用BEM命名规范?

聚沙成塔每天进步一点点 ⭐ 专栏简介⭐ BEM命名规范⭐ 为什么使用BEM命名规范&#xff1f;⭐ 写在最后 ⭐ 专栏简介 前端入门之旅&#xff1a;探索Web开发的奇妙世界 记得点击上方或者右侧链接订阅本专栏哦 几何带你启航前端之旅 欢迎来到前端入门之旅&#xff01;这个专栏是为…...

JavaScript:交集和差集的应用场景

在集合A和集合B中&#xff0c;属于集合A&#xff0c;同时也属于集合B的元素组成的集合&#xff0c;就是交集。 在A中所有不属于集合B元素&#xff0c;组合成集合&#xff0c;就是差集。 那么在平时的开发中&#xff0c;如何使用差集和交集来解决问题呢&#xff1f; 现在有这…...

达梦数据库表空间创建和管理

概述 本文将介绍在达梦数据库如何创建和管理表空间。 1.创建表空间 1.1表空间个数限制 理论上最多允许有65535个表空间&#xff0c;但用户允许创建的表空间 ID 取值范围为0~32767&#xff0c; 超过 32767 的只允许系统使用&#xff0c;ID 由系统自动分配&#xff0c;ID不能…...

三、MySQL 数据库安装集

一、CentOS—YUM 1. MySQL—卸载 # 1、查看存在的MySQL。 rpm -qa | grep -i mysql rpm -qa | grep mysql# 2、删除存在的MySQL。 rpm -e –-nodeps 包名# 3、查找存在的MySQL目录。 find / -name mysql# 4、删除存在的MySQL目录。 rm -rf 目录# 5、删除存在的MySQL配置文件。…...

【BASH】回顾与知识点梳理(三十九)

【BASH】回顾与知识点梳理 三十九 三十九. make、tarball、函数库及软件校验39.1 用 make 进行宏编译为什么要用 makemakefile 的基本语法与变量 39.2 Tarball 的管理与建议使用原始码管理软件所需要的基础软件Tarball 安装的基本步骤一般 Tarball 软件安装的建议事项 (如何移除…...

蓝蓝设计-UI设计公司案例-HMI列车监控系统界面设计解决方案

2013年&#xff0c;为加拿大庞巴迪(Bombardier)设计列车监控系统界面设计。 2015-至今&#xff0c;为中车集团旗下若干公司提供HMI列车监控系统界面设计,综合考虑中车特点、城轨车、动车组的不同需求以及HMI硬键屏和触摸 屏的不同操作方式&#xff0c;重构框架设计、交互设计、…...

Blazor前后端框架Known-V1.2.13

V1.2.13 Known是基于C#和Blazor开发的前后端分离快速开发框架&#xff0c;开箱即用&#xff0c;跨平台&#xff0c;一处代码&#xff0c;多处运行。 Gitee&#xff1a; https://gitee.com/known/KnownGithub&#xff1a;https://github.com/known/Known 概述 基于C#和Blazo…...

vue 复制文本

一个常用的库就是 clipboard.js&#xff0c;它可以帮助您实现跨浏览器的复制到剪贴板功能 首先&#xff0c;安装 clipboard.js&#xff1a; cnpm install clipboard 创建一个 Vue 组件并使用 clipboard.js&#xff1a; <template><div><input v-model"…...

西瓜书第三章

广义线性模型 考虑单点可微函数 g ( ⋅ ) g(\cdot) g(⋅)&#xff0c;令 y g − 1 ( ω T x b ) yg^{-1}(\omega^{T}xb) yg−1(ωTxb)&#xff0c;这样得到的模型称为“广义线性模型”&#xff0c;其中函数 g ( ⋅ ) g(\cdot) g(⋅)称为“联系函数”。显然&#xff0c;对数线…...

关于python如何使用sqlalchemy连接sap_hana数据库

1.先安装sqlalchemy pip install sqlalchemy 2.from sqlalchemy import create_engine 3.创建数据库连接方式&#xff1a; 假设数据连接方式如下&#xff1a; usernameH_TEOPT passwordww122222 jdbcUrljdbc:sap://192.163.1.161:21681/?currentschema 那么使用sqlalchemy 的…...

微信小程序教学系列(5)

微信小程序教学系列 第五章&#xff1a;小程序发布与推广 第一节&#xff1a;小程序发布流程介绍 小伙伴们&#xff0c;欢迎来到第五章的教学啦&#xff01;在这一章中&#xff0c;我们将一起来探索小程序的发布与推广流程。你准备好了吗&#xff1f;让我们开始吧&#xff0…...

【计算机网络篇】TCP协议

✅作者简介&#xff1a;大家好&#xff0c;我是小杨 &#x1f4c3;个人主页&#xff1a;「小杨」的csdn博客 &#x1f433;希望大家多多支持&#x1f970;一起进步呀&#xff01; TCP协议 1&#xff0c;TCP 简介 TCP&#xff08;Transmission Control Protocol&#xff09;是…...

Disruptor并发编程框架

Disruptor是一款高性能的并发编程框架,主要具有以下特点和功能: 1. RingBuffer环形数据结构 Disruptor的核心数据结构是RingBuffer环形队列,用于存储客户端的并发数据并在生产者和消费者之间传递。队列以批量方式的顺序存储,可以高效地进行并发读写操作。 2. 无锁设计 Disrup…...

matlab 点云精配准(1)——point to point ICP(点到点的ICP)

目录 一、算法原理参考文献二、代码实现三、结果展示四、参考链接本文由CSDN点云侠原创,爬虫自重。如果你不是在点云侠的博客中看到该文章,那么此处便是不要脸的爬虫。 一、算法原理 参考文献 [1] BESL P J,MCKAY N D.A method for registration of 3-Dshapes[J].IEEE Tran…...

【JVM】运行时数据区域

文章目录 说明程序计数器虚拟机栈本地方法栈Java堆方法区运行时常量池直接内存 说明 Java虚拟机在执行Java程序的过程中会把它所管理的内存划分为若干个不同的数据区域。这些区域有各自的用途&#xff0c;以及创建和销毁的时间&#xff0c;有的区域随着虚拟机进程的启动而一直…...

uniapp踩坑合集

1、onPullDownRefresh下拉刷新不生效 pages.json对应的style中enablePullDownRefresh设置为true&#xff0c;开启下拉刷新 {"path" : "pages/list/list","style" :{"navigationBarTitleText": "页面标题名称","enable…...

再JAVA中如何使用qsort对类进行排序?

目录 结论&#xff1a; 解析&#xff1a; 结论&#xff1a; import java.util.Arrays;class Person implements Comparable<Person>{public String name;public int age;public Person(String name, int age) {this.name name;this.age age;}Overridepublic Stri…...

docker安装clickhouse

安装 docker安装 创建clickhouse目录 mkdir -P /data/clickhouse/datamkdir -P /data/clickhouse/confmkdir -P /data/clickhouse/log 拉取镜像 这里直接拉取最新镜像, 如果需要某个特定版本, 则再拉取的时候指定版本号即可. docker pull clickhouse/clickhouse-server 启动临…...

解决`idea`中`database`工具查询起别名乱码问题

文章目录 解决idea中database工具查询起别名乱码问题场景复现如何解决方式一 设置编码方式二&#xff1a;修改字体 原因说明 解决idea中database工具查询起别名乱码问题 场景复现 使用Idea做查询的并且起别名出现了中文乱码 如何解决 方式一 设置编码 settings->输入框输…...

UE4/5Niagara粒子特效之Niagara_Particles官方案例:1.5->2.3

目录 之前的文章&#xff1a; 1.5 Blend Attributes by Value 发射器更新 粒子生成 粒子更新 2.1 Static Beams ​编辑 发射器更新&#xff1a; 粒子生成 粒子更新 2.2 Dynamic Beams 没有开始模拟前的效果是&#xff1a; 开始模拟后的效果是&#xff1a; 发射器更新 …...

Docker 容器数据卷

Docker挂载主机目录访问如果出现cannot open directory .: Permission denied 解决办法&#xff1a;在挂载目录后多加一个--privilegedtrue参数即可 如果是CentOS7安全模块会比之前系统版本加强&#xff0c;不安全的会先禁止&#xff0c;所以目录挂载的情况被默认为不安全的行…...

STM32--MPU6050与I2C外设

文章目录 前言MPU6050参数电路MPU6050框图 IIC外设框图 IIC的基本结构软件IIC实现MPU6050硬件IIC实现MPU6050 前言 在51单片机专栏中&#xff0c;用过I2C通信来进行实现AT24C02的数据存储&#xff1b; 里面介绍的是利用程序的编程来实现I2C的时序&#xff0c;进而实现AT24C02与…...

项目管理实战笔记1:项目管理常识

序 看了下极客时间的《项目管理实战》&#xff0c;觉得跟之前学习PMP的标准资料还是有所侧重。重新整理下&#xff0c;相比书上繁杂的知识&#xff0c;这个更通俗易懂。 1 角色转换&#xff1a;三大误区 误区1&#xff1a;事必躬亲 自己做事情是可控的&#xff0c;做项目依赖…...

时序分解 | MATLAB实现基于SVMD逐次变分模态分解的信号分解分量可视化

时序分解 | MATLAB实现基于SVMD逐次变分模态分解的信号分解分量可视化 目录 时序分解 | MATLAB实现基于SVMD逐次变分模态分解的信号分解分量可视化效果一览基本介绍程序设计参考资料 效果一览 基本介绍 SVMD分解算法&#xff0c;分解结果可视化&#xff0c;MATLAB程序&#xff…...

阿里云访问端口被限制解决方法记录

阿里云服务器&#xff0c;80端口可以访问&#xff0c;但是加入了安全组端口8080 通过公网访问改端口策略&#xff0c;发现不能被访问 问题出在防火墙&#xff0c;需要重置一下 解决方法&#xff1a; 在运行的服务器上执行如下命令&#xff1a; # iptables -A INPUT -j ACCEP…...

antd5源码调试环境启动(MacOS)

将源码下载至本地 这里antd5 版本是5.8.3 $ git clone gitgithub.com:ant-design/ant-design.git $ cd ant-design $ npm install $ npm start前提&#xff1a;安装python3、node版本18.14.0(这是本人当前下载的版本&#xff09; python3安装教程可参考&#xff1a;https://…...

单片机使用基于时间片轮询系统的-状态机-[1]

目的&#xff1a;【1】用C实现一个超轻量化任务管理系统 【2】具有任务suspend, resume, runonce ,auto loop ,task_delay功能 【3】易于移植&#xff0c;不涉及硬件底层。 示例例码&#xff1a; 利用switch case结构实现了单一层的 task_delay功能。弊端就是switch..case不…...

前端开发怎么解决性能优化的问题? - 易智编译EaseEditing

前端性能优化是确保网站或应用在加载速度、响应性和用户体验等方面达到最佳状态的关键任务。以下是一些解决前端性能优化问题的方法&#xff1a; 压缩和合并代码&#xff1a; 压缩和合并CSS、JavaScript和HTML文件可以减少文件大小&#xff0c;加快加载速度。使用压缩工具&am…...

共享球拍小程序:打破拥有束缚,尽享运动乐趣

市场前景&#xff1a; 随着健身和运动的流行趋势&#xff0c;越来越多的人加入了各种体育项目。然而&#xff0c;拥有球拍作为体育装备的成本较高&#xff0c;对于想要尝试不同运动的人来说&#xff0c;这可能是个阻碍。共享球拍小程序迎合了这一需求&#xff0c;提供了一个经济…...

uniapp 微信小程序 绘制海报,长按图片分享,保存海报

uView UI 2.0 dcloud 插件市场地址 弹窗海报源码 <template><!-- 推荐商品弹窗 --><u-popup :show"haibaoShow" mode"center" round26rpx z-index10076 bgColortransparent safeAreaInsetTop close"goodsclose"><image …...

爬虫异常捕获与处理方法详解

Hey&#xff01;作为一名专业的爬虫代理供应商&#xff0c;我今天要和大家分享一些关于爬虫异常捕获与处理的方法。在进行爬虫操作时&#xff0c;我们经常会遇到各种异常情况&#xff0c;例如网络连接错误、请求超时、数据解析错误等等。这些异常情况可能会导致程序崩溃或数据丢…...

制作网络课堂学习平台(标签嵌套,后代选择器)

网络课堂学习平台 课程 1 这是课程 1 的描述。 模块 1 这是模块 1 的描述。 查看详情 模块 2 这是模块 2 的描述。 查看详情 课程 2 这是课程 2 的描述。 模块 1 这是块 2 的描述。 查看详情...

基于医疗领域数据微调LLaMA——ChatDoctor模型

文章目录 ChatDoctor简介微调实战下载仓库并进入目录创建conda环境并配置环境&#xff08;安装相关依赖&#xff09;下载模型文件微调数据微调过程全量微调基于LoRA的微调基于微调后的模型推理 ChatDoctor简介 CHatDoctor论文&#xff1a; ChatDoctor: A Medical Chat Model F…...

UDP TCP 报文内容

1.UDP 2.TCP 源/目的端口号:表示数据是从哪个进程来,到哪个进程去; 32位序号/32位确认号:后面详细讲;4位TCP报头长度:表示该TCP头部有多少个32位bit(有多少个4字节);所以TCP头部最大长度是15*460 6位标志位: o URG:紧急指针是否有效 ——urgent 紧急的 o ACK:确认号是否有…...

Boost开发指南-4.8operators

operators C提供了强大且自由的操作符重载能力&#xff0c;可以把大多数操作符重新定义为函数&#xff0c;使操作更加简单直观。这方面很好的例子就是标准库中的string和 complex&#xff0c;可以像操作内置类型int、double那样对它们进行算术运算和比较运算&#xff0c;非常方…...

c# 泛型约束

在C#中&#xff0c;泛型约束用于指定泛型类型参数的限制条件&#xff0c;以确保类型参数满足特定的条件。以下是C#中常见的泛型约束&#xff1a; where T : struct&#xff1a; 这个约束要求类型参数必须是一个值类型&#xff08;如int、float等&#xff09;。 where T : cla…...

android frida

Frida 是一个用于动态分析、调试和修改 Android 应用程序的强大工具。它的主要作用包括&#xff1a; 代码注入和Hooking&#xff1a; Frida 允许您在运行时修改和监视应用程序的行为。您可以通过Frida注入JavaScript代码到目标应用程序中&#xff0c;然后使用该代码来Hook&…...

Linux下的Shell编程——正则表达式入门(四)

前言&#xff1a; 正则表达式使用单个字符串来描述、匹配一系列符合某个语法规则的字符串。在很多文本编辑器里&#xff0c;正则表达式通常被用来检索、替换那些符合某个模式的文本。 在Linux 中&#xff0c;grep&#xff0c;sed&#xff0c;awk 等文本处理工具都支持…...

使用VisualStudio制作上位机(一)

文章目录 使用VisualStudio制作上位机(一)写在前面第一部分:创建应用程序第二部分:GUI主界面设计使用VisualStudio制作上位机(一) Author:YAL 写在前面 1.达到什么目的呢 本文主要讲怎么通过Visual Studio 制作上位机,全文会以制作过程来介绍怎么做,不会去讲解具体…...

【前端从0开始】JavaSript——自定义函数

函数 函数是一个可重用的代码块&#xff0c;用来完成某个特定功能。每当需要反复执行一段代码时&#xff0c;可以利用函数来避免重复书写相同代码。函数包含着的代码只能在函数被调用时才会执行&#xff0c;就可以避免页面载入时执行该脚本在JavaScript中&#xff0c;可以使用…...

如何在Windows、Mac和Linux操作系统上安装Protocol Buffers(protobuf)编译器

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

简单介绍 CPU 的工作原理

内部架构 CPU 的根本任务就是执行指令&#xff0c;对计算机来说最终都是一串由 0 和 1 组成的序列。CPU 从逻辑上可以划分成 3 个模块&#xff0c;分别是控制单元、运算单元和存储单元 。其内部架构如下&#xff1a; 【1】控制单元 控制单元是整个CPU的指挥控制中心&#xff…...

UE4/5数字人MetaHuman的控制绑定资产使用

目录 开始操作 找到控制绑定资产 放入控制绑定资产 ​编辑 生成动画资产 开始操作 首先我们创建一个关卡序列&#xff1a; 打开后将我们的数字人放进去【右键&#xff0c;第一个添加进去】&#xff1a; 我们会自动进入动画模式&#xff0c;没有的话&#xff0c;就自己…...