算法基礎之分治法

算法原理

對于一個規模為 n n n 的子問題,若該問題可以容易地解決則直接解決,否則將其分解為 k k k 個規模較小的子問題,這些子問題相互獨立且與原問題形式相同。遞歸地解決這些子問題,然后將各子問題的解合并得到原問題的解,這種算法設計策略叫分治法。

分治法所能解決的問題一般具有以下特征:

  • 該問題的規模縮小到一定的程度就可以容易地解決。
  • 該問題可以分解為若干個規模較小的相似問題。
  • 利用該問題分解出的子問題的解可以合并為該問題的解。
  • 該問題所分解出的各子問題是相互獨立的,即子問題之間不包含公共的子問題。

分治法的一般算法設計如下:

SolutionType Solve(ProblemType P) {if(Small(P)) return Result(P);else {Divector<int>de(P,P1,P2,…,Pk);return Merge(Solve(P1), Solve(P2),..., Solve(Pk));}
}

其中 S m a l l ( P ) Small(P) Small(P) 用來判斷問題 P P P 的規模是否已經足夠小,當問題 P P P 的規模足夠小時,直接進行求解并返回結果 R e s u l t ( P ) Result(P) Result(P),否則,將問題 P P P 分解為若干子問題,并逐個進行求解,最后將所有子問題的解 R i Ri Ri 合并得到問題 P P P 的解并返回。

冒泡排序的交換次數

題目描述

給定一個數列 a a a,求對這個數列進行冒泡排序所需要的交換次數(此處冒泡排序指每次找到滿足 a i > a i + 1 a_i>a_{i+1} ai?>ai+1? i i i,交換 a i a_i ai? a i + 1 a_{i+1} ai+1?,直到這樣的 i i i 不存在為止的算法)。

輸入輸出

輸入:數列元素個數 n n n n n n 個數列元素。

輸出:交換次數。

解題思路

求所需交換次數等價于求滿足 i < j i<j ij a i > a j a_i>a_j ai?aj? ( i , j ) (i,j) (i,j) 數對的個數,也即求數列 a a a 的逆序數。

假設要統計數列 A A A 中逆序對的個數,為此,可以將數列 A A A 分成兩半得到數列 B B B 和數列 C C C,于是,對于數列 A A A 中所有的逆序對 ( a i , a j ) (a_i,a_j) (ai?,aj?),必然屬于以下情況之一:

  • ( a i , a j ) (a_i,a_j) (ai?,aj?)屬于數列 B B B 的逆序對;
  • ( a i , a j ) (a_i,a_j) (ai?,aj?) 屬于數列 C C C 的逆序對;
  • a i a_i ai? 屬于數列 B B B a j a_j aj? 屬于數列 C C C

對于情況①和②,可以通過遞歸求得。對于情況③,需要做的就是對于 C C C 中的每一個元素,統計在數列 B B B 中比它大的元素的個數,再把結果相加。最后再將③中情況所得結果相加,便得到數列 A A A 的逆序數。

情況③進行統計時,如果采用普通方法,即使用兩個 f o r for for 循環逐個比較,時間復雜度較高。因此,借鑒歸并排序的思想,在進行統計的同時邊將兩個子數列進行歸并,由遞歸的特性可知,進行歸并時,兩子數列也是有序的,如此,情況③統計只需掃描一遍數列。

代碼實現
typedef vector<int> vi;int main() {// 輸入int n;cin >> n;vi A(n);for (int i = 0; i < n; ++i)cin >> A[i];// 求解并輸出cout << solve(A);
}
/*** 求數列a的逆序數* @param a 數列* @return 	逆序數*/
int solve(vector<int> &a) {int n = a.size();// 數列元素個數小于等于1,逆序數為0if (n <= 1)return 0;// 二分vector<int> b(a.begin(), a.begin() + n / 2);vector<int> c(a.begin() + n / 2, a.end());// 情況1與情況2(子問題)int cnt = solve(b) + solve(c);// 情況3int ai = 0, bi = 0, ci = 0;while (ai < n) {if (bi < b.size() && (ci == c.size() || b[bi] <= c[ci])) {a[ai++] = b[bi++];} else {// b[bi..n/2-1] 都比 c[ci] 大cnt += n / 2 - bi;a[ai++] = c[ci++];}}// 返回合并所得解return cnt;
}

時間復雜度: O ( n l o g ( n ) ) O(nlog(n)) O(nlog(n))

空間復雜度: O ( n l o g ( n ) ) O(nlog(n)) O(nlog(n)),歸并排序的空間復雜度實際可降至 O ( n ) O(n) O(n)

最近點對問題

題目描述

給定平面上的 n n n 個點,求距離最近的兩個點的距離。

輸入輸出

