簡單線段樹的講解(一點點的心得體會)

目錄

一、初識線段樹

圖例:

?編輯

數組存儲:

指針存儲:

理由:

build函數建樹

二、線段樹的區間修改維護

區間修改維護:

區間修改的操作:

遞歸更新過程:

區間修改update:

三、線段樹的區間查詢

區間查詢:

區間查詢的操作:

遞歸查詢過程:

區間查詢query:

例題:

完整代碼:

數組實現:

指針實現:

總結


前言

今天我們來學習一下線段樹

模板題目:P3372 【模板】線段樹 1 - 洛谷


一、初識線段樹

首先我們來了解一下什么是線段樹

? ? ? 線段樹是一種數據結構,通常用來解決區間查詢的問題。它主要用于對一個包含有 n 個元素的數組進行區間操作,如查詢某個區間內的最大值、最小值、區間和等。

? ? ? ?線段樹的基本思想是將整個數組按照一定規則進行分割,每個節點代表一個區間。每個節點保存區間內的信息,如最大值、最小值、區間和等。父節點的信息可以通過子節點的信息合并得到,這樣就可以快速進行區間查詢。

? ? ? 線段樹通常是一棵完全二叉樹,葉子節點對應于數組中的元素,每個非葉子節點表示了其區間的信息。對于一個包含 n 個元素的數組,線段樹的節點數一般是 2n-1 或 2^k-1,其中 k 是大于等于 n 的最小整數。線段樹的構建包括建樹和更新兩個主要操作,查詢時可以通過遞歸的方式進行。

? ? ? ?線段樹在解決區間查詢問題時效率很高,時間復雜度一般為 O(logn),其中 n 是數組元素個數。因此,線段樹被廣泛應用于需要頻繁進行區間查詢的場景,如動態區間最值查詢、區間和查詢等。

我這次是聯系模板題目P3372 【模板】線段樹 1 - 洛谷講解的最簡單的類型

圖例:

? ? ? 通過這幅圖我們可以看出,線段樹是根據不斷的從子節點拿值,來更新父節點的值,直到得到整個區間的值,和分治的思想有點像,感覺

線段樹的存儲方式有兩種常見的實現方法:數組存儲和指針存儲。

  1. 數組存儲:

    • 在數組存儲中,線段樹被表示為一個靜態的完全二叉樹。數組的下標從 1 開始,對于節點 i,其左子節點為 2i,右子節點為 2i+1。
    • 如果線段樹的葉子節點數量為 n,那么數組的大小一般取 4n,以確保足夠的空間。
    • 線段樹根節點一般存儲在數組下標為 1 的位置。
    • 通過按照規則在數組中存儲線段樹的節點,可以方便地進行查詢和更新操作。
  2. 指針存儲:

    • 在指針存儲中,線段樹被表示為一個動態的樹結構,每個節點通過指針指向其左右子節點。
    • 每個節點通常由一個包含左右子節點指針的結構體或類表示。
    • 指針存儲方式在構建線段樹時會動態生成節點,相對于數組存儲來說更加靈活,但可能會消耗更多的內存空間。

? ? ?無論是數組存儲還是指針存儲,線段樹的基本操作都是相似的,包括建樹、查詢和更新。選擇適合具體應用場景的存儲方式可以更好地利用線段樹的優勢,提高算法效率。

? ? ? 首先是數組存儲,我們最先要知道的是數組的大小需要開多大,在線段樹的數組存儲中,通常會將數組的大小設置為 4n,其中 n 表示線段樹的葉子節點數量。

理由:

  1. 完全二叉樹性質:線段樹一般是一棵完全二叉樹,具有規律性的結構。在數組存儲方式下,為了方便表示完全二叉樹,需要保證數組的大小是某一層節點數量的上限。對于一棵深度為 k 的完全二叉樹,葉子節點數量最多為 2^k,因此數組大小一般設置為 4 * 2^k,以確保足夠的空間。

  2. 節點的父子關系:在數組存儲方式中,節點 i 的左子節點一般存儲在位置 2i,右子節點存儲在位置 2i+1。設置數組大小為 4n 可以保證對于任意節點 i,其子節點在數組中的位置都是有效的,不會越界。

  3. 方便計算左右子樹位置:在線段樹的查詢和更新操作中,經常需要根據節點的索引快速定位其左右子樹節點。通過設置數組大小為 4n,可以方便地根據節點索引計算出其左右子節點的位置,簡化操作。

