問題:
有序的線性表採用:折半/二分、插值、斐波那契查找相比順序查找效率得到提高,但是在插入和刪除時效率低(爲維持數據的有序性)
在高效實現查找操作時,如何提高插入和刪除的效率?
在一些應用場景:在查找時需要插入和刪除
解決方法:
二叉排序樹
二叉排序樹特點
1)若左子樹不爲空,左子樹上所有結點的值均小於或等於它的根節點的值
2)若右子樹不爲空,右子樹上所有結點的值均大於或等於它的根節點的值
3)左右子樹也分別爲二叉排序樹
二叉排序算法的複雜度:
時間複雜度:二分查找的思想,查找次數爲二叉查找樹的高度,若樹爲平衡二叉樹則爲O(logn),否則最壞的情況爲右斜樹O(n)
二叉排序算法的缺點是:
二叉樹的構建類型多種,不同的二叉樹形狀會導致查找的性能差異很大,例如普通的二叉樹和一棵右斜樹。
二叉排序樹的查找:
1)查找數據key,判斷key是否等於樹的根節點數據
2)若待查數據key小於根結點數據則遞歸的在左子樹查找
3)若待查數據key大於根結點數據則遞歸的在右子樹查找
C僞代碼:
//定義結點結構
typedef struct BiTNode
{
int data; //結點數據
struct BiTNode *lchild, *rchild;//左右孩子指針
}BiTNode,*BiTree
/遞歸查找二叉排序樹T中是否存在key
//f指向T的雙親
//p獲得查找到的結點位置
Status SearchBST(BiTree T,int key,BiTree f,BiTree *p)
{
//查找不成功
if(!T)//判斷當前二叉樹是否到葉子結點
{
*p=f;//指針p指向查找路徑上訪問的最後一個結點並返回false
return False;
}
//查找成功
else if(key==T->data)
{
*p=T;
return True;
}
else if(key<T->data)//待查找元素小於結點數據
return SearchBST(T->lchild,key,T,p);//在左子樹繼續查找
else
return SearchBST(T->rchild,key,T,p);//在右子樹繼續查找
}
二叉排序樹的插入操作:
1)在二叉排序樹找不到待插入的數據key則執行2)步驟
2)待插數據初始化爲結點s,若樹爲空則直接賦值結點s給樹
3)待插入數據key小於根結點數據則插入爲左孩子
4) 待插入數據key大於根結點數據則插入爲右孩子
//定義結點結構
typedef struct BiTNode
{
int data; //結點數據
struct BiTNode *lchild, *rchild;//左右孩子指針
}BiTNode,*BiTree
//二叉樹的數據插入
Status InsertBST(BiTree *T,int key)
{
BiTree p,s;//創建二叉樹結點
//在二叉排序樹中找不到key
if(!SearchBST(*T,key,NULL,&p))
{
//s結點的初始化
s=(BiTree)malloc(sizeof(BiTNode));
s->data=key;
s->lchild=s->rchild=NULL;
//若p結點爲空
if(!p)
*T=s;
else if (key<p->data)//待插入的值key小於p結點指向的數據
p->lchild=s;//s插入爲左孩子
else//待插入的值key大於p結點指向的數據
p->rchild=s;//s插入爲右孩子
return True;
}
//樹中已有關鍵字相同的結點,不再插入
else
return False;
}
構建一棵二叉排序樹示例:
//生成一棵二叉樹
int i;
int a[10]={62,88,58,47,35,73,51,99,37,93};
Bitree T=NULL;
for(i=0;i<10;i++)
{
InsertBST(&T,a[i]);
}
二叉排序樹的刪除操作:
刪除結點的三種情況:
1)刪除葉子結點
2)刪除的結點只有左或右子樹的
3)刪除的結點有左右子樹
//定義結點結構
typedef struct BiTNode
{
int data; //結點數據
struct BiTNode *lchild, *rchild;//左右孩子指針
}BiTNode,*BiTree
//刪除元素等於key的數據結點
Status DeleteBST(BiTree *T,int key)
{
//不存在關鍵字等於key的數據元素
if(!*T)
return False;
else
{
if(key==(*T)->data) //找到關鍵字等於key的數據元素
return Delete(T);
else if(key<(*T)->data)//待刪除的元素key小於查找到的元素---則在結點的左子樹搜索
return DeleteBST(&(*T)->lchild,key);
else //待刪除的元素key大於查找到的元素---則在結點的右子樹搜索
return DeleteBST(&(*T)->rchild,key);
}
}
Status Delete(BiTree *p)
{
BiTree q,s;
//第一種情況,刪除結點只有左子樹或右子樹
if((*p)->rchild==NULL)//只有左子樹
{
q=*p;
*p=(*p)->lchild;
free(q);
}
else if((*p)->lchild==NULL)//只有右子樹
{
q=*p;
*p=(*p)->rchild;
free(q);
}
//第二種情況:刪除的結點有左子樹和右子樹
else
{
q=*p;//待刪除的結點給臨時變量q
s=(*p)->lchild;//待刪除結點指向的左子樹給臨時變量s
while(s->rchild)//左子樹s一直向右找,直到找到待刪結點的前驅
{
q=s;
s=s->rchild;
}
(*p)->data=s->data;//s指向被刪結點的直接前驅,將它的值直接賦值給要刪除的結點*p
if(q!=*p)//被刪結點的直接前驅p!=被刪結點的直接前驅的根結點q
q->rchild=s->lchild;//根結點q的右孩子指針指向被刪結點的直接前驅的左孩子
else
q->lchild=s->lchild;
free(s);
}
}
上述代碼需注意:
1)q!=*p的情況:
2) q=*p的情況:
若上圖的結構修改爲:
沒有結點37和36,s指向35。
p和q的指向相同都是47結點處,則將s->lchild指向的29賦值給q->lchild
後續:如何解決二叉排序樹多次插入新結點而導致的不平衡?