插入排序算法和歸并排序算法的分水嶺坯台,你造嗎?

很多情況下瘫寝,只要跟算法相關(guān)的可能都有現(xiàn)成的開(kāi)源工具包拿來(lái)就用蜒蕾,比如排序算法

現(xiàn)實(shí)業(yè)務(wù)開(kāi)發(fā)時(shí)焕阿,為了提升效率咪啡,確實(shí)建議使用現(xiàn)成的成熟的開(kāi)源工具。

但是暮屡,我們?nèi)绻恢朗褂瞄_(kāi)源工具包的話撤摸,如果有一天你去面試被問(wèn)到的話...

所以,我們應(yīng)用抽點(diǎn)時(shí)間褒纲,去學(xué)習(xí)我們常用的工具包或框架的實(shí)現(xiàn)思想和細(xì)節(jié)准夷。以備急需。

這也就是我們需要不停學(xué)習(xí)的原因之一吧莺掠。

插入排序算法

排序算法場(chǎng)景不用多說(shuō)了衫嵌,比如棋牌游戲中一鍵排序撲克牌,比如電商系統(tǒng)按銷量從高到低排序等等彻秆。

排序算法實(shí)現(xiàn)有多種渐扮,比如插入排序论悴,希爾排序,快速排序墓律,冒泡排序膀估,歸并排序等等。排序算法其實(shí)大有講究耻讽,每一種算法在不同的輸入規(guī)模需要的時(shí)間均不一樣察纯。用專業(yè)術(shù)語(yǔ)來(lái)說(shuō)就是他們的時(shí)間復(fù)雜度不同。下面我們主要來(lái)看一下插入排序和歸并排序的實(shí)現(xiàn)原理和不同輸入規(guī)模下需要的計(jì)算時(shí)間针肥。

圖解原理

下面我們通過(guò)畫圖+文字方式來(lái)講解插入排序原理饼记。先假定某此會(huì)議有四個(gè)人,會(huì)議要求他們坐成一排慰枕,且按編號(hào)從左邊開(kāi)始從小到大排列具则,假定他們到場(chǎng)初始化坐位編號(hào)排序?yàn)?/span>:

?

現(xiàn)在需要按照會(huì)議要求按編號(hào)從小到大進(jìn)行排序具帮,其中一人就提議按照插入排序法方式進(jìn)行位置的調(diào)整,從第二位開(kāi)始(插入排序法一般從第二項(xiàng)開(kāi)始)進(jìn)行匪凡,具體如下:

?

編號(hào)5開(kāi)始調(diào)整座位:

?

?

編號(hào)3調(diào)整座位:

?

編號(hào)6調(diào)整座位:

?

?


可以看到病游,現(xiàn)在順序變成:

?

?

代碼實(shí)現(xiàn)

排序算法:

?

