【C++詳解】AVL樹深度剖析與模擬實現(單旋、雙旋、平衡因?更新、平衡檢測)

文章目錄

  • 一、AVL樹的概念
  • 二、AVL樹的實現
    • AVL樹的結構
    • AVL樹的插?
      • AVL樹插??個值的?概過程
      • 平衡因?更新
        • 更新原則
        • 更新停?條件
        • 插入結點及更新平衡因子的代碼實現
      • 旋轉
        • 旋轉的原則
        • 右單旋
          • 右單旋代碼實現
        • 左單旋
          • 左單旋代碼實現
        • 左右雙旋
        • 左右雙旋代碼實現
        • 右左雙旋代碼實現
        • 判斷旋轉
    • 中序遍歷
    • 平衡檢測(求高度)
    • 查找
    • Size(求結點個數)
  • 三、源碼


一、AVL樹的概念

  • AVL樹是最先發明的?平衡?叉查找樹,AVL是?顆空樹,或者具備下列性質的?叉搜索樹:它的左右?樹都是AVL樹,且左右?樹的?度差的絕對值不超過1。AVL樹是?顆?度平衡搜索?叉樹,通過控制?度差去控制平衡。
  • AVL樹得名于它的發明者G. M. Adelson-Velsky和E. M. Landis是兩個前蘇聯的科學家,他們在1962年的論?《An algorithm for the organization of information》中發表了它。
  • AVL樹實現這?我們引??個平衡因?(balance factor)的概念,每個結點都有?個平衡因?,任何結點的平衡因?等于右?樹的?度減去左?樹的?度,(也可以左減右)也就是說任何結點的平衡因?等于0/1/-1,AVL樹并不是必須要平衡因?,但是有了平衡因?可以更?便我們去進?觀察和控制樹是否平衡,就像?個?向標?樣。
  • 思考?下為什么AVL樹是?度平衡搜索?叉樹,要求?度差不超過1,?不是?度差是0呢?0不是更好的平衡嗎?畫畫圖分析我們發現,不是不想這樣設計,?是有些情況是做不到?度差是0的。?如?棵樹是2個結點,4個結點等情況下,?度差最好就是1,?法做到?度差是0。
  • AVL樹整體結點數量和分布和完全?叉樹類似,?度可以控制在 logN,那么增刪查改的效率也可以控制在 O(logN),相??叉搜索樹有了本質的提升。

二、AVL樹的實現

AVL樹的結構

template<class K, class V>
struct AVLTreeNode
{// 需要parent指針,后續更新平衡因?可以看到pair<K, V> _kv;AVLTreeNode<K, V>* _left;AVLTreeNode<K, V>* _right;AVLTreeNode<K, V>* _parent;int _bf; // balance factorAVLTreeNode(const pair<K, V>& kv):_kv(kv), _left(nullptr), _right(nullptr), _parent(nullptr), _bf(0){}
};template<class K, class V>
class AVLTree
{typedef AVLTreeNode<K, V> Node;
public://...
private:Node* _root = nullptr;
};

結點的成員變量這一塊我們不再分別定義key和value,而是用pair包起來,和map保持一致,方便后續模擬實現myset和mymap。相比以前多了一個指向parent的指針,后面的平衡旋轉操作會用到。還多了一個平衡因子_bf。
至于AVL樹的定義基本和之前的二叉搜索樹一樣。

AVL樹的插?

AVL樹插??個值的?概過程

  1. 插入:插??個值按?叉搜索樹規則進?插?。
  2. 更新平衡因?:新增結點以后,只會影響該結點祖先結點的?度,也就是可能會影響部分祖先結點的平衡因?,所以更新從新增結點->根結點路徑上的平衡因?,實際中最壞情況下要更新到根,有些情況更新到中間就可以停?了,具體情況我們下?再詳細分析。

更新平衡因?過程中沒有出現不平衡,則插?結束。
更新平衡因?過程中出現不平衡,對不平衡?樹旋轉,旋轉后調平衡的同時,本質降低了?樹的?度,不會再影響上?層,所以插?結束。

平衡因?更新

更新原則
  • 平衡因? = 右?樹?度-左?樹?度。
  • 只有?樹?度變化才會影響當前結點平衡因?。
  • 插?結點,會增加當前結點parent的子樹?度,如果新增結點在parent的右?樹,parent的平衡因?++,新增結點在parent的左?樹,parent平衡因?–。
  • parent所在?樹的?度是否變化決定了是否會繼續往上更新,判斷?度是否變化需要看下面介紹的更新停止條件。
