T:線段樹入門(無區間更新)

線段樹

  • .線段樹介紹
  • .線段樹框架
  • .理解線段樹
  • .圖式整個過程
  • .線段樹代碼逐層解析
  • .代碼匯總
  • .leetcode練習

.線段樹介紹

線段樹(SegmentTree)\;\;\;\;\;\;\;\;線段樹(SegmentTree)線段樹(SegmentTree) is 用于高效處理區間查詢單點修改的數據結構,和樹狀數組很像,不知道什么是樹狀數組?看這個樹狀數組入門。
( 需要說明,區間查詢可以是不同的操作。假設區間[L,R][L,R][L,R]。那么查詢操作包括但不限于:區間最大值,最小值,區間和等等。后續做題如果遇到更多的操作,將會在此處補充。)


.線段樹框架

\;\;\;\;\;\;\;\;大致分為四個內容

建樹
更新
查找
修改

.理解線段樹

\;\;\;\;\;\;\;\;線段樹的本質是分而治之,并且對于維護每個區間。
\;\;\;\;\;\;\;\;老規矩,先拋出一個問題,然后嘗試解決這個問題。最后優化的時候就會解釋線段樹的用處是什么。

QuesTion

給你兩個長度為 n 的整數數組,fruits 和 baskets,其中 fruits[i] 表示第 i 種水果的 數量,baskets[j]表示第 j 個籃子的容量。

你需要對 fruits 數組從左到右按照以下規則放置水果:

  1. 每種水果必須放入第一個容量大于等于該水果數量的最左側可用籃子中。
  2. 每個籃子只能裝一種水果。如果一種水果無法放入任何籃子,它將保持未放置。返回所有可能分配完成后,剩余未放置的水果種類的數量。

(這個問題就是leettcode的.3479)
暴力:
\;\;\;\;\;\;\;\;如果按照暴力的做法。依此遍歷fruitsfruitsfruits數組,對每一個fruits[i]fruits[i]fruits[i],從頭到尾遍歷basketsbasketsbaskets數組,找到第一個大于等于fruits[i]fruits[i]fruits[i]的值。每一次查找的復雜度是O(n)O(n)O(n),所以總體復雜度是O(n2)O(n^2)O(n2),因此總體時間復雜度是O(n?log2n)O(n*log_2^n)O(n?log2n?)

線段樹優化:
\;\;\;\;\;\;\;\;線段樹通過 “分治 + 區間預存” 實現高效操作。本質上是空間換時間。通過開辟新的數組構建二叉樹來存儲原數組的區間最值。二叉樹每個節點都表示其左右孩子節點的最值。這樣只需要一直往樹的孩子節點搜索,就可以找到第一個大于等于fruits[i]fruits[i]fruits[i]的節點值,因為二叉樹的結構,查找一個節點的時間復雜度是O(log2n)O(log_2^n)O(log2n?)


.圖式整個過程

用上面Question的示例:
下面來用簡單的示例和圖來解釋:將basket數組作為葉子節點構建線段樹。每個節點存儲其子樹的節點最大值。(可以根據題目需求修改)

假設F (fruits)數組{3,1,5,2,6,4}{\{3 ,1 ,5,2 ,6,4}\}{315264}
并且B(basket)數組{3,5,2,4,6,2,7}{\{ 3, 5, 2,4,6,2,7\}}{3,5,2,4,6,2,7}

1.建樹,葉子節點就是B數組(整個二叉樹需要建立多少個節點,下面會說的)
在這里插入圖片描述

2.初始化線段樹,維護節點信息(當前節點表示當前區間的最大值)
在這里插入圖片描述
3.查詢(看出線段樹高效率的核心步驟)
對于F 數組{3,1,5,2,6,4}{\{3 ,1 ,5,2 ,6,4}\}{315264}

