1. 程式人生 > >C++實現二叉樹的插入、刪除、查詢、遍歷

C++實現二叉樹的插入、刪除、查詢、遍歷

1.二叉樹的概念

       樹是一些節點的集合,節點之間用邊連結,節點之間不能有環路。上層的節點稱為父節點,下層節點稱為子節點。最上層的節點稱為根節點。

      二叉樹是特殊的樹。對於每個節點而言,與之直接相連的子節點不能超過兩個(可以為0)。左邊的子節點稱為左子樹,右邊的子節點稱為右子樹。如下圖就是一顆二叉樹:


與樹相關的一些概念:

         沒有任何子節點的節點稱為樹葉,或葉子節點。

        深度:對於任意節點N,其深度指的是從根節點到N的唯一路徑的長。根的深度為0。深度最深的葉子節點的深度為樹的深度。可以理解為:樹根是一個入口,離樹根越遠,就越深。如上圖:A、B、C的深度為1,D、E的深度為2。

          高:  對於任意節點N,從N到一片樹葉的最遠路徑的長為N的高度。(只可以從從上到下不能經過從下到上的節點。)樹葉的高為0。樹的高為根的高。如上圖,根的高度為2。A的高度為1,其他節點高度為0。

2.二叉樹的應用和時間複雜度

         二叉樹是一種常見的資料結構,常常用於查詢,也運用於unix等常見作業系統的檔案系統中。c++STL(標準模板庫)中的set和map也使用二叉樹中的紅黑樹實現。

        二叉樹的查詢思想基於:在二叉樹中,對於任意節點N,左子樹中的所有項的值不大於節點N中儲存的值,左子樹中的所有項的值不小於節點N中儲存的值。如下圖:


這樣,在查詢時,只需要不斷比較需要查詢的x與N的大小,若小於N中的值,只需要搜尋左子樹,若大於N中的值,只需要搜尋

右子樹。這樣每次就能縮小搜尋的範圍。經過證明,普通二叉樹的平均時間複雜度是O(LogN)。

       看到這裡,我們發現其實二叉樹的搜尋思想和二分查詢一致,每次不斷的減少搜尋範圍。但是二者之間還是有區別的。

       對於二分查詢而言,每次的時間複雜度不會超過O(LogN)。但是對於二叉樹,搜尋時間的複雜度取決於樹的形狀。在最壞情況下可能達到O(N)。如下圖,如果要找到10,則要查詢5次。


         那我們為什麼還要使用二叉樹而不直接使用二分查詢來代替?

        這是因為,二分查詢一般基於陣列,如果需要插入或刪除資料,則會帶來很大的開銷。因為每次插入或者刪除資料需要將改變節點之後的資料往後挪或者往前挪。但是對於二叉樹而言,只需要改變一下指向下一個節點的指標就可以很方便的實現插入或者刪除。而且一些特殊的二叉樹如紅黑樹可以保證查詢的最壞複雜度不超過O(LogN)。

       所以,如果是對於靜態資料,不需要改變的資料而言,採用陣列儲存,使用二分查詢比較好。而對於動態資料,需要頻繁插入或者刪除資料的,採取二叉樹儲存是較好的。

3.c++實現二叉樹的插入、查詢、遍歷、刪除(遞迴實現)

3.1二叉樹的插入

       思路:插入資料x,從根節點開始,不斷比較節點與x的大小。若x小於節點,下一次比較x與節點的左子樹,反之,比較x與節點的右子樹。直到遇到一個空的節點,插入資料。(我們不考慮插入重複資料) 。如下圖:


過程:比較4與7,4<7,再比較4與7的左子樹6,4<6,比較4與6的左子樹3,4>3,比較4與3的右子樹,為空,插入4。

程式碼:

template< typename T>
void BinaryTree<T>::insert(const T &theElement, BinaryNode * &t ) {
    if ( nullptr == t ){
        t = new BinaryNode (theElement);
} else if ( theElement < t->element ) {
          insert( theElement, t->leftNode );
} else if ( theElement > t->element ) {
          insert ( theElement, t->rightNode );
} else {//重複的資料不新增到樹中
}
};

3.2二叉樹的查詢

         思路:與插入類似,不斷比較插入值與節點的值。帶程式碼如下:

template< typename T>
bool BinaryTree<T>::isFind(const T &theElement, BinaryNode * t ) const {
    if ( nullptr == t ){
        return false;
} else if ( theElement < t->element ) {
        return isFind( theElement, t->leftNode );
} else if ( theElement > t->element ) {
         return isFind ( theElement, t->rightNode );
} else { //匹配
return true;
}
};

3.3二叉樹的遍歷

      二叉樹的遍歷有三種方式:

      前序遍歷(DLR):首先訪問根結點。然後如果有子樹,則對於左孩子也採用DLR的遍歷規則。沒有就忽略。然後如果有右子樹,則對右子樹也採用DRL的遍歷規則。沒有就忽略。

      中序遍歷(LDR):首先訪問根節點的左子樹(對左子樹也採用LDR),沒有則忽略。再訪問根節點。最後則對右子樹也採用LDR的遍歷規則,沒有就忽略。

      後序遍歷:首先訪問根節點的左子樹(對左子樹也採用LRD),沒有則忽略。再對右子樹也採用LRD的遍歷規則,沒有就忽略。最後則對右子樹也採用LRD的遍歷規則,沒有就忽略。

    三種遍歷方式其實是根據根節點的訪問順序命名的。根最先方位為前序,次之訪問為中序遍歷。最後訪問為後序遍歷。

先序遍歷圖1的二叉樹,結點的訪問順序為:  e→b→a→d→c→f→g

中序遍歷圖1的二叉樹,結點的訪問順序為:abcdefg

後序遍歷圖1的二叉樹,結點的訪問順序為:acdbgfe



                             圖1

    這裡採用遞迴方式實現:

   前序遍歷:

template< typename T>
void BinaryTree<T>::preOrder( BinaryNode *bNode ) const {
    if( nullptr != bNode ) {
        std::cout << bNode->element << " " ;
preOrder(bNode->leftNode);
preOrder(bNode->rightNode);
}

};

中序遍歷:

template< typename T>
void BinaryTree<T>::inOrder( BinaryNode *bNode ) const {
    if( nullptr != bNode ) {
        inOrder(bNode->leftNode);
std::cout << bNode->element << " " ;
inOrder(bNode->rightNode);
}
};

後序遍歷:

template< typename T>
void BinaryTree<T>::postOrder( BinaryNode *bNode ) const {
    postOrder(bNode->leftNode);
postOrder(bNode->rightNode);
std::cout << bNode->element << " " ;
};

3.4二叉樹的刪除

      二叉樹的插入需要分三種情況考慮。第一種,刪除節點是樹葉,則直接刪除;第二種是被刪除的節點只有一個子節點,此時只需要將刪除節點的上一個節點的指向該節點的指標指向該節點唯一的子節點;第三種是被刪除的節點有兩個子節點,這種情況是最麻煩的。我們採用的思想是將該節點的該節點右子樹中最小的一個節點的值覆蓋該節點中的值,然後再刪除該節點的右子樹中的最小的那個子節點。因為,該節點的右子樹中的最小的那個子節點的值剛好大於被刪除節點的左子樹中所有的值,又小於被刪除節點的右子樹中所有的值。最小的那個子節點不可能有左子樹,不然它就不是最小的節點,刪除該節點就轉換為刪除一個只有一個子節點的節點,即第二種情況。



                         第二種情況(刪除節點7)


 

                     第三種情況(刪除節點5)

                     其實是將5的那個節點賦值為6,然後刪除節點6.

程式碼:

template< typename T>
void BinaryTree<T>::remove(const T &theElement, BinaryNode * &t ) {
    if( nullptr == t ) {
        return;
} else {
        if ( theElement < t->element) {
            remove(t->leftNode);
} else if ( theElement > t->element ) {
            remove (t->rightNode);
} else if  (nullptr != t->leftNode && nullptr != t->rightNode ) {  //需要刪除的節點兩個兒子
t->element = findMin(t->rightNode)->element;
remove(t->element, t->rightNode);
} else {
            BinaryNode * oldNode = t;
t = ( nullptr!= t->leftNode) ? t->leftNode : t->rightNode;
delete oldNode;
}
    }
};

template< typename T>
typename BinaryTree<T>::BinaryNode * BinaryTree<T>::findMin(BinaryNode *bNode) const {
    if ( nullptr!= bNode) {
        while( nullptr != bNode->leftNode) {
            bNode = bNode->leftNode;
}
    }

    return bNode;
}

整個二叉樹的工程檔案在本人github上可以查閱:

https://github.com/yuanzoudetuzi/binaryTree