欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

C++數(shù)據(jù)結(jié)構(gòu)之二叉搜索樹的實現(xiàn)詳解

 更新時間:2022年08月22日 14:19:42   作者:。菀枯。  
二叉搜索樹作為一個經(jīng)典的數(shù)據(jù)結(jié)構(gòu),具有鏈表的快速插入與刪除的特點,同時查詢效率也很優(yōu)秀,所以應用十分廣泛。本文將詳細講講二叉搜索樹的C++實現(xiàn),需要的可以參考一下

前言

今天我們來學一個新的數(shù)據(jù)結(jié)構(gòu):二叉搜索樹。

介紹

二叉搜索樹也稱作二叉排序樹,它具有以下性質(zhì):

  • 非空左子樹的所有鍵值小于其根節(jié)點的鍵值
  • 非空右子樹的所有鍵值大于其根節(jié)點的鍵值
  • 左,右子樹都是二叉搜索樹

那么我先畫一個二叉搜索樹給大家看看,是不是真的滿足上面的性質(zhì)。

我們就以根節(jié)點6為例子來看,我們會發(fā)現(xiàn)比6小的都在6的左邊,而比6大的都在6的右邊。對于6的左右子樹來說,所有的節(jié)點都遵循這個規(guī)則。

同時我們還可以發(fā)現(xiàn)如果對搜索二叉樹進行一個中序遍歷,我們得到的序列是個有序序列,這就是為什么二叉搜索樹也可以稱作二叉排序樹。

實現(xiàn)

節(jié)點的實現(xiàn)

template <typename K>
struct BTreeNode
{
	BTreeNode<K>* _left;
	BTreeNode<K>* _right;
	K _key;

	BTreeNode(const K& key)
		:_key(key), _left(nullptr), _right(nullptr)
	{}
};

二叉搜索樹的查找

二叉搜索樹的查找思路很簡單:要找的值比當前節(jié)點小就去左子樹找,比當前節(jié)點大就往右子樹找,找到空節(jié)點就說明沒找到返回false即可。

首先我們先看看遞歸的版本。

bool findR(const T &val, Node *root) //T為節(jié)點的K, Node為節(jié)點
{
    if (root == nullptr)
    {
        return false;
    }

    if (root->_key < val)
    {
        return findR(root->_right, val);
    }
    else if (root->_key > val)
    {
        return findR(root->_left, val);
    }
    else
    {
        return true;
    }
}

接著看看非遞歸的版本

bool find(const T &val)
{
    Node *cur = _root; //_root為二叉搜索樹的根節(jié)點
    while (cur)
    {
        if (val < cur->_key)
        {
            cur = cur->_left;
        }
        else if (val > cur->_key)
        {
            cur = cur->_right;
        }
        else
        {
            return true;
        }
    }
    return false;
}

二叉搜索樹的插入

二叉搜索樹的插入和查找差別不大,首先尋找插入位置,比當前節(jié)點小就往左子樹找,比當前節(jié)點大就往右子樹找,直到找到空指針時,就可以進行一個插入。

那么要插入的值和當前節(jié)點相同該怎么辦呢?我們此時實現(xiàn)的二叉搜索樹是一個無重復元素的二叉搜索樹,所以對于相同的值我采取的方式是返回一個false,大家如果想實現(xiàn)一個有重復元素的二叉搜索樹,可以選擇將重復的值放在右子樹或左子樹都可。

二叉搜索樹的插入和查找一樣,有遞歸和非遞歸兩個版本,首先我們先來看看非遞歸的版本。

bool insert(const T &val)
{
    //空樹直接改變根節(jié)點
    if (_root == nullptr)
    {
        _root = new Node(val);
        return true;
    }
    
     //非空樹先尋找插入位置
    Node *pre = nullptr;
    Node *cur = _root;
    while (cur)
    {
        if (val > cur->_key)
        {
            pre = cur;
            cur = cur->_right;
        }
        else if (val < cur->_key)
        {
            pre = cur;
            cur = cur->_left;
        }
        else
        {
            return false;
        }
    }
    //判斷新節(jié)點該插入到哪里
    cur = new Node(val);
    if (val < pre->_key)
    {
        pre->_left = cur;
    }
    else
    {
        pre->_right = cur;
    }
    return true;
}

接下來用一個動畫來表示一下這個插入過程。