在這里插入圖片描述
依此類推,顯然,每次查詢,遇到大于等于x的就往下走,否則往上走,這樣查詢一次做多尋找Logn次。這個例子只是明了知道線段樹的分而治之這個特點,實際上具體例子具體分析,但是都離不開分而治之這四個字。

.線段樹代碼逐層解析

線段樹私有成員變量

class SegmentTree
{
private:int n;   //線段樹一共n個節點空間,但不一定用到n個vector<int>tree;
}

更新操作節點最大值

int mergeLR(int& a,int& b)
{	return max(a,b);  //根據題目改
}
void maintain(int idx) //更新當前節點的最大值(也就是左右孩子節點的最大值)
{tree[idx]=mergeLR(tree[idx*2],tree[idx*2+1]);
}

建樹

//idx表示當前節點,[l,r]表示當前建樹的區間,當然,idx在[l,r]內
void built(vector<int>&a,int idx,int l,int r) 
{if(l==r)  //當是葉子節點的時候,說明當前位置就是放置元素的位置。{	tree[idx]=a[l];return;}int m=(l+r)>>1;bilut(a,idx*2,l,m);   // 遞歸初始化左子樹built(a,idx*2+1,m+1,r);   //遞歸初始化右子樹maintain(idx);  //更新當前的節點值,發現,節點值的更新是后續遍歷
}

修改值(也叫更新,但是這個更新是查找+更新)

//線段樹下標i的值修改為val
void update(int idx,int l,int r,int i,int val)
{if(l==r}  //到葉子節點說明找到了{tree[idx]=mergeLR(tree[idx],val);return;}int m=(l+r)/2;if(i<=m)   //在左子樹{update(idx*2,l,m,i,val);}else   //在右子樹{update(idx*2+1,m+1,r,i,val);}maintain(idx);  //更新完左右子樹,更新當前節點
}

查詢區間[ql,qr]最大值

int query(int idx,int l,int r,int ql,int qr)const
{if(ql<=l&&r<=qr)// 當前子樹完全在 [ql, qr] 內return tree[idx];int m=(l+r)>>1;if(qr<=m)   //[ql,qr]完全在左子樹return query(idx*2,l,m,ql,qr);if(qr>m)   //[ql,qr]完全在右子樹return query(idx*2+1,m+1,r,ql,qr);//否則[ql,qr]在左右子樹各有部分int l_res=query(idx*2,l,m,ql,m);int r_res=query(idx*2+1,m+1,r,m+1,qr);return mergeLR(l_res,r_res);  //合并左右子樹
}

構造函數初始化