public static void insertSortAsc(Integer[] source) {    if (source == null || source.length < 1) {        return;    }    int len = source.length;    //i = 1表示從第二項(xiàng)開(kāi)始稠通,下標(biāo)從0開(kāi)始    for (int i = 1; i < len; i++) {        //當(dāng)前項(xiàng)        int currentVal = source[i];        //當(dāng)前項(xiàng)的前面項(xiàng)最大索引        int j = i - 1;        //j >= 0表示當(dāng)前存在哥們衬衬,需要繼續(xù)詢問(wèn)        //currentVal < source[j]表示詢問(wèn)你比我大么?        while (j >= 0 && currentVal < source[j]) {            //進(jìn)入這里表示前面項(xiàng)確實(shí)比我大            //source[j + 1] = source[j]表示那你往后挪一步            source[j + 1] = source[j];            //繼續(xù)向前詢問(wèn)            j--;        }        //前面哥們比自己小改橘,或者前面沒(méi)有哥們滋尉,回退一步回來(lái)坐下        source[j + 1] = currentVal;    }}

?

測(cè)試:

public static void main(String[] args) {
Integer[] test = {7,5,3,6};
System.out.println("排序前:" + JSON.toJSONString(test)); insertSortAsc(test); System.out.println("排序后:" + JSON.toJSONString(test));}


?

輸出:

?

?

復(fù)雜度分析

插入排序算法的時(shí)間復(fù)雜度范圍為:?O(n) - O(n * n),其中n為輸入項(xiàng)的數(shù)量唧龄,比如上面輸入項(xiàng)為4項(xiàng),最好情況下時(shí)間復(fù)雜度為O(n)奸远。最好情況一般指輸入項(xiàng)本身已經(jīng)排好順序既棺。最差和平均情況時(shí)間復(fù)雜度為O(n * n),最差情況一般指輸入項(xiàng)本身按目標(biāo)排序反向順序懒叛。時(shí)間一般指執(zhí)行所耗費(fèi)的時(shí)間

?

插入排序算法空間復(fù)雜度為O(1)丸冕,表示不管輸入項(xiàng)數(shù)量大小,運(yùn)行核心計(jì)算所需要的空間相較于其他排序算法來(lái)說(shuō)是固定的薛窥,空間一般指內(nèi)存使用胖烛。

歸并排序算法

歸并算法類似二分查找眼姐,它遵循分治法范疇,分治法的核心思想是將原問(wèn)題分解為幾個(gè)規(guī)模較小但類似于原問(wèn)題的子問(wèn)題佩番。然后通過(guò)遞歸的方式去求解這些子問(wèn)題众旗。最后一層一層的合并子問(wèn)題的解來(lái)得到原問(wèn)題的解。

分治法在每層遞歸一般有以下三個(gè)步驟:

分解:分解原問(wèn)題為若干子問(wèn)題

求解:遞歸求解子問(wèn)題

合并:合并子問(wèn)題的解得到原問(wèn)題的解

歸并排序算法實(shí)現(xiàn)分治法其原理如下:

分解:遞歸分解待排序的n個(gè)元素的序列/數(shù)組為n/2個(gè)子序列/數(shù)組

求解:遞歸排序一分為二的兩個(gè)子序列

合并:遞歸合并一分為二已經(jīng)排好序的序列

文字描述可能太抽象和枯燥趟畏,下面我們圖解算法方式講解算法的實(shí)現(xiàn)原理

圖解原理

我們復(fù)用上面的案例來(lái)講解通過(guò)歸并算法如何實(shí)現(xiàn)會(huì)議座位從左到右從小到大排序赋秀。

上面待排序項(xiàng)如下:

?

分解歸并排序如下:

?

上圖完全展示了歸并算法的核心思想绍弟,分解 -> 排序 -> 歸并樟遣。就拿5年碘,7屿衅,36排序來(lái)講解响迂,具體步驟如下:

分解:5蔗彤,7然遏,36大致對(duì)半分解為兩組子問(wèn)題5秧倾,73那先,6胃榕,繼續(xù)將5苦掘,7大致按對(duì)半分解為兩組57鹤啡。同樣也將3递瑰,6按對(duì)半分解為36兩組子問(wèn)題,此時(shí)對(duì)n項(xiàng)輸入分解為6個(gè)子問(wèn)題來(lái)處理

求解:從低往上排序慎颗,例如7排序后得到7俯萎,5排序后得到5,然后進(jìn)行遞歸合并撇眯。

首次歸并將7熊榛,5歸并為一組来候,將3营搅,6歸并為一組。歸并過(guò)程進(jìn)行求解(排序)休蟹,將7赂弓,5排序得到5盈魁,7,將3珊膜,6排序得到3车柠,6

繼續(xù)歸并將5溶褪,73猿妈,6歸并為57俯抖,3芬萍,6柬祠。歸并過(guò)程進(jìn)行求解(排序)所以最后得到3嗜愈,6蠕嫁,57迟赃。

合并:由于合并和求解在遞歸過(guò)程并行纤壁,所以合并操作在求解中已經(jīng)有所體現(xiàn)酌媒。

核心解釋:每次排序是將兩個(gè)子問(wèn)題進(jìn)行排序,每個(gè)子問(wèn)題實(shí)際是放到兩個(gè)序列/數(shù)組上的雨席,比如75這兩個(gè)分別放到不同的數(shù)組陡厘。然后在循環(huán)迭代體將這兩個(gè)進(jìn)行比較,把小的放前面谤饭,大的放后面保存到另一個(gè)數(shù)組中宜岛。

代碼實(shí)現(xiàn)

遞歸按n/2進(jìn)行分解,當(dāng)分解為最小子問(wèn)題時(shí)(遞歸到只有一個(gè)元素不可再分解時(shí))辟汰,遞歸開(kāi)始回升帖汞,進(jìn)入歸并排序,回升一層催首,歸并排序一層

/** * 通過(guò)分治算法實(shí)現(xiàn)歸并排序 * * @param source     需要排序的List * @param beginIndex 開(kāi)始下標(biāo) 從0開(kāi)始,包含開(kāi)始下標(biāo) * @param endIndex   結(jié)束下標(biāo)  從0開(kāi)始舶治, 包含結(jié)束下標(biāo) */public static void mergeSortAsc(List<Integer> source, int beginIndex, int endIndex) {    //當(dāng)前面一次遞歸只有一個(gè)元素時(shí),開(kāi)始回升當(dāng)前遞歸    if (beginIndex < endIndex) {        int subIndex = (beginIndex + endIndex) / 2;        //排序左邊段惜浅,當(dāng)遞歸回升后左邊所有已經(jīng)排好序        mergeSortAsc(source, beginIndex, subIndex);        //排序右邊段,當(dāng)遞歸回升后右邊所有已經(jīng)排好序        mergeSortAsc(source, subIndex + 1, endIndex);        //合并并排序前面遞歸排好序的兩段        doMergeSortAsc(source, beginIndex, subIndex, endIndex);    }}


歸并排序:

private static void doMergeSortAsc(List<Integer> source, int beginIndex, int subBeginIndex, int endIndex) {
if (SetsUtils.isEmpty(source)) { return; } if (endIndex > source.size()) { throw new RuntimeException("endIndex Not greater than source size"); }
int num1 = subBeginIndex - beginIndex + 1; int num2 = endIndex - subBeginIndex;
List<Integer> left = SetsUtils.list(num1); List<Integer> right = SetsUtils.list(num2);
for (int i = 0; i < num1; i++) { left.add(source.get(beginIndex + i)); } left.add(num1, Integer.MAX_VALUE);
for (int i = 0; i < num2; i++) { right.add(source.get(subBeginIndex + i + 1)); } right.add(num2, Integer.MAX_VALUE);

int i = 0; int j = 0;
for (int k = beginIndex; k <= endIndex; k++) { if (left.get(i) > right.get(j)) { source.set(k, left.get(i)); i++; } else { source.set(k, right.get(j)); j++; } }}


測(cè)試:

?

public static void main(String[] args) {
List<Integer> test = SetsUtils.list(); test.add(7); test.add(5); test.add(3); test.add(6);
System.out.println("排序前:" + JSON.toJSONString(test)); mergeSortAsc(test, 0, test.size() -1); System.out.println("排序后:" + JSON.toJSONString(test));
}

?

輸出:

?

?

復(fù)雜度分析

歸并排序算法的時(shí)間復(fù)雜度為:?O(nlogn))画饥,其中n為輸入項(xiàng)的數(shù)量抖甘。時(shí)間一般指執(zhí)行所耗費(fèi)的時(shí)間

?

歸并排序算法空間復(fù)雜度為O(n),輸入項(xiàng)數(shù)量越大艰额,運(yùn)行核心計(jì)算所需要的空間相較于其他排序算法來(lái)說(shuō)越大柄沮,空間一般指內(nèi)存使用祖搓。

對(duì)比

對(duì)比一般需要得出適用場(chǎng)景的結(jié)論棕硫,一般通過(guò)分析時(shí)間復(fù)雜度和空間復(fù)雜度進(jìn)行定論哈扮,很多人喜歡討論時(shí)間換空間滑肉,空間換時(shí)間靶庙,這些都是一些術(shù)語(yǔ)而已六荒,進(jìn)一步是要詳細(xì)分析計(jì)算出算法的具體時(shí)間復(fù)雜度和空間復(fù)雜度掏击,然后得出結(jié)論灯变。

我們?nèi)绾卧诙喾N不同的排序算法中選擇一種算法添祸,其實(shí)者首先取決于我們的關(guān)注的刃泌,比如我們的計(jì)算機(jī)有足夠的空間去浪費(fèi)耙替,我們需要的是速度,例如電商網(wǎng)站的商品排序混坞。那么我們就應(yīng)該分析不同算法的時(shí)間復(fù)雜度然后進(jìn)行對(duì)比。相反爹凹,如果我們更多的關(guān)注內(nèi)存占用而不關(guān)心時(shí)間禾酱,例如大數(shù)據(jù)分析的任務(wù)調(diào)度颗管,那么我們就應(yīng)該分析不同算法的空間復(fù)雜度然后進(jìn)行對(duì)比垦江。由于時(shí)間問(wèn)題比吭,本篇不詳細(xì)計(jì)算插入排序和歸并排序的時(shí)間復(fù)雜度,后面再寫一篇文章進(jìn)行補(bǔ)充涛漂,筆者這里先給出答案:

1底哗、插入排序法和歸并排序法的時(shí)間復(fù)雜度對(duì)比取決于輸入規(guī)模

2跋选、當(dāng)輸入規(guī)模大于1W時(shí)歸并排序法優(yōu)于插入排序法

3坠韩、相反炼列,當(dāng)輸入規(guī)模小于1W時(shí)俭尖,插入排序法優(yōu)于歸并排序法

4稽犁、當(dāng)輸入規(guī)模達(dá)到10W以上時(shí)熊赖,不推薦使用插入排序法

5震鹉、當(dāng)輸入規(guī)模達(dá)到20W以上時(shí),不推薦使用歸并排序法

對(duì)比代碼

public static void main(String[] args) {    List<Integer> test = SetsUtils.list();    int i = 10;    Random r = new Random();    while (i++ < 50000) {        test.add(r.nextInt(10000));    }    List<Integer> test1 = new ArrayList<>(test);    List<Integer> test2 = new ArrayList<>(test);
long t = System.currentTimeMillis();
System.out.println(JSON.toJSONString(test1)); mergeSortAsc(test1, 0, test1.size() - 1); System.out.println(JSON.toJSONString(test1));
long t2 = System.currentTimeMillis();
System.out.println("歸并排序算法耗時(shí):" + (t2 - t) + "ms");
long t3 = System.currentTimeMillis();
System.out.println(JSON.toJSONString(test2)); insertSortAsc(test2); System.out.println(JSON.toJSONString(test2));
long t4 = System.currentTimeMillis();
System.out.println("插入排序算法耗時(shí):" + (t4 - t3) + "ms");}


