排序算法實(shí)現(xiàn)

排序算法實(shí)現(xiàn)

記錄排序算法的實(shí)現(xiàn)康谆,有空就記錄一點(diǎn)柄慰。

冒泡排序

  • 比較相鄰元素,如果第一個(gè)元素大于第二個(gè)元素哩都,那么交換它們
  • 完成上述步驟后魁兼,最后的元素是已排序的元素
  • 對(duì)剩余未排序的元素重復(fù)上述步驟
  • 排序過(guò)程中,如果沒(méi)有發(fā)生至少一次交換漠嵌,那么完成排序

平均時(shí)間復(fù)雜度:O(n^2)
最好情況:O(n)
最壞情況:O(n^2)
空間復(fù)雜度:O(1)
排序方式:In-place
穩(wěn)定性:穩(wěn)定

public class BubbleSort {

    public static void sort(int[] arr) {
        if (arr == null || arr.length < 2) {
            return;
        }

        for (int i = 0; i < arr.length - 1; i++) {
            boolean flag = true;
            for (int j = 0; j < arr.length - 1 - i; j++) {
                // compare adjacent elements
                if (arr[j] > arr[j + 1]) {
                    int tmp = arr[j];
                    arr[j] = arr[j + 1];
                    arr[j + 1] = tmp;
                    flag = false;
                }
            }
            // sorted
            if (flag) {
                break;
            }
        }
    }

    public static void main(String[] args) {
        int[] arr = new int[]{9, 1, 8, 2, 7, 3, 6, 4, 5, 0, 2, 8, 4, 7};
        sort(arr);
    }

}

選擇排序

  • 在未排序的序列中找出最懈拦(大)元素,存放到已排序的序列的末尾
  • 再?gòu)氖S辔磁判蛟刂欣^續(xù)重復(fù)上述步驟

平均時(shí)間復(fù)雜度:O(n^2)
最好情況:O(n^2)
最壞情況:O(n^2)
空間復(fù)雜度:O(1)
排序方式:In-place
穩(wěn)定性:不穩(wěn)定

public class SelectSort {

    public static void sort(int[] arr) {
        if (arr == null || arr.length < 2) {
            return;
        }

        for (int i = 0; i < arr.length - 1; i++) {
            // select min & index
            int minIndex = i;
            int min = arr[i];
            for (int j = i + 1; j < arr.length; j++) {
                if (min > arr[j]) {
                    minIndex = j;
                    min = arr[j];
                }
            }

            // swap if needed
            if (minIndex != i) {
                int tmp = arr[i];
                arr[i] = min;
                arr[minIndex] = tmp;
            }
        }
    }

    public static void main(String[] args) {
        int[] arr = new int[]{9, 1, 8, 2, 7, 3, 6, 4, 5, 0, 2, 8, 4, 7};
        sort(arr);
    }

}

插入排序

  • 從第二個(gè)元素開(kāi)始献雅,循環(huán)到序列結(jié)束
  • 將每一個(gè)元素準(zhǔn)確的插入到前面已排序的序列中
    每次插入碉考,前面已排序的序列很多元素都可能會(huì)發(fā)生挪動(dòng)

平均時(shí)間復(fù)雜度:O(n^2)
最好情況:O(n)
最壞情況:O(n^2)
空間復(fù)雜度:O(1)
排序方式:In-place
穩(wěn)定性:穩(wěn)定

public class InsertionSort {

    public static void sort(int[] arr) {
        if (arr == null || arr.length < 2) {
            return;
        }

        int preIndex;
        int current;
        for (int i = 1; i < arr.length; i++) {
            preIndex = i - 1;
            current = arr[i];
            while (preIndex >= 0 && arr[preIndex] > current) {
                // move to right
                arr[preIndex + 1] = arr[preIndex];
                preIndex--;
            }
            // insert
            arr[preIndex + 1] = current;
        }
    }

    public static void main(String[] args) {
        int[] arr = new int[]{9, 1, 8, 2, 7, 3, 6, 4, 5, 0, 2, 8, 4, 7};
        sort(arr);
    }

}

希爾排序

  • 希爾排序可以視作冒泡排序或者插入排序的泛化
  • 根據(jù)間隙序列,對(duì)原始序列進(jìn)行分組挺身,然后應(yīng)用插入排序
  • 例如:希爾間隙序列為 \frac{N}{2^k}
    第一個(gè)分組的步長(zhǎng)為 \frac{N}{2},應(yīng)用插入排序
    第二個(gè)分組的步長(zhǎng)為 \frac{N}{4}锌仅,應(yīng)用插入排序
    ......
    最后一個(gè)分組的步長(zhǎng)為 1章钾,應(yīng)用插入排序

