(二)基礎(chǔ)(Fundamentals):算法分析

觀察

運(yùn)行時間和輸入本身相對無關(guān)裆赵,主要取決于問題規(guī)模
例:統(tǒng)計文件中三個數(shù)和為0的數(shù)量

public class ThreeSum{
    public static int count(int a[]){
        int N = a.length, cnt = 0;
        for(int i = 0; i < N; i++){
            for(int j = i + 1; j < N; j++){
                for (int k = j + 1; k  < N; k++){
                    if(a[i] + a[j] + a[k] == 0)
                        cnt += 1;
                }
            }
        }
    }
}

一種表示計時器的抽象數(shù)據(jù)類型:

public class StopWatch{
    private final long start;
    public StopWatch(){
        start = System.currentTimeMillis();
    }
    public double elapsedTime(){
        long now = System.currentTimeMillis();
        return (now - start) / 1000.0;
    }
}

數(shù)學(xué)模型

程序運(yùn)行總時間主要和兩點有關(guān):

  • 執(zhí)行每條語句的耗時(取決于計算機(jī)抱怔、Java編譯器和操作系統(tǒng))
  • 執(zhí)行每條語句的頻率(取決于程序本身和輸入)

對于執(zhí)行頻率,有些分析很容易拉一,如在 Three.count() 中將 cnt 設(shè)為 0 的語句只會執(zhí)行一次;有些需要深入分析旧乞,如 Three.count() 中的 if 語句會執(zhí)行 N(N-1)(N-2)/6 次蔚润。

近似

用 ~f(N) 表示所有隨著N增大除以f(N)的結(jié)果趨近于1的函數(shù)。用g(N)~f(N)表示隨著g(N)/f(N) 隨著N增大趨近于1尺栖。

一般用到的近似方式都是g(N)~f(N)嫡纠,其中 f(N)=Nb(logN)c,其中a,b和c均為常數(shù)延赌,將f(N)稱為g(N)的增長數(shù)量級除盏。
常見的增長數(shù)量級函數(shù):

描述 函數(shù) B
常數(shù)級 1
對數(shù)級 logN
線性級 N
線性對數(shù)級 NlogN
平方級 N^2
立方級 N^3
指數(shù)級 2^N

執(zhí)行最頻繁的執(zhí)行決定了程序執(zhí)行的總時間——稱這些指令為程序的內(nèi)循環(huán)

成本模型

成本模型來評估算法的性質(zhì),這個模型定義了所研究算法中的基本操作挫以。例如者蠕,3-Sum問題的成本模型是訪問數(shù)組元素的次數(shù)。

構(gòu)建運(yùn)行時間的數(shù)學(xué)模型所需步驟:

  1. 確定輸入模型掐松,定義問題規(guī)模
  2. 識別內(nèi)循環(huán)
  3. 根據(jù)內(nèi)循環(huán)中的操作確定成本模型
  4. 對給定輸入蠢棱,判斷這些操作的執(zhí)行頻率

如二分查找锌杀,它的輸入模型是大小為N的數(shù)組a[],內(nèi)循環(huán)是while循環(huán)中所有語句泻仙,成本模型是比較操作(比較兩個數(shù)組元素的值)

設(shè)計更快的算法

2-Sum 問題

假設(shè)所有整數(shù)各不相同糕再,這個問題很容易在平方級別——雙重循環(huán)來解決,下面利用歸并排序和二分查找在線性對數(shù)級別解決 2-Sum 問題:

思路:當(dāng)且僅當(dāng) -a[i] 存在于數(shù)組中(且a[i]非0)時玉转,a[i] 存在于某個和為0的整數(shù)對中突想。

public class TwoSumFast{
    public static int count(int[] a){
        //先排序
        Arrays.sort(a);
        int cnt = 0;
        for(int i = 0; i < a.length; i++){
            if(BinarySearch.rank(-a[i], a) > i)
                cnt++;
        }
        return cnt;
    }
}

歸并排序所需時間與NlogN成正比,二分查找所需時間和logN成正比究抓,因此整個算法運(yùn)行時間和NlogN成正比猾担。

3-Sum 問題

同樣假設(shè)所有整數(shù)各不相同,同上面一樣刺下,當(dāng)且僅當(dāng) -(a[i] + a[j]) 在數(shù)組中(不是a[i] 也不是 a[j])時绑嘹,整數(shù)對(a[i] 和 a[j])為某個和為0的三元組的一部分。