然后開一個build函數建樹,具體操作如下:

  1. 定義數組:首先,需要定義一個大小為 4n 的數組,其中 n 是線段樹的葉子節點數量。這個數組將用于存儲線段樹的節點信息。

  2. 構建線段樹:一般將線段樹按照完全二叉樹的形式存儲在數組中。假設根節點在數組中的索引是 1,那么對于節點 i,其左子節點為 2i,右子節點為 2i + 1。

  3. 存儲節點信息:每個節點需要保存代表的區間范圍和相應的信息,比如區間的最大值、最小值、和等等。在數組中,可以按照某種順序依次存儲這些信息,以便后續的查詢和更新操作。

  4. 建立線段樹:通過遞歸或迭代的方式構建線段樹。一般會從葉子節點開始向上構建,通過合并子節點的信息得到父節點的信息,直至構建完整的線段樹。

  5. 查詢和更新:通過線段樹的結構和數組存儲,可以實現高效的區間查詢和更新操作。比如,對于查詢一個區間的最大值,可以通過遞歸向下查詢到包含目標區間的節點,并根據存儲的信息計算出結果。

  6. 記得注意邊界情況:在實現線段樹時,需要考慮樹的邊界情況,比如樹的根節點索引是 1,葉子節點索引從 n+1 開始等,以確保正確地訪問和操作節點。

build函數建樹

void build(LL l, LL r, LL fa) {if (l == r) // //如果左右區間相同,那么必然是葉子節,只有葉子節點是被真實賦值的{t[fa] = a[l];return;}LL mid = (l + r) >> 1;build( l, mid, fa << 1);build(mid + 1, r, fa << 1 | 1);
//使用二分來優化psuh_up(fa);//此處由于我們是要通過子節點來維護父節點,所以push_up的位置應當是在回溯時將子節點的值取和交給父節點
}

二、線段樹的區間修改維護

? ? ? 線段樹是一種用于解決區間查詢和修改問題的數據結構。在線段樹中,區間修改維護指的是在給定一個區間,并修改該區間內所有元素的操作。

  1. 區間修改維護

    • 當需要修改線段樹中某個特定區間的值時,可以通過遞歸的方式向下更新區間。
    • 如果要修改的區間與當前節點表示的區間沒有交集,則無需修改該節點。
    • 如果要修改的區間完全包含當前節點的區間,則直接更新當前節點的信息,并將修改操作下傳給子節點。
    • 如果要修改的區間與當前節點的區間部分相交,則需要先將當前節點的信息更新,然后將修改操作同時下傳給左右子節點。
  2. 區間修改的操作

    • 區間修改的操作通常包括加法、減法、賦值等。
    • 當需要對區間內的每個元素進行相同的修改時,可以利用線段樹的特性進行高效操作。
    • 在修改區間時,需要根據當前節點的區間范圍、待修改區間和修改方式來確定如何操作當前節點和其子節點。
  3. 遞歸更新過程

    • 從線段樹的根節點開始遞歸向下更新,直到找到包含待修改區間的葉子節點。
    • 在遞歸過程中根據節點的區間范圍和待修改區間的關系,決定如何更新節點的信息并向下傳遞修改操作。

? ? ? ?此外,對于區間操作,我們考慮引入一個名叫“?lazy?tag?”(懶標記)的東西——之所以稱其“lazy”,是因為原本區間修改需要通過先改變葉子節點的值,然后不斷地向上遞歸修改祖先節點直至到達根節點,時間復雜度最高可以到達?O(nlogn)?的級別。但當我們引入了懶標記之后,區間更新的期望復雜度就降到了?O(logn)?的級別且甚至會更低。