// 線段樹維護一個長為 n 的數組(下標從 0 到 n-1),元素初始值為 init_val
SegmentTree((int n,int init_val):SegmentTree(vector<int>(n,init_val)){}// 線段樹維護數組 a
SegmentTree(const vector<int>& a) : n(a.size()), tree(2 << bit_width(a.size() - 1)) {build(a, 1, 0, n - 1);
}

線段樹為什么要開辟2 << bit_width(a.size() - 1)個元素空間?


.代碼匯總

和上面略有不同,這是靈神的代碼,邏輯是一樣的。代碼來源

// 線段樹有兩個下標,一個是線段樹節點的下標,另一個是線段樹維護的區間的下標
// 節點的下標:從 1 開始,如果你想改成從 0 開始,需要把左右兒子下標分別改成 node*2+1 和 node*2+2
// 區間的下標:從 0 開始
template<typename T>
class SegmentTree 
{// 注:也可以去掉 template<typename T>,改在這里定義 T// using T = pair<int, int>;int n;vector<T> tree;// 合并兩個 valT merge_val(T a, T b) const {return max(a, b); // **根據題目修改**}// 合并左右兒子的 val 到當前節點的 valvoid maintain(int node) {tree[node] = merge_val(tree[node * 2], tree[node * 2 + 1]);}// 用 a 初始化線段樹// 時間復雜度 O(n)void build(const vector<T>& a, int node, int l, int r) {if (l == r) { // 葉子tree[node] = a[l]; // 初始化葉節點的值return;}int m = (l + r) / 2;build(a, node * 2, l, m); // 初始化左子樹build(a, node * 2 + 1, m + 1, r); // 初始化右子樹maintain(node);}void update(int node, int l, int r, int i, T val) {if (l == r) { // 葉子(到達目標)// 如果想直接替換的話,可以寫 tree[node] = valtree[node] = merge_val(tree[node], val);return;}int m = (l + r) / 2;if (i <= m) { // i 在左子樹update(node * 2, l, m, i, val);} else { // i 在右子樹update(node * 2 + 1, m + 1, r, i, val);}maintain(node);}T query(int node, int l, int r, int ql, int qr) const {if (ql <= l && r <= qr) { // 當前子樹完全在 [ql, qr] 內return tree[node];}int m = (l + r) / 2;if (qr <= m) { // [ql, qr] 在左子樹return query(node * 2, l, m, ql, qr);}if (ql > m) { // [ql, qr] 在右子樹return query(node * 2 + 1, m + 1, r, ql, qr);}T l_res = query(node * 2, l, m, ql, qr);T r_res = query(node * 2 + 1, m + 1, r, ql, qr);return merge_val(l_res, r_res);}
public:// 線段樹維護一個長為 n 的數組(下標從 0 到 n-1),元素初始值為 init_valSegmentTree(int n, T init_val) : SegmentTree(vector<T>(n, init_val)) {}// 線段樹維護數組 aSegmentTree(const vector<T>& a) : n(a.size()), tree(2 << bit_width(a.size() - 1)) {build(a, 1, 0, n - 1);}// 更新 a[i] 為 merge_val(a[i], val)// 時間復雜度 O(log n)void update(int i, T val) {update(1, 0, n - 1, i, val);}// 返回用 merge_val 合并所有 a[i] 的計算結果,其中 i 在閉區間 [ql, qr] 中// 時間復雜度 O(log n)T query(int ql, int qr) const {return query(1, 0, n - 1, ql, qr);}// 獲取 a[i] 的值// 時間復雜度 O(log n)T get(int i) const {return query(1, 0, n - 1, i, i);}
};int main() 
{SegmentTree t(8, 0LL); // 如果這里寫 0LL,那么 SegmentTree 存儲的就是 long long 數據t.update(0, 1LL << 60);cout << t.query(0, 7) << endl;vector<int> nums = {3, 1, 4, 1, 5, 9, 2, 6};// 注意:如果要讓 SegmentTree 存儲 long long 數據,需要傳入 vector<long long>SegmentTree t2(nums); // 這里 SegmentTree 存儲的是 int 數據cout << t2.query(0, 7) << endl;return 0;
}

.leetcode練習

2286.Booking Concert Tickets in Groups
3479.Fruits into Baskets III
2940.Find Building Where Alice and Bob Can Meet
2231.由單個字符重復的最長子字符串
題單完整見
Click here: More

對于2231.比較更具體地了解線段樹地本質,至少我是這樣認為的。

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

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

相關文章

【ISP】Charlite工具實操

實習一周了&#xff0c;參與了客觀拍攝和測試&#xff0c;復習一下nv工具 BLACK LEVEL&#xff08;黑電平&#xff09; eg&#xff1a; $ nv_ob 0 in_dir <input directory> out_name <ob file> nv_ob 0 in_dir D:\study\nvraw\ob1 out_name D:\study\nvraw\my_out…

普藍機器人 AutoTrack-IR-DR200 外設配置全指南

為什么外設配置對機器人研究如此重要&#xff1f;在當今機器人技術飛速發展的時代&#xff0c;高校學生研究團隊正成為創新的重要力量。無論是參加機器人競賽、開展畢業設計&#xff0c;還是進行學術研究&#xff0c;正確配置和使用外設設備都是成功的關鍵。尤其學生組裝一個服…

8、Python性能優化與代碼工程化

學習目標&#xff1a;掌握Python程序性能分析和優化的通用方法&#xff0c;建立工程化開發的規范意識&#xff0c;為后續AI項目開發奠定堅實的編程基礎在數據科學和AI開發中&#xff0c;代碼性能往往決定了項目的可行性。一個處理時間從幾小時縮短到幾分鐘的優化&#xff0c;可…

【算法--鏈表】117.填充每個節點的下一個右側節點指針Ⅱ--通俗講解

通俗算法講解推薦閱讀: 【算法–鏈表】83.刪除排序鏈表中的重復元素–通俗講解 【算法–鏈表】刪除排序鏈表中的重復元素 II–通俗講解 【算法–鏈表】86.分割鏈表–通俗講解 【算法】92.翻轉鏈表Ⅱ–通俗講解 【算法–鏈表】109.有序鏈表轉換二叉搜索樹–通俗講解 【算法–鏈…

分詞器(Tokenizer)總結(89)

分詞器(Tokenizer)總結 分詞器(Tokenizer) 分詞器的詞表(vocabulary)長度通常短于模型嵌入層(embedding layer)的長度。 結束標記(EOS token)應僅用于標記文本結尾,不可用于其他用途。 填充標記(PAD token)通常未預先定義,但你仍可能需要用到它: 對于生成式模型…

19 webUI應用中 Controlnet精講(05)-圖像修復與編輯

前面的篇章已經詳細講解了線條約束、三維關系與空間深度、人體姿態等幾類controlnet的功能與應用&#xff0c;本節內容將對通過controlnet對圖像修復與編輯進行講解。 通過controlnet也可以對圖片進行編輯、重繪及放大等操作&#xff0c;具體包括Recolor、Inpaint、Tile等&…

消息推送的三種常見方式:輪詢、SSE、WebSocket

摘要&#xff1a;本文介紹消息推送的三種常見方式&#xff1a;輪詢&#xff08;定時請求&#xff0c;易增負擔&#xff09;與長輪詢&#xff08;阻塞請求至有數據 / 超時&#xff0c;減少請求&#xff09;、SSE&#xff08;HTTP 單向實時傳輸&#xff0c;純文本、自動重連&…

論文閱讀:ACL 2024 Stealthy Attack on Large Language Model based Recommendation

總目錄 大模型相關研究&#xff1a;https://blog.csdn.net/WhiffeYF/article/details/142132328 https://arxiv.org/pdf/2402.14836 https://www.doubao.com/chat/19815566713551106 文章目錄速覽攻擊方法速覽一、攻擊核心目標與前提1. 核心目標2. 攻擊前提二、模型無關的簡單…

自動駕駛中的傳感器技術43——Radar(4)

本文對目前毫米波雷達中的天線設計進行比較全面的羅列&#xff0c;并進行簡單的設計評述 1、實際設計案例 圖1 涵蓋能寬窄覆蓋的天線設計&#xff08;無俯仰分辨率&#xff09;圖2 Bosch前雷達的天線設計&#xff08;有俯仰的分辨率但比較弱&#xff0c;也涵蓋了擴展覆蓋&…

使用反轉法線材質球,實現切換天空盒相同的功能,優點:包體變小

切換天空盒第一步先把SKY 天空球資源導入到工程里&#xff0c; 第二步&#xff1a;天空球文件下的SKY預制件拖入到場景里 第三步 選著SKY材質球&#xff0c;拖入自己的全景圖片(圖片分辨率不能超過5000*5000&#xff0c;否則手機無法顯示) 如果并沒有效果&#xff0c;看看圖…

真正有效的數據指標體系應該長什么樣?

真正有效的數據指標體系應該長什么樣&#xff1f;為什么大多數企業的指標體系都是"花架子"&#xff1f;真正有效的指標體系應該長什么樣&#xff1f;從數據到洞察&#xff1a;讓指標真正"活"起來結語在這個人人都在談數字化轉型的時代&#xff0c;企業就像…

分布式專題——6 Redis緩存設計與性能優化

1 多級緩存架構2 緩存設計 2.1 緩存穿透 2.1.1 簡介緩存穿透是什么&#xff1f;當查詢一個根本不存在的數據時&#xff0c;緩存層和存儲層都不會命中。正常邏輯下&#xff0c;存儲層查不到數據就不會寫入緩存層。這會導致&#xff1a;每次請求這個不存在的數據&#xff0c;都要…

一文了解大模型壓縮與部署

一文了解大模型壓縮與部署&#xff1a;從 INT4 量化到 MoE&#xff0c;讓大模型跑在手機、邊緣設備和云端&#x1f3af; 為什么需要模型壓縮與部署&#xff1f;你訓練了一個強大的大模型&#xff08;如 Qwen-72B、LLaMA-3-70B&#xff09;&#xff0c;但在部署時發現&#xff1…

新手向:中文語言識別的進化之路

自然語言處理&#xff08;NLP&#xff09;技術正在以前所未有的速度改變我們與機器的交互方式。根據Gartner最新報告顯示&#xff0c;全球NLP市場規模預計在2025年將達到430億美元&#xff0c;年復合增長率高達21%。而中文作為世界上使用人數最多的語言&#xff08;全球約15億使…

LeetCode100-206反轉鏈表

本文基于各個大佬的文章上點關注下點贊&#xff0c;明天一定更燦爛&#xff01;前言Python基礎好像會了又好像沒會&#xff0c;所有我直接開始刷leetcode一邊抄樣例代碼一邊學習吧。本系列文章用來記錄學習中的思考&#xff0c;寫給自己看的&#xff0c;也歡迎大家在評論區指導…

uniapp開源多商戶小程序商城平臺源碼 支持二次開發+永久免費升級

在電商行業競爭日益激烈的今天&#xff0c;擁有一個功能強大、靈活可拓展的多商戶小程序商城至關重要。今天給大家分享一款 uniapp 開源多商戶小程序商城平臺源碼&#xff0c;它不僅具備豐富的基礎功能&#xff0c;還支持二次開發&#xff0c;更能享受永久免費升級服務&#xf…

使用腳本一鍵更新NTP服務器地址為自定義地址

【使用場景】 在銀河麒麟桌面操作系統V10SP1-2303版本中使用腳本一鍵修改NTP服務器地址為自定義地址。 【操作步驟】 步驟1. 編寫shell腳本 ```bash desktop2303@desktop2303-pc:~$ vim setntptimeserver.sh #!/bin/bashfunction modifykylinconf() { # 檢查是否已存在目標配置…

linux內核 - 內核架構概覽

當 Linux 系統啟動時,內核會在啟動過程的早期階段接管控制——緊跟在固件(BIOS 或 UEFI)和引導加載程序完成任務之后。此時,壓縮的 Linux 內核鏡像會被加載到內存中,通常會附帶一個稱為 initramfs 的最小臨時根文件系統,它用于在切換到真實根文件系統并繼續系統初始化之前…

[react] react-router-dom是啥?

頁面路由&#xff0c;注意頁面路由不是路由器&#xff0c;因為我之前總是把路由和路由器搞混。而且我總是把前端頁面的路由和路由器的路由搞混。那么這里一定要明白&#xff0c;這里我所說的頁面路由就是指在瀏覽器里面的導航路由。 npm create vitelatest my-react-app – --t…

HTTP簡易客戶端實現

&#x1f310; HTTP簡易客戶端實現 流程圖&#xff1a; 引用&#xff1a; chnroutes2.cpp#L474 chnroutes2_getiplist() chnroutes2.cpp#L443 http_easy_get(…) &#x1f552; 1. 超時管理機制 (http_easy_timeout) &#x1f539; 核心功能&#xff1a;創建定時器自動關…