常見排序的Java實(shí)現(xiàn)

背景

通過對(duì)常見排序的規(guī)則了解涣易,按照自己理解實(shí)現(xiàn)。其實(shí)部分代碼可再簡(jiǎn)化缨硝,為了更好的體現(xiàn)自己的實(shí)現(xiàn)步驟就沒簡(jiǎn)化击罪,這樣有利于之后回顧思路乌妒。

常見排序

  • 冒泡排序
  • 選擇排序
  • 快速排序
  • 歸并排序
  • 堆排序
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;

import java.util.Arrays;

/**
 * @author XiaoJia
 * @since 2020/5/3 21:21
 */
public class SimpleSortTest {
    int[] numbers;

    @Before
    public void before() {
        numbers = new int[]{0, 8, 7, 6, 5, 3, 4, 9, 2, 1};
    }

    @After
    public void after() {
        Assert.assertEquals(Arrays.toString(numbers), "[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]");
    }

    /**
     * 冒泡排序
     * <p>
     * 規(guī)則:按照升序(或降序)每次從第一個(gè)元素開始選出最大(或最小)的數(shù)
     * <p>
     * 時(shí)間復(fù)雜度:(n-1)+(n-2)+...+1 = n(n-1) / 2 看做 n^2
     */
    @Test
    public void bubbleSort() {
        // 每次獲取最大數(shù)的填充位置 i
        for (int i = numbers.length - 1; i > 0; i--) {
            // 正在比較的數(shù)外邓,通過交換位置保持比較結(jié)果
            for (int j = 0; j < i; j++) {
                if (numbers[j] > numbers[j + 1]) {
                    int temp = numbers[j + 1];
                    numbers[j + 1] = numbers[j];
                    numbers[j] = temp;
                }
            }
        }
    }

    /**
     * 選擇排序
     * <p>
     * 規(guī)則:依次為每個(gè)位置選擇最小的數(shù)組(相對(duì)冒泡排序無需不必要的數(shù)據(jù)位置交換)
     * <p>
     * 時(shí)間復(fù)雜度:(n-1)+(n-2)+...+1 = n(n-1) / 2 看做 n^2
     */
    @Test
    public void selectSort() {
        // 每次存放選擇結(jié)果的位置 - i
        for (int i = 0; i < numbers.length - 1; i++) {
            // 存放臨時(shí)比較的最大值與下標(biāo)
            int temp = numbers[i];
            int tempIndex = i;
            // 比較兩個(gè)數(shù)得較小值
            for (int j = i + 1; j < numbers.length; j++) {
                if (temp > numbers[j]) {
                    temp = numbers[j];
                    tempIndex = j;
                }
            }
            numbers[tempIndex] = numbers[i];
            numbers[i] = temp;
        }
    }

    /**
     * 快速排序
     * <p>
     * 規(guī)則:將數(shù)據(jù)拆分左右兩個(gè)集合撤蚊,左邊集合所有元素必須都小于右邊集合。如果集合元素大于1則按相同規(guī)則繼續(xù)拆分
     * <p>
     * 時(shí)間復(fù)雜度:
     * 1. 最壞情況:每次拆分左邊集合都只有一個(gè)元素损话,則時(shí)間復(fù)雜度與冒泡一樣 n(n-1) / 2
     * 2. 最好情況:每次拆分左右集合元素相仿侦啸,則經(jīng)過 log2(n) 次則拆分完成,每次拆分都會(huì)進(jìn)行 n-1 次比較
     * 故 時(shí)間復(fù)雜度為 (n-1)log2(n) 看做 nlog2(n)
     */
    @Test
    public void quickSort() {
        // 選擇第一個(gè)元素作為拆分元素丧枪,小于該元素則與其交互位置
        quickSplit(0, numbers.length - 1);
    }