更新停?條件
  • 更新后parent的平衡因?等于0,說明更新中parent的平衡因?變化為-1->0 或者 1->0,說明更新前parent?樹?邊??邊低,新增的結點插?在低的那邊,插?后parent所在的?樹?度不變,不會影響parent的?親結點的平衡因?,更新結束。

在這里插入圖片描述

  • 更新后parent的平衡因?等于1 或 -1,更新前更新中parent的平衡因?變化為0->1 或者 0->-1,說明更新前parent?樹兩邊?樣?,新增的插?結點后,parent所在的?樹?邊??邊低,parent所在的?樹符合平衡要求,但是?度增加了1,會影響parent的?親結點的平衡因?,所以要繼續向上更新。

在這里插入圖片描述

  • 更新后parent的平衡因?等于2 或 -2,更新前更新中parent的平衡因?變化為1->2 或者 -1->-2,說明更新前parent?樹?邊??邊低,新增的插?結點在?的那邊,parent所在的?樹?的那邊更?了,破壞了平衡,parent所在的?樹不符合平衡要求,需要旋轉處理,旋轉的?標有兩個:1、把parent?樹旋轉平衡。2、降低parent?樹的?度,恢復到插?結點以前的?度。所以旋轉后也不需要繼續往上更新,更新停止。

在這里插入圖片描述

  • 不斷更新,更新到根,根的平衡因?是1或-1也停?更新。
插入結點及更新平衡因子的代碼實現

插入操作和之前二叉搜索樹的邏輯相同,重點是這里更新平衡因子的代碼實現。 我們前面介紹過了更新平衡因子有三種可能(0, 1-1, 2-2),但是我們在代碼里最好不要就只分三種情況討論,因為這三種情況是建立在前面代碼邏輯都正常的情況下,但是代碼是有可能出錯的,所以我們需要在代碼里再多寫一個出現錯誤的情況,如果走到這種情況了那么程序一定出現了問題,我們可以assert或者拋異常。
更行結束標志也有三種,一種是更新到根節點,parent指向nullptr,停止更新。一種是parent平衡因子更新到0,停止更新。還有一種是parent平衡因子更新到2或者-2,不平衡了需要旋轉,旋轉完后因為高度復原了所以停止更新。