因此,我們再弄一個tag數組,大小也是4*N

區間修改update:

void psuh_up(LL fa) {t[fa] = t[fa << 1] + t[fa << 1 | 1];//向上不斷維護父節點
}
void push_down(LL l,LL r,LL fa) {LL mid = (l + r) >> 1;t[fa << 1] += tag[fa] * (mid - l + 1);tag[fa << 1] += tag[fa];t[fa << 1|1] += tag[fa] * (r-mid);tag[fa << 1|1] += tag[fa];tag[fa] = 0;// //每次將懶惰標識下放到兩個兒子節點,自身置為0,以此不斷向下傳遞 
}
void update(LL ql, LL qr, LL l, LL r, LL k, LL fa) {if (ql <= l && qr >= r) //如果區間被包含,直接返回該節點的懶惰標識{t[fa] +=k * (r - l + 1);tag[fa] += k;return;}LL mid = (l + r) >> 1;push_down(l, r, fa);//下放懶惰標識if (ql <= mid)update(ql, qr, l, mid,k, fa << 1);//朝左邊下放if (qr > mid)update(ql, qr, mid + 1, r,k, fa << 1 | 1);//右邊psuh_up(fa);//再將修改后的值向上返回,維護父節點
}

三、線段樹的區間查詢

  1. 區間查詢

    • 當需要查詢線段樹中某個特定區間的信息時,可以通過遞歸的方式向下查詢區間。
    • 如果要查詢的區間與當前節點表示的區間沒有交集,則無需查詢該節點,直接返回默認值(如0或無窮大)。
    • 如果要查詢的區間完全包含當前節點的區間,則直接返回該節點存儲的信息。
    • 如果要查詢的區間與當前節點的區間部分相交,則需要同時查詢左右子節點,并根據查詢結果合并得到最終結果。
  2. 區間查詢的操作

    • 區間查詢的操作通常包括求和、求最大值、求最小值等。
    • 在查詢區間時,需要根據當前節點的區間范圍、待查詢區間和查詢方式來確定如何操作當前節點和其子節點。
  3. 遞歸查詢過程

    • 從線段樹的根節點開始遞歸向下查詢,直到找到包含待查詢區間的葉子節點。
    • 在遞歸過程中根據節點的區間范圍和待查詢區間的關系,決定如何查詢節點的信息并向下傳遞查詢操作。
    • 最終將所有查詢結果合并得到最終的區間查詢結果。

? ? ? 通過以上方法,可以實現對線段樹中特定區間的查詢操作。線段樹區間查詢是線段樹的一個重要功能,能夠快速有效地獲取區間內的信息,提高了區間查詢的效率。

區間查詢query:

LL query(LL ql, LL qr, LL l, LL r, LL fa) {LL ret = 0;if (ql <= l && qr >= r) 如果區間被包含,直接返回該節點的懶惰標識{return t[fa];}LL mid = (l + r) >> 1;push_down(l, r, fa);//沒有被包含,下放任務if (ql <= mid)ret += query(ql, qr, l, mid, fa << 1);if (qr > mid)ret += query(ql, qr, mid + 1, r, fa << 1|1);//在查詢范圍的左區間和右區間的值相加并返回return ret;
}
例題:

模板題目:P3372 【模板】線段樹 1 - 洛谷

