算法之旅(八)算法核心思想之一 分治算法

一、分治算法的三個主要步驟

  1. 分解(Divide):將原問題分解成規(guī)模較小且相互獨立的子問題。
  2. 解決(Conquer):遞歸地求解各個子問題。
  3. 合并(Combine):將各個子問題的解合并成原問題的解。

二、分治算法的核心思想

分治算法是一種重要的算法設計范式超升,其核心思想是將一個復雜的問題分解為若干個規(guī)模較小且相互獨立的子問題,遞歸地解決這些子問題柱查,然后再合并其結(jié)果廓俭,得到原問題的解。

  • 減少問題規(guī)模:通過分解唉工,將大問題轉(zhuǎn)化為小問題研乒,降低了解決問題的復雜度。
  • 利用遞歸思想:子問題的結(jié)構(gòu)與原問題相似淋硝,可以遞歸地解決雹熬。
  • 提高效率:如果子問題足夠小,求解起來會更簡單高效谣膳。

三竿报、分治算法的常見應用場景

分治算法的應用場景有很多,我以三種不同的數(shù)據(jù)結(jié)構(gòu)為例继谚,分別演示下分治算法的應用

  1. 排序算法 - 歸并排序(數(shù)組)
  2. 樹的算法 - 二叉樹的最大深度(樹)
  3. 矩陣運算 - 矩陣乘法的Strassen算法(矩陣)

示例一:歸并排序(Merge Sort) - 數(shù)組

1. 問題描述

對一個無序的數(shù)組進行排序烈菌,使其按照從小到大的順序排列。

2. 分治思想應用

  • 分解(Divide):將數(shù)組從中間分成兩半花履,形成兩個子數(shù)組芽世。
  • 解決(Conquer):遞歸地對這兩個子數(shù)組進行歸并排序。
  • 合并(Combine):將已排序的子數(shù)組合并诡壁,得到完整的排序數(shù)組济瓢。

3. 代碼示例

public class MergeSortExample {
    // 歸并排序主函數(shù)
    public static void mergeSort(int[] array) {
        if (array.length <= 1) {
            return; // 遞歸終止條件
        }
        // 分解
        int mid = array.length / 2;
        int[] left = new int[mid];
        int[] right = new int[array.length - mid];

        // 拷貝數(shù)組元素
        System.arraycopy(array, 0, left, 0, mid);
        System.arraycopy(array, mid, right, 0, array.length - mid);

        // 遞歸解決
        mergeSort(left);
        mergeSort(right);

        // 合并
        merge(array, left, right);
    }

    // 合并兩個已排序的數(shù)組
    private static void merge(int[] result, int[] left, int[] right) {
        int i = 0; // 左數(shù)組索引
        int j = 0; // 右數(shù)組索引
        int k = 0; // 合并后數(shù)組索引

        while (i < left.length && j < right.length) {
            if (left[i] <= right[j]) {
                result[k++] = left[i++];
            } else {
                result[k++] = right[j++];
            }
        }
        // 處理剩余元素
        while (i < left.length) {
            result[k++] = left[i++];
        }
        while (j < right.length) {
            result[k++] = right[j++];
        }
    }

    // 測試代碼
    public static void main(String[] args) {
        int[] array = {38, 27, 43, 3, 9, 82, 10};
        System.out.println("原始數(shù)組:" + Arrays.toString(array));

        mergeSort(array);

        System.out.println("排序后數(shù)組:" + Arrays.toString(array));
    }
}

輸出結(jié)果:

原始數(shù)組:[38, 27, 43, 3, 9, 82, 10]
排序后數(shù)組:[3, 9, 10, 27, 38, 43, 82]

4. 算法復雜度

  • 時間復雜度:O(n log n)
    • 每次分解將數(shù)組長度減半,遞歸深度為 log n妹卿。
    • 合并過程需要 O(n) 時間旺矾。
  • 空間復雜度:O(n)
    • 需要額外的數(shù)組來存儲分解的子數(shù)組和合并的結(jié)果。

5. 思路說明

  • 遞歸分解:不斷將數(shù)組對半分夺克,直到子數(shù)組長度為 1箕宙。
  • 遞歸合并:從最小的子數(shù)組開始,兩兩合并成有序數(shù)組铺纽。
  • 優(yōu)勢:適用于大規(guī)模數(shù)據(jù)的排序扒吁,性能穩(wěn)定。

示例二:二叉樹的最大深度(Binary Tree Maximum Depth) -

1. 問題描述