希爾排序,發(fā)明者 Donald Shell
希爾排序有很多著名的間隙序列热芹,不同間隙序列的希爾排序的時(shí)間復(fù)雜度也不同
參考 https://en.wikipedia.org/wiki/Shellsort 的 Gap sequences

平均時(shí)間復(fù)雜度:依賴間隙序列
最好情況:O(n\log n)贱傀、O(n\log^2 n)
最壞情況:O(n^2)O(n\log^2 n)
空間復(fù)雜度:O(1)
排序方式:In-place
穩(wěn)定性:不穩(wěn)定

public class ShellSort {

    public static void shellSort(int[] arr) {
        if (arr == null || arr.length < 2) {
            return;
        }
        // 希爾增量伊脓,間隙序列 N/(2^k)府寒,最壞時(shí)間復(fù)雜度是 O(n^2)
        for (int step = arr.length >> 1; step > 0; step >>= 1) {
            sort(arr, step);
        }
    }

    public static void hibbardSort(int[] arr) {
        if (arr == null || arr.length < 2) {
            return;
        }

        // Hibbard 增量,間隙序列 2^k-1报腔,最壞時(shí)間復(fù)雜度是 O(n^(3/2))
        for (int k = (int) Math.sqrt(arr.length + 1); k > 0; k--) {
            int step = (int) Math.pow(2, k) - 1;
            sort(arr, step);
        }
    }

    // insertion sort with step
    private static void sort(int[] arr, int step) {
        int preIndex;
        int current;
        for (int i = step; i < arr.length; i += step) {
            preIndex = i - step;
            current = arr[i];
            while (preIndex >= 0 && arr[preIndex] > current) {
                arr[preIndex + step] = arr[preIndex];
                preIndex -= step;
            }
            arr[preIndex + step] = current;
        }
    }

    public static void main(String[] args) {
        int[] arr = new int[]{9, 1, 8, 2, 7, 3, 6, 4, 5, 0, 2, 8, 4, 7};
        shellSort(arr);
        hibbardSort(arr);
    }

}

歸并排序

  • 將序列從中間分割株搔,遞歸直到只剩 1 個(gè)元素
  • 排序合并序列

平均時(shí)間復(fù)雜度:O(n\log n)
最好情況:O(n\log n)
最壞情況:O(n\log n)
空間復(fù)雜度:O(n)
排序方式:Out-place
穩(wěn)定性:穩(wěn)定

與快速排序比較類(lèi)似:

  • 歸并排序 先將序列從中間遞歸拆成只剩 1 個(gè)元素,然后排序合并序列
  • 快速排序 先把基準(zhǔn)元素放到正確的位置纯蛾,然后將序列拆成左右序列纤房,遞歸排序
  • 但是,歸并排序的時(shí)間復(fù)雜度是固定的翻诉。因?yàn)闅w并排序總是從中間拆開(kāi)炮姨,而快速排序的基準(zhǔn)元素的正確位置卻不一定在中間捌刮,最壞情況是在端點(diǎn)。
public class MergeSort {

    public static int[] sort(final int[] arr) {
        if (arr.length < 2) {
            return arr;
        }

        int middle = (int) Math.floor(arr.length / 2.0);
        // left
        int[] left = sort(Arrays.copyOfRange(arr, 0, middle));
        // right
        int[] right = sort(Arrays.copyOfRange(arr, middle, arr.length));
        // merge
        return merge(left, right);
    }

    private static int[] merge(final int[] left, final int[] right) {
        int[] result = new int[left.length + right.length];
        int index = 0;
        int leftIndex = 0;
        int rightIndex = 0;

        // merge
        while (leftIndex < left.length && rightIndex < right.length) {
            if (left[leftIndex] <= right[rightIndex]) {
                result[index++] = left[leftIndex++];
            } else {
                result[index++] = right[rightIndex++];
            }
        }

        // fill
        while (leftIndex < left.length) {
            result[index++] = left[leftIndex++];
        }

        // fill
        while (rightIndex < right.length) {
            result[index++] = right[rightIndex++];
        }

        return result;
    }

    public static void main(String[] args) {
        int[] arr = new int[]{9, 1, 8, 2, 7, 3, 6, 4, 5, 0, 2, 8, 4, 7};
        arr = sort(arr);
    }

}

快速排序

  • 將基準(zhǔn)值放到正確的位置舒岸,假設(shè)該位置的索引為 pivot
    • 基準(zhǔn)值绅作,一般選擇 arr[low]
  • 以 pivot 為中心,將 arr 分為兩部分蛾派,進(jìn)行步驟 1

