順序存儲的線性表查找我們可以使用折半、插值、斐波那契等查找算法來實現,因爲有序,所以在插入和刪除上就需要耗費大量的時間,今天要討論的算法既可以獲得不錯的插入刪除效率,也可以比較高效的實現查找——二叉排序樹。
二叉排序樹又叫二叉查找樹,它或者是一棵空樹,或者是具有下列性質的二叉樹。
1、如果它的左子樹不爲空,則左子樹上所有節點的值均小於它的根節點的值。
2、如果它的右子樹不爲空,則右子樹上所有節點的值均大於它的根節點的值。
3、它的左右子樹也分別爲二叉排序樹。
從二叉排序樹的定義可知,左子樹節點一定比雙親節點小,右子樹節點一定比雙親節點大。構造一顆二叉樹的目的並不是爲了排序,而是爲了提高插入和刪除關鍵字的速度。畢竟在一個有序數據集上的查找,速度=總是要快於無序的數據集的。
二叉排序樹的查找
typedef struct BiNode
{
int data;
struct BiNode *lChild, *rChild; /*左右孩子指針*/
};
typedef struct BiNode* BiTree;
/*遞歸查找二叉排序樹T中是否存在key*/
/*指針f指向T的雙親,其初始調用值爲NULL*/
/*若查找成功,則指針p指向該數據元素節點,並返回TRUE*/
/*指針p指向查找路徑上訪問的最後一個節點,並返回FALSE*/
Status SearchBST(BiTree T, int key, BiTree f, BiTree* p)
{
if (!T)
{
*p = f;
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);//在右子樹繼續查找
}
}
代碼很簡單,不再贅述。接下來看下二叉排序樹的插入操作
二叉排序樹的插入
/*當二叉排序樹中不存在關鍵字等於key的數據元素時,插入key並返回TRUE,否則返回FALSE*/
Status InsertBST(BiTree T, int key)
{
BiTree p, s;
if (!SearchBST(T, key, NULL, &p))
{
s = (BiTree)malloc(sizeof(BiNode));
s->data = key;
s->lChild = NULL;
s->rChild = NULL;
if (!p) //首次插入時特殊處理
{
T = s;
}
else if (key < p->data)
{
p->lChild = s;
}
else
{
p->rChild = s;
}
return TRUE;
}
else
{
return FALSE;
}
}
插入的過程就是構造二叉排序樹的過程。
二叉排序樹的刪除
當我們想刪除二叉排序樹的節點時,按照孩子節點的數量可分爲以下三種情況:
1、葉子節點,直接刪除即可
2、僅有左子樹或右子樹的節點,刪除該節點,將它的左子樹或右子樹移動到刪除節點的位置即可
3、既有左子樹又有右子樹,這時我們可以對二叉排序樹中序遍歷,得到待刪除節點的前驅和後繼節點,用前驅或者後繼節點來代替原來待刪節點的位置。
實現算法如下:
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;
s = p->lChild;
while (s->rChild) //尋找待刪節點左子樹的最末右子樹,即待刪節點的前驅(也可以找後繼節點)
{
q = s;
s = s->rChild;
}
p->data = s->data;
if(p != q)
{
q->rChild = s->lChild; //重接q的右子樹
}
else
{
q->lChild = p->lChild; // 重接q的左子樹
}
free(s);
}
return TRUE;
}
Status DeleteBST(BiTree T, int key)
{
if (!T) //不存在關鍵字等於key的數據元素
{
return FALSE;
}
else
{
if (key == T->data)
{
return Delete(T);
}
else if (key < T->data)
{
return DeleteBST(T->lChild, key);
}
else
{
return DeleteBST(T->rChild, key);
}
}
}
上述代碼中我們是使用帶刪除節點的前驅來替換待刪節點,使用後繼節點也是一樣的,這裏不再贅述。
總結:
二叉排序樹保持了鏈式存儲結構在執行插入和刪除操作時不需要移動元素的優點,只要找到合適的插入和刪除位置後,僅需修改鏈接指針即可。對於二叉排序樹的查找,走的就是從根節點到要查找節點的路徑,比較次數等於給定的節點在二叉排序樹中的層數,最少爲1次,最多也不會超過樹的深度。因此其時間複雜度爲O(logn)。