日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪(fǎng)問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程资源 > 编程问答 >内容正文

编程问答

掉一根头发,搞定二叉排序(搜索)树

發(fā)布時(shí)間:2025/3/20 编程问答 23 豆豆
生活随笔 收集整理的這篇文章主要介紹了 掉一根头发,搞定二叉排序(搜索)树 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

文章已收錄在 數(shù)據(jù)結(jié)構(gòu)與算法學(xué)習(xí)倉(cāng)庫(kù)

前言

前面介紹學(xué)習(xí)的大多是線(xiàn)性表相關(guān)的內(nèi)容,把指針搞懂后其實(shí)也沒(méi)有什么難度,規(guī)則相對(duì)是簡(jiǎn)單的,后面會(huì)講解一些比較常見(jiàn)的數(shù)據(jù)結(jié)構(gòu),用多圖的方式讓大家更容易吸收。

在數(shù)據(jù)結(jié)構(gòu)與算法中,樹(shù)是一個(gè)比較大的家族,家族中有很多厲害的成員,這些成員有二叉樹(shù)和多叉樹(shù)(例如B+樹(shù)等),而二叉樹(shù)的大家族中,二叉搜索樹(shù)(又稱(chēng)二叉排序樹(shù))是最最基礎(chǔ)的,在這基礎(chǔ)上才能繼續(xù)拓展學(xué)習(xí)AVL(二叉平衡樹(shù))、紅黑樹(shù)等知識(shí)。

對(duì)于二叉排序樹(shù)而言,本章重點(diǎn)關(guān)注其實(shí)現(xiàn)方式以及插入、刪除步驟流程,我們會(huì)手寫(xiě)一個(gè)二叉排序樹(shù),二叉樹(shù)遍歷部分的內(nèi)容比較多會(huì)單獨(dú)詳細(xì)講解。

什么是樹(shù)

樹(shù)是一種數(shù)據(jù)結(jié)構(gòu),它是由n(n>=1)個(gè)有限結(jié)點(diǎn)組成一個(gè)具有層次關(guān)系的集合。把它叫做“樹(shù)”是因?yàn)樗雌饋?lái)像一棵倒掛的樹(shù),也就是說(shuō)它是根朝上,而葉朝下的。

樹(shù)是遞歸的,將樹(shù)的任何一個(gè)節(jié)點(diǎn)以及節(jié)點(diǎn)下的節(jié)點(diǎn)都能組合成一個(gè)新的樹(shù),所以樹(shù)的很多問(wèn)題都是使用遞歸去完成。

根節(jié)點(diǎn): 最上面的那個(gè)節(jié)點(diǎn)(root),根節(jié)點(diǎn)沒(méi)有父節(jié)點(diǎn),只有子節(jié)點(diǎn)(0個(gè)或多個(gè)都可以)

層數(shù): 一般認(rèn)為根節(jié)點(diǎn)是第1層(有的也說(shuō)第0層),而樹(shù)的高度就是層數(shù)最高(上圖層數(shù)開(kāi)始為1)節(jié)點(diǎn)的層數(shù)

節(jié)點(diǎn)關(guān)系:

  • 父節(jié)點(diǎn):連接該節(jié)點(diǎn)的上一層節(jié)點(diǎn),
  • 孩子節(jié)點(diǎn): 和父節(jié)點(diǎn)對(duì)應(yīng),上下關(guān)系。而祖先節(jié)點(diǎn)是父節(jié)點(diǎn)的父節(jié)點(diǎn)(或者祖先)節(jié)點(diǎn)。
  • 兄弟節(jié)點(diǎn):擁有同一個(gè)父節(jié)點(diǎn)的節(jié)點(diǎn)們!

節(jié)點(diǎn)的度: 就是節(jié)點(diǎn)擁有孩子節(jié)點(diǎn)的個(gè)數(shù)(是直接連接的孩子不是子孫).

樹(shù)的度: 就是所有節(jié)點(diǎn)中最大 (節(jié)點(diǎn)的度)。同時(shí),如果度大于0的節(jié)點(diǎn)是分支節(jié)點(diǎn),度等于0的節(jié)點(diǎn)是葉子節(jié)點(diǎn)(沒(méi)有子孫)。