計算給定二叉樹的最大深度,即從根節(jié)點到葉子節(jié)點的最長路徑上的節(jié)點數(shù)雕崩。

2. 分治思想應用

  • 分解(Divide):將問題分解為求左子樹和右子樹的最大深度。
  • 解決(Conquer):遞歸地求解左子樹和右子樹的最大深度融撞。
  • 合并(Combine):取左右子樹深度的最大值盼铁,加 1(當前節(jié)點),得到整個樹的最大深度尝偎。

3. 代碼示例

public class MaxDepthBinaryTree {
    // 定義二叉樹節(jié)點
    static class TreeNode {
        int val;
        TreeNode left;
        TreeNode right;
        TreeNode(int val) { this.val = val; }
    }

    // 計算二叉樹的最大深度
    public static int maxDepth(TreeNode root) {
        if (root == null) {
            return 0; // 遞歸終止條件
        }
        // 遞歸計算左子樹和右子樹的深度
        int leftDepth = maxDepth(root.left);
        int rightDepth = maxDepth(root.right);

        // 合并:取最大值饶火,加上當前節(jié)點
        return Math.max(leftDepth, rightDepth) + 1;
    }

    // 測試代碼
    public static void main(String[] args) {
        /*
         * 構(gòu)建二叉樹:
         *         1
         *        / \
         *       2   3
         *      / \
         *     4   5
         */
        TreeNode root = new TreeNode(1);
        root.left = new TreeNode(2); 
        root.right = new TreeNode(3); 
        root.left.left = new TreeNode(4); 
        root.left.right = new TreeNode(5); 

        int depth = maxDepth(root);
        System.out.println("二叉樹的最大深度是:" + depth); // 輸出 3
    }
}

輸出結(jié)果:

二叉樹的最大深度是:3

4. 算法復雜度

  • 時間復雜度:O(n)
    • 每個節(jié)點都被訪問一次。
  • 空間復雜度:O(h)
    • 遞歸調(diào)用棧的深度為樹的高度 h致扯。

5. 思路說明

  • 遞歸遍歷:通過遞歸遍歷每個節(jié)點肤寝,分別計算左子樹和右子樹的深度。
  • 比較合并:在回溯時抖僵,取左右子樹深度的最大值鲤看,并加上當前節(jié)點。
  • 優(yōu)勢:代碼簡潔耍群,利用遞歸自然地遍歷樹結(jié)構(gòu)义桂。

示例三:矩陣乘法的Strassen算法 - 矩陣

1. 問題描述

傳統(tǒng)的矩陣乘法對于兩個 n x n 的矩陣,時間復雜度為 O(n3)蹈垢。Strassen 算法使用分治思想慷吊,將時間復雜度降低到約 O(n2.81)。

2. 分治思想應用

  • 分解(Divide):將矩陣分成 8 個 n/2 x n/2 的子矩陣曹抬。
  • 解決(Conquer):遞歸地計算 7 個乘積(而不是傳統(tǒng)的 8 個)溉瓶。
  • 合并(Combine):通過加減運算組合這 7 個乘積,得到最終結(jié)果谤民。

3. 代碼示例

public class StrassenMatrixMultiplication {
    // 矩陣加法
    private static int[][] add(int[][] A, int[][] B) {
        int n = A.length;
        int[][] result = new int[n][n];
        for(int i = 0; i < n; i++)
            for(int j = 0; j < n; j++)
                result[i][j] = A[i][j] + B[i][j];
        return result;
    }

    // 矩陣減法
    private static int[][] subtract(int[][] A, int[][] B) {
        int n = A.length;
        int[][] result = new int[n][n];
        for(int i = 0; i < n; i++)
            for(int j = 0; j < n; j++)
                result[i][j] = A[i][j] - B[i][j];
        return result;
    }