接下來我們來看看遞歸版本是如何實現(xiàn)的

bool insertR(const T &val, Node *&root)
{
    if (root == nullptr)
    {
        Node *newNode = new Node(val);
        root = newNode;
    }

    if (root->_key < val)
    {
        return insertR(val, root->_right);
    }
    else if (root->_key > val)
    {
        return insertR(val, root->_left);
    }
    else
    {
        return false;
    }
}

此時我們可以發(fā)現(xiàn),遞歸版本沒有非遞歸版本中的parent指針了,參數(shù)列表中只有一個root指針,這是為什么呢?

我們可以看見我們的root指針不僅是一個指針,同時它還是一個引用。這就意味著我們對root的修改也可以影響上一層傳遞過來的指針的值。所以此時我們不需要parent指針,就可以對上一個節(jié)點的指針進行一個修改。

二叉搜索樹的刪除

理論部分:

二叉搜索樹的刪除相比前面兩個要麻煩那么一丟丟,首先當然是找要刪除的節(jié)點,找到后通常有以下三種情況:

  • 此節(jié)點無左右子樹
  • 此節(jié)點有右子樹或右子樹
  • 此節(jié)點有左右子樹

我們要做的就是對這三種情況分別進行一個處理。

1.首先是此節(jié)點無左右子樹,這種情況我們直接將此節(jié)點刪除即可

2.然后是此節(jié)點只有一顆子樹,這個也比較簡單,如果此節(jié)點是父節(jié)點的左節(jié)點,那么我們只需要將父節(jié)點的左指針改成指向此節(jié)點的子樹即可。

3.最后一種就是既有左子樹又有右子樹的情況了,此時為了不破壞結(jié)構(gòu),我們需要用到替換刪除法。首先我們先找到要刪除的節(jié)點,然后找節(jié)點的左子樹的最右節(jié)點或右子樹的最左節(jié)點,將兩個節(jié)點進行一下互換,再將原節(jié)點刪除。

代碼部分:

首先是非遞歸版本

bool erase(const T &val)
{
    Node *cur = _root;
    Node *pre = nullptr;
    //尋找刪除位置
    while (cur)
    {
        if (cur->_key < val)
        {
            pre = cur;
            cur = cur->_right;
        }
        else if (cur->_key > val)
        {
            pre = cur;
            cur = cur->_left;
        }
        else //找到了進行刪除
        {
            if (cur->_left == nullptr) //左子樹為空
            {
                if (cur == _root)
                {
                    _root = cur->_right;
                }
                else
                {
                    if (cur == pre->_left)
                    {
                        pre->_left = cur->_right;
                    }
                    else
                    {
                        pre->_right = cur->_right;
                    }
                }
                delete cur;
            }
            else if (cur->_right == nullptr) //右子樹為空
            {
                if (cur == _root)
                {
                    _root = cur->_left;
                }
                else
                {
                    if (cur == pre->_left)
                    {
                        pre->_left = cur->_left;
                    }
                    else
                    {
                        pre->_right = cur->_left;
                    }
                }
                delete cur;
            }
            else //左右子樹都不為空
            {
                //找左子樹的最右節(jié)點
                Node *tmp = cur->_left;
                Node *tmp_pre = cur;
                while (tmp->_right) 
                {
                    tmp_pre = tmp;
                    tmp = tmp->_right;
                }
				//節(jié)點互換
                cur->_key = tmp->_key;

                if (tmp == tmp_pre->_left)
                {
                    tmp_pre->_left = tmp->_left;
                }
                else
                {
                    tmp_pre->_right = tmp->_left;
                }

                delete tmp;
            }
            return true;
        }
    }
    return false;
}

接下來是遞歸版本

bool eraseR(const T &val, Node *&root)
{
    //找不到返回false
    if (root == nullptr)
    {
        return false;
    }
	//尋找插入位置
    if (root->_key < val)
    {
        return eraseR(val, root->_right);
    }
    else if (root->_key > val)
    {
        return eraseR(val, root->_left);
    }
    else
    {
        if (root->_left == nullptr)//左子樹為空
        {
            root = root->_right;
        }
        else if (root->_right == nullptr)//右子樹為空
        {
            root = root->_left;
        }
        else //左右子樹都不為空
        {
            Node *cur = root->_left;
            while (cur->_right)
            {
                cur = cur->_right;
            }
            swap(cur->_key, root->_key);
            return eraseR(val, root->_left);
        }
        return true;
    }
}