    /**
     * 歸并排序
     * <p>
     * 規(guī)則:分為兩個(gè)步驟
     * 步驟一:拆分?jǐn)?shù)據(jù)光涂,每次將數(shù)據(jù)均分兩個(gè)集合,直到集合元素個(gè)數(shù)為1
     * 步驟二:合并數(shù)據(jù)拧烦,依次從元素小的集合合并成較大的有序集合忘闻,直到所有集合都被合并
     * <p>
     * 時(shí)間復(fù)雜度:數(shù)據(jù)拆分次數(shù)為log2(n),最壞情況是每個(gè)元素歸并時(shí)都需要比較一次恋博,
     * 故 時(shí)間復(fù)雜度為 (n-1)log2(n)
     */
    @Test
    public void mergeSort() {
        int[] result = new int[numbers.length];
        mergeSplitAndMerge(result, 0, numbers.length - 1);
    }

    /**
     * 堆通常是一個(gè)可以被看做一棵樹的數(shù)組對(duì)象齐佳。堆總是滿足下列性質(zhì):
     * 1、堆中某個(gè)節(jié)點(diǎn)的值總是不大于或不小于其父節(jié)點(diǎn)的值债沮;
     * 2炼吴、堆總是一棵完全二叉樹。
     * <p>
     * 堆排序
     * <p>
     * 規(guī)則:先將無序集合調(diào)整為符合堆規(guī)范的集合疫衩,再將堆頂元素作為已排好序的數(shù)填充到指定位置(從最后位置到第一個(gè)位置依次填充)
     * 最大堆:父節(jié)點(diǎn)總大于或等于左右節(jié)點(diǎn)
     * 最小堆:父節(jié)點(diǎn)總小于或等于左右節(jié)點(diǎn)
     * <p>
     * 時(shí)間復(fù)雜度:log2(n) + log2(n-1) + ... + log2(1) = log2(n) * n / 2 看做 nlog2(n)
     */
    @Test
    public void headSort() {
        // 從最后一個(gè)非葉子節(jié)點(diǎn)從下往上硅蹦、從右往左依次獲取最大的父節(jié)點(diǎn)(最后一個(gè)元素的父節(jié)點(diǎn)為最后一個(gè)非葉子節(jié)點(diǎn))
        for (int i = numbers.length / 2 - 1; i >= 0; i--) {
            adjustNode(i, numbers[i], numbers.length);
        }

        // 依次將通過堆獲取的最大值保存到當(dāng)次排序值的末尾,然后從上往下、從左往右依次獲取最大的父節(jié)點(diǎn)
        for (int i = numbers.length - 1; i > 0; i--) {
            int temp = numbers[0];
            numbers[0] = numbers[i];
            numbers[i] = temp;
            adjustNode(0, numbers[0], i);
        }
    }

    private void quickSplit(int begin, int end) {
        if (begin >= end) {
            return;
        }

        // 記錄當(dāng)前拆分標(biāo)準(zhǔn)的位置
        int compare = numbers[begin];
        // 記錄開始切分的位置
        int splitIndex = begin;
        for (int i = begin + 1; i <= end; i++) {
            if (compare > numbers[i]) {
                splitIndex++;
                // 判斷是否交互位置
                if (splitIndex != i) {
                    int temp = numbers[i];
                    numbers[i] = numbers[splitIndex];
                    numbers[splitIndex] = temp;
                }
            }
        }

        // 拆分標(biāo)準(zhǔn)的位置與最后一個(gè)小于拆分標(biāo)準(zhǔn)的調(diào)換位置
        if (splitIndex != begin) {
            int temp = numbers[splitIndex];
            numbers[splitIndex] = numbers[begin];
            numbers[begin] = temp;
        }

        quickSplit(begin, splitIndex - 1);
        quickSplit(splitIndex + 1, end);
    }