輸入:第一行輸入點的個數 n n n,第二行輸入 n n n 個點的橫坐標 x i x_i xi?,第三行輸入 n n n 個點的縱坐標 y i y_i yi?

輸出:距離最近的兩個點的距離。

解題思路

將所有點按x坐標(按y坐標也可)分成左右兩半,那么最近點對的距離就是下面三者的最小值。

  • p p p q q q 同屬于左半邊時,點對 ( p , q ) (p,q) (p,q) 距離的最小值;
  • p p p q q q 同屬于右半邊時,點對 ( p , q ) (p,q) (p,q) 距離的最小值;
  • p p p q q q 屬于不同區域時,點對 ( p , q ) (p,q) (p,q) 距離的最小值。

對于情況①和②可以遞歸求解,情況③稍微復雜。假設情況①和②所求得的最小距離為 d d d,所以在情況③中便不需要考慮距離顯然大于等于 d d d 的點對。

先考慮 x x x 坐標。假設將點劃分為左右兩半的直線為 l l l,其 x x x 坐標為 x 0 x_0 x0?,只需考慮那些到直線 l l l 距離小于 d d d 的點,也即 x x x 坐標滿足 x 0 ? d < x < x 0 + d x_0-d<x<x_0+d x0??dxx0?+d 的點。

再考慮 y y y 坐標。對于每個點,只考慮那些 y y y 坐標相差小于 d d d 的點,同時,為了避免重復計算,規定只考慮與 y y y 坐標不比自己大的點組成的點對。因此,對于每個點 p p p,只需要考慮與 y y y 坐標滿足 y p ? d < y < y p y_p-d<y<y_p yp??dyyp? 的點組成的點對。

為了將所有點按 x x x 坐標分成左右兩半,需要先將所有點按 x x x 坐標排序。為了避免重復考慮,在處理情況③前,需要將待考慮的點按 y y y 坐標排序(由于分治法求解最近點對問題與歸并排序在結構上的相似性,此處借鑒歸并排序的思想)。

代碼實現
#define INF 1.79E+308
typedef pair<double, double> pdd;int main() {// 輸入int n;cin >> n;pdd *ps = new pdd[n];for (int i = 0; i < n; ++i)cin >> ps[i].first;for (int i = 0; i < n; ++i)cin >> ps[i].second;// 按x坐標排序sort(ps, ps + n, compX);// 求解并輸出最近點對的距離cout << solve(ps, n);
}
/*** 求最近點對的距離* @param ps 所有點* @param n 點的個數* @return 最近點對的距離*/
double solve(pdd *ps, int n) {if (n <= 1)return INF;// 中線int m = n >> 1;double x = ps[m].first;// 處理情況1和2,得到目前距離最小值ddouble d = min(solve(ps, m), solve(ps + m, n - m));// 按y坐標從小到大進行排序(歸并排序)inplace_merge(ps, ps + m, ps + n, compY);// 處理情況3vector<pdd> pl;for (int i = 0; i < n; ++i) {// 排除到直線l的距離大于等于d的點if (fabs(ps[i].first - x) >= d)continue;// 從后往前檢查b中y坐標相差小于d的點for (int j = pl.size() - 1; j >= 0; --j) {double dy = ps[i].second - pl[j].second;if (dy >= d)break;double dx = ps[i].first - pl[j].first;d = min(d, sqrt(dx * dx + dy * dy));}// 記錄到直線l的距離小于d的點pl.push_back(ps[i]);}return d;
}
// 按x坐標從小到大排序
bool compX(const pdd &p1, const pdd &p2) {return p1.first < p2.first;
}// 按y坐標從小到大排序(歸并排序)
bool compY(const pdd &p1, const pdd &p2) {return p1.second < p2.second;
}

時間復雜度: O ( n l o g ( n ) ) O(nlog(n)) O(nlog(n))

空間復雜度: O ( n ) O(n) O(n)

經驗總結

由于遞歸特別適合解決結構自相似問題,故分治法通常采用遞歸實現,但并非只能采用遞歸實現。當采用遞歸實現時,在每層遞歸中,需要完成“分”、“治”、“合”三個步驟。

“分”指的是,將原問題分解為若干規模較小、相互獨立、與原問題形式相同的子問題。子問題的規模應大致相同,子問題的個數 k k k 視具體情況而定,一般來說, k k k 可以取 2 2 2。“分”這一步尤為重要,若不能將原問題分解為若干符合要求的子問題,說明此問題不適合采用分治法,若分解不恰當,會降低算法效率,甚至得出錯誤結果。數列通常按中點位置進行二分,樹通常按重心分割(重心指使得刪除該頂點后得到的最大子樹的頂點數最少的頂點),平面通常按照坐標進行分割。

