C++實(shí)現(xiàn)紅黑樹(shù)核心插入實(shí)例代碼
一、紅黑樹(shù)概念介紹
概念:
紅黑樹(shù),也是一種二叉搜索樹(shù),它是在每個(gè)結(jié)點(diǎn)上增加一個(gè)存儲(chǔ)位表示結(jié)點(diǎn)的顏色,可以是紅或黑,然后通過(guò)對(duì)任何一條從根到葉子的路徑上各個(gè)結(jié)點(diǎn)著色方式的限制,保證了沒(méi)有一條路徑會(huì)能超過(guò)其他路徑的倆倍,因而是近似平衡的。map和set的底層數(shù)據(jù)結(jié)構(gòu)就是用紅黑樹(shù)來(lái)封裝的。
性質(zhì):
1.根節(jié)點(diǎn)是黑色的
2.不能出現(xiàn)連續(xù)的紅色節(jié)點(diǎn)
3. 每條路徑上有相同數(shù)量的黑色節(jié)點(diǎn)
4. 每個(gè)葉子(空節(jié)點(diǎn))結(jié)點(diǎn)都是黑色
5. 每個(gè)結(jié)點(diǎn)不是紅色就是黑色
滿足上面的性質(zhì),紅黑樹(shù)就能保證:最長(zhǎng)路徑中節(jié)點(diǎn)個(gè)數(shù)不會(huì)超過(guò)最短路徑節(jié)點(diǎn)個(gè)數(shù)的2倍。
優(yōu)勢(shì):
假設(shè)全部的黑色節(jié)點(diǎn)有N個(gè) 最短路徑長(zhǎng)度是logN 整棵樹(shù)的節(jié)點(diǎn)數(shù)量:[N,2N],最長(zhǎng)路徑長(zhǎng)度:2logN。假設(shè)10億個(gè)節(jié)點(diǎn),AVL:最多查找30次左右;RB:最多查找60次左右。
綜合而言,其實(shí)對(duì)于查找大量數(shù)據(jù)30次和60次沒(méi)太大差別,而紅黑樹(shù)不要求絕對(duì)平衡,只需保證最長(zhǎng)路徑不超過(guò)最短路徑的2倍,相對(duì)來(lái)說(shuō)降低了插入和旋轉(zhuǎn)的次數(shù),所以在經(jīng)常進(jìn)行增刪的結(jié)構(gòu)中性能比AVL樹(shù)更優(yōu)。
二、紅黑樹(shù)模擬實(shí)現(xiàn)
(1)紅黑樹(shù)節(jié)點(diǎn)
enum Color
{
RED=0,
BLACK,
};
template<class K,class V>
struct RbTreeNode
{
RbTreeNode<K, V>* _left;
RbTreeNode<K, V>* _right;
RbTreeNode<K, V>* _parent;
pair<K, V>_kv;
Color _col;
RbTreeNode(const pair<K, V>& kv)
:_left(nullptr)
, _right(nullptr)
, _parent(nullptr)
, _kv(kv)
, _col(RED)
{}
};里面同樣用三叉鏈,要有左孩子右孩子,父節(jié)點(diǎn),以及pair類(lèi)的類(lèi)型來(lái)存儲(chǔ)kv兩個(gè)相同或不同類(lèi)型的值(當(dāng)我們使用make_pair函數(shù)創(chuàng)建一個(gè)pair對(duì)象,該函數(shù)會(huì)自動(dòng)推斷參數(shù)類(lèi)型),并在里面增加一個(gè)枚舉,用來(lái)表示一個(gè)節(jié)點(diǎn)不是紅色就是黑色。關(guān)于構(gòu)造函數(shù)里面的顏色初始化為黑色還是紅色,在插入會(huì)講解。
(2)紅黑樹(shù)插入分析(核心)
插入和前面的AVL插入部分是一樣的,在里面如果是根節(jié)點(diǎn),那就把跟點(diǎn)為置為黑色。
那這有一個(gè)問(wèn)題就是節(jié)點(diǎn)構(gòu)造函數(shù)里面的一個(gè)問(wèn)題:我們?cè)谏暾?qǐng)一個(gè)新節(jié)點(diǎn)時(shí),寧愿新增紅色還是黑色?
因?yàn)椴还苁窃瞿姆N顏色,本質(zhì)上是違反規(guī)則三還是違反規(guī)則四的問(wèn)題,如果插入黑色,一定違反規(guī)則四,但它代價(jià)太大,違反規(guī)則三就不一定,因?yàn)樗母腹?jié)點(diǎn)可能為紅色也可能為黑色,如果在黑色節(jié)點(diǎn)下面插入就沒(méi)影響,反之就會(huì)影響。
對(duì)于上面的做法會(huì)出現(xiàn)下面的兩大情況,第一GPC為一一條直線,第二GPC為折線。
如圖:

G為祖父節(jié)點(diǎn),P為父親節(jié)點(diǎn),C為當(dāng)前節(jié)點(diǎn),U為叔父節(jié)點(diǎn)
1、先解決當(dāng)GPC為一條直線的時(shí)候:
(1)如果U存在且為紅色,P必須變黑,U也得變黑,左右都增加了黑,那就把G變紅,這樣久能保持黑色節(jié)點(diǎn)不變,因?yàn)镚現(xiàn)在變成紅了,但是G的父節(jié)點(diǎn)如果是紅色的還得繼續(xù)處理 ,那就把G當(dāng)成C節(jié)點(diǎn),繼續(xù)算它的祖父,再看U,如果U是紅再把它變成黑。

它的所對(duì)應(yīng)的抽象圖以及具象圖:

(2)如果U不存在或者存在且為黑又是兩種情況
如果U不存在,再增加紅節(jié)點(diǎn),且有連續(xù)的紅色節(jié)點(diǎn),有可能會(huì)超過(guò)它的最短路徑,只能旋轉(zhuǎn)來(lái)降高度。把P變黑,把G給P的左,再把P作為左,同時(shí)把原來(lái)的G變紅,這里就是左單旋加G和P變色。
如下例圖:

它所對(duì)應(yīng)的抽象及具象圖:

如果U存在且為黑色
U存在且為黑是由第一種請(qǐng)況變化而來(lái),這里發(fā)生了右單旋,P的左孩子作為G的左孩子,而G又作為P的右孩子,且P變?yōu)楹冢珿變?yōu)榧t。

對(duì)于上面的U不存在或者存在為黑兩種情況進(jìn)行總結(jié): P為G的左孩子時(shí),C為P的左孩子進(jìn)行右單旋轉(zhuǎn),如果P為G的右孩子,C為P的右孩子,進(jìn)行左單旋轉(zhuǎn)。P變?yōu)楹谏珿變?yōu)榧t色。
旋轉(zhuǎn)的目的就是為了防止最長(zhǎng)路徑超過(guò)最短路徑的2倍。GPC為一條線的時(shí)候就是左單旋轉(zhuǎn)或右單旋。
2、再解決GPC為折線的情況
(1)U存在且為黑
出現(xiàn)這種折線情況的原因是由第一種情況先變色而產(chǎn)生,然后p變成g的左,c變成p的右,c為紅,p為紅,g為黑,接著我們先以p為軸進(jìn)行左旋轉(zhuǎn)進(jìn)行降低高度,在以g為軸進(jìn)行右旋,且g變紅,c變黑,因?yàn)檫@樣才能保證不出現(xiàn)連續(xù)紅結(jié)點(diǎn)且保證每個(gè)路徑黑色數(shù)量一樣。以上實(shí)際進(jìn)行了雙旋。

(2)U不存在,直接右單旋
(3)插入代碼思路(如何快速寫(xiě)插入算法)