    private void mergeSplitAndMerge(int[] temp, int begin, int end) {
        if (begin >= end) {
            return;
        }

        // 拆分并合并子數(shù)據(jù)
        int splitIndex = begin + (end - begin + 1) / 2 - 1;
        mergeSplitAndMerge(temp, begin, splitIndex);
        mergeSplitAndMerge(temp, splitIndex + 1, end);

        // 合并數(shù)據(jù)
        int firstIndex = begin;
        int secondIndex = splitIndex + 1;
        for (int i = begin; i <= end; i++) {
            if (numbers[firstIndex] < numbers[secondIndex]) {
                temp[i] = numbers[firstIndex];
                firstIndex++;
            } else {
                temp[i] = numbers[secondIndex];
                secondIndex++;
            }

            // 任何一個(gè)集合全部已排序則將另一個(gè)集合的未排序元素直接拷貝到目標(biāo)集合中
            if (firstIndex > splitIndex) {
                System.arraycopy(numbers, secondIndex, temp, i + 1, end - i);
                break;
            } else if (secondIndex > end) {
                System.arraycopy(numbers, firstIndex, temp, i + 1, end - i);
                break;
            }
        }

        System.arraycopy(temp, begin, numbers, begin, end - begin + 1);
    }

    private void adjustNode(int index, int value, int length) {
        int temp = index;
        while (true) {
            int leftIndex = 2 * (temp + 1) - 1;
            int rightIndex = 2 * (temp + 1);
            // 左右節(jié)點(diǎn)的較大值
            int maxIndex;
            if (leftIndex > length - 1) {
                break;
            } else if (rightIndex > length - 1) {
                maxIndex = leftIndex;
            } else {
                maxIndex = numbers[leftIndex] > numbers[rightIndex] ? leftIndex : rightIndex;
            }
            // 與父節(jié)點(diǎn)的比較童芹,如果父節(jié)點(diǎn)小于子節(jié)點(diǎn)則需要已子節(jié)點(diǎn)作為父節(jié)點(diǎn)再次更新
            if (numbers[maxIndex] > value) {
                numbers[temp] = numbers[maxIndex];
                numbers[maxIndex] = value;
                temp = maxIndex;
            } else {
                break;
            }
        }
    }
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末涮瞻,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子假褪,更是在濱河造成了極大的恐慌署咽,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,311評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件嗜价,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡幕庐,警方通過查閱死者的電腦和手機(jī)久锥,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來异剥,“玉大人瑟由,你說我怎么就攤上這事≡┦伲” “怎么了歹苦?”我有些...
    開封第一講書人閱讀 152,671評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)督怜。 經(jīng)常有香客問我殴瘦,道長(zhǎng),這世上最難降的妖魔是什么号杠? 我笑而不...
    開封第一講書人閱讀 55,252評(píng)論 1 279
  • 正文 為了忘掉前任蚪腋,我火速辦了婚禮,結(jié)果婚禮上姨蟋,老公的妹妹穿的比我還像新娘屉凯。我一直安慰自己,他們只是感情好眼溶,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,253評(píng)論 5 371
  • 文/花漫 我一把揭開白布悠砚。 她就那樣靜靜地躺著,像睡著了一般堂飞。 火紅的嫁衣襯著肌膚如雪灌旧。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,031評(píng)論 1 285
  • 那天绰筛,我揣著相機(jī)與錄音节榜,去河邊找鬼。 笑死别智,一個(gè)胖子當(dāng)著我的面吹牛宗苍,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 38,340評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼讳窟,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼让歼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起丽啡,我...
    開封第一講書人閱讀 36,973評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤谋右,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后补箍,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體改执,經(jīng)...
    沈念sama閱讀 43,466評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,937評(píng)論 2 323
  • 正文 我和宋清朗相戀三年坑雅,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了辈挂。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,039評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡裹粤,死狀恐怖终蒂,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情遥诉,我是刑警寧澤拇泣,帶...
    沈念sama閱讀 33,701評(píng)論 4 323
  • 正文 年R本政府宣布,位于F島的核電站矮锈,受9級(jí)特大地震影響霉翔,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜苞笨,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,254評(píng)論 3 307
  • 文/蒙蒙 一早龟、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧猫缭,春花似錦葱弟、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至射窒,卻和暖如春藏杖,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背脉顿。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來泰國(guó)打工实夹, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留匙握,地道東北人赊级。 一個(gè)月前我還...
    沈念sama閱讀 45,497評(píng)論 2 354
  • 正文 我出身青樓蚊俺,卻偏偏與公主長(zhǎng)得像敢辩,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子弟疆,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,786評(píng)論 2 345