“治”指的是,若子問題規模 n n n 足夠小則直接求解,否則,遞歸求解子問題。子問題規模 n n n 究竟小到何種程度才算足夠小需要視具體問題而定。

“合”指的是,合并各個子問題的解,得到原問題的解。需要注意的是,當子問題所考慮到的解對于原問題來說不完整時,還需要考慮遺漏的解,如最近點對問題中的情況③。

當遞歸體中需要使用到排序時,可以借鑒歸并排序的思想,從而做到邊求解邊排序,降低排序的時間復雜度。分治法的基本思想較為簡單,就是不斷地將問題劃分成子問題,直到子問題能夠快速求解,當然,需要滿足實驗原理中所列的條件。

需要注意的就是,劃分子集時需要做到不遺漏、不重復。對于最值問題(最大值、最短距離等),有時為了代碼編寫方便,可以允許部分重復,但如果重復考慮的情況太多,可能會提高時間復雜度,這需要權衡,對于計數類問題(方案數等),一般情況下是不允許重復的,如果重復考慮某些情況,很可能就會得出錯誤的結果。

END

文章文檔:公眾號 字節幺零二四 回復關鍵字可獲取本文文檔。

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

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

相關文章

單鏈表詳解(2)

三、函數定義 查找節點 //查找結點 SLTNode* SLTNodeFind(SLTNode* phead, SLTDataType x) {assert(phead);SLTNode* pcur phead;while (pcur){if (pcur->data x){return pcur;}pcur pcur->next;}return NULL; } 查找節點我們是通過看數據域來查找的&#xff0c;查…

Arm64 基礎指令集介紹

按照字母排序順序&#xff1a; ● ADC&#xff1a;帶進位加法。 ● ADCS&#xff1a;帶進位加法&#xff0c;設置標志位。 ● ADD (extended register)&#xff1a;擴展寄存器加法。 ● ADD (immediate)&#xff1a;立即數加法。 ● ADD (shifted register)&#xff1a;移位寄存…

【MySQL05】【 undo 日志】

文章目錄 一、前言二、undo 日志&#xff08;回滾日志&#xff09;1. 事務 id2. undo 日志格式2.1 INSERT 對應的 undo 日志2.2 DELETE 對應的 undo 日志2.3 UPDATE 對應的 undo 日志2.3.1 不更新主鍵2.3.2 更新主鍵 2.3 增刪改操作對二級索引的影響2.4 roll_pointer 3. FIL_PA…

Windows 網絡重置

netsh int ip reset 命令是用于重置 Windows 操作系統中的網絡設置和配置的命令。 在網絡故障排除、修復網絡連接問題以及清除可能存在的網絡配置沖突時非常有用。 命令詳解&#xff1a; netsh: 用于配置各種網絡設置 int: 用于管理網絡接口 ip: 用于管理網絡接口的 IP 配…

layui項目中的layui.define、layui.config以及layui.use的使用

第一步:創建一個layuiTest項目&#xff0c;結構如下 第二步&#xff1a;新建一個test.js,利用layui.define定義一個模塊test,并向外暴露該模塊&#xff0c;該模塊里面有兩個方法method1和method2. 第三步&#xff1a;新建一個test.html&#xff0c;在該頁面引入layui.js&#x…

基于FPGA的LDPC編譯碼算法設計基礎知識

基于FPGA的LDPC編譯碼算法設計基礎知識 數字電路&#xff08;數電&#xff09;知識模擬電路&#xff08;模電&#xff09;知識1. 放大器1.1. 晶體管放大器1.2. 運算放大器1.3. 管子放大器&#xff08;真空管放大器&#xff09;微處理器/單片機知識其他相關知識 基于FPGA的算法設…

neo4j 圖數據庫:Cypher 查詢語言、醫學知識圖譜

neo4j 圖數據庫&#xff1a;Cypher 查詢語言、醫學知識圖譜 Cypher 查詢語言創建數據查詢數據查詢并返回所有節點查詢并返回所有帶有特定標簽的節點查詢特定屬性的節點及其所有關系和關系的另一端節點查詢從名為“小明”的節點到名為“小紅”的節點的路徑 更新數據更新一個節點…

python爬蟲和用騰訊云API接口進行翻譯并存入excel,通過本機的Windows任務計劃程序定時運行Python腳本!

項目場景&#xff1a; 提示&#xff1a;這里簡述項目相關背景&#xff1a;定時爬取外網的某個頁面&#xff0c;并將需要的部分翻譯為中文存入excel 接下了的&#xff0c;沒學過的最好看一下 基本爬蟲的學習 【爬蟲】requests 結合 BeautifulSoup抓取網頁數據_requests beauti…

Vue CoreVideoPlayer 一款基于 vue.js 的輕量級、優秀的視頻播放器組件