完整代碼:
數組實現:
#include<iostream>
using namespace std;
const int N = 1e5 + 10;
typedef long long LL;
LL n, m, t[N * 4], tag[N * 4], a[N];
void psuh_up(LL fa) {t[fa] = t[fa << 1] + t[fa << 1 | 1];//向上不斷維護父節點
}
void push_down(LL l,LL r,LL fa) {LL mid = (l + r) >> 1;t[fa << 1] += tag[fa] * (mid - l + 1);tag[fa << 1] += tag[fa];t[fa << 1|1] += tag[fa] * (r-mid);tag[fa << 1|1] += tag[fa];tag[fa] = 0;// //每次將懶惰標識下放到兩個兒子節點,自身置為0,以此不斷向下傳遞 
}
LL query(LL ql, LL qr, LL l, LL r, LL fa) {LL ret = 0;if (ql <= l && qr >= r) 如果區間被包含,直接返回該節點的懶惰標識{return t[fa];}LL mid = (l + r) >> 1;push_down(l, r, fa);//沒有被包含,下放任務if (ql <= mid)ret += query(ql, qr, l, mid, fa << 1);if (qr > mid)ret += query(ql, qr, mid + 1, r, fa << 1|1);//在查詢范圍的左區間和右區間的值相加并返回return ret;
}
void update(LL ql, LL qr, LL l, LL r, LL k, LL fa) {if (ql <= l && qr >= r) //如果區間被包含,更新懶惰標識并返回{t[fa] +=k * (r - l + 1);tag[fa] += k;return;}LL mid = (l + r) >> 1;push_down(l, r, fa);//下放懶惰標識if (ql <= mid)update(ql, qr, l, mid,k, fa << 1);//朝左邊下放if (qr > mid)update(ql, qr, mid + 1, r,k, fa << 1 | 1);//右邊psuh_up(fa);//再將修改后的值向上返回,維護父節點
}
void build(LL l, LL r, LL fa) {if (l == r) // //如果左右區間相同,那么必然是葉子節,只有葉子節點是被真實賦值的{t[fa] = a[l];return;}LL mid = (l + r) >> 1;build(l, mid, fa << 1);build(mid + 1, r, fa << 1 | 1);//使用二分來優化psuh_up(fa);//此處由于我們是要通過子節點來維護父節點,所以push_up的位置應當是在回溯時將子節點的值取和交給父節點
}
int main() {cin >> n >> m;for (int i = 1; i <= n; i++)cin >> a[i];build(1, n, 1);while (m--) {int op; cin >> op;if (op == 1) {LL x, y, k; cin >> x >> y >> k;update(x, y, 1, n, k, 1);}else if(op==2){LL x, y;cin >> x >> y;cout << query(x, y, 1, n, 1) << endl;}}return 0;
}
指針實現:
#include <iostream>
#include <vector>using namespace std;struct Node {int start, end;int sum;Node *left, *right;Node(int start, int end) : start(start), end(end), sum(0), left(nullptr), right(nullptr) {}
};Node* buildSegmentTree(vector<int>& nums, int start, int end) {if (start > end) {return nullptr;}Node* root = new Node(start, end);if (start == end) {root->sum = nums[start];} else {int mid = start + (end - start) / 2;root->left = buildSegmentTree(nums, start, mid);root->right = buildSegmentTree(nums, mid + 1, end);root->sum = root->left->sum + root->right->sum;}return root;
}int query(Node* root, int qs, int qe) {if (root == nullptr || qs > root->end || qe < root->start) {return 0;} else if (qs <= root->start && qe >= root->end) {return root->sum;} else {return query(root->left, qs, qe) + query(root->right, qs, qe);}
}int main() {vector<int> nums = {1, 3, 5, 7, 9, 11};Node* root = buildSegmentTree(nums, 0, nums.size() - 1);cout << "Sum of elements in range [2, 4]: " << query(root, 2, 4) << endl;return 0;
}


總結

本文關于線段樹的講解就到這里,有什么疑問或者有什么錯誤的地方歡迎一起交流學習

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/pingmian/77373.shtml
繁體地址,請注明出處:http://hk.pswp.cn/pingmian/77373.shtml
英文地址,請注明出處:http://en.pswp.cn/pingmian/77373.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Jenkins 2.492.2 LTS 重置管理員密碼

文章目錄 1. Jenkins 關閉用戶認證2. jenkins 修改密碼 如果忘記了 Jenkins 的管理員密碼的話&#xff0c;也不用擔心&#xff0c;只要你有權限訪問 Jenkins 的根目錄&#xff0c;就可以輕松地重置密碼。 1. Jenkins 關閉用戶認證 // 查看 jenkins 家目錄&#xff08;使用 doc…

