1. 程式人生 > >二叉排序樹(BST)的思路及C語言實現

二叉排序樹(BST)的思路及C語言實現

請注意,為了能夠更好的理解二叉排序樹,我建議各位在看程式碼時能夠設定好斷點一步一步跟蹤函式的執行過程以及各個變數的變化情況

一.動態查詢所面臨的問題

在進行動態查詢操作時,如果我們是在一個無序的線性表中進行查詢,在插入時可以將其插入表尾,表長加1即可;刪除時,可以將待刪除元素與表尾元素做個交換,表長減1即可。反正是無序的,當然是怎麼高效怎麼操作。但如果是有序的呢?回想學習線性表順序儲存時介紹的順序表的缺點,就在於插入刪除操作的複雜與低效。

正因為如此,我們引入了鏈式儲存結構,似乎這樣就能解決上面遇到的問題了。但細想一下,我們要進行的是查詢操作,而鏈式結構的好處在於插入刪除,但在查詢方面則顯得無能為力。那麼有沒有一種結構能讓我們既提高查詢的效率,又不影響插入刪除的效率呢?

二.如何解決上述問題

先從最簡單的情況考慮,我們現在有一個只有一個數據元素的集合,假設為{20},現在我需要查詢其中是否含有’56’這個數字,沒有則插入;那麼很顯然執行該操作後集合變成了{20,56},繼續查詢集合中是否有’16’這個元素,沒有則插入並且不改變原先的有序性。似乎要插入新資料就必須做移動。有沒有辦法不移動呢?有人想到了用二叉樹來儲存資料,讓20做根節點,56比20大所以做右子樹,16比20小所以做左子樹,這樣不就不用移動其位置了嗎?思路見下圖

這裡寫圖片描述

這裡寫圖片描述

三.二叉排序樹(又稱二叉搜尋樹)

1.定義

二叉排序樹或者是一棵空樹,或者是一棵有下列性質的二叉樹:

1.若它的左子樹不為空,則它左子樹上所有結點的值必然小於根結點的值

2.若它的右子樹不為空,則它右子樹上所有結點的值必然大於根結點的值

3.它的左右子樹也為二叉排序樹

之所以要構造這樣一棵樹,不是為了排序,而是為了提高查詢,插入和刪除的效率

2.構建一棵二叉排序樹

要構建一棵二叉排序樹並實現相關操作,首先應理解下列的三個操作

1.查詢操作:



給定key值,首先將其與根節點比較,相等則返回根結點,否則將其與根結點值進行比較,小於根結點則在左子樹中遞迴查詢;大於根結點則在右子樹中遞迴查詢,程式碼如下:
/*在BST中查詢值為key的元素,f為指向雙親的指標,p用來返回查詢路徑上最後一個結點*/ 
Status SearchBST(BiTree T,int key,BiTree f,BiTree *p){
    if
(!T){ *p=f; return ERROR; } if(T->data==key){ *p=T; return OK; } else{ /*如果key值小於當前值,則進入左子樹中查詢*/ if(key<T->data) return SearchBST(T->Lchild,key,T,p);/*注意這裡,遞迴呼叫的時候其雙親結點f應該為T*/ else return SearchBST(T->Rchild,key,T,p); } }

2.插入操作

要進行插入,首先要在BST中進行查詢,若key值已經存在,則應返回ERROR;不存在時,由於第一步的search操作已經返回了查詢路徑上的最後一個結點,只需要把key值與最後一個節點的值進行比較,比它小則為左子樹,反之為右子樹,程式碼如下:

/*查詢key是否存在於BST中,如果不存在,則插入key到合適位置*/
Status InsertBST(BiTree *T,int key){
    /*found用於返回其查詢過程中最後一個結點,insert為指向待插入結點的指標*/
    BiTree found=NULL,insert=NULL;
    /*沒找到*/
    if(!SearchBST(*T,key,NULL,&found)){
        insert=(BiTree)malloc(sizeof(BiTreeNode));
        if(!insert){
            printf("malloc failed\n");
            return ERROR;
        }
        insert->data=key;
        insert->Lchild=NULL;
        insert->Rchild=NULL;
//      if(*T==found)
        /*如果found空,說明根節點值應為key*/ 
        if(!found)
           *T=insert;
        else{
            /*key值小於上一節點的data時,做左子樹*/
            if(key<found->data)
                found->Lchild=insert;
            if(key>found->data)
                found->Rchild=insert;
        }
    }
    /*如果找到了,返回ERROR*/
    else
      return ERROR;
}

