Java數(shù)據(jù)結(jié)構(gòu)之線段樹(shù)的原理與實(shí)現(xiàn)
簡(jiǎn)介
線段樹(shù)是一種二叉搜索樹(shù),是用來(lái)維護(hù)區(qū)間信息的數(shù)據(jù)結(jié)構(gòu)??梢栽贠(logN)的時(shí)間復(fù)雜度內(nèi)實(shí)現(xiàn)單點(diǎn)修改、區(qū)間修改、區(qū)間查詢(區(qū)間求和,求區(qū)間最大值,求區(qū)間最小值)等操作。接下來(lái)我以實(shí)現(xiàn)區(qū)間求和為例子來(lái)講解線段樹(shù)(最大值和最小值與求和實(shí)現(xiàn)方式幾乎無(wú)異),假設(shè)存在一個(gè)數(shù)組[1,4,6,3,9]。
實(shí)現(xiàn)思路
從線段樹(shù)的定義,我們首先需要定義一個(gè)樹(shù)節(jié)點(diǎn),節(jié)點(diǎn)包含區(qū)間和(23),區(qū)間([1-5]),左節(jié)點(diǎn),右節(jié)點(diǎn)等。(如果要實(shí)現(xiàn)求區(qū)間最大值,最小值,則還需包含這些)。然后需要提供構(gòu)建線段樹(shù),線段樹(shù)支持修改節(jié)點(diǎn)操作方法。
節(jié)點(diǎn)定義
@Data public?static?class?Node?{ ? ?//區(qū)間起始下標(biāo) ? ?private?int?start; ? ?//區(qū)間結(jié)尾下標(biāo) ? ?private?int?end; ? ?//當(dāng)前區(qū)間和值 ? ?private?int?value; ? ?private?Node?left; ? ?private?Node?right; ? ?Node(int?start,?int?end,?int?value) { ? ? ? ?this.start?=?start; ? ? ? ?this.end?=?end; ? ? ? ?this.value?=?value; ? } }
構(gòu)建線段樹(shù)
因?yàn)闃?gòu)建線段樹(shù)時(shí)候需要計(jì)算當(dāng)前區(qū)間和,所以我們可以先初始化一個(gè)前綴和數(shù)組,在構(gòu)建線段樹(shù)時(shí)候利用下標(biāo)快速計(jì)算出區(qū)間和,同時(shí)為了保證每個(gè)節(jié)點(diǎn)有一致的操作,初始化一個(gè)頭節(jié)點(diǎn),指向root(這是鏈表樹(shù)等常用的簡(jiǎn)化操作的方法)
//head 指向線段樹(shù)root節(jié)點(diǎn)的指針,使得root節(jié)點(diǎn)與其余節(jié)點(diǎn)操作保持一致 ? ?Node?head; ? ?int?size; ? ?List<Integer>?nums; ? ?//前綴和數(shù)組,便于構(gòu)建線段樹(shù)時(shí)候計(jì)算區(qū)間值,用于初次構(gòu)建輔助 ? ?List<Integer>?prefixSum?; ? ?public?void?init(List<Integer>?nums) { ? ? ? ?//初始化一個(gè)頭節(jié)點(diǎn),便于操作 ? ? ? ?this.head?=?new?Node(-1,?-1,?-1); ? ? ? ?this.nums?=?nums; ? ??//初始化前綴和數(shù)組 ? ? ? ?prefixSum?=?new?ArrayList<>(nums.size()); ? ? ? ?prefixSum.add(0); ? ? ? ?for?(int?i?=?0;?i?<?nums.size() ;?i++) { ? ? ? ? ? ?prefixSum.add(prefixSum.get(prefixSum.size()?-?1)?+?nums.get(i)); ? ? ? } ? ??//構(gòu)建線段樹(shù) ? ? ? ?this.build(1,?nums.size()); ? ? ? ?size?=?nums.size(); ? } ? ?//構(gòu)建線段樹(shù) ? ?public?void?build(int?start,?int?end) { ? ? ??Node?root?=?new?Node(start,?end,?prefixSum.get(end)?-?prefixSum.get(start?-?1)); ? ? ?//將頭節(jié)點(diǎn)右子樹(shù)指向root ? ? ??head.right?=?root; ? ? ?//從root開(kāi)始構(gòu)建線段樹(shù) ? ? ??this.madeChild(root,?start,?end); ? } private?void?madeChild(Node?node,?int?start,?int?end) { ? ? ? ?if?(start?>=?end) { ? ? ? ? ? ?return; ? ? ? } ? ? ? ?//分個(gè)左右子樹(shù),左子樹(shù)取start~mid,右子樹(shù)取mid+1~end ? ? ? ?int?mid?=?start?+?((end?-?start)?>>?1); ? ? ? ?if?(start?<=?mid) { ? ? ? ? ? ?Node?left?=?new?Node(start,?mid,?prefixSum.get(mid)?-?prefixSum.get(start?-?1)); ? ? ? ? ? ?node.left?=?left; ? ? ? ? ? ?madeChild(left,?start,?mid); ? ? ? } ? ? ? ?if?(mid?+?1?<=?end) { ? ? ? ? ? ?Node?right?=?new?Node(mid?+?1,?end,?prefixSum.get(end)?-?prefixSum.get(mid)); ? ? ? ? ? ?node.right?=?right; ? ? ? ? ? ?madeChild(right,?mid?+?1,?end); ? ? ? } ? }
求解區(qū)間和
求解區(qū)間和過(guò)程就是遍歷線段樹(shù),將求解區(qū)間與當(dāng)前節(jié)點(diǎn)區(qū)間進(jìn)行比較,如果全部存在于左子樹(shù)或者右子樹(shù),則直接深度繼續(xù)在左子樹(shù)右子樹(shù)遍歷即可,但是如果求解區(qū)間在當(dāng)前節(jié)點(diǎn)的左右子樹(shù)均有部分,則需要將當(dāng)前區(qū)間分為兩個(gè)部分,然后分別深度遍歷左右子樹(shù),最后將結(jié)果相加。
//求解區(qū)間和 ? ?public?int?findSectionSum(int?start,?int?end) { ? ? ? ?//深度遍歷線段樹(shù),找到對(duì)應(yīng)區(qū)間 ? ? ? ?if?(start?<?1?||?end?>?size?||?start?>?end) { ? ? ? ? ? ?return?-1; ? ? ? } ? ? ? ?return?dfsFindSectionSum(head.right,?start,?end); ? } ? ? /** ? ??* 深度遍歷線段樹(shù)結(jié)構(gòu),分為三種情況 ? ??* 1.區(qū)間在當(dāng)前節(jié)點(diǎn)的左子樹(shù)中 ? ??* 2.區(qū)間在當(dāng)前節(jié)點(diǎn)的右子樹(shù)中 ? ??* 3.左子樹(shù)中一部分,右子樹(shù)中一部分 ? ??* @param node ? ??* @param start ? ??* @param end ? ??* @return ? ??*/ ? ?private?int?dfsFindSectionSum(Node?node,?int?start,?int?end) { ? ? ? ?if?(node.start?==?start?&&?node.end?==?end) { ? ? ? ? ? ?//找到區(qū)間 ? ? ? ? ? ?return?node.value; ? ? ? } ? ? ? ?if?(this.isContain(node.left.start,?node.left.end,?start,?end)) { ? ? ? ? ? ?//在左子樹(shù)中 ? ? ? ? ? ?return?this.dfsFindSectionSum(node.left,?start,?end); ? ? ? } ? ? ? ?if?(this.isContain(node.right.start,?node.right.end,?start,?end)) { ? ? ? ? ? ?//包含在右子樹(shù)中 ? ? ? ? ? ?return?this.dfsFindSectionSum(node.right,?start,?end); ? ? ? } ? ? ? ?//左邊一部分,右邊一部分 ? ? ? ?return?this.dfsFindSectionSum(node.left,?start,?node.left.end)?+?this.dfsFindSectionSum(node.right,?node.right.start,?end); ? } ? ?/** ? ??* 判斷區(qū)間[start2, end2]是否包含在[start1, end1]中 ? ??* @param start1 ? ??* @param end1 ? ??* @param start2 ? ??* @param end2 ? ??* @return ? ??*/ ? ?private?boolean?isContain(int?start1,?int?end1,?int?start2,?int?end2){ ? ? ? ?return?start2?>=?start1?&&?end2?<=?end1; ? }
更新線段樹(shù)
當(dāng)更新指定位置元素的值的時(shí)候,我們需要將線段樹(shù)中區(qū)間包含該節(jié)點(diǎn)的區(qū)間和進(jìn)行更新。我們可以從根節(jié)點(diǎn)開(kāi)始深度遍歷線段樹(shù),如果當(dāng)前節(jié)點(diǎn)包含該位置,我們更新區(qū)間和,然后根據(jù)當(dāng)前節(jié)點(diǎn)左右子節(jié)點(diǎn)的區(qū)間,判斷走左子樹(shù)還是右子樹(shù),直至更新到葉子節(jié)點(diǎn),則更新完成。
//更新線段樹(shù),將index位置的值更新為value,需要更新沿路的值 ? ?public?void?update(int?index,?int?value) { ? ? ? ?Node?root?=?head.right; ? ? ? ?while?(null?!=?root) { ? ? ? ? ? ?if?(index?>=?root.start?&&?index?<=?root.end) { ? ? ? ? ? ? ? ?root.value?+=?value?-?nums.get(index?-?1); ? ? ? ? ? } ? ? ? ? ? ?int?mid?=?root.start?+?((root.end?-?root.start)?>>?1); ? ? ? ? ? ?if?(index?<=?mid) { ? ? ? ? ? ? ? ?root?=?root.left; ? ? ? ? ? ? ? ?continue; ? ? ? ? ? } ? ? ? ? ? ?root?=?root.right; ? ? ? } ? ? ? ?nums.set(index?-?1,?value); ? }
以上就是Java數(shù)據(jù)結(jié)構(gòu)之線段樹(shù)的原理與實(shí)現(xiàn)的詳細(xì)內(nèi)容,更多關(guān)于Java 線段樹(shù)的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
一文告訴你為什么要重寫hashCode()方法和equals()方法
本篇文章帶大家了解一下為什么重寫hashCode()方法和equals()方法,文中有非常詳細(xì)的說(shuō)明以及代碼示例,對(duì)正在學(xué)習(xí)java的小伙伴們很有幫助,需要的朋友可以參考下2021-05-05SpringBoot集成Swagger2構(gòu)建在線API文檔的代碼詳解
這篇文章主要介紹了SpringBoot集成Swagger2構(gòu)建在線API文檔,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-12-12SpringMVC中RequestParam注解的簡(jiǎn)單理解
@RequestMapping RequestMapping是一個(gè)用來(lái)處理請(qǐng)求地址映射的注解,可用于類或方法上,下面這篇文章主要給大家介紹了關(guān)于SpringMVC中RequestParam注解的簡(jiǎn)單理解,需要的朋友可以參考下2022-03-03Java實(shí)現(xiàn)簡(jiǎn)易撲克牌游戲的完整實(shí)例
這篇文章主要介紹了Java實(shí)現(xiàn)簡(jiǎn)易撲克牌游戲的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2021-04-04Opencv創(chuàng)建車牌圖片識(shí)別系統(tǒng)方法詳解
本文主要介紹了一個(gè)基于spring?boot+maven+opencv實(shí)現(xiàn)的圖像識(shí)別及訓(xùn)練項(xiàng)目,可以實(shí)現(xiàn)車牌識(shí)別功能,感興趣的可以跟隨小編一起試一試2022-01-01SpringBoot整合Elasticsearch實(shí)現(xiàn)索引和文檔的操作方法
Elasticsearch 基于 Apache Lucene 構(gòu)建,采用 Java 編寫,并使用 Lucene 構(gòu)建索引、提供搜索功能,本文分步驟通過(guò)綜合案例給大家分享SpringBoot整合Elasticsearch的相關(guān)知識(shí),感興趣的朋友跟隨小編一起看看吧2021-05-05MyBatis動(dòng)態(tài)Sql之if標(biāo)簽的用法詳解
這篇文章主要介紹了MyBatis動(dòng)態(tài)Sql之if標(biāo)簽的用法,本文給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值 ,需要的朋友可以參考下2019-07-07Springmvc調(diào)用存儲(chǔ)過(guò)程,并返回存儲(chǔ)過(guò)程返還的數(shù)據(jù)方式
這篇文章主要介紹了Springmvc調(diào)用存儲(chǔ)過(guò)程,并返回存儲(chǔ)過(guò)程返還的數(shù)據(jù)方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-11-11