《AI大模型應知應會100篇》第26篇:Chain-of-Thought:引導大模型進行步驟推理

第26篇&#xff1a;Chain-of-Thought&#xff1a;引導大模型進行步驟推理 摘要 在自然語言處理&#xff08;NLP&#xff09;和人工智能領域&#xff0c;如何讓大模型像人類一樣進行逐步推理是一個核心挑戰。Chain-of-Thought (思維鏈) 技術的出現為這一問題提供了強有力的解決…

SICAR 標準 安全門鎖操作箱 按鈕和指示燈說明

1、安全門鎖操作箱 2、按鈕和指示燈說明 一、指示燈說明 紅燈&#xff1a; 常亮&#xff1a;表示安全門已解鎖&#xff1b;閃爍&#xff1a;表示安全門未復位&#xff1b;熄滅&#xff1a;表示安全門已復位。 黃燈&#xff1a; 常亮&#xff1a;表示處于維修模式。 綠燈&…

MAC-??需求??:10萬訂單異步執行庫存扣減、短信通知。

批量任務并行處理?? 實現,通過拆分任務、異步執行和線程池管理提升處理。 ??10萬訂單異步處理方案設計?? 基于圖中代碼的批量處理框架,結合訂單業務需求,以下是 ??庫存扣減與短信通知的異步實現??: ??1. 代碼實現(基于原有框架改造)?? @Service public…

python 庫 下載 ,整合在一個小程序 UIUIUI

上圖 import os import time import threading import requests import subprocess import importlib import tkinter as tk from tkinter import ttk, messagebox, scrolledtext from concurrent.futures import ThreadPoolExecutor, as_completed from urllib.parse import…

Flutter與FastAPI的OSS系統實現

作者&#xff1a;孫嘉成 目錄 一、對象存儲 二、FastAPI與對象存儲 2.1 繽紛云S4服務API對接與鑒權實現 2.2 RESTful接口設計與異步路由優化 三、Flutter界面與數據交互開發 3.1 應用的創建 3.2頁面的搭建 3.3 文件的上傳 關鍵詞&#xff1a;對象存儲、FastAPI、Flutte…

洛谷P3373線段樹詳解【模板】

洛谷P3373題目概述 洛谷P3373是一道關于線段樹的模板題&#xff0c;題目名稱為“【模板】線段樹 2”。題目的主要要求是對一個長度為 n 的數列進行如下操作&#xff1a; 將某區間每個數乘上一個數。將某區間每個數加上一個數。求出某區間所有數的和。 線段樹簡介 線段樹是一…

【計算機視覺】CV實戰項目- COVID 社交距離檢測(covid-social-distancing-detection)

COVID 社交距離檢測&#xff08;covid-social-distancing-detection&#xff09; 一、項目概述二、項目架構三、環境搭建四、運行項目五、輸出結果六、常見問題及解決方法報錯1. cv2.error: OpenCV(4.11.0) :-1: error: (-5:Bad argument) in function circle報錯2 cv2.circle(…

CMake使用教程

一、CMake 簡介 CMake 是一個跨平臺的構建工具&#xff0c;用于自動化生成不同平臺&#xff08;如 Makefile、Visual Studio、Xcode 等&#xff09;的構建文件。它的核心是編寫 CMakeLists.txt 文件&#xff0c;定義項目的構建規則。 二、安裝 CMake Linux: sudo apt-get ins…

大模型Rag - 兩大檢索技術

一、稀疏檢索&#xff1a;關鍵詞匹配的經典代表 稀疏檢索是一種基于關鍵詞統計的傳統檢索方法。其基本思想是&#xff1a;通過詞頻和文檔頻率來衡量一個文檔與查詢的相關性。 核心原理 文檔和查詢都被表示為稀疏向量&#xff08;如詞袋模型&#xff09;&#xff0c;只有在詞…

LNA設計

