【數據結構】二叉搜索樹


🚀 作者簡介:一名在后端領域學習,并渴望能夠學有所成的追夢人。
🐌 個人主頁:蝸牛牛啊
🔥 系列專欄:🛹數據結構、🛴C++
📕 學習格言:博觀而約取,厚積而薄發
🌹 歡迎進來的小伙伴,如果小伙伴們在學習的過程中,發現有需要糾正的地方,煩請指正,希望能夠與諸君一同成長! 🌹


文章目錄

  • 二叉搜索樹的概念
  • 二叉搜索樹的操作及實現
    • 二叉搜索樹的結構
    • 二叉搜索樹的構造函數
    • 二叉搜索樹的接口(非遞歸實現)
      • 二叉搜索樹的插入
      • 二叉搜索樹的中序遍歷
      • 二叉搜索樹的查找
      • 二叉搜索樹的刪除
    • 二叉搜索樹的接口(遞歸實現)
      • 二叉搜索樹的插入
      • 二叉搜索樹的查找
      • 二叉搜索樹的刪除
    • 二叉搜索樹的拷貝構造
    • 二叉搜索樹的賦值
    • 二叉搜索樹的析構函數
  • 二叉搜索樹的應用
  • 二叉搜索樹性能分析

二叉搜索樹的概念

二叉搜索樹又稱二叉排序樹,它或者是一棵空樹,或者是具有以下性質的二叉樹:

  • 若它的左子樹不為空,則左子樹上所有節點的值都小于根節點的值

  • 若它的右子樹不為空,則右子樹上所有節點的值都大于根節點的值

  • 它的左右子樹也分別為二叉搜索樹

image-20230724112755992

二叉搜索樹還有一個特征:按照中序走的話是一個升序的狀態。所以二叉樹搜索樹可以叫做二叉排序樹或二叉查找樹。

二叉搜索樹的操作及實現

二叉搜索樹的結構

首先實現一個結點類,結點類當中包含三個成員變量:結點值、左指針、右指針,同時結點類當中要對成員變量進行初始化,需要實現一個構造函數,用于將結點的左右指針置空和初始化指定結點值。

結點類的代碼實現:

//二叉樹搜索樹結點類
template<class K>
struct BSTreeNode {BSTreeNode<K>* _left;//左指針BSTreeNode<K>* _right;//右指針K _key;//節點值//構造函數BSTreeNode(const K& key):_left(nullptr),_right(nullptr),_key(key){}
};
template<class K>
class BSTree {//喜歡在類里進行類型重定義,因為受到類域的限制typedef BSTreeNode<K> Node;
private:Node* _root = nullptr; //可以給一個構造函數,也可以直接寫一個缺省值
};

二叉搜索樹的構造函數

//構造函數
BSTree():root(nullptr)
{}

我們也可以這樣寫:

//default:默認情況下不會生成,讓其強制生成構造函數
BSTree() = default;//指定強制生成默認構造

二叉搜索樹的接口(非遞歸實現)

二叉搜索樹的插入

通過插入函數在二叉搜索樹中插入一個值,如果成功返回true,失敗返回false。

當我們想進行插入數據時,要和樹的根結點及各個子樹的根結點進行比較,如果待插入結點值比當前結點小就插入到該結點的左子樹;如果待插入結點值比當前節點值大就插入到該結點的右子樹。

默認的搜索二叉樹是不允許冗余的,有相同的值會插入失敗。

根的值是怎么來的?插入的第一個值就是根。所以如果是同樣的值,插入的順序不同二叉搜索樹的形狀就不同。

在實現時我們要定義一個parent指針,方便新增節點和父結點鏈接。同時在鏈接的時候還要判斷一下是和父親的左邊鏈接還是右邊鏈接。

image-20230724163746453

代碼實現:

bool Insert(const K& key)
{//確定插入的是否是第一個值,如果是第一個值插入的值就是根結點if (_root == nullptr){_root = new Node(key);//申請一個新節點return true;}//當根不是空時找對應的位置Node* cur = _root;//從根結點開始向后找Node* parent = nullptr;//父結點while (cur){if (cur->_key > key){parent = cur;cur = cur->_left;}else if (cur->_key < key){parent = cur;cur = cur->_right;}//當相等時返回false,搜索二叉樹中不能有相等的else{return false;}}cur = new Node(key);//將其鏈接到父結點上if (parent->_key > key){parent->_left = cur;}else if (parent->_key < key){parent->_right = cur;}return true;
}	

二叉搜索樹的中序遍歷

二叉搜索樹中序遍歷出來的順序是升序的,我們可以實現一個中序遍歷來驗證。

void InOrder(Node* root)
{if (root == nullptr){return;}InOrder(root->_left);cout << root->_key << endl;InOrder(root->_right);
}

但是我們發現這個函數調用時候不好處理,因為要傳根結點,但是并沒有根結點,如果參數沒有根,又沒辦法遞歸。

所以我們可以套上一層函數:調用無參的函數,當我們調用時調用無參的函數就可以實現中序遍歷了。

//套用一層函數
void InOrder()
{_InOrder(_root);
}
//實現中序遍歷,中序遍歷打印出來是升序的
void _InOrder(Node* root)
{if (root == nullptr){return;}_InOrder(root->_left);cout << root->_key << " ";_InOrder(root->_right);
}

測試一下:

void Test_BSTree()
{int a[] = { 8, 3, 1, 10, 6, 4, 7, 14, 13 };BSTree<int> t1;for (auto e : a){//插入t1.Insert(e);}//中序遍歷t1.InOrder();
}
int main()
{Test_BSTree();return 0;
}

打印結果:

image-20230724164737575

二叉搜索樹的查找

在二叉搜索樹中也可以通和根結點和左右子樹的根結點比較查找指定節點值,如果找到返回true,沒找到返回false。

代碼實現:

//查找接口
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;
}

二叉搜索樹的刪除

首先查找要刪除的元素是否存在,如果不存在,則返回;否則要刪除的結點可能分下面三種情況:

a.要刪除的結點只有左孩子結點(包含要刪除的結點無孩子結點)

b.要刪除的結點只有右孩子結點

c.要刪除的結點有左、右孩子結點

所以我們針對上面三種情況進行分析:

情況a:要刪除的結點只有左孩子結點(包含要刪除的結點無孩子結點)

此時刪除要刪除的結點之后,使被刪除節點的父結點指向被刪除節點的左孩子結點(直接刪除法)

image-20230724171954648

情況b.要刪除的結點只有右孩子結點

此時刪除要刪除的結點之后,使被刪除節點的父結點指向被刪除節點的右孩子結點(直接刪除法)

image-20230724172146756

情況c.要刪除的結點有左、右孩子結點

若待刪除結點有左、右孩子結點,可以使用替換法進行刪除。
可以找到待刪除結點左子樹中結點值最大的結點,或者是待刪除結點右子樹中結點值最小的結點來代替待刪除結點被刪除。代替待刪除結點被刪除的結點,在左右子樹當中至少有一個為空樹,那刪除該結點之后可以利用上面兩種情況來處理。

必須是待刪除結點左子樹中結點值最大的結點,或者是待刪除結點右子樹中結點值最小的結點代替待刪除結點被刪除,只有這樣才能保證刪除后的二叉樹仍保持二叉搜索樹的特性。

image-20230724174830726

刪除的時候也要用parent記錄父結點,保證當被刪除結點還有孩子時能夠被接管。

代碼實現:

bool Erase(const K& key)
{Node* parent = nullptr;//父結點Node* cur = _root;//循環查找while (cur){if (cur->_key > key){parent = cur;cur = cur->_left;}else if (cur->_key < key){parent = cur;cur = cur->_right;}//找到該節點,刪除該節點else {//如果該結點只有左孩子if (cur->_right == nullptr){//當只有一邊時,需要更新_rootif (cur == _root){//左孩子為空,讓_root等于右孩子_root = cur->_left;}else{//判斷是哪邊的,讓其接管if (parent->_left == cur){parent->_left = cur->_left;}else{parent->_right = cur->_left;}}//刪除該結點delete cur;}//該節點只有右孩子else if (cur->_left == nullptr){//當只有右邊時,需要更新_rootif (cur == _root){_root = cur->_right;}else {if (parent->_right == cur){parent->_right = cur->_right;}else{parent->_left = cur->_left;}}//刪除結點delete cur;}//該節點有左右孩子else{//找右樹的最小結點替代//這里不能等于空//Node* pminRight = nullptr;Node* pminRight = cur;Node* minRight = cur->_right;while (minRight->_left){pminRight = minRight;minRight = minRight->_left;}//找到右樹的最小結點之后再把key傳過去cur->_key = minRight->_key;if (pminRight->_left == minRight){pminRight->_left = minRight->_right;}else{pminRight->_right = minRight->_right;}delete minRight;}return true;}}return false;
}

image-20230724193645593

同時我們在實現時應該注意當其為如下特殊情況時,更新_root

image-20230724192934049

二叉搜索樹的接口(遞歸實現)

一般在類里面寫遞歸都要套上一層。

二叉搜索樹的插入

要在搜索樹里面進行一個插入:比根結點大的值和右子樹根結點比較插入;比根結點小的值和左子樹根結點比較插入,需要注意插入要和父結點鏈接起來。我們可以傳入父結點,但是這里使用引用是最優的,root_root->left或者_root->right的別名(上一層的別名),就能夠鏈接上。要注意C++的引用不能改變指向,循環里面不能用引用。

遞歸實現插入代碼:

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

二叉搜索樹的查找

查找,如果在二叉樹中返回true,否則返回false。

遞歸實現查找代碼:

//套用一層函數
bool _FindR(Node* root,const K& key) 
{if (root == nullptr){return false;}if (root->_key == key){return true;}if (root->_key > key){return _FindR(root->_left, key);}else{return _FindR(root->_right, key);}
}
bool FindR(const K& key)
{return _FindR(_root,key);
}

二叉搜索樹的刪除

刪除的思路和非遞歸方式一樣,當要刪除的結點有左右孩子的時候使用替換法,找左子樹的最大值或者右子樹的最小值(下面的遞歸實現采用的是找左子樹的最大值),但是注意遞歸這里不能使用root->_key = maxLeft->_key,如果這樣兩個值相同了,不能找到要刪除的結點。

遞歸刪除函數子函數中必須使用引用接收參數,保證能夠鏈接起來。

root是指針,直接讓指針指向其指定結點就可以了,不用找到父節點。要保存一下要刪除的結點Node* del = root,不然改變root指針后沒辦法刪除要刪除的結點。

bool _EarseR(Node*& root,const K& key)
{if (root == nullptr){return false;}if (root->_key > key){return _EarseR(root->_left, key);}else if (root->_key < key){return _EarseR(root->_right, key);}else {Node* del = root;//保存一下要刪除的結點if (root->_left == nullptr)//root是指針,直接讓指針指向其指定結點,這時就鏈接成功了root = root->_right;else if (root->_right == nullptr)root = root->_left;else{//去找左樹的最大值Node* maxLeft = root->_left;while (maxLeft->_right){maxLeft = maxLeft->_right;}//找到進行替代,直接交換swap(root->_key, maxLeft->_key);//交換值的時候不能使用root->_key = maxLeft->_key,如果這樣兩個值相同了,不能找到要刪除的結點。return _EarseR(root->_left,key);//轉換成在子樹去刪除}delete del;}
}
bool EarseR(const K& key)
{return _EarseR(_root, key);
}

return _EraseR(root->_left, key);這里不能使用maxLeft,因為要使用引用,maxLeft只是一個局部變量,會出問題的,引用在遞歸里面又變成別名。

二叉搜索樹的拷貝構造

當我們沒有實現拷貝構造時候,使用的都是默認拷貝構造函數,屬于淺拷貝。

當我們在沒有實現析構函數時使用以下代碼時并不會出問題:

void Test_BSTree() {int a[] = { 8, 3, 1, 10, 6, 4, 7, 14, 13 };BSTree<int> t1;for (auto e : a){t1.Insert(e);}t1.InOrder();cout << endl;BSTree<int> t2(t1);t2.InOrder();
}

監視窗口如下:

image-20230724203331219

但是當我們實現析構函數之后就會報錯:

image-20230724204001640

所以拷貝構造我們要寫成深拷貝(推薦使用遞歸去實現):

主要思想就是先去創建結點,在返回的時候才開始將各個節點鏈接起來。

從根結點開始,不能使用插入函數,因為插入順序不一樣,形狀不一樣:

BSTree(const BSTree<K>& t)
{_root = Copy(t._root);
}
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;
}