?

對(duì)比結(jié)果

?


?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市蝶棋,隨后出現(xiàn)的幾起案子玩裙,更是在濱河造成了極大的恐慌吃溅,老刑警劉巖螺垢,帶你破解...
    沈念sama閱讀 211,194評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件枉圃,死亡現(xiàn)場(chǎng)離奇詭異孽亲,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)犯祠,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,058評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)痰娱,“玉大人梨睁,你說(shuō)我怎么就攤上這事”榉兀” “怎么了愿伴?”我有些...
    開(kāi)封第一講書人閱讀 156,780評(píng)論 0 346
  • 文/不壞的土叔 我叫張陵鹅经,是天一觀的道長(zhǎng)瘾晃。 經(jīng)常有香客問(wèn)我,道長(zhǎng)赞庶,這世上最難降的妖魔是什么剩燥? 我笑而不...
    開(kāi)封第一講書人閱讀 56,388評(píng)論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮阀圾,結(jié)果婚禮上哪廓,老公的妹妹穿的比我還像新娘。我一直安慰自己初烘,他們只是感情好涡真,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,430評(píng)論 5 384
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著肾筐,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書人閱讀 49,764評(píng)論 1 290
  • 那天座菠,我揣著相機(jī)與錄音,去河邊找鬼甜紫。 笑死拓型,一個(gè)胖子當(dāng)著我的面吹牛压固,可吹牛的內(nèi)容都是我干的拦键。 我是一名探鬼主播碳柱,決...
    沈念sama閱讀 38,907評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼越妈!你這毒婦竟也來(lái)了店归?” 一聲冷哼從身側(cè)響起秩伞,我...
    開(kāi)封第一講書人閱讀 37,679評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤毡代,失蹤者是張志新(化名)和其女友劉穎酪耕,沒(méi)想到半個(gè)月后藏斩,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,122評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡帆赢,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,459評(píng)論 2 325
  • 正文 我和宋清朗相戀三年僻造,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片轩娶。...
    茶點(diǎn)故事閱讀 38,605評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡贤重,死狀恐怖借卧,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情戏售,我是刑警寧澤,帶...
    沈念sama閱讀 34,270評(píng)論 4 329
  • 正文 年R本政府宣布功偿,位于F島的核電站颤诀,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏爽室。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,867評(píng)論 3 312
  • 文/蒙蒙 一狐榔、第九天 我趴在偏房一處隱蔽的房頂上張望尽纽。 院中可真熱鬧,春花似錦弥搞、人聲如沸酱鸭。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 30,734評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)祝蝠。三九已至邻耕,卻和暖如春御滩,著一層夾襖步出監(jiān)牢的瞬間氛驮,已是汗流浹背盏缤。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 31,961評(píng)論 1 265
  • 我被黑心中介騙來(lái)泰國(guó)打工拆宛, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留厂榛,地道東北人痰驱。 一個(gè)月前我還...
    沈念sama閱讀 46,297評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像瞳浦,于是被迫代替她去往敵國(guó)和親担映。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,472評(píng)論 2 348

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

  • 歸并排序和快速排序都用到了分治思想,非常巧妙矗蕊。我們可以借鑒這個(gè)思想短蜕,來(lái)解決非排序的問(wèn)題。 歸并排序 歸并排序的核心...
    被吹落的風(fēng)閱讀 1,326評(píng)論 0 3
  • 一些概念 數(shù)據(jù)結(jié)構(gòu)就是研究數(shù)據(jù)的邏輯結(jié)構(gòu)和物理結(jié)構(gòu)以及它們之間相互關(guān)系拔妥,并對(duì)這種結(jié)構(gòu)定義相應(yīng)的運(yùn)算忿危,而且確保經(jīng)過(guò)這...
    Winterfell_Z閱讀 5,695評(píng)論 0 13
  • 概述 排序有內(nèi)部排序和外部排序,內(nèi)部排序是數(shù)據(jù)記錄在內(nèi)存中進(jìn)行排序没龙,而外部排序是因排序的數(shù)據(jù)很大铺厨,一次不能容納全部...
    蟻前閱讀 5,168評(píng)論 0 52
  • 搞懂基本排序算法 上篇文章寫了關(guān)于 Java 內(nèi)部類的基本知識(shí)缎玫,感興趣的朋友可以去看一下:搞懂 JAVA 內(nèi)部類;...
    醒著的碼者閱讀 1,176評(píng)論 3 4
  • 一. 寫在前面 要學(xué)習(xí)算法解滓,“排序”是一個(gè)回避不了的重要話題赃磨,在分析完并查集算法和常用數(shù)據(jù)結(jié)構(gòu)之后,今天我們終于可...
    Leesper閱讀 2,523評(píng)論 0 40