相關(guān)性質(zhì)

二叉樹(shù)

二叉樹(shù)是一樹(shù)的一種,但應(yīng)用比較多,所以需要深入學(xué)習(xí),二叉樹(shù)的每個(gè)節(jié)點(diǎn)最多只有兩個(gè)子節(jié)點(diǎn)(但不一定非得要有兩個(gè)節(jié)點(diǎn))。

二叉樹(shù)與度為2的樹(shù)的區(qū)別:
1、度為2的的樹(shù)必須有三個(gè)節(jié)點(diǎn)以上(否則就不叫度為二了,一定要先存在),二叉樹(shù)可以為空。
2、二叉樹(shù)的度不一定為2,比如斜樹(shù)。
3、二叉樹(shù)有左右節(jié)點(diǎn)區(qū)分,而度為2的樹(shù)沒(méi)有左右節(jié)點(diǎn)的區(qū)分。

幾種特殊二叉樹(shù):

滿(mǎn)二叉樹(shù):高度為n的滿(mǎn)二叉樹(shù)有(2^n) -1個(gè)節(jié)點(diǎn)

完全二叉樹(shù):上面一層全部滿(mǎn),最下一層從左到右順序排列

二叉排序樹(shù):樹(shù)按照一定規(guī)則插入排序(本文詳解)。
平衡二叉樹(shù):樹(shù)上任意節(jié)點(diǎn)左子樹(shù)和右子樹(shù)深度差距不超過(guò)1(后文詳解).

二叉樹(shù)性質(zhì):

1、二叉樹(shù)有用樹(shù)的性質(zhì)

2、非空二叉樹(shù)葉子節(jié)點(diǎn)數(shù)=度為2的節(jié)點(diǎn)數(shù)+1.本來(lái)一個(gè)節(jié)點(diǎn)如果度為1.那么一直延續(xù)就一個(gè)葉子,但如果出現(xiàn)一個(gè)度為2除了延續(xù)原來(lái)的一個(gè)節(jié)點(diǎn),會(huì)多出一個(gè)節(jié)點(diǎn)需要維系。所以到最后會(huì)多出一個(gè)葉子。

3、非空第i層最多有2^(i-1)個(gè)節(jié)點(diǎn)。

4、高為h的樹(shù)最多有(2^h)-1個(gè)節(jié)點(diǎn)(等比求和)。

二叉樹(shù)一般用鏈?zhǔn)酱鎯?chǔ),這樣內(nèi)存利用更高,但二叉樹(shù)也可以用數(shù)組存儲(chǔ)的(經(jīng)常會(huì)遇到),各個(gè)節(jié)點(diǎn)對(duì)應(yīng)的下標(biāo)是可以計(jì)算出來(lái)的,就拿一個(gè)完全二叉樹(shù)若從左往右,從上到下編號(hào)如圖:

二叉排序(搜索)樹(shù)

概念

前面鋪墊那么多,咱們言歸正傳,詳細(xì)講解并實(shí)現(xiàn)一個(gè)二叉排序樹(shù),二叉搜索樹(shù)擁有二叉樹(shù)的性質(zhì),同時(shí)有一些自己的規(guī)則:

首先要了解二叉排序樹(shù)的規(guī)則:從任意節(jié)點(diǎn)開(kāi)始,節(jié)點(diǎn)左側(cè)節(jié)點(diǎn)值總比節(jié)點(diǎn)右側(cè)值要小。

例如一個(gè)二叉排序樹(shù)依次插入15,6,23,7,4,71,5,50會(huì)形成下圖順序

構(gòu)造

二叉排序樹(shù)是由若干節(jié)點(diǎn)(node)構(gòu)成的,對(duì)于node需要這些屬性:left,right,和value。其中l(wèi)eft和right是左右指針指向左右孩子子樹(shù),而value是儲(chǔ)存的數(shù)據(jù),這里用int 類(lèi)型。

node類(lèi)構(gòu)造為:

class node {//結(jié)點(diǎn)public int value;public node left;public node right;public node(){}public node(int value){this.value=value;this.left=null;this.right=null;}public node(int value,node l,node r){this.value=value;this.left=l;this.right=r;} }

既然節(jié)點(diǎn)構(gòu)造好了,那么就需要節(jié)點(diǎn)等其他信息構(gòu)造成樹(shù),有了鏈表構(gòu)造經(jīng)驗(yàn),很容易得知一棵樹(shù)最主要的還是root根節(jié)點(diǎn)。
所以樹(shù)的構(gòu)造為:

public class BinarySortTree {node root;//根public BinarySortTree(){root=null;}public void makeEmpty()//變空{root=null;}public boolean isEmpty()//查看是否為空{return root==null;}//各種方法 }

主要方法

既然已經(jīng)構(gòu)造好一棵樹(shù),那么就需要實(shí)現(xiàn)主要的方法,因?yàn)槎媾判驑?shù)中每個(gè)節(jié)點(diǎn)都能看作一棵樹(shù)。所以我們創(chuàng)建方法的是時(shí)候加上節(jié)點(diǎn)參數(shù)(方便一些遞歸調(diào)用)

findmax(),findmin()

findmin()找到最小節(jié)點(diǎn):

因?yàn)樗泄?jié)點(diǎn)的最小都是往左插入,所以只需要找到最左側(cè)的返回即可,具體實(shí)現(xiàn)可使用遞歸也可非遞歸while循環(huán)。

findmax()找到最大節(jié)點(diǎn):

因?yàn)樗泄?jié)點(diǎn)大的都是往右面插入,所以只需要找到最右側(cè)的返回即可,實(shí)現(xiàn)方法與findmin()方法一致。

代碼使用遞歸函數(shù)

public node findmin(node t)//查找最小返回值是node,調(diào)用查看結(jié)果時(shí)需要.value {if(t==null) {return null;}else if(t.left==null) {return t;}else return(findmin(t.left)); } public node findmax(node t)//查找最大 {if(t==null) {return null;}else if(t.right==null) {return t;}else return(findmax(t.right)); }

isContains(int x)

這里的意思是查找二叉查找樹(shù)中是否存在值為x的節(jié)點(diǎn)。

在具體實(shí)現(xiàn)上,根據(jù)二叉排序樹(shù)左側(cè)更小,右側(cè)更大的性質(zhì)進(jìn)行往下查找,如果找到值為x的節(jié)點(diǎn)則返回true,如果找不到就返回false,當(dāng)然實(shí)現(xiàn)上可以采用遞歸或者非遞歸,我這里使用非遞歸的方式。

public boolean isContains(int x)//是否存在 {node current=root;if(root==null) {return false;}while(current.value!=x&&current!=null) {if(x<current.value) {current=current.left;}if(x>current.value) {current=current.right;}if(current==null) {return false;}//在里面判斷如果超直接返回}//如果在這個(gè)位置判斷是否為空會(huì)導(dǎo)致current.value不存在報(bào)錯(cuò)if(current.value==x) {return true;} return false; }

insert(int x)

插入的思想和前面isContains(int x)類(lèi)似,找到自己的位置(空位置)插入。

但是具體實(shí)現(xiàn)上有需要注意的地方,我們要到待插入位置上一層節(jié)點(diǎn),你可能會(huì)疑問(wèn)為什么不直接找到最后一個(gè)空,然后將current賦值過(guò)去current=new node(x),這樣的化current就相當(dāng)于指向一個(gè)new node(x)節(jié)點(diǎn),和原來(lái)樹(shù)就脫離關(guān)系(原樹(shù)相當(dāng)于沒(méi)有任何操作),所以要提前通過(guò)父節(jié)點(diǎn)判定是否為空找到位置,找到合適位置通過(guò)父節(jié)點(diǎn)的left或者right節(jié)點(diǎn)指向新創(chuàng)建的節(jié)點(diǎn)才能完成插入的操作。

public node insert(int x)// 插入 t是root的引用 {node current = root;if (root == null) {root = new node(x);return root;}while (current != null) {if (x < current.value) {if (current.left == null) {return current.left = new node(x);}else current = current.left;}else if (x > current.value) {if (current.right == null) {return current.right = new node(x);}else current = current.right;}}return current;//其中用不到 }

