<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
今天我們來學一個新的資料結構:二元搜尋樹。
二元搜尋樹也稱作二叉排序樹,它具有以下性質:
那麼我先畫一個二元搜尋樹給大家看看,是不是真的滿足上面的性質。
我們就以根節點6為例子來看,我們會發現比6小的都在6的左邊,而比6大的都在6的右邊。對於6的左右子樹來說,所有的節點都遵循這個規則。
同時我們還可以發現如果對搜尋二元樹進行一箇中序遍歷,我們得到的序列是個有序序列,這就是為什麼二元搜尋樹也可以稱作二叉排序樹。
template <typename K> struct BTreeNode { BTreeNode<K>* _left; BTreeNode<K>* _right; K _key; BTreeNode(const K& key) :_key(key), _left(nullptr), _right(nullptr) {} };
二元搜尋樹的查詢思路很簡單:要找的值比當前節點小就去左子樹找,比當前節點大就往右子樹找,找到空節點就說明沒找到返回false即可。
首先我們先看看遞迴的版本。
bool findR(const T &val, Node *root) //T為節點的K, Node為節點 { 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為二元搜尋樹的根節點 while (cur) { if (val < cur->_key) { cur = cur->_left; } else if (val > cur->_key) { cur = cur->_right; } else { return true; } } return false; }
二元搜尋樹的插入和查詢差別不大,首先尋找插入位置,比當前節點小就往左子樹找,比當前節點大就往右子樹找,直到找到空指標時,就可以進行一個插入。
那麼要插入的值和當前節點相同該怎麼辦呢?我們此時實現的二元搜尋樹是一個無重複元素的二元搜尋樹,所以對於相同的值我採取的方式是返回一個false,大家如果想實現一個有重複元素的二元搜尋樹,可以選擇將重複的值放在右子樹或左子樹都可。
二元搜尋樹的插入和查詢一樣,有遞迴和非遞迴兩個版本,首先我們先來看看非遞迴的版本。
bool insert(const T &val) { //空樹直接改變根節點 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; } } //判斷新節點該插入到哪裡 cur = new Node(val); if (val < pre->_key) { pre->_left = cur; } else { pre->_right = cur; } return true; }
接下來用一個動畫來表示一下這個插入過程。
接下來我們來看看遞迴版本是如何實現的
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; } }
此時我們可以發現,遞迴版本沒有非遞迴版本中的parent指標了,參數列中只有一個root指標,這是為什麼呢?
我們可以看見我們的root指標不僅是一個指標,同時它還是一個參照。這就意味著我們對root的修改也可以影響上一層傳遞過來的指標的值。所以此時我們不需要parent指標,就可以對上一個節點的指標進行一個修改。
理論部分:
二元搜尋樹的刪除相比前面兩個要麻煩那麼一丟丟,首先當然是找要刪除的節點,找到後通常有以下三種情況:
我們要做的就是對這三種情況分別進行一個處理。
1.首先是此節點無左右子樹,這種情況我們直接將此節點刪除即可
2.然後是此節點只有一顆子樹,這個也比較簡單,如果此節點是父節點的左節點,那麼我們只需要將父節點的左指標改成指向此節點的子樹即可。
3.最後一種就是既有左子樹又有右子樹的情況了,此時為了不破壞結構,我們需要用到替換刪除法。首先我們先找到要刪除的節點,然後找節點的左子樹的最右節點或右子樹的最左節點,將兩個節點進行一下互換,再將原節點刪除。
程式碼部分:
首先是非遞迴版本
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 //左右子樹都不為空 { //找左子樹的最右節點 Node *tmp = cur->_left; Node *tmp_pre = cur; while (tmp->_right) { tmp_pre = tmp; tmp = tmp->_right; } //節點互換 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; } }
大家覺得二元搜尋樹的時間複雜度是多少呢?O(logn)嗎?不,它的時間複雜度還是O(n),當插入資料是有序的,二元搜尋樹會發生退化,變成一個單支樹。比如下面這張圖:
為了解決這個問題,有人對二元搜尋樹進行了一些優化,如:AVL樹和紅黑樹,之後我也會帶著大家來實現一個完整的AVL樹和紅黑樹
到此這篇關於C++資料結構之二元搜尋樹的實現詳解的文章就介紹到這了,更多相關C++二元搜尋樹內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45