查找-之二叉排序樹(查找、插入、刪除)

問題:

有序的線性表採用:折半/二分、插值、斐波那契查找相比順序查找效率得到提高,但是在插入和刪除時效率低(爲維持數據的有序性)

 在高效實現查找操作時,如何提高插入和刪除的效率?

在一些應用場景:在查找時需要插入和刪除

解決方法:

二叉排序樹

二叉排序樹特點

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

 

後續:如何解決二叉排序樹多次插入新結點而導致的不平衡?

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章