平均時(shí)間復(fù)雜度:O(n\log n)
最好情況:O(n\log n)
最壞情況:O(n^2)
空間復(fù)雜度:O(n\log n)
排序方式:In-place
穩(wěn)定性:不穩(wěn)定

提示
在 Java 中俄认,不做額外的配置,2 萬(wàn)個(gè)數(shù)據(jù)左右的快速排序碍脏,就會(huì)發(fā)生 StackOverflowError 異常梭依。這與斐波那契遞歸不同,因?yàn)槲策f歸很容易被 JIT 優(yōu)化掉典尾,用斐波那契遞歸測(cè)編程語(yǔ)言的性能役拴,你甚至能得到 Java 性能比 C/C++ 高的錯(cuò)誤結(jié)論。

實(shí)現(xiàn) 1

public class QuickSort {

    public static void sort(int[] arr, int low, int high) {
        if (low < high) {
            // partition
            int pivot = partition(arr, low, high);
            // left
            sort(arr, low, pivot - 1);
            // right
            sort(arr, pivot + 1, high);
        }
    }

    private static int partition(int[] arr, int low, int high) {
        int pivot = arr[low];

        while (low != high) {
            // right
            while (low < high && arr[high] >= pivot) {
                high--;
            }
            arr[low] = arr[high];

            // left
            while (low < high && arr[low] <= pivot) {
                low++;
            }
            arr[high] = arr[low];
        }

        arr[low] = pivot;
        return low;
    }

    public static void main(String[] args) {
        int[] arr = new int[]{9, 1, 8, 2, 7, 3, 6, 4, 5, 0, 2, 8, 4, 7};
        sort(arr, 0, arr.length - 1);
    }

}

堆排序

基礎(chǔ)知識(shí)
堆滿足下面條件

  • 堆是一顆完全二叉樹(shù)(自己額外補(bǔ)習(xí))钾埂,可以與一維數(shù)組相互轉(zhuǎn)換
  • 大頂堆:每個(gè)節(jié)點(diǎn)的值都大于或等于其子節(jié)點(diǎn)的值河闰,用于升序排列
  • 小頂堆:每個(gè)節(jié)點(diǎn)的值都小于或等于其子節(jié)點(diǎn)的值,用于降序排列
  • 先將序列轉(zhuǎn)為大頂堆(小頂堆)
  • 將根節(jié)點(diǎn)與最后的節(jié)點(diǎn)交換位置
    • 如果是大頂堆褥紫,交換后姜性,最后元素的值為最大值
    • 如果是小頂堆,交換后髓考,最后元素的值為最小值
  • 此時(shí)只需要將數(shù)組長(zhǎng)度減 1部念,重復(fù)上述步驟即可完成排序

平均時(shí)間復(fù)雜度:O(n\log n)
最好情況:O(n\log n)
最壞情況:O(n\log n)
空間復(fù)雜度:O(1)
排序方式:In-place
穩(wěn)定性:不穩(wěn)定

其中,heapifyUp 方法在堆排序中是沒(méi)有必要的氨菇,保留只是個(gè)人想記錄一下儡炼。

public class HeapSort {

    public static void sort(final int[] arr) {
        if (arr.length < 2) {
            return;
        }

        int len = arr.length;

        // 構(gòu)建堆,因?yàn)楹罄m(xù)的操作要求必須是堆
        buildHeap(arr, len);

        for (int size = len - 1; size > 0; size--) {
            // 首尾交換
            int tmp = arr[0];
            arr[0] = arr[size];
            arr[size] = tmp;

            // 交換后查蓉,此時(shí)不是一個(gè)堆了乌询,所以需要執(zhí)行 heapify 操作
            // 而最后的元素是已排序的,所以這里用 size豌研,而不是 len
            heapifyDown(arr, size, 0);
        }
    }

    private static void buildHeap(final int[] arr, int size) {
        // 從尾到首執(zhí)行 heapify 操作
        // 因?yàn)槿~子節(jié)點(diǎn)沒(méi)有 left 和 right 子節(jié)點(diǎn)妹田,沒(méi)有向下執(zhí)行 heapify 的必要
        // 因此可以優(yōu)化成,從最后一個(gè)元素的 parent(即 [size / 2] - 1)開(kāi)始
        for (int i = (int) (Math.floor(size / 2.0) - 1); i >= 0; i--) {
            heapifyDown(arr, size, i);
        }
    }