    // Strassen算法實現(xiàn)
    public static int[][] strassen(int[][] A, int[][] B) {
        int n = A.length;
        int[][] result = new int[n][n];

        // 基本情況
        if (n == 1) {
            result[0][0] = A[0][0] * B[0][0];
        } else {
            int newSize = n / 2;
            // 初始化子矩陣
            int[][] A11 = new int[newSize][newSize];
            int[][] A12 = new int[newSize][newSize];
            int[][] A21 = new int[newSize][newSize];
            int[][] A22 = new int[newSize][newSize];

            int[][] B11 = new int[newSize][newSize];
            int[][] B12 = new int[newSize][newSize];
            int[][] B21 = new int[newSize][newSize];
            int[][] B22 = new int[newSize][newSize];

            // 分解矩陣
            for (int i = 0; i < newSize; i++) {
                for (int j = 0; j < newSize; j++) {
                    A11[i][j] = A[i][j]; // 左上
                    A12[i][j] = A[i][j + newSize]; // 右上
                    A21[i][j] = A[i + newSize][j]; // 左下
                    A22[i][j] = A[i + newSize][j + newSize]; // 右下

                    B11[i][j] = B[i][j]; // 左上
                    B12[i][j] = B[i][j + newSize]; // 右上
                    B21[i][j] = B[i + newSize][j]; // 左下
                    B22[i][j] = B[i + newSize][j + newSize]; // 右下
                }
            }

            // 計算 M1 到 M7
            int[][] M1 = strassen(add(A11, A22), add(B11, B22));
            int[][] M2 = strassen(add(A21, A22), B11);
            int[][] M3 = strassen(A11, subtract(B12, B22));
            int[][] M4 = strassen(A22, subtract(B21, B11));
            int[][] M5 = strassen(add(A11, A12), B22);
            int[][] M6 = strassen(subtract(A21, A11), add(B11, B12));
            int[][] M7 = strassen(subtract(A12, A22), add(B21, B22));

            // 合并子矩陣
            int[][] C11 = add(subtract(add(M1, M4), M5), M7);
            int[][] C12 = add(M3, M5);
            int[][] C21 = add(M2, M4);
            int[][] C22 = add(subtract(add(M1, M3), M2), M6);

            // 組合結(jié)果
            for (int i = 0; i < newSize; i++) {
                for (int j = 0; j < newSize; j++) {
                    result[i][j] = C11[i][j];
                    result[i][j + newSize] = C12[i][j];
                    result[i + newSize][j] = C21[i][j];
                    result[i + newSize][j + newSize] = C22[i][j];
                }
            }
        }
        return result;
    }

    // 測試代碼
    public static void main(String[] args) {
        int n = 2; // 矩陣大小必須是2的冪
        int[][] A = { {1, 2}, {3, 4} };
        int[][] B = { {5, 6}, {7, 8} };

        int[][] result = strassen(A, B);

        // 輸出結(jié)果
        System.out.println("矩陣A和B的乘積:");
        for (int[] row : result) {
            System.out.println(Arrays.toString(row));
        }
    }
}

輸出結(jié)果:

矩陣A和B的乘積:
[19, 22]
[43, 50]

4. 算法復雜度

  • 時間復雜度:約為 O(n^2.81)
    • 比傳統(tǒng)的 O(n3) 更快堰酿,但由于常數(shù)因素較大,實際應用中需要權(quán)衡赖临。
  • 空間復雜度:O(n^2)
    • 需要額外空間存儲子矩陣胞锰。

5. 思路說明

  • 遞歸分解:將矩陣分割成更小的子矩陣,直到規(guī)模足夠小兢榨。
  • 減少乘法次數(shù):通過巧妙的計算嗅榕,只需進行 7 次遞歸乘法,而不是傳統(tǒng)的 8 次吵聪。
  • 合并結(jié)果:通過矩陣的加減運算凌那,組合子矩陣得到最終結(jié)果。
  • 適用性:適用于大型矩陣的乘法運算吟逝,但需要矩陣大小為 2 的冪次方帽蝶。

高級技術(shù)棧實踐-實時流計算之一flink的應用

Flink 通過并行處理和分布式計算來高效地處理大規(guī)模數(shù)據(jù)集。
以下是一些關(guān)鍵應用場景:

  1. 數(shù)據(jù)聚合:在進行數(shù)據(jù)聚合(如求和块攒、平均值等)時励稳,可以將數(shù)據(jù)分成多個子集佃乘,分別計算各自的聚合值,最后合并結(jié)果驹尼。

  2. 批處理:在處理大規(guī)模批量數(shù)據(jù)時趣避,F(xiàn)link 能夠?qū)?shù)據(jù)分塊處理,每個塊可以獨立地執(zhí)行計算新翎,最終合并輸出結(jié)果程帕。

  3. 實時流處理:在實時數(shù)據(jù)流處理中,F(xiàn)link 可以將輸入流分解為多個流進行并行處理地啰,以提高處理效率愁拭。

通過使用分治策略,可以顯著提升數(shù)據(jù)處理性能亏吝,特別是在處理復雜的數(shù)據(jù)轉(zhuǎn)換和分析任務時岭埠。

這個技術(shù)棧主要應用于大數(shù)據(jù)的實時流計算中,對于實時流計算還有很多實現(xiàn)方式顺呕,flink只是其中一種實現(xiàn)方式枫攀。