bool Insert(const pair<K, V>& kv)
{//單純插入if (_root == nullptr){_root = new Node(kv);return true;}else{Node* parent = nullptr;Node* cur = _root;while (cur){if (cur->_kv.first > kv.first) {parent = cur;cur = cur->_left;}else if(cur->_kv.first < _kv.first){parent = cur;cur = cur->_right;}else{return false;}}cur = Node(kv);if (parent->_kv.first > kv.first)parent->_left = cur;elseparent->_right = cur;cur->_parent = parent;}//更新平衡因子while (parent) //若更新到根結點,更新結束---1{if (parent->_left == cur)parent->_bf--;elseparent->_bf++;if (parent->_bf == 0){//更新結束---2break;}else if (parent->_bf == 1 || parent->_bf == -1){//繼續向上更新cur = parent;parent = parent->_parent;}else if (parent->_bf == 2 || parent->_bf == -2){//不平衡了,旋轉處理//旋轉完后,更新結束---3break;}else{//代碼出錯assert(false);}}return true;

旋轉

旋轉的原則

1、旋轉后保持搜索樹的規則
2. 讓旋轉的樹從不滿?變平衡,其次降低旋轉樹的?度。
3. 旋轉總共分為四種,左單旋/右單旋/左右雙旋/右左雙旋。
說明:下?的圖中,有些結點我們給的是具體值,如10和5等結點,這?是為了?便講解,實際中是什么值都可以,只要??關系符合搜索樹的性質即可。

右單旋
  • 圖中展?的是10為根的樹,有a/b/c三棵抽象為?度h的?樹(h>=0),a/b/c均符合AVL樹的要求。10可能是整棵樹的根,也可能是?個整棵樹中局部的?樹的根。這?a/b/c是?度為h的?樹,是?種概括抽象表?,他代表了所有右單旋的場景,實際右單旋形態有很多種。
  • 在a?樹中插??個新結點,導致a?樹的?度從h變成h+1,不斷向上更新平衡因?,導致10的平衡因?從-1變成-2,10為根的樹左右?度差超過1,違反平衡規則。10為根的樹左邊太?了,需要往右邊旋轉,控制兩棵樹的平衡。
  • 旋轉核?步驟,因為5 < b?樹的值 < 10,將b變成10的左?樹,10變成5的右?樹,5變成這棵樹新的根,符合搜索樹的規則,控制了平衡,同時這棵的?度恢復到了插?之前的h+2,符合旋轉原則。如果插?之前10是整棵樹的?個局部?樹,旋轉后局部?樹的高度恢復到插入之前,不會再影響上?層,插?結束。
    這里要把a打包看成一個整體,如果插入a也會引起旋轉那么a旋轉的步驟也和我們這里討論的一樣,所以這里我們只是講的是一個旋轉的模板,不管是a子樹里面的旋轉還是在10上面的樹的旋轉都適用。(如果10不是根節點的話)

在這里插入圖片描述

右單旋代碼實現

一、首先把parent->_left = subLR, subL->_right = parent,這兩個調整孩子的步驟很簡單。
二、但是我們要記住AVL樹是三叉鏈,它的每個結點還有一個指向parent的指針,所以還需要調整subLR、parent、subL的_parent指針,調整_parent比較復雜。

  • subLR->_parent = parent: 這里要注意subLR是可能為空的,所以需要判斷一下,為空就無需調整subLR的_parent。
  • parent->_parent = subL: 這里沒有什么問題。
  • subL->_parent 這里要分兩種情況討論,一種是當parent是根節點,那么需要把_root給給subL,subL->_parent = nullptr。一種是parent不是根節點,那么parent只是其中一顆子樹的根節點,所以需要維護我們在旋轉子樹與整個樹的關系,需要把subL->parent指向parent->parent,然后把parent->parent的_left或者_right指向subL,具體細節看下面代碼的注釋。

三、最后調整平衡因子,旋轉過程只有parent和subL的平衡因子會改變,而且都是變為0。

void RotateR(Node* parent)
{//調整兩個結點的孩子指針Node* subL = parent->_left;Node* subLR = subL->_right;parent->_left = subLR;subL->_right = parent;//調整三個結點的父親指針if (subLR) // 結點1subLR->_parent = parent;//提前保存一下parent->_parentNode* parentParent = parent->_parent;parent->_parent = subL; // 結點2//subL的_parent分三種情況if (parent == _root){//parent是根節點_root = subL;subL->_parent = nullptr;}else{subL->_parent = parentParent;if (parent == parentParent->_left){parentParent->_left = subL;}else{parentParent->_right = subL; // 結點3}}//調整平衡因子subL->_bf = parent->_bf = 0;
}
左單旋

左單旋和右單旋邏輯上基本一致,小編這里就不細講了。

在這里插入圖片描述

左單旋代碼實現
void RotateL(Node* parent)
{//調整兩個結點的孩子指針Node* subR = parent->_right;Node* subRL = subR->_left;parent->_right = subRL;subR->_left = parent;//調整三個結點的父親指針if (subRL) // 結點1subRL->_parent = parent;//提前保存一下parent->_parentNode* parentParent = parent->_parent;parent->_parent = subR; // 結點2//subR的parent分三種情況if (parent == _root){//parent是根節點_root = subR;subR->_parent = nullptr;}else{subR->_parent = parentParent;if (parent == parentParent->_left){parentParent->_left = subR;}else{parentParent->_right = subR; // 結點3}}//調整平衡因子subR->_bf = parent->_bf = 0;
}
左右雙旋

通過下面兩幅圖可以看到,左邊?時,如果插?位置不是在a?樹,?是插?在b?樹,b?樹?度從h變成h+1,引發旋轉,右單旋?法解決問題,右單旋后,我們的樹依舊不平衡。右單旋解決的純粹的左邊?,但是插?在b?樹中,10為跟的?樹不再是單純的左邊?,對于10是左邊?,但是對于5是右邊?,需要?兩次旋轉才能解決。以5為旋轉點進??個左單旋,(把一開始根的左邊小的右邊小調整成一邊小,這里被調整后都是左邊小)以10為旋轉點進??個右單旋,這棵樹就平衡了。(旋轉前是一邊小再單旋就可以平衡)

在這里插入圖片描述
在這里插入圖片描述

以5為旋轉點進??個左單旋,以10為旋轉點進??個右單旋,這棵樹這棵樹就平衡了。

在這里插入圖片描述

實際上左右雙旋是可以一步到位的,相當于把subLR的左邊分給了subL的右邊,subLR的右邊分給了parent的左邊,最后把subLR推上去做根,我們看下面這幅圖:

在這里插入圖片描述

但是左右雙旋體現在代碼上還是一步一步來,因為可以復用單旋的代碼,更爽一點。

平衡因子的更新:
左右雙旋的旋轉過程如上所示,只有一種情況,但是三個結點平衡因子的更新有三種情況,分別是將結點插入到b子樹的e子樹(情況一)、b子樹的f子樹(情況二)、b子樹本身為空(情況三),插入的結點本身就成了b子樹。插入時的前兩種情況旋轉結束后體現在subL右子樹的高度和parent左子樹的高度,最后一種情況表明旋轉只有三個結點參與,三個結點都沒有子樹。
最后平衡因子的結果是subLR始終為0,情況一subL為0,parent為-1,情況二subL為-1,parent為0,情況三subL、parent都為0。
要判別平衡因子的更新是哪種情況就需要記錄插入后subLR的平衡因子,而且需要在兩次單旋之前把值記錄下來,因為單旋會把結點的平衡因子都置為0,(因為單旋是以只有一邊大的邏輯來旋轉和調整平衡因子的,所以左右雙旋復用單旋代碼后需要手動調整平衡因子)如果subLR的平衡因子為-1那么是情況1,為1那么是情況2,為0那么是情況3。

左右雙旋代碼實現
void RotateLR(Node* parent)
{Node* subL = parent->_left;Node* subLR = subL->_right;int tembf = subLR->_bf;RotateL(parent->_left);RotateR(parent);//更新平衡因子subLR->_bf = 0;if (tembf == -1)     //情況一{subL->_bf = 0;parent->_bf = 1;}else if(tembf == 1)  //情況二{subL->_bf = -1;parent->_bf = 0;}else if(tembf == 0)  //情況三{subL->_bf = subLR->_bf = 0;}else{assert(false);}
}
右左雙旋代碼實現
void RotateRL(Node* parent)
{Node* subR = parent->_right;Node* subRL = subR->_left;int tembf = subRL->_bf;RotateR(subR);RotateL(parent);//更新平衡因子subRL->_bf = 0;if (tembf == -1)      //情況一{subR->_bf = 1;parent->_bf = 0;}else if(tembf == 1)   //情況二{subR->_bf = 0;parent->_bf = -1;}else if (tembf == 0)  //情況三{subR->_bf = subRL->_bf = 0;}else{assert(false);}
}
判斷旋轉
if (parent->_bf == -2 && cur->_bf == -1)    //右單旋
{RotateR(parent);
}
else if (parent->_bf == 2 && cur->_bf == 1) //左單旋
{RotateL(parent);
}
else if (parent->_bf == -2 && cur->_bf == 1) //左右雙旋(平衡因子異號)
{RotateLR(parent);
}
else if (parent->_bf == 2 && cur->_bf == -1) //右左雙旋
{RotateRL(parent);
}

我們可以總結發現,單旋時兩個結點的平衡因子同號,雙旋時兩個結點的平衡因子異號。

中序遍歷

void InOrder()
{_InOrder(_root);cout << endl;
}void _InOrder(Node* root)
{if (root == nullptr)return;_InOrder(root->_left);cout << root->_kv.first << " ";_InOrder(root->_right);
}

平衡檢測(求高度)

我們實現的AVL樹是否合格,就是檢查每一個結點的平衡因子是否平衡,但是不能只檢查平衡因子,因為平衡因子也可能出問題,所以需要遞歸求出每一個結點的平衡因子是否符合要求,然后再拿算的平衡因子和結點內部的平衡因子比較。方法就是通過遞歸求當前結點左右子樹的高度,然后求當前結點左右?樹?度差進?反向驗證。
(代碼中的abs 是 C++ 標準庫中的一個函數,用于計算整數的絕對值(即去掉符號,只保留數值大小)

int Height()
{return _Height(_root);
}bool IsBalanceTree()
{return _IsBalanceTree(_root);
} int _Height(Node* root)
{if (root == nullptr)return 0;int LHeight = _Height(root->_left);int RHeight = _Height(root->_right);return 1 + (LHeight > RHeight ? LHeight : RHeight);
}bool _IsBalanceTree(Node* root)
{if (root == nullptr)return true;   //空樹也是AVL樹int diff = _Height(root->_right) - _Height(root->_left);if (abs(diff) >= 2){cout << "高度差異常" << endl;return false;}else if (diff != root->_bf){cout << "平衡因子異常" << endl;return false;}return _IsBalanceTree(root->_left) && _IsBalanceTree(root->_right);
}

查找

bool Find(const K& x)
{Node* cur = _root;while (cur){if (cur->_kv.first == x)return true;else if (cur->_kv.first > x)cur = cur->_left;elsecur = cur->_right;}return false;
}

Size(求結點個數)

int Size()
{return _Sise(_root);
}int _Sise(Node* _root)
{if (_root == nullptr)return 0;return 1 + _Sise(_root->_left) + _Sise(_root->_right);
}

三、源碼

AVLTree.h:

#pragma once
using namespace std;
#include <iostream>
#include <assert.h>
#include <vector>template<class K, class V>
struct AVLTreeNode
{// 需要parent指針,后續更新平衡因?可以看到pair<K, V> _kv;AVLTreeNode<K, V>* _left;AVLTreeNode<K, V>* _right;AVLTreeNode<K, V>* _parent;int _bf; // balance factorAVLTreeNode(const pair<K, V>& kv):_kv(kv),_left(nullptr),_right(nullptr),_parent(nullptr),_bf(0){}
};template<class K, class V>
class AVLTree
{typedef AVLTreeNode<K, V> Node;
public:bool Insert(const pair<K, V>& kv){//單純插入if (_root == nullptr){_root = new Node(kv);return true;}Node * parent = nullptr;Node* cur = _root;while (cur){if (cur->_kv.first > kv.first){parent = cur;cur = cur->_left;}else if (cur->_kv.first < kv.first){parent = cur;cur = cur->_right;}else{return false;}}cur = new Node(kv);if (parent->_kv.first > kv.first) //判斷cur插在parent的那邊parent->_left = cur;elseparent->_right = cur;cur->_parent = parent;//更新平衡因子while (parent) //若更新到根結點,更新結束---1{if (parent->_left == cur)parent->_bf--;elseparent->_bf++;if (parent->_bf == 0){//更新結束---2break;}else if (parent->_bf == 1 || parent->_bf == -1){//繼續向上更新cur = parent;parent = parent->_parent;}else if (parent->_bf == 2 || parent->_bf == -2){//不平衡了,旋轉處理if (parent->_bf == -2 && cur->_bf == -1)    //右單旋{RotateR(parent);}else if (parent->_bf == 2 && cur->_bf == 1) //左單旋{RotateL(parent);}else if (parent->_bf == -2 && cur->_bf == 1) //左右雙旋(平衡因子異號){RotateLR(parent);}else if (parent->_bf == 2 && cur->_bf == -1) //右左雙旋{RotateRL(parent);}//旋轉完后,更新結束---3break;}else{//代碼出錯assert(false);}}return true; }void InOrder(){_InOrder(_root);cout << endl;}int Height(){return _Height(_root);}bool IsBalanceTree(){return _IsBalanceTree(_root);}  bool Find(const K& x){Node* cur = _root;while (cur){if (cur->_kv.first == x)return true;else if (cur->_kv.first > x)cur = cur->_left;elsecur = cur->_right;}return false;}int _Height(Node* root){if (root == nullptr)return 0;int LHeight = _Height(root->_left);int RHeight = _Height(root->_right);return 1 + (LHeight > RHeight ? LHeight : RHeight);}bool _IsBalanceTree(Node* root){if (root == nullptr)return true;   //空樹也是AVL樹int diff = _Height(root->_right) - _Height(root->_left);if (abs(diff) >= 2){cout << "高度差異常" << endl;return false;}else if (diff != root->_bf){cout << "平衡因子異常" << endl;return false;}return _IsBalanceTree(root->_left) && _IsBalanceTree(root->_right);}int Size(){return _Sise(_root);}int _Sise(Node* _root){if (_root == nullptr)return 0;return 1 + _Sise(_root->_left) + _Sise(_root->_right);}void _InOrder(Node* root){if (root == nullptr)return;_InOrder(root->_left);cout << root->_kv.first << " ";_InOrder(root->_right);}void RotateR(Node* parent){//調整兩個結點的孩子指針Node* subL = parent->_left;Node* subLR = subL->_right;parent->_left = subLR;subL->_right = parent;//調整三個結點的父親指針if (subLR) // 結點1subLR->_parent = parent;//提前保存一下parent->_parentNode* parentParent = parent->_parent;parent->_parent = subL; // 結點2//subL的_parent分三種情況if (parent == _root){//parent是根節點_root = subL;subL->_parent = nullptr;}else{subL->_parent = parentParent;if (parent == parentParent->_left){parentParent->_left = subL;}else{parentParent->_right = subL; // 結點3}}//調整平衡因子subL->_bf = parent->_bf = 0;}void RotateL(Node* parent){//調整兩個結點的孩子指針Node* subR = parent->_right;Node* subRL = subR->_left;parent->_right = subRL;subR->_left = parent;//調整三個結點的父親指針if (subRL) // 結點1subRL->_parent = parent;//提前保存一下parent->_parentNode* parentParent = parent->_parent;parent->_parent = subR; // 結點2//subR的parent分三種情況if (parent == _root){//parent是根節點_root = subR;subR->_parent = nullptr;}else{subR->_parent = parentParent;if (parent == parentParent->_left){parentParent->_left = subR;}else{parentParent->_right = subR; // 結點3}}//調整平衡因子subR->_bf = parent->_bf = 0;}void RotateLR(Node* parent){Node* subL = parent->_left;Node* subLR = subL->_right;int tembf = subLR->_bf;RotateL(parent->_left);RotateR(parent);//更新平衡因子subLR->_bf = 0;if (tembf == -1)     //情況一{subL->_bf = 0;parent->_bf = 1;}else if(tembf == 1)  //情況二{subL->_bf = -1;parent->_bf = 0;}else if(tembf == 0)  //情況三{subL->_bf = subLR->_bf = 0;}else{assert(false);}}void RotateRL(Node* parent){Node* subR = parent->_right;Node* subRL = subR->_left;int tembf = subRL->_bf;RotateR(subR);RotateL(parent);//更新平衡因子subRL->_bf = 0;if (tembf == -1)      //情況一{subR->_bf = 1;parent->_bf = 0;}else if(tembf == 1)   //情況二{subR->_bf = 0;parent->_bf = -1;}else if (tembf == 0)  //情況三{subR->_bf = subRL->_bf = 0;}else{assert(false);}}private:Node* _root = nullptr;
};

test.c:

#include "AVLTree.h"void TestAVLTree1()
{AVLTree<int, int> t;// 常規的測試?例int a[] = { 16, 3, 7, 11, 9, 26, 18, 14, 15 };// 特殊的帶有雙旋場景的測試?例//int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14 };for (auto e : a){t.Insert({ e, e });}t.InOrder();cout << t.IsBalanceTree() << endl;
}// 插??堆隨機值,測試平衡,順便測試?下?度和性能等
void TestAVLTree2()
{const int N = 100000;vector<int> v;v.reserve(N);srand(time(0));for (size_t i = 0; i < N; i++){v.push_back(rand() + i);}size_t begin2 = clock();AVLTree<int, int> t;for (auto e : v){t.Insert(make_pair(e, e));}size_t end2 = clock();cout << "Insert:" << end2 - begin2 << endl;cout << t.IsBalanceTree() << endl;cout << "Height:" << t.Height() << endl;cout << "Size:" << t.Size() << endl;size_t begin1 = clock();// 確定在的值for (auto e : v){t.Find(e);}// 隨機值//for (size_t i = 0; i < N; i++)//{//	t.Find((rand() + i));//}size_t end1 = clock();cout << "Find:" << end1 - begin1 << endl;
}int main()
{//TestAVLTree1();TestAVLTree2();return 0;
}

以上就是小編分享的全部內容了,如果覺得不錯還請留下免費的關注和收藏
如果有建議歡迎通過評論區或私信留言,感謝您的大力支持。
一鍵三連好運連連哦~~

在這里插入圖片描述

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

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

相關文章

C++ 中的 enable_shared_from_this 詳解

# C 中的 enable_shared_from_this 詳解enable_shared_from_this 是 C 標準庫中的一個模板類&#xff0c;用于解決在類的成員函數中需要獲取指向自身的 shared_ptr 的問題。## 基本概念當一個對象由 shared_ptr 管理時&#xff0c;如果你想在對象的成員函數中獲得一個指向自身的…

day11 - 浮動

1. 標簽之間的空白問題 1.1. 問題重現 <!DOCTYPE html> <html lang="en"> <head><meta charset="UTF-8"><title>Document</title><style>img {width: 100px;}</style> </head> <body><a…

MetaBit基金會加碼投資圖靈協議,深化去中心化金融與元宇宙生態合作

2025年7月15日 —— 新加坡MetaBit基金會宣布進一步加大對圖靈協議&#xff08;Turing Protocol&#xff09;的戰略投資&#xff0c;涵蓋其去中心化交易所&#xff08;DEX&#xff09;、聚合交易平臺&#xff08;CEX&#xff09;及公鏈生態的技術與資金支持。雙方還將圍繞元宇宙…

NWinfo(硬件信息檢測工具)v1.4.20綠色免費版,U盤隨走隨檢,結果即刻導出

[軟件名稱]: NWinfo(硬件信息檢測工具)v1.4.20綠色免費版 [軟件大小]: 1.4 MB [軟件大小]: 夸克網盤 | 迅雷網盤 軟件介紹 NWinfo 誕生于給老舊機器做體檢的需求&#xff1a;一個單文件、零依賴的 Win32 小程序&#xff0c;卻能像放大鏡一樣把機箱里的故事讀出來。它不借助…

Numpy科學計算與數據分析:Numpy高效數據處理與優化

Numpy性能優化 學習目標 本課程將深入探討如何利用Numpy庫的特性來優化Python代碼的性能&#xff0c;重點講解向量化操作、避免Python循環等技術&#xff0c;幫助學員掌握高效的數據處理方法。 相關知識點 Numpy性能優化 學習內容 1 Numpy性能優化 1.1 Numpy數組與Pytho…

鴻蒙HarmonyOS中Axios網絡庫封裝與文件上傳功能實現

在開發鴻蒙HarmonyOS應用時&#xff0c;網絡請求功能是必不可少的。axios是一個非常流行的基于Promise的HTTP客戶端&#xff0c;適用于瀏覽器和Node.js環境。本文將介紹如何在鴻蒙HarmonyOS中封裝axios庫&#xff0c;使其能夠支持文件上傳&#xff0c;并提供額外的配置選項以滿…

【AI】從零開始的文本分類模型實戰:從數據到部署的全流程指南

目錄 引言 一、項目背景與目標 二、環境準備 三、數據獲取與探索 3.1 數據獲取 3.2 數據探索 四、數據預處理 4.1 文本清洗 4.2 分詞 4.3 標簽編碼 4.4 數據集劃分 4.5 特征提取 五、模型構建與訓練 5.1 邏輯回歸模型 5.2 LSTM 模型 六、模型評估 6.1 邏輯回歸…

Rust學習心得---特征對象和泛型區別

區別特性泛型&#xff08;靜態分發&#xff09;特征對象&#xff08;動態分發&#xff09;決策時機編譯時單態化&#xff08;生成具體類型的代碼&#xff09;運行時通過vtable查找方法運行性能零運行時開銷&#xff08;直接內聯調用&#xff09;有額外開銷&#xff08;指針跳轉…

ESP32-menuconfig(2) -- Application manager

按順序來說&#xff0c;第二篇本來應該是Security features&#xff0c;但是這塊內容應該到小批量才用的到&#xff0c;而一些愛好者可能永遠都不會修改這塊&#xff0c;所以先看看更常用Application manager&#xff0c;這部分內容也比較少。 Application managerCONFIG_APP_C…

ArgoCD 與 GitOps:K8S 原生持續部署的實操指南

容器技術的爆發讓 Kubernetes&#xff08;K8s&#xff09;成為了「云原生時代的操作系統」—— 它能高效編排成千上萬的容器&#xff0c;解決彈性伸縮、資源調度等核心問題。但隨著企業應用規模擴大&#xff0c;K8s 的「部署與管理」逐漸暴露新的挑戰&#xff1a; 多環境&…

Day36--動態規劃--1049. 最后一塊石頭的重量 II,494. 目標和,474. 一和零

Day36–動態規劃–1049. 最后一塊石頭的重量 II&#xff0c;494. 目標和&#xff0c;474. 一和零 遇到難題&#xff0c;思考超過20分鐘沒有思路的&#xff0c;要跳過&#xff01;不然時間效率太低了。 **看題解同理&#xff0c;看20分鐘看不懂的&#xff0c;也要跳過&#xff0…

前端開發技術深度總結報告

前端開發技術深度總結報告 &#x1f4cb; 項目背景 基于 Vue 3 TypeScript Element Plus 的企業級產品管理系統&#xff0c;重點解決產品表單的數據緩存、頁面導航、用戶體驗等核心問題。&#xfffd;&#xfffd; 遇到的問題及解決方案 1. 瀏覽器控制臺錯誤處理 問題: 大量第…

Linux 單機部署 Kafka 詳細教程(CentOS 7+)

系列博客專欄&#xff1a; SpringBoot與微服務實踐系列博客Java互聯網高級培訓教程 一、環境準備 1. 操作系統要求 Kafka 可以在多種 Linux 發行版上運行&#xff0c;本文以 CentOS 7 為例&#xff0c;其他發行版步驟類似&#xff0c;只需調整包管理命令。 2. Java 環境要…

解析工業機器視覺中的飛拍技術

在工業機器視覺的領域&#xff0c;"飛拍"這個術語時常被提起&#xff0c;尤其是在高速檢測和動態捕捉的場景中。但你真的了解飛拍是什么嗎&#xff1f;它到底如何工作&#xff0c;能為工業應用帶來哪些突破性改進呢&#xff1f;讓我們一起來解密。1. 飛拍的核心概念 …

[特殊字符]企業游學 | 探秘字節,解鎖AI科技新密碼

寶子們&#xff0c;想知道全球科技巨頭字節跳動的成功秘籍嗎&#xff1f;一場企業游學&#xff0c;帶你深入字節跳動創新基地&#xff0c;探索AI新科技&#xff0c;揭開規模化增長背后的神秘面紗?字節跳動&#xff1a;全球經濟價值的創造者字節跳動可太牛啦&#xff01;TikTok…

主流大數據框架深度解析:從介紹到選型實戰

主流大數據框架深度解析:從介紹到選型實戰 在數據驅動的時代,選擇合適的大數據處理框架是構建高效、可靠數據平臺的關鍵。 深入剖析 Hadoop MapReduce、Apache Spark、Apache Flink 和 Kafka Streams 四大主流框架,從框架介紹、具體使用場景、優缺點、選擇建議到實際案例,…

座艙HMI軟件開發架構:核心功能與案例解析

隨著智能座艙的持續演進&#xff0c;HMI&#xff08;Human Machine Interface&#xff0c;人與機器交互界面&#xff09;系統已從單一的顯示控制器演變為集多屏聯動、多模態交互、車載服務集成于一體的智能系統&#xff0c;需要一個多系統、多設備協同運行的復雜架構來支撐。本…

把“思考”塞進 1 KB:我用純 C 語言給單片機手搓了一個微型 Transformer 推理引擎

標簽&#xff1a;TinyML、Transformer、單片機、Cortex-M、量化、KV-Cache、裸機編程 ---- 1. 為什么要在 64 KB SRAM 的 MCU 上跑 Transformer&#xff1f; 2024 年以前&#xff0c;TinyML ≈ CNN CMSIS-NN&#xff0c;做語音喚醒或簡單分類就到頭了。 但產品同事突然拍腦袋&…

什么是CLI?

什么是CLI&#xff1f;CLI&#xff08;Command Line Interface&#xff09;是命令行界面的縮寫&#xff0c;是一種通過文本命令與計算機程序交互的方式。通俗比喻CLI就像是一個"智能助手"&#xff1a;你輸入命令&#xff0c;它執行任務就像和機器人對話一樣&#xff…

mysql基本sql語句大全

十分想念順店雜可。。。以下是 MySQL 中常用的基本 SQL 語句大全&#xff0c;按功能分類整理&#xff0c;包含語法和示例&#xff0c;方便參考使用&#xff1a;一、數據庫操作&#xff08;DDL&#xff09;用于創建、刪除、切換數據庫。創建數據庫-- 基本語法 CREATE DATABASE […