B-树的插入、查找、删除
轉(zhuǎn)自:http://blog.163.com/zhoumhan_0351/blog/static/39954227200910231032917/
?
?前面討論的查找都是內(nèi)查詢算法,被查詢的數(shù)據(jù)都在內(nèi)存。當查詢的數(shù)據(jù)放在外存,用平衡二叉樹作磁盤文件的索引組織時,若以結(jié)點為內(nèi)外存交換的單位,則找到需要的關(guān)鍵字之前,平均要進行lgn次磁盤讀操作,而磁盤、光盤的讀寫時間要比隨機存取的內(nèi)存代價大得多。其二,外存的存取是以“頁”為單位的,一頁的大小通常是1024字節(jié)或2048字節(jié)。
?針對上述特點,1972年R.Bayer和E.M.Cright提出了一種B-樹的多路平衡查找樹,以適合磁盤等直接存取設(shè)備上組織動態(tài)查找表。B-樹上算法的執(zhí)行時間主要由讀、寫磁盤的次數(shù)來決定,故一次I/O操作應(yīng)讀寫盡可能多的信息。因此B-樹的結(jié)點規(guī)模一般以一個磁盤頁為單位。一個結(jié)點包含的關(guān)鍵字及其孩子個數(shù)取決于磁盤頁的大小。
一、基本概念
B-樹又稱為多路平衡查找樹。
?????????一棵度為m的B-樹稱為m階B_樹。一個結(jié)點有k個孩子時,必有k-1個關(guān)鍵字才能將子樹中所有關(guān)鍵字劃分為k個子集。B-樹中所有結(jié)點的孩子結(jié)點最大值稱為B-樹的階,通常用m表示。從查找效率考慮,一般要求m≥3。一棵m階的B-樹或者是一棵空樹,或者是滿足下列要求的m叉樹:
(1)根結(jié)點或者為葉子,或者至少有兩棵子樹,至多有m棵子樹。
(2)除根結(jié)點外,所有非終端結(jié)點至少有ceil(m/2)棵子樹,至多有m棵子樹。
(3)所有葉子結(jié)點都在樹的同一層上。
(4)每個結(jié)點的結(jié)構(gòu)為:
???????(n,A0,K1,A1,K2,A2,…??,Kn,An)
其中,Ki(1≤i≤n)為關(guān)鍵字,且Ki<Ki+1(1≤i≤n-1)。
????????Ai(0≤i≤n)為指向子樹根結(jié)點的指針。且Ai所指子樹所有結(jié)點中的關(guān)鍵字均小于Ki+1。An所指子樹中所有結(jié)點的關(guān)鍵字均大于Kn。
n為結(jié)點中關(guān)鍵字的個數(shù),滿足ceil(m/2)-1≤n≤m-1。
????????比如,一棵3階B-樹,m=3。它滿足:?
(1)每個結(jié)點的孩子個數(shù)小于等于3。?
(2)除根結(jié)點外,其他結(jié)點至少有=2個孩子。?
(3)根結(jié)點有兩個孩子結(jié)點。?
(4)除根結(jié)點外的所有結(jié)點的n大于等于=1,小于等于2。?
(5)所有葉結(jié)點都在同一層上。
???
二、B-樹查找的算法思想
1、B-樹的查找
B-樹的查找過程:根據(jù)給定值查找結(jié)點和在結(jié)點的關(guān)鍵字中進行查找交叉進行。首先從根結(jié)點開始重復(fù)如下過程:
???????若比結(jié)點的第一個關(guān)鍵字小,則查找在該結(jié)點第一個指針指向的結(jié)點進行;若等于結(jié)點中某個關(guān)鍵字,則查找成功;若在兩個關(guān)鍵字之間,則查找在它們之間的指針指向的結(jié)點進行;若比該結(jié)點所有關(guān)鍵字大,則查找在該結(jié)點最后一個指針指向的結(jié)點進行;若查找已經(jīng)到達某個葉結(jié)點,則說明給定值對應(yīng)的數(shù)據(jù)記錄不存在,查找失敗。
2.??B-樹的插入
插入的過程分兩步完成:
???(1)利用前述的B-樹的查找算法查找關(guān)鍵字的插入位置。若找到,則說明該關(guān)鍵字已經(jīng)存在,直接返回。否則查找操作必失敗于某個最低層的非終端結(jié)點上。
???(2)判斷該結(jié)點是否還有空位置。即判斷該結(jié)點的關(guān)鍵字總數(shù)是否滿足n<=m-1。若滿足,則說明該結(jié)點還有空位置,直接把關(guān)鍵字k插入到該結(jié)點的合適位置上。若不滿足,說明該結(jié)點己沒有空位置,需要把結(jié)點分裂成兩個。
分裂的方法是:生成一新結(jié)點。把原結(jié)點上的關(guān)鍵字和k按升序排序后,從中間位置把關(guān)鍵字(不包括中間位置的關(guān)鍵字)分成兩部分。左部分所含關(guān)鍵字放在舊結(jié)點中,右部分所含關(guān)鍵字放在新結(jié)點中,中間位置的關(guān)鍵字連同新結(jié)點的存儲位置插入到父結(jié)點中。如果父結(jié)點的關(guān)鍵字個數(shù)也超過(m-1),則要再分裂,再往上插。直至這個過程傳到根結(jié)點為止。
3、B-樹的刪除
在B-樹上刪除關(guān)鍵字k的過程分兩步完成:
???(1)利用前述的B-樹的查找算法找出該關(guān)鍵字所在的結(jié)點。然后根據(jù)?k所在結(jié)點是否為葉子結(jié)點有不同的處理方法。
???(2)若該結(jié)點為非葉結(jié)點,且被刪關(guān)鍵字為該結(jié)點中第i個關(guān)鍵字key[i],則可從指針son[i]所指的子樹中找出最小關(guān)鍵字Y,代替key[i]的位置,然后在葉結(jié)點中刪去Y。
因此,把在非葉結(jié)點刪除關(guān)鍵字k的問題就變成了刪除葉子結(jié)點中的關(guān)鍵字的問題了。
在B-樹葉結(jié)點上刪除一個關(guān)鍵字的方法是
首先將要刪除的關(guān)鍵字?k直接從該葉子結(jié)點中刪除。然后根據(jù)不同情況分別作相應(yīng)的處理,共有三種可能情況:
(1)如果被刪關(guān)鍵字所在結(jié)點的原關(guān)鍵字個數(shù)n>=ceil(m/2),說明刪去該關(guān)鍵字后該結(jié)點仍滿足B-樹的定義。這種情況最為簡單,只需從該結(jié)點中直接刪去關(guān)鍵字即可。
(2)如果被刪關(guān)鍵字所在結(jié)點的關(guān)鍵字個數(shù)n等于ceil(m/2)-1,說明刪去該關(guān)鍵字后該結(jié)點將不滿足B-樹的定義,需要調(diào)整。
調(diào)整過程為:如果其左右兄弟結(jié)點中有“多余”的關(guān)鍵字,即與該結(jié)點相鄰的右(左)兄弟結(jié)點中的關(guān)鍵字數(shù)目大于ceil(m/2)-1。則可將右(左)兄弟結(jié)點中最小(大)關(guān)鍵字上移至雙親結(jié)點。而將雙親結(jié)點中小(大)于該上移關(guān)鍵字的關(guān)鍵字下移至被刪關(guān)鍵字所在結(jié)點中。
(3)如果左右兄弟結(jié)點中沒有“多余”的關(guān)鍵字,即與該結(jié)點相鄰的右(左)兄弟結(jié)點中的關(guān)鍵字數(shù)目均等于ceil(m/2)-1。這種情況比較復(fù)雜。需把要刪除關(guān)鍵字的結(jié)點與其左(或右)兄弟結(jié)點以及雙親結(jié)點中分割二者的關(guān)鍵字合并成一個結(jié)點,即在刪除關(guān)鍵字后,該結(jié)點中剩余的關(guān)鍵字加指針,加上雙親結(jié)點中的關(guān)鍵字Ki一起,合并到Ai(是雙親結(jié)點指向該刪除關(guān)鍵字結(jié)點的左(右)兄弟結(jié)點的指針)所指的兄弟結(jié)點中去。如果因此使雙親結(jié)點中關(guān)鍵字個數(shù)小于ceil(m/2)-1,則對此雙親結(jié)點做同樣處理。以致于可能直到對根結(jié)點做這樣的處理而使整個樹減少一層。
總之,設(shè)所刪關(guān)鍵字為非終端結(jié)點中的Ki,則可以指針Ai所指子樹中的最小關(guān)鍵字Y代替Ki,然后在相應(yīng)結(jié)點中刪除Y。對任意關(guān)鍵字的刪除都可以轉(zhuǎn)化為對最下層關(guān)鍵字的刪除。
如圖示:
a、被刪關(guān)鍵字Ki所在結(jié)點的關(guān)鍵字數(shù)目不小于ceil(m/2),則只需從結(jié)點中刪除Ki和相應(yīng)指針Ai,樹的其它部分不變。
b、被刪關(guān)鍵字Ki所在結(jié)點的關(guān)鍵字數(shù)目等于ceil(m/2)-1,則需調(diào)整。調(diào)整過程如上面所述。
c、被刪關(guān)鍵字Ki所在結(jié)點和其相鄰兄弟結(jié)點中的的關(guān)鍵字數(shù)目均等于ceil(m/2)-1,假設(shè)該結(jié)點有右兄弟,且其右兄弟結(jié)點地址由其雙親結(jié)點指針Ai所指。則在刪除關(guān)鍵字之后,它所在結(jié)點的剩余關(guān)鍵字和指針,加上雙親結(jié)點中的關(guān)鍵字Ki一起,合并到Ai所指兄弟結(jié)點中(若無右兄弟,則合并到左兄弟結(jié)點中)。如果因此使雙親結(jié)點中的關(guān)鍵字數(shù)目少于ceil(m/2)-1,則依次類推。
三、B-樹的C語言描述
1、存儲結(jié)構(gòu)
2、插入
3、查找
四、B-樹的C語言實現(xiàn)
#include "stdio.h"
#include "stdlib.h"
#include "math.h"
#define OK 1
#define ERROR -1
#define m 3 //3階樹
#define N 16 //數(shù)據(jù)元素個數(shù)
#define MAX 5 //字符串最大長度+1
typedef int KeyType;
struct Others? //記錄的其它部分
{
char info[MAX];
};
struct Record
{
KeyType key; //關(guān)鍵字
Others others; //其它部分
};
typedef struct BTNode
{
int keynum; //結(jié)點中關(guān)鍵字個數(shù)
BTNode *parent;//指向雙親節(jié)點
?? struct Node? //結(jié)點向量類型
?? {
?? KeyType key; //關(guān)鍵字向量
?? BTNode *ptr;//子樹指針向量
?? Record *recptr; //記錄向量指針
?? }node[m+1]; //key,recptr的0號單元未用
}BTNode,*BTree;
struct Result //B樹的查找結(jié)果類型?
{
BTNode *pt; //指向找到的結(jié)點
int i; //在節(jié)點中關(guān)鍵字序號,1...m
int tag; //1表示查找成功,0表示查找失敗。
};
int InitDSTable(BTree &DT)
{
DT=NULL;
return OK;
}//InitDSTable
void DestroyDSTable(BTree &DT)
{
int i;
if(DT) //非空樹
??? {
???? for(i=0;i<=DT->keynum;i++)
?? ???? ?DestroyDSTable(DT->node[i].ptr);
?? ? free(DT);
?? ? DT=NULL;
??? }//if
}//DestroyDSTable
int Search(BTree p,KeyType K)
{//在p->node[1...keytype].key中查找i,使得p->node[i].key<=K<
?? ?//p->node[i+1].key
?? ?int i=0,j;
?? ?for(j=1;j<=p->keynum;j++)
?? ???? if(p->node[j].key<=K)
?? ???? ??? i=j;
?? ?return i;
}//Search
void Insert(BTree &q,int i,Record *r,BTree ap)
{//將r->key、r和ap分別插入到q->key[i+1]、
?? ?//q->recptr[????????????? i+1]和q->ptr[i+1]中
?? ?int j;
?? ?for(j=q->keynum;j>i;j--) //空出q->node[i+1]
?? ? q->node[j+1]=q->node[j];
??? q->node[i+1].key=r->key;
??? q->node[i+1].ptr=ap; //前加入的結(jié)點,還沒有兒子結(jié)點
??? q->node[i+1].recptr=r;
??? q->keynum++;
}//Insert
void NewRoot(BTree &T,Record *r,BTree ap)
{// 生成含信息(T,r,ap)的新的根結(jié)點*T,原T和ap為子樹指針
BTree p;
p=(BTree)malloc(sizeof(BTNode));
p->node[0].ptr=T;
T=p;
if(T->node[0].ptr)
?? ?T->node[0].ptr->parent=T;
T->parent=NULL;
T->keynum=1;
T->node[1].key=r->key;
T->node[1].recptr=r;
T->node[1].ptr=ap;
if(T->node[1].ptr)
?? ?T->node[1].ptr->parent=T;
}//NewRoot
void split(BTree &q,BTree &ap)
{// 將結(jié)點q分裂成兩個結(jié)點,前一半保留,后一半移入新生結(jié)點ap
int i,s=(m+1)/2;
ap=(BTree)malloc(sizeof(BTNode));//生成新結(jié)點ap
ap->node[0].ptr=q->node[s].ptr;//原來結(jié)點中間位置關(guān)鍵字相應(yīng)指針指向的子樹放到
?????????????????????????????? //新生成結(jié)點的0棵子樹中去
for(i=s+1;i<=m;i++) //后一半移入ap
?? {
?? ap->node[i-s]=q->node[i];
?? if(ap->node[i-s].ptr)
?? ??? ap->node[i-s].ptr->parent=ap;
?? }//for
?? ap->keynum=m-s;
?? ap->parent=q->parent;
?? q->keynum=s-1; // q的前一半保留,修改keynum
}//split
void InsertBTree(BTree &T,Record *r,BTree q,int i)
{//在m階B樹T上結(jié)點*q的key[i]與key[i+1]之間插入關(guān)鍵字K的指針r。若引起
?? // 結(jié)點過大,則沿雙親鏈進行必要的結(jié)點分裂調(diào)整,使T仍是m階B樹。
BTree ap=NULL;
int finished=false;
int s;
Record *rx;
rx=r;
while(q&&!finished)
?? {
??? Insert(q,i,rx,ap);//將r->key、r和ap分別插入到q->key[i+1]、
?? ?????????????????? //q->recptr[i+1]和q->ptr[i+1]中
?? ?if(q->keynum<m)
?? ???? finished=true;
?? ?else
?? ?? {//分裂結(jié)點*q
????? s=(m+1)/2;
?? ?? rx=q->node[s].recptr;
?? ?? split(q,ap);//將q->key[s+1..m],q->ptr[s..m]和q->recptr[s+1..m]
?? ???? ??? ??? ? //移入新結(jié)點*ap
?? ?? q=q->parent;
?? ?? if(q)
?? ???? ? i=Search(q,rx->key);//在雙親結(jié)點*q中查找rx->key的插入位置
?? ?? }//else
?? }//while
if(!finished) //T是空樹(參數(shù)q初值為NULL)或根結(jié)點已分裂為
?? ?????????? //結(jié)點*q和*ap
NewRoot(T,rx,ap);?? ?
}//InsertBTree
Result SearchBTree(BTree T,KeyType K)
{// 在m階B樹T上查找關(guān)鍵字K,返回結(jié)果(pt,i,tag)。若查找成功,則特征值
// tag=1,指針pt所指結(jié)點中第i個關(guān)鍵字等于K;否則特征值tag=0,等于K的
// 關(guān)鍵字應(yīng)插入在指針Pt所指結(jié)點中第i和第i+1個關(guān)鍵字之間。
BTree p=T,q=NULL; //初始化,p指向待查結(jié)點,q指向p的雙親
int found=false;
int i=0;
Result r;
while(p&&!found)
?? {
?? i=Search(p,K);//p->node[i].key≤K<p->node[i+1].key
?? if(i>0&&p->node[i].key==K)
?? ??? found=true;
?? else?
???? {
???? q=p;
?? ? p=p->node[i].ptr;//在子樹中繼續(xù)查找
?? ? }//else
??? }//while
?? r.i=i;
?? if(found)
???? {
????? r.pt=p;
?? ?? r.tag=1;
???? }//if
?? else?
????? {
?????? r.pt=q;
?? ??? r.tag=0;
????? }//else
??? return r;
}//SearchBTree
void print(BTNode c,int i) // TraverseDSTable()調(diào)用的函數(shù)
?{
?? printf("(%d,%s)",c.node[i].key,c.node[i].recptr->others.info);
?}//print
void TraverseDSTable(BTree DT,void(*Visit)(BTNode,int))
{// 初始條件: 動態(tài)查找表DT存在,Visit是對結(jié)點操作的應(yīng)用函數(shù)
// 操作結(jié)果: 按關(guān)鍵字的順序?qū)T的每個結(jié)點調(diào)用函數(shù)Visit()一次且至多一次
int i;
if(DT) //非空樹
??? {
????? if(DT->node[0].ptr) // 有第0棵子樹
???????? TraverseDSTable(DT->node[0].ptr,Visit);
????? for(i=1;i<=DT->keynum;i++)
??????? {
???????? Visit(*DT,i);
???????? if(DT->node[i].ptr) // 有第i棵子樹
???????? TraverseDSTable(DT->node[i].ptr,Visit);
??????? }//for
??? }//if
}//TraverseDSTable
void InputBR(BTree &t,Record r[])
{
Result s;?? ?
for(int i=0;i<N;i++)
?? {
???? s=SearchBTree(t,r[i].key);
???? if(!s.tag)
?????? InsertBTree(t,&r[i],s.pt,s.i);
?? }
}//InputBR
void UserSearch(BTree t)
{
int i;
Result s;
printf("\n請輸入待查找記錄的關(guān)鍵字: ");
scanf("%d",&i);
s=SearchBTree(t,i);
if(s.tag)
print(*(s.pt),s.i);
else
printf("沒找到");
printf("\n");
}//UserSearch
void DeleteIt(BTree t,BTNode *dnode,int id)
{
if(dnode->keynum>=ceil(m/2))
?? {
??? dnode->keynum--;
?? ?dnode->node[id].ptr=NULL;
?? }//if被刪關(guān)鍵字Ki所在結(jié)點的關(guān)鍵字數(shù)目不小于ceil(m/2),則只需從結(jié)點中刪除Ki和相應(yīng)指針Ai,樹的其它部分不變。
else if((dnode->keynum==(ceil(m/2)-1))&&((id+1)<(m-1))&&dnode->parent->node[id+1].ptr->keynum>(ceil(m/2)-1))
?? {
??? for(int i=1;i<m&&dnode->parent->node[i].key < dnode->parent->node[id+1].ptr->node[1].key;i++)
?? ???? dnode->node[i].key=dnode->parent->node[i].key;
?? ?dnode->parent->node[1].key=dnode->parent->node[id+1].ptr->node[1].key;
?? ?(dnode->parent->node[id+1].ptr->keynum)--;
?? }//else if 被刪關(guān)鍵字Ki所在結(jié)點的關(guān)鍵字數(shù)目等于ceil(m/2)-1,則需調(diào)整。本次為與右兄弟調(diào)整
else if((dnode->keynum==(ceil(m/2)-1))&&((id-1)>0??? )&&dnode->parent->node[id-1].ptr->keynum>(ceil(m/2)-1))
?? {
??? for(int i=1;i<m&&dnode->parent->node[i].key > dnode->parent->node[id-1].ptr->node[dnode->parent->node[id-1].ptr->keynum].key;i++)
?? ???? dnode->node[i].key=dnode->parent->node[i].key;
?? ?dnode->parent->node[1].key=dnode->parent->node[id-1].ptr->node[dnode->parent->node[id-1].ptr->keynum].key;
?? ?(dnode->parent->node[id-1].ptr->keynum)--;
?? }//2-else if被刪關(guān)鍵字Ki所在結(jié)點的關(guān)鍵字數(shù)目等于ceil(m/2)-1,則需調(diào)整。本次為與左兄弟調(diào)整
else if((dnode->keynum==(ceil(m/2)-1))&&((id+1)<(m-1))&&dnode->parent->node[id+1].ptr->keynum==(ceil(m/2)-1))
?? {
??? do
?? ?? {
?? ???? BTree tmp;
?? ???? tmp=dnode;
?? ??? dnode->parent->node[id+1].ptr->node[2]=dnode->parent->node[id+1].ptr->node[1];
?? ??? dnode->parent->node[id+1].ptr->node[1]=dnode->parent->node[1];
?? ??? dnode->parent->node[id+1].ptr->keynum++;
?? ??? dnode->parent->node[id+1].ptr->node[0].ptr=dnode->node[1].ptr;
?? ??? dnode->parent->keynum--;
?? ??? dnode->parent->node[id].ptr=NULL;
?? ??? tmp=dnode;
?? ??? if(dnode->parent->keynum>=(ceil(m/2)-1))
?? ???? ?? dnode->parent->node[1]=dnode->parent->node[2];
?? ??? dnode=dnode->parent;
?? ??? free(tmp);
?? ?? }while(dnode->keynum<(ceil(m/2)-1));??? //雙親中keynum<
?? }//3-else if被刪關(guān)鍵字Ki所在結(jié)點和其相鄰兄弟結(jié)點中的的關(guān)鍵字數(shù)目均等于ceil(m/2)-1,本次假設(shè)右兄弟存在?
else if((dnode->keynum==(ceil(m/2)-1))&&(id-1)>0????? &&dnode->parent->node[id-1].ptr->keynum==(ceil(m/2)-1))
?? {
??? do
?? ?? {
?? ???? BTree tmp;
?? ???? tmp=dnode;
?? ??? dnode->parent->node[id-1].ptr->node[2]=dnode->parent->node[id-1].ptr->node[1];
?? ??? dnode->parent->node[id-1].ptr->node[1]=dnode->parent->node[1];
?? ??? dnode->parent->node[id-1].ptr->keynum++;
?? ??? dnode->parent->node[id-1].ptr->node[0].ptr=dnode->node[1].ptr;
?? ??? dnode->parent->keynum--;
?? ??? dnode->parent->node[id].ptr=NULL;
?? ??? tmp=dnode;
?? ??? if(dnode->parent->keynum>=(ceil(m/2)-1))
?? ???? ?? dnode->parent->node[1]=dnode->parent->node[2];
?? ??? dnode=dnode->parent;
?? ??? free(tmp);
?? ?? }while(dnode->keynum<(ceil(m/2)-1)); //雙親中keynum<
?? }//4-else if被刪關(guān)鍵字Ki所在結(jié)點和其相鄰兄弟結(jié)點中的的關(guān)鍵字數(shù)目均等于ceil(m/2)-1,本次假設(shè)左兄弟存在?
?? ?else printf("Error!"); //出現(xiàn)異常
}//DeleteIt
void UserDelete(BTree t)
{
KeyType date;
Result s;
printf("Please input the date you want to delete:\n");
scanf("%d",&date);
s=SearchBTree(t,date);
if(!s.tag)? printf("Search failed,no such date\n");
else DeleteIt(t,s.pt,s.i);
}//UserDelete
int main()
{
Record r[N]={{24,"1"},{45,"2"},{53,"3"},{12,"4"},{37,"5"},
?? ???? {50,"6"},{61,"7"},{90,"8"},{100,"9"},{70,"10"},
?? ???? {3,"11"},{30,"12"},{26,"13"},{85,"14"},{3,"15"},
?? ???? {7,"16"}};?? ?
BTree t;
InitDSTable(t);
InputBR(t,r);
printf("按關(guān)鍵字的順序遍歷B_樹:\n");
TraverseDSTable(t,print);
UserSearch(t);
UserDelete(t);
TraverseDSTable(t,print);
DestroyDSTable(t);
return 1;
}
五、復(fù)雜度分析
B-樹查找包含兩種基本動作:
?????●在B-樹上查找結(jié)點
?????●在結(jié)點中找關(guān)鍵字
前一操作在磁盤上進行,后一操作在內(nèi)存進行。因此查找效率主要由前一操作決定。在磁盤上查找的次數(shù)取決于關(guān)鍵字結(jié)點在B-樹上的層次數(shù)。
定理:若n≥1,m≥3,則對任意一棵具有n個關(guān)鍵字的m階B-樹,其樹高度h至多為logt((n+1)/2)+1,t= ceil(m/2)。也就是說根結(jié)點到關(guān)鍵字所在結(jié)點的路徑上涉及的結(jié)點數(shù)不超過logt((n+1)/2)+1。推理如下:
?
轉(zhuǎn)載于:https://www.cnblogs.com/acm-jing/p/4535599.html
總結(jié)
以上是生活随笔為你收集整理的B-树的插入、查找、删除的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 你用过这种奇葩的C#注释吗?如何看待
- 下一篇: KVM虚拟化(2)