大家好,我是程序視點的小二哥!今天小二哥給大家推薦一款非常優秀的視頻播放組件 效果欣賞 介紹 Vue-CoreVideoPlayer 一款基于vue.js的輕量級的視頻播放器插件。 采用Adobd XD進行UI設計&#xff0c;支持移動端適配,不僅功能強大&#xff0c;顏值也是超一流&#xff01; Vue-…

第一次構建一個對話機器人流程解析(二)

1. 問答機器人的組成-基于知識圖譜的搜索 在教育場景下&#xff0c;若學生有關于學習內容的提問&#xff0c;或業務層面的提問&#xff0c;則要求問答機器人的回答必須精準&#xff0c;來滿足業務的要求因此需要通過知識圖譜來快速檢索&#xff0c;所提內容的相關信息&#xf…

數字系統與進制轉換

數字系統 數字邏輯是計算機科學的基礎&#xff0c;它研究的是如何通過邏輯門電路&#xff08;與門、或門、非門等&#xff09;實現各種邏輯功能。數字系統則是由數字邏輯電路組成的系統&#xff0c;可以實現各種復雜的運算和控制功能。在計算機科學中&#xff0c;數字邏輯和數…

C++ 假設今天是星期日,那么過a^b天之后是星期幾?

題目 假設今天是星期日&#xff0c;那么過a^b天之后是星期幾&#xff1f; 【輸入】 兩個正整數a&#xff0c;b&#xff0c;中間用單個空格隔開。0<a≤100,0<b≤10000。 【輸出】 一個字符串&#xff0c;代表過a^b天之后是星期幾。 其中&#xff0c;Monday是星期一&…

自定義波形圖View,LayoutInflater動態加載控件保存為本地圖片

效果圖: 頁面布局: <?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"android:layout_width="…

C#多線程并行計算實例

在C#中實現多線程并行計算可以通過使用 Task 和 Parallel 類來實現。這里給出兩個簡單的示例&#xff0c;一個是使用 Task&#xff0c;另一個是使用 Parallel.ForEach。 使用 Task 進行多線程并行計算 using System; using System.Threading.Tasks;class Program {static voi…

Kubernetes基于helm部署jenkins

Kubernetes基于helm安裝jenkins jenkins支持war包、docker鏡像、系統安裝包、helm安裝等。在Kubernetes上使用Helm安裝Jenkins可以簡化安裝和管理Jenkins的過程。同時借助Kubernetes&#xff0c;jenkins可以實現工作節點的動態調用伸縮&#xff0c;更好的提高資源利用率。通過…

MySQL Innodb存儲引擎中,當頁默認的大小是16K時,頁中最多存放多少行的記錄?

1、題目引入 Innodb存儲引擎是面向行的(row-oriented)&#xff0c;也就是說數據的存放按行進行&#xff0c;每頁存放的行記錄是有硬性定義的&#xff0c;當頁默認的大小是16K時&#xff0c;頁中最多存放多少行的記錄&#xff1f; A、1600 行B、8192 行C、16383 行D、7992 行 …

基于Python協同過濾的旅游景點推薦系統,采用Django框架,MySQL數據存儲,Bootstrap前端,echarts可視化實現

隨著旅游業的迅速發展&#xff0c;個性化旅游推薦系統成為提升用戶體驗和促進旅游市場增長的重要工具。本研究旨在設計并實現一種基于Python協同過濾的旅游景點推薦系統&#xff0c;結合Django框架、MySQL數據庫存儲、Bootstrap前端框架以及echarts數據可視化技術&#xff0c;為…

Flask發布一個及時止損(止盈)服務(二)

生成可視化的止盈止損結果&#xff08;圖片&#xff09; 媽的&#xff0c;還是得用 akshare&#xff0c;還需要指定python版本3.9以上 conda remove -n fonxsys --all conda search pythonconda create -n fonxsys python3.9 conda activate fonxsys python.exe -m pip insta…

【粉絲福利 | 第8期】值得收藏!推薦10個好用的數據血緣工具

?? 寫在前面參與規則&#xff01;&#xff01;&#xff01; ?參與方式&#xff1a;關注博主、點贊、收藏、評論&#xff0c;任意評論&#xff08;每人最多評論三次&#xff09; ??本次送書1~4本【取決于閱讀量&#xff0c;閱讀量越多&#xff0c;送的越多】 目前市面上絕…

數據遷移探索

概念 數據遷移是指將數據從一個計算環境或存儲系統移動到另一個計算環境或存儲系統。 隨著公司業務的發展&#xff0c;出于成本優化、系統升級、分庫分表、整合數據等原因。數據遷移工作在日常工作中會陸續出現。 我們可以將數據遷移分成兩個部分&#xff0c;第一部分是數據…