前面我可能提到了中間件kafka本身的可靠性投遞并不難,但一旦結(jié)合像flink這種實時計算株茶,或者其他的綜合數(shù)據(jù)流處理技術(shù)棧結(jié)合来涨。數(shù)據(jù)的可靠性投遞、兜底方案也會跟著技術(shù)棧組合使用改變启盛,來實現(xiàn)閉環(huán)蹦掐。


四、總結(jié)

分治算法的核心在于將復雜問題分解為更小的子問題僵闯,利用遞歸解決卧抗,并將結(jié)果合并。

  • 優(yōu)點
    • 可以顯著降低算法的時間復雜度鳖粟,提高效率社裆。
    • 適用于許多遞歸性質(zhì)的問題。
  • 缺點
    • 可能增加空間復雜度向图,需要額外的存儲空間泳秀。
    • 遞歸調(diào)用可能導致調(diào)用棧溢出,需要注意遞歸深度榄攀。

應用場景

  • 排序算法:如歸并排序嗜傅、快速排序。
  • 搜索算法:如二分查找檩赢。
  • 數(shù)學計算:如大整數(shù)乘法吕嘀、矩陣運算。
  • 圖形處理:如快速傅里葉變換(FFT)。

選擇合適的數(shù)據(jù)結(jié)構(gòu)和算法偶房,可以有效地解決復雜的問題趁曼。
理解分治算法的思想,對于算法設計和優(yōu)化具有重要意義棕洋。
簡單來說就是拆解復雜問題彰阴,細化問題再在每一層細化問題使用最優(yōu)解決方案。
結(jié)合解耦的思想拍冠,可以逐步解決問題。
應用于各個高級框架簇抵。屬于高級技術(shù)框架的底層思想庆杜。


最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市碟摆,隨后出現(xiàn)的幾起案子晃财,更是在濱河造成了極大的恐慌,老刑警劉巖典蜕,帶你破解...
    沈念sama閱讀 211,265評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件断盛,死亡現(xiàn)場離奇詭異,居然都是意外死亡愉舔,警方通過查閱死者的電腦和手機钢猛,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,078評論 2 385
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來轩缤,“玉大人命迈,你說我怎么就攤上這事』鸬模” “怎么了壶愤?”我有些...
    開封第一講書人閱讀 156,852評論 0 347
  • 文/不壞的土叔 我叫張陵,是天一觀的道長馏鹤。 經(jīng)常有香客問我征椒,道長,這世上最難降的妖魔是什么湃累? 我笑而不...
    開封第一講書人閱讀 56,408評論 1 283
  • 正文 為了忘掉前任勃救,我火速辦了婚禮,結(jié)果婚禮上脱茉,老公的妹妹穿的比我還像新娘剪芥。我一直安慰自己,他們只是感情好琴许,可當我...
    茶點故事閱讀 65,445評論 5 384
  • 文/花漫 我一把揭開白布税肪。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,772評論 1 290
  • 那天拳锚,我揣著相機與錄音捐名,去河邊找鬼。 笑死削罩,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播荆永,決...
    沈念sama閱讀 38,921評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼国章!你這毒婦竟也來了具钥?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,688評論 0 266
  • 序言:老撾萬榮一對情侶失蹤液兽,失蹤者是張志新(化名)和其女友劉穎骂删,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體四啰,經(jīng)...
    沈念sama閱讀 44,130評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡宁玫,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,467評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了柑晒。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片欧瘪。...
    茶點故事閱讀 38,617評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖敦迄,靈堂內(nèi)的尸體忽然破棺而出恋追,到底是詐尸還是另有隱情,我是刑警寧澤罚屋,帶...
    沈念sama閱讀 34,276評論 4 329
  • 正文 年R本政府宣布苦囱,位于F島的核電站,受9級特大地震影響脾猛,放射性物質(zhì)發(fā)生泄漏撕彤。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,882評論 3 312
  • 文/蒙蒙 一猛拴、第九天 我趴在偏房一處隱蔽的房頂上張望羹铅。 院中可真熱鬧,春花似錦愉昆、人聲如沸职员。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,740評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽焊切。三九已至扮授,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間专肪,已是汗流浹背刹勃。 一陣腳步聲響...
    開封第一講書人閱讀 31,967評論 1 265
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留嚎尤,地道東北人荔仁。 一個月前我還...
    沈念sama閱讀 46,315評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像芽死,于是被迫代替她去往敵國和親乏梁。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 43,486評論 2 348

推薦閱讀更多精彩內(nèi)容