public class ThreeSumFast{
    public static int count(int[] a){
        //先排序
        Arrays.sort(a);
        int cnt = 0;
        for(int i = 0; i < a.length; i++){
            for(int j = i + 1; j < a.length; j++)
                if(BinarySearch.rank(-(a[i] + a[j]), a) > j)
                    cnt++;
        }
        return cnt;
    }
}

內(nèi)存

對象

典型對象的內(nèi)存需求.png

數(shù)組

數(shù)組對內(nèi)存的典型需求.png

字符串對象

字符串的內(nèi)存需求.png

當(dāng)調(diào)用substring()方法時橘茉,就創(chuàng)建了一個新的String對象(40字節(jié))工腋,但仍然重用了相同的value[]數(shù)組,因此該字符串的子字符串只會使用40字節(jié)的內(nèi)存畅卓,也就是說擅腰,一個子字符串的額外內(nèi)存是一個常數(shù),構(gòu)造一個子字符串所需時間也是常數(shù)翁潘。

舉例:union-find 算法

動態(tài)連通性

問題的輸入是一列整數(shù)對趁冈,其中每個整數(shù)都表示一個某種類型的對象,一對整數(shù)p q可以被理解為“p和q是相連的”拜马,具有如下性質(zhì)渗勘;

  • 自反性:p和p是
  • 對稱性:若p和q相連,則q和p也相連
  • 傳遞性:若p和q相連且q和r相連俩莽,則p和r也相連

union-find 算法API

方法名 操作
UF(int N) 以整數(shù)標(biāo)識(0到N-1)初始化N個觸點
void union(int p, int q) 在p和q之間添加一條連接
int find(int p) p所在的分量的標(biāo)識符(0到N-1)
boolean connected(int p, int q) 如果p和q存在于同一個分量則返回true
int count() 連通分量的數(shù)量

用一個以觸點為索引的數(shù)組id[]作為基本數(shù)據(jù)結(jié)構(gòu)來表示所有分量呀邢。初始有N個分量,每個觸點都構(gòu)成了一個只含有它自己的分量豹绪,因此將id[i]初始化為i,其中i為0到N-1申眼。find()判定它所在分量所需的信息保存在id[i]之中瞒津。connected()方法只有一條語句find(p)==find(q),它返回一個布爾值括尸。

實現(xiàn)

1 quick-find 算法

代碼
public class UF{
    private int[] id;
    private int count;
    public UF(int N){
        id = new int[N];
        count = N;
        for(int i = 0; i < N; i++){
            id[i] = i;
        }
    }
    public void union(int p, int q){
        int pid = find(p);
        int qid = find(q);
        if(pid == qid){
            return;
        }
        for(int i = 0; i < id.length; i++){
            if(id[i] == pid){
                id[i] = qid;
            }
        }
        count--;
    }
    public int find(int p){
        return id[p];
    }
    public boolean connected(int p, int q){
        return id[p] == id[q];
    }
    public int count(){
        return count;
    }
}
分析

上述代碼的find方法十分高效巷蚪,因為僅僅需要一次數(shù)組讀取操作就能夠找到該節(jié)點的組號,但是問題隨之而來濒翻,對于需要添加新路徑的情況屁柏,就涉及到對于組號的修改啦膜,因為并不能確定哪些節(jié)點的組號需要被修改,因此就必須對整個數(shù)組進(jìn)行遍歷淌喻,找到需要修改的節(jié)點僧家,逐一修改,每次添加新路徑帶來的復(fù)雜度就是線性關(guān)系了裸删,如果要添加的新路徑的數(shù)量是M八拱,節(jié)點數(shù)量是N,那么最后的時間復(fù)雜度就是MN涯塔,顯然是一個平方階的復(fù)雜度肌稻,對于大規(guī)模的數(shù)據(jù)而言,平方階的算法是存在問題的匕荸,這種情況下爹谭,每次添加新路徑就是“牽一發(fā)而動全身”,想要解決這個問題榛搔,關(guān)鍵就是要提高union方法的效率诺凡,讓它不再需要遍歷整個數(shù)組。

2 quick-union 算法

代碼