比如說(shuō)上面樹(shù)插入值為51的節(jié)點(diǎn)。

delete(int x)

刪除操作算是一個(gè)相對(duì)較難理解的操作了,因?yàn)榇齽h除的點(diǎn)可能在不同位置所以具體處理的方式也不同,如果是葉子即可可直接刪除,有一個(gè)孩子節(jié)點(diǎn)用子節(jié)點(diǎn)替換即可,有兩個(gè)子節(jié)點(diǎn)的就要先找到值距離待刪除節(jié)點(diǎn)最近的點(diǎn)(左子樹(shù)最大點(diǎn)或者右子樹(shù)最小點(diǎn)),將值替換掉然后遞歸操作在子樹(shù)中刪除已經(jīng)替換的節(jié)點(diǎn),當(dāng)然沒(méi)具體分析可以看下面:

刪除的節(jié)點(diǎn)沒(méi)有子孫:

這種情況不需要考慮,直接刪除即可(節(jié)點(diǎn)=null即可)(圖中紅色點(diǎn)均滿(mǎn)足這種方式)。

一個(gè)子節(jié)點(diǎn)為空:

此種情況也很容易,直接將刪除點(diǎn)的子節(jié)點(diǎn)放到被刪除位置即可。

左右節(jié)點(diǎn)均不空

左右孩子節(jié)點(diǎn)都不為空這種情況是相對(duì)比較復(fù)雜的,因?yàn)椴荒苤苯佑闷渲幸粋€(gè)孩子節(jié)點(diǎn)替代當(dāng)前節(jié)點(diǎn)(放不下,如果孩子節(jié)點(diǎn)也有兩個(gè)孩子那么有一個(gè)節(jié)點(diǎn)無(wú)法放,例如拿下面71節(jié)點(diǎn)替代)

如果拿19或者71節(jié)點(diǎn)填補(bǔ)。雖然可以保證部分側(cè)大于小于該節(jié)點(diǎn),但是會(huì)引起合并的混亂.比如你若用71替代23節(jié)點(diǎn)。那么你需要考慮三個(gè)節(jié)點(diǎn)(19,50,75)之間如何處理,還要考慮他們是否滿(mǎn),是否有子女,這是個(gè)復(fù)雜的過(guò)程,不適合考慮。

所以,我們要分析我們要的這個(gè)點(diǎn)的屬性:能夠保證該點(diǎn)在這個(gè)位置仍滿(mǎn)足二叉搜索樹(shù)的性質(zhì)(找到值最近的),那么子樹(shù)中哪個(gè)節(jié)點(diǎn)滿(mǎn)足這樣的關(guān)系呢?

左子樹(shù)中最右側(cè)節(jié)點(diǎn)或者右子樹(shù)中最左側(cè)節(jié)點(diǎn)都滿(mǎn)足,我們可以選一個(gè)節(jié)點(diǎn)將待刪除節(jié)點(diǎn)值替換掉(這里替換成左子樹(shù)最右側(cè)節(jié)點(diǎn))。

這個(gè)點(diǎn)替換之后該怎么辦呢?很簡(jiǎn)單啊,二叉樹(shù)用遞歸思路解決問(wèn)題,再次調(diào)用刪除函數(shù)在左子樹(shù)中刪除替換的節(jié)點(diǎn)即可。

這里演示是選取左子樹(shù)最大節(jié)點(diǎn)(最右側(cè))替代,當(dāng)然使用右子樹(shù)最小節(jié)點(diǎn)也能滿(mǎn)足在這待刪除的大小關(guān)系,原理一致。整個(gè)刪除算法流程為:

這部分操作的代碼為:

public node remove(int x, node t)// 刪除節(jié)點(diǎn) {if (t == null) {return null;}if (x < t.value) {t.left = remove(x, t.left);} else if (x > t.value) {t.right = remove(x, t.right);} else if (t.left != null && t.right != null)// 左右節(jié)點(diǎn)均不空{t.value = findmin(t.right).value;// 找到右側(cè)最小值替代t.right = remove(t.value, t.right);} else // 左右單空或者左右都空{if (t.left == null && t.right == null) {t = null;} else if (t.right != null) {t = t.right;} else if (t.left != null) {t = t.left;}return t;}return t; }