設計目的 為后級提供足夠的增益以克服后級電路噪聲 盡可能小的噪聲和信號失真 確保輸入和輸出端的阻抗匹配 確保信號線性度 評價標準 噪聲系數 功率增益 工作頻率和帶寬 輸入信號功率動態范圍 端口電壓駐波比 穩定性 基于SP模型的LNA設計 直流分析 S參數分析 設計指標 &#xf…

Vue 常見組件及使用方式全解析

一、引言 在 Vue 開發中&#xff0c;組件是構建復雜用戶界面的基石。通過使用各種常見組件&#xff0c;我們可以快速搭建出功能豐富、交互性強的應用程序。本文將詳細介紹 Vue 開發中一些常見組件及其使用方式。 二、基礎 UI 組件 &#xff08;一&#xff09;按鈕組件&#…

設計測試用例模板

面試時問你一個場景&#xff0c;要你設計測試用例&#xff0c;你會怎么回答&#xff1f; 面試官讓你設計一個功能的測試用例&#xff0c;比如“上傳文件功能”&#xff0c;其實就是想考你&#xff1a; 思維是否全面能不能抓住重點會不會分類和使用測試方法有沒有考慮異常情況…

Git 解決“Filename too long”問題

在 Windows 系統中使用 Git 時&#xff0c;遇到 Filename too long 錯誤通常是由于系統默認的路徑長度限制&#xff08;260 字符&#xff09;導致的。以下是綜合多種場景的解決方案&#xff1a; 一、快速解決方法 啟用 Git 長路徑支持 通過 Git 配置命令允許處理超長文件名&am…

Spring Boot 3 + SpringDoc:打造接口文檔

1、背景公司 新項目使用SpringBoot3.0以上構建&#xff0c;其中需要對外輸出接口文檔。接口文檔一方面給到前端調試&#xff0c;另一方面給到測試使用。 2、SpringDoc 是什么&#xff1f; SpringDoc 是一個基于 Spring Boot 項目的庫&#xff0c;能夠自動根據項目中的配置、…

Swagger2Refit

把swagger相關接口轉成refit格式&#xff0c;以便其他服務調用 使用工具Refitter. Refitter 項目使用教程 Refit Client API Generator for OpenAPI 項目地址: github.com GitCode - 全球開發者的開源社區,開源代碼托管平臺 安裝 Refitter CLI 工具 首先&#xff0c;通過…

【java 13天進階Day05】數據結構,List,Set ,TreeSet集合,Collections工具類

常見的數據結構種類 集合是基于數據結構做出來的&#xff0c;不同的集合底層會采用不同的數據結構。不同的數據結構&#xff0c;功能和作用是不一樣的。數據結構&#xff1a; 數據結構指的是數據以什么方式組織在一起。不同的數據結構&#xff0c;增刪查的性能是不一樣的。不同…

systemctl管理指令

今天我們來繼續學習服務管理指令,接下來才是重頭戲-systemctl,那么話不多說,直接開始吧. systemctl管理指令 1.基本語法: systemctl [start | stop | restart | status]服務 注&#xff1a;systemctl指令管理的服務在/usr/lib/ systemd/system查看 2.systemctl設置服務的自…

STM32單片機教程:從零開始打造智能天氣時鐘

STM32單片機教程&#xff1a;從零開始打造智能天氣時鐘 大家好&#xff01;今天我想為大家詳細介紹一下我們的STM32課程&#xff0c;以及如何從零基礎逐步掌握單片機開發技能&#xff0c;最終實現一個完整的智能天氣時鐘項目。 課程面向人群 本課程主要面向那些已經通過野火…

Neovim插件深度解析:mcphub.nvim如何用MCP協議重構開發體驗

在AI與工具鏈深度融合的今天,Neovim 作為現代開發者的生產力工具,正通過插件生態不斷突破邊界。mcphub.nvim 作為一款基于 MCP(Model Context Protocol) 協議的插件,重新定義了Neovim與智能工具的交互方式。它不僅簡化了MCP服務器的集成與管理,更通過直觀的UI和生態整合,…