id[] 數(shù)組用父鏈接的形式表示了一片森林药薯,從任何觸點所對應(yīng)的節(jié)點開始跟隨鏈接绑洛,最終都將到達(dá)含有該節(jié)點的樹的根節(jié)點。quick-union 中 union() 的實現(xiàn)只用了一條語句就將一個根節(jié)點變?yōu)榱硪粋€根節(jié)點的父節(jié)點童本,從而歸并了兩棵樹真屯。

public class QuickUnionUF{
    private int[] id;
    private int count;
    public QuickUnionUF(int N){
        id = new int[N];
        count = N;
        for(int i = 0; i < N; i++){
            id[i] = i;
        }
    }
    public void union(int p, int q){
        int pRoot = find(p);
        int qRoot = find(q);
        if(pRoot == qRoot){
            return;
        }
        id[pRoot] = qRoot;
        count--;
    }
    public int find(int p){
        while(p != id[p]){
            p = id[p];
        }
        return p;
    }
    public boolean connected(int p, int q){
        return find(p) == find(q);
    }
    public int count(){
        return count;
    }
}
分析
quick-union算法概述.png

quick-union 算法看起來比 quick-find 算法更快,因為它不需要為每對輸入遍歷整個數(shù)組穷娱。但樹這種數(shù)據(jù)結(jié)構(gòu)容易出現(xiàn)極端情況绑蔫,因為在建樹的過程中,樹的最終形態(tài)嚴(yán)重依賴于輸入數(shù)據(jù)本身的性質(zhì)泵额,比如數(shù)據(jù)是否排序配深,是否隨機(jī)分布等等。比如在輸入數(shù)據(jù)是有序的情況下嫁盲,構(gòu)造的BST會退化成一個鏈表篓叶。如下圖所示。

quick-union最壞情況.png

定義:一棵樹的大小是它的節(jié)點的數(shù)量羞秤。樹中一個節(jié)點的深度是它到根節(jié)點的路徑上的鏈接(也就是邊)數(shù)缸托。樹的高度是它的所有節(jié)點中的最大深度。
命題:quick-union 算法中 find() 方法訪問數(shù)組的次數(shù)為1加上給定觸點所對應(yīng)的節(jié)點的深度的兩倍瘾蛋。union() 和 connected() 訪問數(shù)組的次數(shù)為兩次find() 操作(若union()中給定的兩個觸點分別存在于不同的樹中則還需要加1)俐镐。

由命題G可知,對于整數(shù)對0 i哺哼,union()操作訪問數(shù)組的次數(shù)為2i+2(觸點0的深度為i佩抹,觸點i的深度為0)叼风。因此,處理N對整數(shù)所需的所有find()操作訪問數(shù)組的總次數(shù)為2(1+2+...+N)~N^2棍苹。

3 加權(quán) quick-union 算法

只需簡單修改quick-union算法就能保證像這樣的最壞情況不會出現(xiàn)无宿。與其在 union() 中隨意將一棵樹連接到另一棵樹,現(xiàn)在記錄每棵樹的大小并總是將較小的樹連接到大數(shù)上廊勃。此時需添加一個數(shù)組來記錄樹中節(jié)點數(shù)懈贺,將這種算法稱為加權(quán) quick-union 算法。該算法構(gòu)造的樹的高度也遠(yuǎn)遠(yuǎn)小于未加權(quán)版本構(gòu)造的樹的高度坡垫。

代碼
public class WeightedUnionUF{
    private int[] id;
    private int count;
    private int[] sz;
    public WeightedUnionUF(int N){
        id = new int[N];
        count = N;
        for(int i = 0; i < N; i++){
            id[i] = i;
            sz[i] = 1;
        }
    }
    public void union(int p, int q){
        int pRoot = find(p);
        int qRoot = find(q);
        if(pRoot == qRoot){
            return;
        }
        //比較樹大小梭灿,小樹指向大樹,修改大樹大小
        if(sz[pRoot] < sz[qRoot]){
            id[pRoot] = qRoot;
            sz[qRoot] += sz[pRoot];
        }else{
            id[qRoot] = pRoot;
            sz[pRoot] += sz[qRoot];
        }
        count--;
    }
    public int find(int p){
        while(p != id[p]){
            p = id[p];
        }
        return p;
    }
    public boolean connected(int p, int q){
        return find(p) == find(q) ;
    }
    public int count(){
        return count;
    }
}
分析