    /**
     * 向上執(zhí)行 heapify 操作鹃共。適用場(chǎng)景:原先必須是一個(gè)堆鬼佣,并且僅有索引為 index 的元素發(fā)生了變化。
     *
     * @param arr   完全二叉樹(shù)
     * @param size  二叉樹(shù)的大小
     * @param index 開(kāi)始節(jié)點(diǎn)的索引
     */
    private static void heapifyUp(final int[] arr, final int size, int index) {
        while (index < size) {
            // parent exists || already a heap
            int parentIndex = (int) Math.floor((index - 1) / 2.0);
            if (parentIndex < 0 || arr[parentIndex] >= arr[index]) {
                return;
            }

            // swap
            int tmp = arr[index];
            arr[index] = arr[parentIndex];
            arr[parentIndex] = tmp;

            // update index
            index = parentIndex;
        }
    }

    /**
     * 向下執(zhí)行 heapify 操作及汉。適用場(chǎng)景:原先必須是一個(gè)堆沮趣,并且僅有索引為 index 的元素發(fā)生了變化。
     *
     * @param arr   完全二叉樹(shù)
     * @param size  二叉樹(shù)的大小
     * @param index 開(kāi)始節(jié)點(diǎn)的索引
     */
    private static void heapifyDown(final int[] arr, final int size, int index) {
        while (index < size) {
            // left child not exists
            int leftIndex = 2 * index + 1;
            if (leftIndex >= size) {
                return;
            }

            int largerIndex;
            int rightIndex = 2 * index + 2;
            // right child exists && larger than left child
            if (rightIndex < size && arr[leftIndex] < arr[rightIndex]) {
                largerIndex = rightIndex;
            } else {
                largerIndex = leftIndex;
            }

            // already a heap
            if (arr[index] >= arr[largerIndex]) {
                return;
            }

            // swap
            int tmp = arr[largerIndex];
            arr[largerIndex] = arr[index];
            arr[index] = tmp;

            // update index
            index = largerIndex;
        }
    }

    public static void main(String[] args) {
        int[] arr = new int[]{9, 1, 8, 2, 7, 3, 6, 4, 5, 0, 2, 8, 4, 7};
        sort(arr);
    }

}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末坷随,一起剝皮案震驚了整個(gè)濱河市房铭,隨后出現(xiàn)的幾起案子驻龟,更是在濱河造成了極大的恐慌,老刑警劉巖缸匪,帶你破解...
    沈念sama閱讀 216,997評(píng)論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件翁狐,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡凌蔬,警方通過(guò)查閱死者的電腦和手機(jī)露懒,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,603評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)砂心,“玉大人懈词,你說(shuō)我怎么就攤上這事”绲” “怎么了坎弯?”我有些...
    開(kāi)封第一講書(shū)人閱讀 163,359評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)译暂。 經(jīng)常有香客問(wèn)我抠忘,道長(zhǎng),這世上最難降的妖魔是什么外永? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,309評(píng)論 1 292
  • 正文 為了忘掉前任崎脉,我火速辦了婚禮,結(jié)果婚禮上伯顶,老公的妹妹穿的比我還像新娘囚灼。我一直安慰自己,他們只是感情好祭衩,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,346評(píng)論 6 390
  • 文/花漫 我一把揭開(kāi)白布啦撮。 她就那樣靜靜地躺著,像睡著了一般汪厨。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上愉择,一...
    開(kāi)封第一講書(shū)人閱讀 51,258評(píng)論 1 300
  • 那天劫乱,我揣著相機(jī)與錄音,去河邊找鬼锥涕。 笑死衷戈,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的层坠。 我是一名探鬼主播殖妇,決...
    沈念sama閱讀 40,122評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼破花!你這毒婦竟也來(lái)了谦趣?” 一聲冷哼從身側(cè)響起疲吸,我...
    開(kāi)封第一講書(shū)人閱讀 38,970評(píng)論 0 275
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎前鹅,沒(méi)想到半個(gè)月后摘悴,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,403評(píng)論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡舰绘,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,596評(píng)論 3 334
  • 正文 我和宋清朗相戀三年蹂喻,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片捂寿。...
    茶點(diǎn)故事閱讀 39,769評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡口四,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出秦陋,到底是詐尸還是另有隱情蔓彩,我是刑警寧澤,帶...
    沈念sama閱讀 35,464評(píng)論 5 344
  • 正文 年R本政府宣布踱侣,位于F島的核電站粪小,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏抡句。R本人自食惡果不足惜探膊,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,075評(píng)論 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望待榔。 院中可真熱鬧逞壁,春花似錦、人聲如沸锐锣。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,705評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)雕憔。三九已至姿骏,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間斤彼,已是汗流浹背分瘦。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,848評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留琉苇,地道東北人嘲玫。 一個(gè)月前我還...
    沈念sama閱讀 47,831評(píng)論 2 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像并扇,于是被迫代替她去往敵國(guó)和親去团。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,678評(píng)論 2 354

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