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

Java數(shù)據(jù)結(jié)構(gòu)之線段樹(shù)的原理與實(shí)現(xiàn)

 更新時(shí)間:2022年06月15日 09:19:50   作者:Carol  
線段樹(shù)是一種二叉搜索樹(shù),是用來(lái)維護(hù)區(qū)間信息的數(shù)據(jù)結(jié)構(gòu)。本文將利用示例詳細(xì)講講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)文章

最新評(píng)論