二叉搜索樹的賦值

利用拷貝構造然后將其根結點交換即可。

BSTree& operator=(const BSTree<K> t)
{swap(_root, t._root);return *this;//支持連續賦值
}

二叉搜索樹的析構函數

二叉樹搜索樹的析構函數我們采用一個后序的遞歸刪除完成:

//析構函數
~BSTree()
{Destroy(_root);_root = nullptr;
}
void Destroy(Node* root)
{if (root == nullptr){return;}Destroy(root->_left);Destroy(root->_right);delete root;
}

也可以在Destroy那里使用引用傳參,從而可以不在析構函數那里寫_root = nullptr;,直接在Destroy函數實現,因為root就是_root的別名。

~BSTree()
{Destroy(_root);
}
void Destroy(Node*& root)
{if (root == nullptr){return;}Destroy(root->_left);Destroy(root->_right);delete root;root = nullptr;
}

二叉搜索樹的應用

K模型

K模型,即只有key作為關鍵碼,結構中只需存儲key即可,關鍵碼即為需要搜索到的值。

我們在本篇中講到的構建、查找、插入就屬于K模型。

KV模型

KV模型,對于每一個關鍵碼key,都有與之對應的值value,即<key, value>的鍵值對。

通過一個值查找另一個值:如中英文互譯字典、電話號碼查詢快遞信息等。

我們可以通過改一下本篇中的二叉樹搜索樹來認識一下key-value模型,之前的二叉樹搜索樹是key模型。

將代碼修改,主要修改模板參數,增加一個參數:

namespace kv {template<class K,class V>struct BSTreeNode {BSTreeNode<K,V>* _left;BSTreeNode<K,V>* _right;K _key;V _value;//構造函數BSTreeNode(const K& key,const V& value):_left(nullptr), _right(nullptr), _key(key),_value(value){}};template<class K, class V>class BSTree {//喜歡在類里進行類型重定義,因為受到類域的限制typedef BSTreeNode<K,V> Node;public://插入成功返回true,插入失敗返回falsebool Insert(const K& key,const V& value){if (_root == nullptr){_root = new Node(key,value);return true;}Node* cur = _root;Node* parent = nullptr;while (cur){if (cur->_key > key){parent = cur;cur = cur->_left;}else if (cur->_key < key){parent = cur;cur = cur->_right;}else{return false;}}cur = new Node(key,value);if (parent->_key > key){parent->_left = cur;}else {parent->_right = cur;}return true;}bool Erase(const K& key){Node* parent = nullptr;//父結點Node* cur = _root;//循環查找while (cur){if (cur->_key > key){parent = cur;cur = cur->_left;}else if (cur->_key < key){parent = cur;cur = cur->_right;}//找到該節點,刪除該節點else {//如果該結點只有左孩子if (cur->_right == nullptr){//當只有一邊時,需要更新_rootif (cur == _root){//左孩子為空,讓_root等于右孩子_root = cur->_left;}else{//判斷是哪邊的,讓其接管if (parent->_left == cur){parent->_left = cur->_left;}else{parent->_right = cur->_left;}}//刪除該結點delete cur;}//該節點只有右孩子else if (cur->_left == nullptr){//當只有右邊時,需要更新_rootif (cur == _root){_root = cur->_right;}else {if (parent->_right == cur){parent->_right = cur->_right;}else{parent->_left = cur->_left;}}//刪除結點delete cur;}//該節點有左右孩子else{//找右樹的最小結點替代//這里不能等于空//Node* pminRight = nullptr;Node* pminRight = cur;Node* minRight = cur->_right;while (minRight->_left){pminRight = minRight;minRight = minRight->_left;}//找到右樹的最小結點之后再把key傳過去cur->_key = minRight->_key;if (pminRight->_left == minRight){pminRight->_left = minRight->_right;}else{pminRight->_right = minRight->_right;}delete minRight;}return true;}}return false;}Node* 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 cur;}}return nullptr;}//套用一層函數void InOrder(){_InOrder(_root);}//實現中序遍歷void _InOrder(Node* root){if (root == nullptr){return;}_InOrder(root->_left);cout << root->_key << ":" << root->_value << endl;_InOrder(root->_right);}private:Node* _root = nullptr;};
}

實現之后我們通過實現一個單詞翻譯來測試一下:

//測試
void Test_BSTree()
{kv::BSTree<string, string> dict;dict.Insert("sort", "排序");dict.Insert("left", "左邊");dict.Insert("right", "右邊");dict.Insert("string", "字符串");dict.Insert("insert", "插入");dict.Insert("erase", "刪除");string str;while (cin >> str){//kv::BSTreeNode<std::string,std::string>* ret = dict.Find(str);auto ret = dict.Find(str);if (ret){cout << ":" << ret->_value << endl;}else{cout << "無此單詞" << endl;}}
}
int main()
{Test_BSTree();return 0;
}

測試結果:

image-20230724212828292

這種程序怎么結束呢?while (cin >> str)ctrl+c是發送終止信號,也可以使用ctrl+z+換行來結束。

我們還可以使用修改后的代碼用來測試統計水果出現的次數:

void Test_BSTree()
{string arr[] = { "蘋果", "西瓜", "蘋果", "西瓜", "蘋果", "蘋果", "西瓜","蘋果", "香蕉", "蘋果", "香蕉" };kv::BSTree<string, int> countTree;for (auto str : arr){//kv::BSTreeNode<string, int>* ret = countTree.Find(str);auto ret = countTree.Find(str);if (ret == nullptr){countTree.Insert(str, 1);}else{ret->_value++;}}countTree.InOrder();
}
int main()
{Test_BSTree();return 0;
}

測試結果(這里的順序是按照string數組中出現的先后順序來排序的):

image-20230724213300066

二叉搜索樹性能分析

插入和刪除操作都必須先查找,查找效率代表了二叉搜索樹中各個操作的性能。

對有n個結點的二叉搜索樹,若每個元素查找的概率相等,則二叉搜索樹平均查找長度是結點在二叉搜索樹的深度的函數,即結點越深,則比較次數越多。但對于同一個關鍵碼集合,如果各關鍵碼插入的次序不同,可能得到不同結構的二叉搜索樹:

image-20230724205939957

對于有N個結點的二叉搜索樹,最優的情況下,二叉搜索樹為完全二叉樹,其平均比較次數為:logN;最差的情況下,二叉搜索樹退化為單支樹,其平均比較次數為:N/2。

而時間復雜度描述的是最壞情況下算法的效率,因此普通二叉搜索樹各個操作的時間復雜度都是O(N)。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/39986.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/39986.shtml
英文地址,請注明出處:http://en.pswp.cn/news/39986.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

億賽通電子文檔安全管理系統任意文件上傳漏洞復現

0x01 產品簡介 億賽通電子文檔安全管理系統&#xff08;簡稱&#xff1a;CDG&#xff09;是一款電子文檔安全加密軟件&#xff0c;該系統利用驅動層透明加密技術&#xff0c;通過對電子文檔的加密保護&#xff0c;防止內部員工泄密和外部人員非法竊取企業核心重要數據資產&…

excel隔行取數求和/均值

問題描述 如圖有好多組數據&#xff0c;需要求每組數據對應位置的平均值 解決方法 SUM(IF(MOD(ROW(C$2:C$81), 8) MOD(ROW(C2), 8), C$2:C$81, 0))/10然后下拉右拉擴充即可&#xff0c;其中需要根據自身需要修改一些數據 SUM(IF(MOD(ROW(起始列$起始行:結束列$結束行), 每…

MATLAB圖論合集(一)基本操作基礎

本帖總結一些經典的圖論問題&#xff0c;通過MATLAB如何計算答案。近期在復習考研&#xff0c;以此來鞏固一下相關知識——雖然考研肯定不能用MATLAB代碼哈哈&#xff0c;不過在實際應用中解決問題還是很不錯的&#xff0c;比C易上手得多~ 圖論中的圖&#xff08;Graph&#xf…

FOSSASIA Summit 2023 - 開源亞洲行

作者 Ted 致歉&#xff1a;本來這篇博客早就該發出&#xff0c;但是由于前幾個月頻繁差旅導致精神不佳&#xff0c;再加上后續我又參加了 Linux 基金會 7/27 在瑞士日內瓦舉辦的 Open Source Congress&#xff0c;以及 7/29-30 臺北的 COSCUP23&#xff0c;干脆三篇連發&#x…

儀表板展示 | DataEase看中國:2023年中國電影市場分析

背景介紹 隨著《消失的她》、《變形金剛&#xff1a;超能勇士崛起》、《蜘蛛俠&#xff1a;縱橫宇宙》、《我愛你》等國內外影片的上映&#xff0c;2023年上半年的電影市場也接近尾聲。據國家電影專資辦初步統計&#xff0c;上半年全國城市院線票房達262億元&#xff0c;已經超…

一、計算機網絡體系結構

Content 1. 計算機網絡的組成2. 計算機網絡的功能3. 計算機網絡的分類4. 計算機網絡的性能指標5. 計算機網絡分層結構OSI模型TCP/IP模型互聯網五層模型共同點&#xff1a; 6. 計算機網絡提供的服務按三種方式分類面向連接服務和無連接服務可靠服務和不可靠服務有連接服務和無連…

服務器卡頓了該如何處理

服務器卡頓了該如何處理 當Windows系統的服務器出現卡頓問題時&#xff0c;以下是一些常見的故障排除步驟&#xff1a; 1.檢查網絡連接&#xff1a;確保服務器的網絡連接正常。檢查網絡設備、交換機、防火墻等設備&#xff0c;確保它們正常運行。嘗試通過其他計算機訪問服務器…

Flink SQL TopN

Flink SQL 對于批處理&#xff08;Batch&#xff09;和流處理&#xff08;streaming&#xff09;模式的SQL&#xff0c;都支持 Top-N 查詢。Top-N 查詢可以根據指定列排序后獲得前 N 個最小或最大值。并且該結果集還可用于進一步分析。Flink 使用 OVER 窗口子句和過濾條件的組合…

近 2000 臺 Citrix NetScaler 服務器遭到破壞

Bleeping Computer 網站披露在某次大規模網絡攻擊活動中&#xff0c;一名攻擊者利用被追蹤為 CVE-2023-3519 的高危遠程代碼執行漏洞&#xff0c;入侵了近 2000 臺 Citrix NetScaler 服務器。 研究人員表示在管理員安裝漏洞補丁之前已經有 1200 多臺服務器被設置了后門&#x…

python學習筆記——軟件安裝

目錄 1. 安裝并驗證Python環境 2. 安裝并設置Visual Studio Code編輯器 3. 設置Visual Studio Code編輯器 4.軟件安裝包 1. 安裝并驗證Python環境 首先&#xff0c;雙擊打開python安裝包。 注意?? &#xff1a; 安裝之前需要關閉殺毒軟件&#xff0c;比如360。 然后&am…

如何快速優化 CnosDB 數據庫性能與延遲:使用 Jaeger 分布式追蹤系統

在正式的生產環境中&#xff0c;數據庫的性能和延遲對于確保系統的穩定和高效運行至關重要。特別是在與 CnosDB 數據庫進行交互時&#xff0c;更深入地了解其表現變得尤為重要。這時Jaeger 分布式追蹤系統發揮了巨大的作用。在本篇博客中&#xff0c;我們將深入探討如何通過使用…

探索網絡架構的關鍵角色:六種常用的服務器類型

在今天的數字時代&#xff0c;服務器是支撐各種在線服務和應用的基石。不同類型的服務器在網絡架構中扮演著不同的角色&#xff0c;從網頁傳輸到電子郵件交換&#xff0c;再到文件傳輸和內容分發。本文將深入探討六種最常用的服務器類型&#xff0c;解釋它們的功能和重要性&…

在 OpenCV 中使用深度學習進行年齡檢測-附源碼

文末附完整源碼和模型文件下載鏈接 在本教程中,我們將了解使用 OpenCV 創建年齡預測器和性別分類器項目的整個過程。 年齡檢測 我們的目標是創建一個程序,使用圖像來預測人的性別和年齡。但預測年齡可能并不像你想象的那么簡單,為什么呢?您可能會認為年齡預測是一個回歸問…

【【萌新的STM32學習-8】】

萌新的STM32學習-8 STM32CubeMX 是由 ST 公司開發的圖形化代碼自動生成工具&#xff0c;能夠快速生成初始化代碼&#xff0c; 如配置 GPIO&#xff0c;時鐘樹&#xff0c;中間件等&#xff0c;使用戶專注于業務代碼的開發。現在 ST 主推 HAL 庫代碼&#xff0c; 經典的標準外設…

數據治理有哪些產品

數據治理是現代企業管理中至關重要的一個環節。隨著企業的數據量不斷增長&#xff0c;如何有效地管理和利用數據成為了一個亟待解決的問題。幸運的是&#xff0c;市場上已經涌現出了許多優秀的數據治理產品&#xff0c;下面就來介紹一些常見的數據治理產品。 首先&#xff0c;我…

配置使用Gitee賬號認證登錄Grafana

三方社會化身份源 集成gitee第三方登錄 第三方登錄的原理 所謂第三方登錄&#xff0c;實質就是 OAuth 授權。用戶想要登錄 A 網站&#xff0c;A 網站讓用戶提供第三方網站的數據&#xff0c;證明自己的身份。獲取第三方網站的身份數據&#xff0c;就需要 OAuth 授權。 舉例來…

Redis中的Key是否在過期時間到達后立即被刪除?詳解Redis的過期策略

AIGC最全資料包 https://zkk-1300025204.cos.ap-nanjing.myqcloud.com/%E5%8F%B2%E4%B8%8A%E6%9C%80%E5%85%A8StableDiffusion%E8%B5%84%E6%96%99%E5%8C%85.csv作者&#xff1a;zhaokk 在現代軟件開發中&#xff0c;性能和數據存儲是至關重要的。為了在高并發環境下提供快速的…

時序預測 | MATLAB實現基于CNN-GRU卷積門控循環單元的時間序列預測-遞歸預測未來(多指標評價)

時序預測 | MATLAB實現基于CNN-GRU卷積門控循環單元的時間序列預測-遞歸預測未來(多指標評價) 目錄 時序預測 | MATLAB實現基于CNN-GRU卷積門控循環單元的時間序列預測-遞歸預測未來(多指標評價)預測結果基本介紹程序設計參考資料 預測結果 基本介紹 MATLAB實現基于CNN-GRU卷積…