命題H:對于N個觸點冰悠,加權(quán)quick-union算法夠早的森林中的任意節(jié)點的深度最多為lgN堡妒。
推論:對于加權(quán)quick-union算法和N個觸點,在最壞情況下find()溉卓、connected()和union()的成本增長數(shù)量級為logN皮迟。

命題H和推論的意義在于加權(quán)quick-union算法是三種算法中唯一可以用于解決大型實際問題的算法。加權(quán)quick-union算法處理N個觸點和M條連接時最多訪問數(shù)組cMlgN次桑寨,c為常數(shù)伏尼,比quick-find(和某些情況下的quick-union)需要訪問數(shù)組至少M(fèi)N次形成鮮明對比。

各種union-find算法性能特點.png

4 最優(yōu)算法——基于quick-union 算法進(jìn)行路徑壓縮

理想情況下尉尾,我們希望每個結(jié)點都直接鏈接到它的根節(jié)點爆阶,但又不想像quick-find那樣通過修改大量鏈接實現(xiàn),實際實現(xiàn)很簡單——在檢查節(jié)點時將它們直連到根節(jié)點沙咏。

public int find(int p){
    while(p != id[p]){
        //若當(dāng)前節(jié)點非根節(jié)點
        //則使當(dāng)前節(jié)點指向父節(jié)點的父節(jié)點/或直接指向根節(jié)點find(index)
        id[p] = id[id[p]];
        p = id[p];
    }
    return p;
}

所得到的結(jié)果幾乎是完全扁平化的樹辨图,即路徑壓縮的加權(quán)quick-union算法是最優(yōu)的算法

參考:

并查集(Union-Find)算法介紹

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市肢藐,隨后出現(xiàn)的幾起案子故河,更是在濱河造成了極大的恐慌,老刑警劉巖吆豹,帶你破解...
    沈念sama閱讀 206,311評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件鱼的,死亡現(xiàn)場離奇詭異,居然都是意外死亡痘煤,警方通過查閱死者的電腦和手機(jī)凑阶,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來速勇,“玉大人,你說我怎么就攤上這事坎拐》炒牛” “怎么了养匈?”我有些...
    開封第一講書人閱讀 152,671評論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長都伪。 經(jīng)常有香客問我呕乎,道長,這世上最難降的妖魔是什么陨晶? 我笑而不...
    開封第一講書人閱讀 55,252評論 1 279
  • 正文 為了忘掉前任猬仁,我火速辦了婚禮,結(jié)果婚禮上先誉,老公的妹妹穿的比我還像新娘湿刽。我一直安慰自己,他們只是感情好褐耳,可當(dāng)我...
    茶點故事閱讀 64,253評論 5 371
  • 文/花漫 我一把揭開白布诈闺。 她就那樣靜靜地躺著,像睡著了一般铃芦。 火紅的嫁衣襯著肌膚如雪雅镊。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,031評論 1 285
  • 那天刃滓,我揣著相機(jī)與錄音仁烹,去河邊找鬼。 笑死咧虎,一個胖子當(dāng)著我的面吹牛卓缰,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播老客,決...
    沈念sama閱讀 38,340評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼僚饭,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了胧砰?” 一聲冷哼從身側(cè)響起鳍鸵,我...
    開封第一講書人閱讀 36,973評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎尉间,沒想到半個月后偿乖,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,466評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡哲嘲,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,937評論 2 323
  • 正文 我和宋清朗相戀三年贪薪,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片眠副。...
    茶點故事閱讀 38,039評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡画切,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出囱怕,到底是詐尸還是另有隱情霍弹,我是刑警寧澤毫别,帶...
    沈念sama閱讀 33,701評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站典格,受9級特大地震影響岛宦,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜耍缴,卻給世界環(huán)境...
    茶點故事閱讀 39,254評論 3 307
  • 文/蒙蒙 一砾肺、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧防嗡,春花似錦变汪、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至荣德,卻和暖如春闷煤,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背涮瞻。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工鲤拿, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人署咽。 一個月前我還...
    沈念sama閱讀 45,497評論 2 354
  • 正文 我出身青樓近顷,卻偏偏與公主長得像,于是被迫代替她去往敵國和親宁否。 傳聞我的和親對象是個殘疾皇子窒升,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,786評論 2 345