3.刪除操作

由於二叉排序樹具有比二叉樹更多的特性,所以刪除操作就變得更復雜,不能因為刪除一個結點導致整棵樹不滿足BST的性質。所以應該分三種情況進行討論:
1.刪除的是葉子節點
2.刪除的是隻有左子樹或右子樹的節點
3.刪除的結點既有左子樹也有右子樹

對於第一種情況,直接刪除即可,葉子結點對整棵樹的性質並沒有影響

對於第二種情況,只有左或右子樹,只需要讓待刪除結點的指標指向其子樹即可,同時釋放原根結點指標即可

對於既有左子樹又有右子樹的情況,就比較複雜了,如圖所示:要刪除下圖中的值為47的節點,並且還要保持其是一棵BST,如何解決呢?

這裡寫圖片描述

我們當然可以刪除47之後再把其子結點上的值插入回原先的BST中,但是這樣做效率十分低下而且會改變二叉樹的深度,我們換個角度考慮,在47的子結點中有沒有哪個值可以代替47的位置呢?尋找一番之後發現37和48好像沒問題,進一步思考,37和48能夠替代47的原因在於:當我們中序遍歷以47為根的二叉樹時,37和48是47的直接前驅和後繼。這樣我們就得到了一個結論,刪除操作時,待刪除結點在中序遍歷的直接前驅或後繼可以替代其位置。配圖一張說明思路:

這裡寫圖片描述
這裡寫圖片描述

看了圖片可能覺得思路清晰一些了,下面看程式碼吧:

/*刪除BST中某一結點,並重接其左右子樹*/
Status Delete(BiTree *T){
    BiTree qmove=NULL,smove=NULL;
    if(!(*T))
       return ERROR;
    /*如果只有右子樹*/
    if((*T)->Lchild==NULL){
        qmove=*T;/*用qmove儲存待刪除結點*/
        (*T)=(*T)->Rchild;/*待刪除結點指向其右子樹*/
        free(qmove);
    } 
    /*這裡要注意一定要寫成else if,否則會出現錯誤*/
    else if((*T)->Rchild==NULL){
        qmove=*T;
        (*T)=(*T)->Lchild;
        free(qmove); 
    }
    else{
        qmove=*T;
        /*先左轉再右轉至指標為空是為了找到其中序遍歷的直接前驅*/
        smove=(*T)->Lchild;
        while(smove->Rchild){
            qmove=smove;
            smove=smove->Rchild;
        }
        /*smove指向待刪除結點的直接前驅*/
        (*T)->data=smove->data;/*首先把smove指向的值賦給待刪除結點,下一步要做的是刪除smove並且把smove的孩子結點接好*/
        if(qmove!=(*T))/*如果qmove不指向待刪除結點,則把smove的左子樹賦給qmove的右子樹*/
            qmove->Rchild=smove->Lchild;
        else
            qmove->Lchild=smove->Lchild;/*否則把smove的左子樹賦給qmove的左子樹*/
        free(smove); 
    }
    return OK;
} 


/*查詢BST中是否存在key,若存在則刪除key*/ 
Status DeleteBST(BiTree *T,int key){
    if(!(*T))
       return ERROR;
    else{
        if((*T)->data==key)
           Delete(T);
        else if(key<(*T)->data)
            DeleteBST(&(*T)->Lchild,key);
        else if(key>(*T)->data)
           DeleteBST(&(*T)->Rchild,key);
    }
}

/*此處順便附上主程式*/
int main(void)
{    
    int i;
    int a[10]={62,88,58,47,35,73,51,99,37,93};
    BiTree T=NULL,p;

    for(i=0;i<10;i++)
    {
        InsertBST(&T, a[i]);
    }
    DeleteBST(&T,93);
    DeleteBST(&T,47);
    Inorder(T);//中序遍歷
//  SearchBST(T,35,NULL,&p);
//  printf("%d",p->data); 
    return 0;
}