(一)先看左面這種大情況
插入C1肯定為紅節(jié)點(diǎn),P1如果是紅,就需要開(kāi)始作處理:先利用三叉鏈,從P1得出G1,進(jìn)入核心環(huán)節(jié):左面的這種情況是P1是G1的左孩子時(shí),里面又要有分兩種情況:
(1)如果是U存在且為紅,我們把P1和U變?yōu)楹?,G1變?yōu)榧t,更新C1到G1的位置,繼續(xù)往上調(diào)整;
(2)如果是U不存在或黑(是一種情況),如果U不存在就不能變黑,而且就算存在為黑說(shuō)明是一定是由繼續(xù)往上調(diào)整的那一步(情況(1))而來(lái)的即繼續(xù)往上處理,我們還是是增加了節(jié)點(diǎn),就可能導(dǎo)致最長(zhǎng)路徑超過(guò)最短路徑的2倍或者一直用情況(1)的方式最終導(dǎo)致每條路徑黑色數(shù)量不相等。這時(shí)候我們就要通過(guò)旋轉(zhuǎn)方式降低高度加變色來(lái)解決。所以在這里又分兩種:G1P1C1為直線和G1P1C1為折線:
如果是C1P1在一條直線上即C1是P1的左孩子,我們以G1為軸進(jìn)行右單旋(哪邊高往哪邊降),G1要變紅,P1要變成黑,因?yàn)镚1下來(lái)了P1作為根,不能出現(xiàn)連續(xù)的紅節(jié)點(diǎn)P要變黑,又因?yàn)楸WC黑色數(shù)量相等,G1要變紅。如果是C1和P1,G1是折線的時(shí)候即C1是P1的右孩子,這時(shí)候需要先以P1為軸進(jìn)行左單旋,再以G1為軸進(jìn)行右單旋,最終C1做了根節(jié)點(diǎn),G1是右孩子,因?yàn)镻1是紅,C1是紅不能出現(xiàn)連續(xù)紅節(jié)點(diǎn),需把C1變黑,但是還要保證黑色數(shù)量相等,就把G1變紅。最后結(jié)束break,只有情況(1)才會(huì)繼續(xù)向上來(lái)回處理。
(二)再看右面這種大情況
和(一)情況類(lèi)似
P2是G2的右孩子,同理那旋轉(zhuǎn)方式就根上面相反。
所以我們的插入函數(shù):
bool Insert(const pair<K, V>& kv)
{
if (_root == nullptr)
{
_root = new Node(kv);
_root->_col = BLACK;
return true;
}
Node* parent = nullptr;
Node* cur = _root;
while (cur)
{
if (cur->_kv.first < kv.first)
{
parent = cur;
cur = cur->_right;
}
else if (cur->_kv.first > kv.first)
{
parent = cur;
cur = cur->_left;
}
else
{
return false;
}
}
cur = new Node(kv);
if (parent->_kv.first > kv.first)
{
parent->_left = cur;
}
else
{
parent->_right = cur;
}
cur->_parent = parent;
while (parent && parent->_col == RED)
{
Node* grandf = parent->_parent;
if (grandf->_left == parent)
{
Node* unc = grandf->_right;
if (unc && unc->_col == RED)//u存在且為紅,變色處理
{
parent->_col = BLACK;
unc->_col = BLACK;
grandf->_col = RED;
cur = grandf;//繼續(xù)往上調(diào)整
parent = cur->_parent;
}
else//u不存在或者黑
{
if (parent->_left == cur)
{
RotateR(grandf);
parent->_col = BLACK;
grandf->_col = RED;
}
else
{
RotateL(parent);
RotateR(grandf);
cur->_col = BLACK;
grandf->_col = RED;
}
break;
}
}
else
{
Node* unc = grandf->_left;
if (unc && unc->_col == RED)
{
parent->_col = BLACK;
unc->_col = BLACK;
grandf->_col = RED;
cur = grandf;
parent = cur->_parent;
}
else
{
if (parent->_right == cur)
{
RotateL(grandf);
parent->_col = BLACK;
grandf->_col = RED;
}
else
{
RotateR(parent);
RotateL(grandf);
cur->_col = BLACK;
grandf->_col = RED;
}
break;
}
}
}
_root->_col = BLACK;
return true;
}(4)判斷平衡函數(shù)
bool IsBalance()
{
if (_root && _root->_col == RED)
{
cout << "根節(jié)點(diǎn)顏色錯(cuò)誤" << endl;
return false;
}
int Bsign = 0;
Node* cur = _root;
while (cur)
{
if (cur->_col == BLACK)
{
++Bsign;
}
cur = cur->_left;
}
return _check(_root, 0, Bsign);
}
bool _check(Node* root, int Bnum, int Bsign)
{
if (root == nullptr)
{
if (Bnum != Bsign)
{
cout << "黑色節(jié)點(diǎn)數(shù)量不相等" << endl;
return false;
}
return true;
}
if (root->_col == BLACK)
{
++Bnum;
}
if (root->_col == RED && root->_parent && root->_parent->_col == RED)
{
cout << "連續(xù)紅色節(jié)點(diǎn)" << endl;
return false;
}
return _check(root->_left,Bnum,Bsign) && _check(root->_right, Bnum, Bsign);
}
void _Destroy(Node* root)
{
if (root == nullptr)
{
return;
}
_Destroy(root->_left);
_Destroy(root->_right);
delete root;
}首先不能以最長(zhǎng)路徑不超過(guò)最短路徑2倍為準(zhǔn)則判斷平衡,它只是結(jié)果。
還是要以前面說(shuō)的規(guī)則:
第一個(gè):先檢查根是不是黑色的;
第二個(gè):再檢查連續(xù)的紅色節(jié)點(diǎn):如果是紅色的就去檢查兒子,這種方式不行,因?yàn)楹⒆涌赡懿淮嬖?,可以改為它的父親,如果是紅一定有父親,如果父親為紅就存在連續(xù)紅色節(jié)點(diǎn)直接返回假;
還有一個(gè):最重要的規(guī)則:判斷黑色節(jié)點(diǎn)的數(shù)量是否相等,我們需要先記錄一下黑色數(shù)量,我們可以先獲取一條路徑的黑色數(shù)量Bsign,然后以它為標(biāo)準(zhǔn),如何讓每條路徑以它標(biāo)準(zhǔn)判斷是否相等?我們可以在參數(shù)里面的形參傳值Bnum,往下遞歸就會(huì)往下傳,下一層++不會(huì)影響上一層++,因?yàn)橄乱粚邮巧弦粚拥目截悾灰幸粭l不相等就返回假。
(5)查找函數(shù)
Node* Find(const K& key)
{
Node* cur = _root;
while (cur)
{
if (cur->_kv.first < key)
{
cur = cur->_right;
}
else if (cur->_kv.first > key)
{
cur = cur->_left;
}
else
{
return cur;
}
}
return nullptr;
}從根開(kāi)始走,如果比它大,就往右邊走,如果比它小,就往左邊走,如果相等說(shuō)明找到,否則返回空。
(6)測(cè)試函數(shù)
void Test()
{
int arr[] = { 33, 22, 6, 1, 3, 5, 66, 7, 16, 14, 16, 3, 7, 11, 9, 36, 18, 14, 15 };
RbTree<int, int> t;
for (auto n : arr)
{
t.Insert(make_pair(n, n));
}
t.Inorder();
cout << endl;
cout << t.IsBalance() << endl;
cout << "高度:"<<t.height() << endl;
cout << endl;
cout << "隨機(jī)數(shù)據(jù)測(cè)試:" << endl;
srand(time(0));
const size_t N = 100000;
RbTree<int, int> r;
for (size_t i = 0; i < N; ++i)
{
size_t x = rand() + i;
r.Insert(make_pair(x, x));
}
cout << r.IsBalance() << endl;
cout <<"高度:"<< r.height() << endl;
}(7)測(cè)試結(jié)果