總結(jié)

大家覺得二叉搜索樹的時間復雜度是多少呢?O(logn)嗎?不,它的時間復雜度還是O(n),當插入數(shù)據(jù)是有序的,二叉搜索樹會發(fā)生退化,變成一個單支樹。比如下面這張圖:

為了解決這個問題,有人對二叉搜索樹進行了一些優(yōu)化,如:AVL樹和紅黑樹,之后我也會帶著大家來實現(xiàn)一個完整的AVL樹和紅黑樹

到此這篇關(guān)于C++數(shù)據(jù)結(jié)構(gòu)之二叉搜索樹的實現(xiàn)詳解的文章就介紹到這了,更多相關(guān)C++二叉搜索樹內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • C語言函數(shù)超詳細講解下篇

    C語言函數(shù)超詳細講解下篇

    函數(shù)是一組一起執(zhí)行一個任務的語句。每個?C?程序都至少有一個函數(shù),即主函數(shù)?main()?,所有簡單的程序都可以定義其他額外的函數(shù),函數(shù)我們分兩篇來講解,接下來開始第二篇
    2022-04-04
  • C++中pair的用法總結(jié)

    C++中pair的用法總結(jié)

    pair是C++STL(標準模板庫)中的一個現(xiàn)有容器,它將2個數(shù)據(jù)整合成一組數(shù)據(jù),當我們類似需求的時候就可以使用到pair,pair的本質(zhì)其實就是個結(jié)構(gòu)體,本文將詳細的給大家介紹pair用法,感興趣的同學可以參考閱讀
    2023-05-05
  • C語言手把手教你實現(xiàn)貪吃蛇AI(中)

    C語言手把手教你實現(xiàn)貪吃蛇AI(中)

    這篇文章主要為大家詳細介紹了C語言手把手教你實現(xiàn)貪吃蛇AI的相關(guān)資料,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2018-01-01
  • 詳解C/C++中const關(guān)鍵字的用法及其與宏常量的比較

    詳解C/C++中const關(guān)鍵字的用法及其與宏常量的比較

    簡單的說const關(guān)鍵字修飾的變量具有常屬性,也就是說它所修飾的變量不能被修改,下文給大家介紹C/C++中const關(guān)鍵字的用法及其與宏常量的比較,需要的朋友可以參考下
    2017-07-07
  • 詳解C語言動態(tài)內(nèi)存的分配

    詳解C語言動態(tài)內(nèi)存的分配

    這篇文章主要為大家介紹了C語言動態(tài)內(nèi)存的分配,具有一定的參考價值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來幫助
    2021-12-12
  • C++趣味算法之偵探推理

    C++趣味算法之偵探推理

    本文詳細講解了C++趣味算法之偵探推理,文中通過示例代碼介紹的非常詳細。對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2021-12-12
  • c++如何將一個char轉(zhuǎn)化為string

    c++如何將一個char轉(zhuǎn)化為string

    這篇文章主要介紹了c++如何將一個char轉(zhuǎn)化為string問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2023-08-08
  • 淺談VC++中的內(nèi)聯(lián)

    淺談VC++中的內(nèi)聯(lián)

    在 Visual C++ 中使用內(nèi)聯(lián)匯編 一、內(nèi)聯(lián)匯編的優(yōu)缺點 因為在Visual C++中使用內(nèi)聯(lián)匯編不需要額外的編譯器和聯(lián)接器,且可以處理Visual C++ 中不能處理的一些事情,而且可以使用在 C/C++中的變量,所以非常方便。
    2015-07-07
  • C++ Boost Thread線程使用示例詳解

    C++ Boost Thread線程使用示例詳解

    Boost是為C++語言標準庫提供擴展的一些C++程序庫的總稱。Boost庫是一個可移植、提供源代碼的C++庫,作為標準庫的后備,是C++標準化進程的開發(fā)引擎之一,是為C++語言標準庫提供擴展的一些C++程序庫的總稱
    2022-11-11
  • C++使用ADO實現(xiàn)存取圖片的方法

    C++使用ADO實現(xiàn)存取圖片的方法

    這篇文章主要介紹了C++使用ADO實現(xiàn)存取圖片的方法,需要的朋友可以參考下
    2014-07-07

最新評論