前兩種情況比較簡單,不再贅述,下面分析第三種情況

第一步:宣告兩個指標,qmove指向待刪除結點,smove指向待刪除結點的左子樹之後讓smove往右子樹移動,同時qmove記錄smove的位置,這一步是為了找到待刪除結點的直接前驅

第二步:將smove的值賦給根節點,如果這時qmove沒有指向根結點,則讓qmove的右子樹指向smove的左子樹;否則qmove的左子樹指向smove的左子樹

第三步,釋放smove

下面這組圖片展示了這段程式碼執行時的每一步的狀態:
這裡寫圖片描述
這裡寫圖片描述
這裡寫圖片描述
這裡寫圖片描述

至此,刪除操作才算告一段落

四.總結

二叉排序樹採用鏈式儲存,有較好的插入刪除的時間效能;查詢方面,最好情況是1次找到,最差情況也不會超過樹的深度。因此,查詢方面的效能取決於輸的形狀,比如有下面這樣的兩棵樹:

這裡寫圖片描述

兩棵樹的資料全部相同,注意右邊的樹也是二叉排序樹,只不過其元素由於是從小到大排列的導致成了一棵右斜樹,要查詢99這個元素,左邊的樹只需要兩次比較,而右邊要 10次比較,差異非常大,已經失去了二叉排序樹的意義,因此,如何讓二叉樹兩邊變得均衡一些,是我們下一個需要研究的問題。

相關推薦

排序(BST)的思路C語言實現

請注意,為了能夠更好的理解二叉排序樹,我建議各位在看程式碼時能夠設定好斷點一步一步跟蹤函式的執行過程以及各個變數的變化情況 一.動態查詢所面臨的問題 在進行動態查詢操作時,如果我們是在一個無序的線性表中進行查詢,在插入時可以將其插入表尾,表長加1即可;刪除時

STL原始碼筆記(17)—排序BSTC++封裝)

二叉排序樹BST STL中還有一類非常重要的容器,就是關聯容器,比如map啊set啊等等,這些容器說實話,在應用層上還不能完全得心應手(比如幾種容器效率的考慮等等),更別說原始碼了,因此這一部分打算穩紮穩打,好好做做筆記研究一番。 說到關聯容器,我們想到了什

搜尋(排序)BST與雙向列表的轉換

目錄 1.二叉排序樹(Binary Sort Tree) 二叉排序樹又稱二叉查詢樹(Binary Search Tree) BST 二叉排序樹或者是一顆空樹,或者是滿足一下性質的一顆二叉樹: 若它的左子樹非空,則左子樹上

排序BST(未完、佔坑)

#include <stdio.h> #include <stdlib.h> typedef struct BSTNode{ int data; struct BSTNode *lchild,*rchild; }BSTNode,*BSTree; int

排序節點刪除(c++)

二叉樹是資料結構中一個重要的概念,而在二叉排序樹的操作中,節點刪除又相對較為複雜。本文把自己寫的演算法做一些總結,同時希望能夠和大家進行交流 首先我們要明白二叉排序樹的性質: 一、二叉排序樹是每個節點最多含有兩個節點的樹,或者是一棵空樹。 二、二叉排序樹中左子節點小於父節

排序(BST)的判定(其實不容易)

      對於BST,一定要理解透徹,下面,我們給出一個有錯誤的BST判定程式: // 程式中的isBST函式的邏輯是有錯誤 #include <iostream> #define N 7 using namespace std; // BST的結點 t

機器學習演算法()——決策分類演算法R語言實現方法

決策樹演算法是分類演算法中最常用的演算法之一。決策樹是一種類似流程圖的樹形結構,可以處理高維資料,直觀易理解,且準確率較高,因此應用廣泛。本篇小博就決策樹的若干演算法:ID3演算法、C4.5演算法以及分類迴歸樹(CART)、C5.0進行對比介紹,並對比C4.5與C5.0處理

C++ 判斷一顆騰訊分是分網站開發否是BST排序)