三、紅黑樹(shù)源代碼
(1)RbTree.h
#pragma once
#include<iostream>
#include<utility>
#include<ctime>
using namespace std;
enum Color
{
RED=0,
BLACK,
};
template<class K,class V>
struct RbTreeNode
{
RbTreeNode<K, V>* _left;
RbTreeNode<K, V>* _right;
RbTreeNode<K, V>* _parent;
pair<K, V>_kv;
Color _col;
RbTreeNode(const pair<K, V>& kv)
:_left(nullptr)
, _right(nullptr)
, _parent(nullptr)
, _kv(kv)
, _col(RED)
{}
};
template <class K,class V>
class RbTree
{
typedef RbTreeNode<K, V> Node;
public:
~RbTree()
{
_Destroy(_root);
_root = nullptr;
}
bool Insert(const pair<K, V>& kv)
{
if (_root == nullptr)
{
_root = new Node(kv);
_root->_col = BLACK;
return true;
}
Node* parent = nullptr;
Node* cur = _root;
while (cur)
{
if (cur->_kv.first < kv.first)
{
parent = cur;
cur = cur->_right;
}
else if (cur->_kv.first > kv.first)
{
parent = cur;
cur = cur->_left;
}
else
{
return false;
}
}
cur = new Node(kv);
if (parent->_kv.first > kv.first)
{
parent->_left = cur;
}
else
{
parent->_right = cur;
}
cur->_parent = parent;
while (parent && parent->_col == RED)
{
Node* grandf = parent->_parent;
if (grandf->_left == parent)
{
Node* unc = grandf->_right;
if (unc && unc->_col == RED)//u存在且為紅,變色處理
{
parent->_col = BLACK;
unc->_col = BLACK;
grandf->_col = RED;
cur = grandf;//繼續(xù)往上調(diào)整
parent = cur->_parent;
}
else//u不存在或者黑
{
if (parent->_left == cur)
{
RotateR(grandf);
parent->_col = BLACK;
grandf->_col = RED;
}
else
{
RotateL(parent);
RotateR(grandf);
cur->_col = BLACK;
grandf->_col = RED;
}
break;
}
}
else
{
Node* unc = grandf->_left;
if (unc && unc->_col == RED)
{
parent->_col = BLACK;
unc->_col = BLACK;
grandf->_col = RED;
cur = grandf;
parent = cur->_parent;
}
else
{
if (parent->_right == cur)
{
RotateL(grandf);
parent->_col = BLACK;
grandf->_col = RED;
}
else
{
RotateR(parent);
RotateL(grandf);
cur->_col = BLACK;
grandf->_col = RED;
}
break;
}
}
}
_root->_col = BLACK;
return true;
}
Node* Find(const K& key)
{
Node* cur = _root;
while (cur)
{
if (cur->_kv.first < key)
{
cur = cur->_right;
}
else if (cur->_kv.first > key)
{
cur = cur->_left;
}
else
{
return cur;
}
}
return nullptr;
}
void Inorder()
{
_InOrder(_root);
}
int height()
{
return _Height(_root);
}
bool IsBalance()
{
if (_root && _root->_col == RED)
{
cout << "根節(jié)點(diǎn)顏色錯(cuò)誤" << endl;
return false;
}
int Bsign = 0;
Node* cur = _root;
while (cur)
{
if (cur->_col == BLACK)
{
++Bsign;
}
cur = cur->_left;
}
return _check(_root, 0, Bsign);
}
private:
void RotateL(Node* parent)//左單旋
{
Node* suR = parent->_right;//11的右孩子即22
Node* suRL = suR->_left;//11的右孩子的左孩子即b
Node* ppnode = parent->_parent;//維護(hù)父結(jié)點(diǎn)的父結(jié)點(diǎn)
parent->_right = suRL;//11右孩子鏈接b
if (suRL)//如果b不為空
suRL->_parent = parent;//更新雙親結(jié)點(diǎn)
suR->_left = parent;//22上提,左孩子鏈接11
parent->_parent = suR;//更新雙親結(jié)點(diǎn)
if (ppnode == nullptr)
{
_root = suR;
_root->_parent = nullptr;
}
else
{
if (ppnode->_left == parent)
{
ppnode->_left = suR;
}
else
{
ppnode->_right = suR;
}
suR->_parent = ppnode;
}
}
void RotateR(Node* parent)//右單旋
{
Node* suL = parent->_left;
Node* suLR = suL->_right;
parent->_left = suLR;
if (suLR)
suLR->_parent = parent;
Node* ppnode = parent->_parent;
suL->_right = parent;
parent->_parent = suL;
if (parent == _root)
{
_root = suL;
_root->_parent = nullptr;
}
else
{
if (ppnode->_left == parent)
{
ppnode->_left = suL;
}
else
{
ppnode->_right = suL;
}
suL->_parent = ppnode;
}
}
void _InOrder(Node* root)
{
if (root == nullptr)
{
return;
}
_InOrder(root->_left);
cout << root->_kv.first << " ";
_InOrder(root->_right);
}
int _Height(Node* root)
{
if (root == NULL)
return 0;
int leftH = _Height(root->_left);
int rightH = _Height(root->_right);
return leftH > rightH ? leftH + 1 : rightH + 1;
}
bool _check(Node* root, int Bnum, int Bsign)
{
if (root == nullptr)
{
if (Bnum != Bsign)
{
cout << "黑色節(jié)點(diǎn)數(shù)量不相等" << endl;
return false;
}
return true;
}
if (root->_col == BLACK)
{
++Bnum;
}
if (root->_col == RED && root->_parent && root->_parent->_col == RED)
{
cout << "連續(xù)紅色節(jié)點(diǎn)" << endl;
return false;
}
return _check(root->_left,Bnum,Bsign) && _check(root->_right, Bnum, Bsign);
}
void _Destroy(Node* root)
{
if (root == nullptr)
{
return;
}
_Destroy(root->_left);
_Destroy(root->_right);
delete root;
}
private:
Node* _root;
};
void Test()
{
int arr[] = { 33, 22, 6, 1, 3, 5, 66, 7, 16, 14, 16, 3, 7, 11, 9, 36, 18, 14, 15 };
RbTree<int, int> t;
for (auto n : arr)
{
t.Insert(make_pair(n, n));
}
t.Inorder();
cout << endl;
cout << t.IsBalance() << endl;
cout << "高度:"<<t.height() << endl;
cout << endl;
cout << "隨機(jī)數(shù)據(jù)測(cè)試:" << endl;
srand(time(0));
const size_t N = 100000;
RbTree<int, int> r;
for (size_t i = 0; i < N; ++i)
{
size_t x = rand() + i;
r.Insert(make_pair(x, x));
}
cout << r.IsBalance() << endl;
cout <<"高度:"<< r.height() << endl;
}(2)Test.cpp
#include"RbTree.h"
int main()
{
Test();
return 0;
}總結(jié)
到此這篇關(guān)于C++實(shí)現(xiàn)紅黑樹(shù)核心插入的文章就介紹到這了,更多相關(guān)C++紅黑樹(shù)核心插入內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- C++詳細(xì)實(shí)現(xiàn)紅黑樹(shù)流程詳解
- C++實(shí)現(xiàn)紅黑樹(shù)應(yīng)用實(shí)例代碼
- C++?STL容器詳解之紅黑樹(shù)部分模擬實(shí)現(xiàn)
- C++數(shù)據(jù)結(jié)構(gòu)之紅黑樹(shù)的實(shí)現(xiàn)
- C++超詳細(xì)分析紅黑樹(shù)
- C++數(shù)據(jù)結(jié)構(gòu)紅黑樹(shù)全面分析
- C++?RBTree紅黑樹(shù)的性質(zhì)與實(shí)現(xiàn)
- C++使用一棵紅黑樹(shù)同時(shí)封裝出map和set實(shí)例代碼
- C++紅黑樹(shù)應(yīng)用之手搓set和map
相關(guān)文章
詳談C++ socket網(wǎng)絡(luò)編程實(shí)例(2)
這篇文章主要為大家介紹了C++ socket網(wǎng)絡(luò)編程實(shí)例,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來(lái)幫助2021-11-11
Qt實(shí)現(xiàn)蘋(píng)果狀態(tài)切換按鈕
這篇文章主要為大家詳細(xì)介紹了Qt實(shí)現(xiàn)蘋(píng)果狀態(tài)切換按鈕,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-08-08
C++實(shí)現(xiàn)LeetCode(75.顏色排序)
這篇文章主要介紹了C++實(shí)現(xiàn)LeetCode(75.顏色排序),本篇文章通過(guò)簡(jiǎn)要的案例,講解了該項(xiàng)技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下2021-07-07
Qt使用事件與定時(shí)器實(shí)現(xiàn)字幕滾動(dòng)效果
我們經(jīng)常能夠在外面看到那種滾動(dòng)字幕,那么本文就拿Qt來(lái)做一個(gè)吧,本文將使用事件與定時(shí)器實(shí)現(xiàn)字幕滾動(dòng)的效果,感興趣的小伙伴可以了解一下2023-06-06
C語(yǔ)言鏈表實(shí)現(xiàn)學(xué)生成績(jī)管理系統(tǒng)
這篇文章主要為大家詳細(xì)介紹了C語(yǔ)言鏈表實(shí)現(xiàn)學(xué)生成績(jī)管理系統(tǒng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-07-07

