Java數(shù)據(jù)結(jié)構(gòu)之二叉搜索樹詳解
前言
今天leetcode的每日一題450是關(guān)于刪除二叉搜索樹節(jié)點(diǎn)的,題目要求刪除指定值的節(jié)點(diǎn),并且需要保證二叉搜索樹性質(zhì)不變,做完之后,我覺得這道題將二叉搜索樹特性凸顯的很好,首先需要查找指定節(jié)點(diǎn),然后刪除節(jié)點(diǎn)并且保持二叉搜索樹性質(zhì)不變,就想利用這個(gè)題目講講二叉搜索樹。
二叉搜索樹作為一個(gè)經(jīng)典的數(shù)據(jù)結(jié)構(gòu),具有鏈表的快速插入與刪除的特點(diǎn),同時(shí)查詢效率也很優(yōu)秀,所以應(yīng)用十分廣泛,例如在文件系統(tǒng)和數(shù)據(jù)庫系統(tǒng)一般會(huì)采用這種數(shù)據(jù)結(jié)構(gòu)進(jìn)行高效率的排序與檢索操作。同時(shí)因?yàn)閷?shí)現(xiàn)也簡單,作為一些公司算法題入門題目也是常有的事情,所以很需要被掌握哦~
所有源碼已經(jīng)放在我的github中,其中包括之前實(shí)現(xiàn)算法及每日一題,可以查看Data-Structures-and-Algorithms哦~
性質(zhì)
二叉搜索樹或者是一棵空樹,或者是具有下列性質(zhì)的一棵二叉樹,如果當(dāng)前節(jié)點(diǎn)具有左子樹,則左子樹上的每一個(gè)節(jié)點(diǎn)值均小于等于當(dāng)前節(jié)點(diǎn)值,如果當(dāng)前節(jié)點(diǎn)具有右子樹,則右子樹上的每一個(gè)節(jié)點(diǎn)值均大于等于當(dāng)前節(jié)點(diǎn)值。依據(jù)這個(gè)性質(zhì),當(dāng)我們前序遍歷二叉搜索樹的時(shí)候,得到的序列應(yīng)該是從小到大的非遞減序列。同時(shí)搜索指定值時(shí),只需要與當(dāng)前節(jié)點(diǎn)比較,根據(jù)相對(duì)大小在左子樹或者右子樹上進(jìn)行搜索。
實(shí)現(xiàn)
根據(jù)二叉搜索樹的性質(zhì)我們接下來需要實(shí)現(xiàn)插入節(jié)點(diǎn),查詢節(jié)點(diǎn),刪除節(jié)點(diǎn)功能。
節(jié)點(diǎn)結(jié)構(gòu)
public class TreeNode { public int val; public TreeNode left; public TreeNode right; public TreeNode() { } public TreeNode(int val) { this.val = val; } public TreeNode(int val, TreeNode left, TreeNode right) { this.val = val; this.left = left; this.right = right; } }
初始化
這里我們假設(shè)所有節(jié)點(diǎn)值大于0,初始化一個(gè)頭節(jié)點(diǎn)。ps:對(duì)于樹,鏈表這類數(shù)據(jù)結(jié)構(gòu),為了使第一個(gè)節(jié)點(diǎn)操作與其他節(jié)點(diǎn)保持一致,方便操作,常見的方法是添加一個(gè)額外的頭節(jié)點(diǎn),指向第一個(gè)節(jié)點(diǎn)。
TreeNode head; private void init() { //添加一個(gè)頭節(jié)點(diǎn) head = new TreeNode(-1); }
插入節(jié)點(diǎn)
從頭節(jié)點(diǎn)開始我們遍歷二叉搜索樹,如果當(dāng)前節(jié)點(diǎn)值小于等于插入節(jié)點(diǎn)值,則插入節(jié)點(diǎn)在當(dāng)前節(jié)點(diǎn)的右子樹上,否則在左子樹上,一直深度遍歷知道當(dāng)前節(jié)點(diǎn)的右子樹(左子樹)為空,則插入。
/** * 插入新節(jié)點(diǎn),假設(shè)新節(jié)點(diǎn)均大于0 * @param val 插入節(jié)點(diǎn)值 * @return 插入的節(jié)點(diǎn) */ public TreeNode insert(int val) { TreeNode temp = head; while (true) { if (temp.val < val) { //val應(yīng)該在右子樹上 if (null != temp.right) { temp = temp.right; continue; } else { temp.right = new TreeNode(val); return temp.right; } } //應(yīng)該在左子樹上 if (null != temp.left) { temp = temp.left; continue; } temp.left = new TreeNode(val); return temp.left; } }
查找節(jié)點(diǎn)
查找節(jié)點(diǎn)的步驟其實(shí)在插入節(jié)點(diǎn)的時(shí)候已經(jīng)有體現(xiàn),其實(shí)就是將查找值與當(dāng)前節(jié)點(diǎn)比較,大于當(dāng)前節(jié)點(diǎn)走右子樹,小于當(dāng)前節(jié)點(diǎn)走左子樹,直到值匹配返回節(jié)點(diǎn),或者沒有找到返回null。ps:這里為了后面方便實(shí)現(xiàn)刪除,同時(shí)返回了當(dāng)前節(jié)點(diǎn)以及當(dāng)前節(jié)點(diǎn)的父節(jié)點(diǎn),這里使用了commons-lang3包下的Pair工具。
/** * 搜索節(jié)點(diǎn)值 * @param val * @return */ public Pair<TreeNode, TreeNode> find(int val) { TreeNode temp = head.right; TreeNode parent = head; while (null != temp) { if (temp.val == val) { return Pair.of(temp, parent); } parent = temp; if (temp.val < val) { //在右子樹上 temp = temp.right; continue; } temp = temp.left; } return null; }
刪除節(jié)點(diǎn)
刪除節(jié)點(diǎn)時(shí)候我們需要先找到刪除節(jié)點(diǎn)的位置,然后做對(duì)應(yīng)操作。有三種情況:
1.如果刪除的是葉子節(jié)點(diǎn)直接刪除
2.如果刪除的節(jié)點(diǎn)只有左子樹或者右子樹,則直接將左子樹或者右子樹節(jié)點(diǎn)放在刪除節(jié)點(diǎn)位置
3.如果刪除節(jié)點(diǎn)同時(shí)有左子樹和右子樹,則將右子樹節(jié)點(diǎn)放在原來節(jié)點(diǎn)位置,將左子樹放在右子樹最左邊節(jié)點(diǎn)左子樹上(反之將左子樹放在原來節(jié)點(diǎn)位置,右子樹放在左子樹最右邊節(jié)點(diǎn)右子樹上也可)
/** * 1.如果刪除的是葉子節(jié)點(diǎn)直接刪除, * 2.如果刪除的節(jié)點(diǎn)只有左子樹或者右子樹,則直接將左子樹或者右子樹節(jié)點(diǎn)放在刪除節(jié)點(diǎn)位置 * 3.如果刪除節(jié)點(diǎn)同時(shí)右左子樹和右子樹,則將右子樹節(jié)點(diǎn)放在原來節(jié)點(diǎn)位置,將左子樹放在右子樹最左邊節(jié)點(diǎn)左子樹上 * @param val */ public void delete(int val) { //找到刪除節(jié)點(diǎn),刪除節(jié)點(diǎn)父節(jié)點(diǎn) Pair<TreeNode, TreeNode> curAndParent = this.find(val); TreeNode cur = curAndParent.getLeft(); TreeNode parent = curAndParent.getRight(); //記錄刪除當(dāng)前節(jié)點(diǎn)后,當(dāng)前節(jié)點(diǎn)位置放置哪個(gè)節(jié)點(diǎn) TreeNode changed; if (null == cur.left && null == cur.right) { changed = null; } else if (null != cur.left && null != cur.right) { TreeNode tempRight = cur.right; while (null != tempRight.left) { //找到最左側(cè)節(jié)點(diǎn) tempRight = tempRight.left; } tempRight.left = cur.left; changed = cur.right; } else if (null != cur.left) { changed = cur.left; } else { changed = cur.right; } if (parent.left == cur) { parent.left = changed; return; } parent.right = changed; }
最后
二叉搜索樹易于實(shí)現(xiàn),思想簡單,被廣泛應(yīng)用,平均查找,插入,刪除時(shí)間均為O(logn),但是在刪除或者插入節(jié)點(diǎn)的過程中,可能因?yàn)閿?shù)據(jù)的特點(diǎn),使得二叉搜索樹極端情況下退化為一棵僅有左子樹或者右子樹的,這時(shí)候就跟普通順序查找無異,時(shí)間復(fù)雜度變?yōu)镺(n),因此后面出現(xiàn)了平衡二叉搜索樹,左右子樹高度相差不超過1,通過旋轉(zhuǎn)將二叉樹高度降低,使得查找、插入、刪除在平均和最壞情況下都是O(logn)。比如常見的AVL自平衡二叉搜索樹,紅黑樹等等。
到此這篇關(guān)于Java數(shù)據(jù)結(jié)構(gòu)之二叉搜索樹詳解的文章就介紹到這了,更多相關(guān)Java二叉搜索樹內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java中使用異或運(yùn)算符實(shí)現(xiàn)加密字符串
這篇文章主要介紹了Java中使用異或運(yùn)算符實(shí)現(xiàn)加密字符串,本文直接給出實(shí)現(xiàn)代碼,以及運(yùn)算結(jié)果加密實(shí)例,需要的朋友可以參考下2015-06-06java發(fā)起http請(qǐng)求調(diào)用post與get接口的方法實(shí)例
在實(shí)際開發(fā)過程中,我們經(jīng)常需要調(diào)用對(duì)方提供的接口或測試自己寫的接口是否合適,下面這篇文章主要給大家介紹了關(guān)于java發(fā)起http請(qǐng)求調(diào)用post與get接口的相關(guān)資料,需要的朋友可以參考下2022-08-08Java8新特性之重復(fù)注解(repeating annotations)淺析
這篇文章主要介紹了Java8新特性之重復(fù)注解(repeating annotations)淺析,這個(gè)新特性只是修改了程序的可讀性,是比較小的一個(gè)改動(dòng),需要的朋友可以參考下2014-06-06springmvc+ajax+formdata上傳圖片代碼實(shí)例
這篇文章主要介紹了springmvc+ajax+formdata上傳圖片代碼實(shí)例,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-09-09MyBatis Oracle 自增序列的實(shí)現(xiàn)方法
這篇文章給大家分享MyBatis Oracle 自增序列的實(shí)現(xiàn)方法及mybatis配置oracle的主鍵自增長的方法,非常不錯(cuò)具有一定的參考借鑒價(jià)值,感興趣的朋友一起看看吧2016-11-11ArrayList及HashMap的擴(kuò)容規(guī)則講解
今天小編就為大家分享一篇關(guān)于ArrayList及HashMap的擴(kuò)容規(guī)則講解,小編覺得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來看看吧2019-02-02idea maven 構(gòu)建本地jar包及pom文件的過程
這篇文章主要介紹了idea maven 構(gòu)建本地jar包及pom文件的過程,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友參考下吧2023-11-11