sizeof 存儲 tno ret turn bre 打印 二叉 添加 因為騰訊分分網站開發 haozbbs.com Q1446595067二叉排序樹的中序遍歷結果是遞增的,所以可以通過中序遍歷存儲結果,再判斷是否為遞增的數組。1) 對樹進行中序遍歷,將結果保存在b[]

資料結構圖文解析之:的簡介排序C++模板實現.

  閱讀目錄 0. 資料結構圖文解析系列 1. 樹的簡介 1.1 樹的特徵 1.2 樹的相關概念 2. 二叉樹簡介 2.1 二叉樹的定義 2.2 斜樹、滿二叉樹、完全二叉樹、二叉查詢樹 2

BST排序的查詢和刪除的完整C程式碼

二叉排序樹的查詢演算法 假定二叉排序樹的根節點指標為root,給定的關鍵字值為key,則查詢演算法可描述為: 置初值:p = root ;如果 key = p -> data ,則查詢成功,演算法結束;否則,如果key < p->data ,而且 p 的

C++ 判斷一顆是否是BST排序)

方法一:使用中序遍歷因為二叉排序樹的中序遍歷結果是遞增的,所以可以通過中序遍歷儲存結果,再判斷是否為遞增的陣列。1) 對樹進行中序遍歷,將結果儲存在b[]陣列中。2) 檢測b[]陣列中元素是否為升序排列。如果是,則這棵樹為BST.時間複雜度: O(n)程式碼如下:#inclu

排序 C++

二叉查找樹 {} 刪除元素 one dem trees system arc oid 二叉排序樹(Binary Sort Tree),又稱二叉查找樹。 1、若左子樹不為空,則左子樹上所有結點的值均小於他的根結構的值; 2、若右子樹不為空,則右子樹上所有結點的值均大於他的根結

C語言——排序

pre span ren 二叉 == nbsp stdio.h spa int 二叉排序樹是一種實現動態查找的樹表,又稱二叉查找樹。 二叉排序樹的性質: 1. 若它的左子樹不為空,則左子樹上所有節點的鍵值均小於它的根節點鍵值 2. 若它的右子樹不為空,則右子樹上所有節

BST排序)的插入與刪除

最小值 temp def oot gpo 一個 記錄 通過 如果 值得一說的是刪除操作,刪除操作我們分為三種情況: 1.要刪的節點有兩個孩子:   找到左子樹中的最大值或者右子樹中的最小值所對應的節點,記為node,並把node的值賦給要刪除的節點del,然後刪除node

排序插入C語言版 遞歸步驟理解

pan 形參 排序樹 tno btn 排序 all png spa 1 //二叉排序樹 插入 (純C語言實現) 2 BTNode * BSTInsert2(BTNode *bt,int key){ 3

資料結構---搜尋BST實現C++)

1. 二叉查詢樹 二叉查詢樹(Binary Search Tree),也稱為二叉搜尋樹、有序二叉樹(ordered binary tree)或排序二叉樹(sorted binary tree),是指一棵空樹或者具有下列性質的二叉樹: 若任意節點的左子樹不空,則左子樹上所有節點的值均小於它的根節點的值

【模板】搜尋排序查詢BST

二叉搜尋樹其實就是滿足左結點小於根,右結點大於根這類規則的樹形結構。  1 int n; 2 int a[MAX_N]; 3 int lt[MAX_N], rt[MAX_N]; 4 // 沒有則為-1 5 // 預設a[0]為根結點 6 7 void Insert(int

排序的構造,插入,刪除,完整c程式碼實現

#include <stdio.h>   #include <stdlib.h>    typedef struct BiTNode{       int data;       s

[原始碼和文件分享]基於C++實現排序

一、使用說明 1.1 專案簡介 依次輸入關鍵字並建立二叉排序樹,實現二叉排序樹的插入和查詢功能。 1.2 專案功能要求 二叉排序樹就是指將原來已有的資料根據大小構成一棵二叉樹,二叉樹中的所有結點資料滿足一定的大小關係,所有的左子樹中的結點均比根結點小,所有的右子樹的結點均比根結點大。

C語言排序

typedef struct BiTNode{ int  data; struct BiTNode *lchild; struct BiTNode *rchild;}Bintree;  #include<stdio.h>