完整代碼

這個(gè)完整代碼是筆者在大三時(shí)候?qū)懙?#xff0c;可能有不少疏漏或者不規(guī)范的地方,僅供學(xué)習(xí)參考,如有疏漏錯(cuò)誤還請(qǐng)指正。

二叉排序樹(shù)完整代碼為:

package 二叉樹(shù);import java.util.ArrayDeque; import java.util.Queue; import java.util.Stack;public class BinarySortTree {class node {// 結(jié)點(diǎn)public int value;public node left;public node right;public node() {}public node(int value) {this.value = value;this.left = null;this.right = null;}public node(int value, node l, node r) {this.value = value;this.left = l;this.right = r;}}node root;// 根public BinarySortTree() {root = null;}public void makeEmpty()// 變空{root = null;}public boolean isEmpty()// 查看是否為空{return root == null;}public node findmin(node t)// 查找最小返回值是node,調(diào)用查看結(jié)果時(shí)需要.value{if (t == null) {return null;} else if (t.left == null) {return t;} elsereturn (findmin(t.left));}public node findmax(node t)// 查找最大{if (t == null) {return null;} else if (t.right == null) {return t;} elsereturn (findmax(t.right));}public boolean isContains(int x)// 是否存在{node current = root;if (root == null) {return false;}while (current.value != x && current != null) {if (x < current.value) {current = current.left;}if (x > current.value) {current = current.right;}if (current == null) {return false;} // 在里面判斷如果超直接返回}// 如果在這個(gè)位置判斷是否為空會(huì)導(dǎo)致current.value不存在報(bào)錯(cuò)if (current.value == x) {return true;}return false;}public node insert(int x)// 插入 t是root的引用{node current = root;if (root == null) {root = new node(x);return root;}while (current != null) {if (x < current.value) {if (current.left == null) {return current.left = new node(x);}else current = current.left;}else if (x > current.value) {if (current.right == null) {return current.right = new node(x);}else current = current.right;}}return current;//其中用不到}public node remove(int x, node t)// 刪除節(jié)點(diǎn){if (t == null) {return null;}if (x < t.value) {t.left = remove(x, t.left);} else if (x > t.value) {t.right = remove(x, t.right);} else if (t.left != null && t.right != null)// 左右節(jié)點(diǎn)均不空{t.value = findmin(t.right).value;// 找到右側(cè)最小值替代t.right = remove(t.value, t.right);} else // 左右單空或者左右都空{if (t.left == null && t.right == null) {t = null;} else if (t.right != null) {t = t.right;} else if (t.left != null) {t = t.left;}return t;}return t;} }

結(jié)語(yǔ)

這里我們學(xué)習(xí)了解了樹(shù)、二叉樹(shù)、以及二叉搜素樹(shù),對(duì)于二叉搜素樹(shù)插入查找比較容易理解,但是實(shí)現(xiàn)的時(shí)候要注意函數(shù)參數(shù)的引用等等。

偏有難度的是二叉樹(shù)的刪除,利用一個(gè)遞歸的思想,分類(lèi)討論待刪除情況,要找到特殊情況和普通情況,遞歸一定程度也是問(wèn)題的轉(zhuǎn)化(轉(zhuǎn)成自己相同問(wèn)題,作用域減小)需要思考。

下面還會(huì)介紹二叉樹(shù)的三序遍歷(遞歸和非遞歸)和層序遍歷。這些都是比較經(jīng)典熱門(mén)的問(wèn)題需要深入了解。

如果看了本文覺(jué)得有收獲歡迎 點(diǎn)贊、收藏、分享一波,也歡迎加我v信好友(q1315426911)一起學(xué)習(xí)交流,我也創(chuàng)了一個(gè)力扣打卡群,里面很多熱情的伙伴希望一起進(jìn)步!

總結(jié)

以上是生活随笔為你收集整理的掉一根头发,搞定二叉排序(搜索)树的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。