八大排序算法(java實現) 冒泡排序 快速排序 堆排序 歸併排序 等

八大排序算法

一、直接插入

1.基本思路

在要排序的一組數中,假設前面(n-1) [n>=2] 個數已經是排好順序的,現在要把第n個數插到前面的有序數中,使得這n個數也是排好順序的。如此反覆循環,直到全部排好順序。

2.代碼實現

  • 1.遍歷數組,每次循環從第二個數字往前插入
  • 2.設定插入數和得到已經排好序列的最後一個數的位數。temp和j=i-1。
  • 3.從最後一個數開始向前循環,如果插入數小於當前數,就將當前數向後移動一位。
public static void insertSort(int[] data) {
    int temp;
    for(int i = 1;i < data.length; i++){// 取第i個數,插入前邊的有序的序列
        temp = data[i];
        int j;
        for(j = i - 1; j>=0; j--) {// 從第i-1的位置上開始比較
            if(data[j] > temp) {// 若前面的數大,則往後挪一位
                data[j+1] = data[j];
            } else {
                break;// 否則,說明要插入的數比較大
            }
        }
        data[j+1] = temp;// 找到這個位置,插入數據
    }
}

3.時間複雜度和空間複雜度

直接插入排序的平均複雜度爲O(n²),最壞時間複雜度:O(n²),空間複雜度:O(1),沒有分配內存。

二、希爾排序

針對直接插入排序下的效率問題,有人對此進行了改進與升級,這就是現在的希爾排序。希爾排序,也稱遞減增量排序算法,是插入排序的一種更高效的改進版本。希爾排序是非穩定排序算法。

1.基本思路

  • 1.數的個數爲length,i=length/2,將下標差值爲i的數分爲一組,構成有序序列。

  • 2.再取i=i/2 ,將下標差值爲i的數分爲一組,構成有序序列。

  • 3.重複第二步,直到k=1執行簡單插入排序。

思路:

  • 1.希爾排序(shell sort)這個排序方法又稱爲縮小增量排序,是1959年D·L·Shell提出來的。該方法的基本思想是:設待排序元素序列有n個元素,首先取一個整數increment(小於n)作爲間隔將全部元素分爲increment個子序列,所有距離爲increment的元素放在同一個子序列中,在每一個子序列中分別實行直接插入排序。然後縮小間隔increment,重複上述子序列劃分和排序工作。直到最後取increment=1,將所有元素放在同一個子序列中排序爲止。
  • 2.由於開始時,increment的取值較大,每個子序列中的元素較少,排序速度較快,到排序後期increment取值逐漸變小,子序列中元素個數逐漸增多,但由於前面工作的基礎,大多數元素已經基本有序,所以排序速度仍然很快。

希爾排序舉例:
在這裏插入圖片描述

2.代碼實現

  • 1.遍歷數組,每次循環從第二個數字往前插入
  • 2.設定插入數和得到已經排好序列的最後一個數的位數。temp和j=i-1。
  • 3.從最後一個數開始向前循環,如果插入數小於當前數,就將當前數向後移動一位。

(1)首先確定每一組序列的下標的間隔,循環每次需要的間隔:int i = length/2; i >0 ; i /= 2

(2)然後將每一組序列中元素進行插入排序,第二組第一個插入的數字是第一組第一個插入數字之後的那個數組,從i之後每個數字都要進行插入排序,就是插入的序列是各自不同的序列,不是一個一個子序列循環,而是在一個循環中for (int j=i;j<length;j++)完成所有子序列的插入排序。

(3)直到i=0爲止。

public static void shellSort(int[] array) {
    int length = array.length;
    for (int i = length / 2; i > 0; i /= 2) {//序列的間隔,一直到間隔爲一,這時候就只有一個子序列
        for (int j = i; j < length; j++) {//從i之後每個數字都要進行插入排序,就是插入的序列是各自不同的序列
            int temp = array[j];//裏面就是直接插入算法
            int k;
            for (k = j - i; k >= 0; k -= i) {//實現各個數字插入排序到不同的序列中,直到間隔爲1的時候,只有一個序列,就是完全的一個直接插入排序
                if (temp < array[k]) {
                    array[k + i] = array[k];
                } else {
                    break;
                }
            }
            array[k + i] = temp;//把數字插入到位置上
        }
    }
    System.out.println(Arrays.toString(array));
}

3.時間複雜度和空間複雜度

希爾排序的平均時間複雜度爲O(n²),空間複雜度O(1) 。

三、簡單選擇

1.基本思路

基本原理如下:對於給定的一組記錄,經過第一輪比較後得到最小的記錄,然後將該記錄的位置與第一個記錄的位置交換;接着對不包括第一個記錄以外的其他記錄進行第二次比較,得到最小記錄並與第二個位置記錄交換;重複該過程,直到進行比較的記錄只剩下一個爲止。

2.代碼實現

  • 1.確定要插入最小值的位置,從0開始到最後int i = 0; i <len ; i++
  • 2.將每次開始位置上的數字暫定爲最小值min,從開始數字之後一個個和min比較,再把最小值存放到min
  • 3.將最小值所在位置上的數字和開始位置上的數字交換
public static void selectSort(int[] array) {
    int len = array.length;
    for (int i = 0; i < len; i++) {//確定每次開始的位置
        int min = array[i];//設定開始數字爲最小的值最小值
        int flag = i;
        for (int j = i + 1; j < len; j++) {//把最小值存放到min,從開始數字向後一個個和min比較,再把最小值存放到min
            if (min > array[j]) {
                min = array[j];
                flag = j;
            }
        }
        if (flag != i) {
            array[flag] = array[i];
            array[i] = min;
        }
    }
    System.out.println(Arrays.toString(array));
}

3.時間複雜度和空間複雜度

簡單選擇排序的時間複雜度爲O(n²)

四、堆排序

1.基本思路

  • 1.若array[0,…,n-1]表示一顆完全二叉樹的順序存儲模式,則雙親節點指針和孩子結點指針之間的內在關係如下:
任意一節點指針 i:
父節點:i==0 ? null : (i-1)/2
左孩子:2*i + 1
右孩子:2*i + 2
  • 2.堆得定義
n個關鍵字序列array[0,...,n-1],當且僅當滿足下列要求:(0 <= i <= (n-1)/2)
① array[i] <= array[2*i + 1] 且 array[i] <= array[2*i + 2]; 稱爲小根堆;
② array[i] >= array[2*i + 1] 且 array[i] >= array[2*i + 2]; 稱爲大根堆;
  • 3.建立大頂堆

n個節點的完全二叉樹array[0,…,n-1],最後一個節點n-1是第(n-1-1)/2個節點的孩子。對第(n-1-1)/2個節點爲根的子樹調整,使該子樹稱爲堆。

對於大根堆,調整方法爲:若【根節點的關鍵字】小於【左右子女中關鍵字較大者】,則交換。

之後向前依次對各節點((n-2)/2 - 1)~ 0爲根的子樹進行調整,看該節點值是否大於其左右子節點的值,若不是,將左右子節點中較大值與之交換,交換後可能會破壞下一級堆,於是繼續採用上述方法構建下一級的堆,直到以該節點爲根的子樹構成堆爲止。

反覆利用上述調整堆的方法建堆,直到根節點。

  • 4.堆排序(大頂堆)
①將存放在array[0,...,n-1]中的n個元素建成初始堆;
②將堆頂元素與堆底元素進行交換,則序列的最大值即已放到正確的位置;
③將數組中array[0,...,n-1]前n-1個元素再次形成大根堆,再重複第②③步,直到堆中僅剩下一個元素爲止。

2.代碼實現


/**
    *  大頂堆排序
    * @param array
    */
public static void maxHeapSort(int[] array) {
    int i;
    int len = array.length;
    // 構建大頂堆
    for (i = len / 2 - 1; i >= 0; i--) {
        adjustMaxHeap(array, i, len);
    }
    // 堆頂是最大值,交換堆頂和最後一個數,再重新調整最大堆,下一次循環   i--
    for (i = len - 1; i >= 0; i--) {
        int temp = array[0];
        array[0] = array[i];
        array[i] = temp;
        adjustMaxHeap(array, 0, i);
    }
    System.out.println(Arrays.toString(array));
}

private static void adjustMaxHeap(int[] a, int pos, int len) {
    int temp;
    int child;
    for (temp = a[pos]; 2 * pos + 1 < len; pos = child) {
        // 數組從0開始,r(i)>=r(2i) r(i)>=r(2i+1)  對應 pos => 2 * pos + 1 和 2 * pos +2
        child = 2 * pos + 1;
        // 有右孩子,且右孩子數值更大
        if (child + 1 < len && a[child] < a[child + 1]) {
            child++;
        }
        // 最大的孩子大於根節點
        if (a[child] > temp) {
            a[pos] = a[child];
        } else {
            break;
        }
    }
    a[pos] = temp;
}

3.時間複雜度和空間複雜度

時間複雜度:建堆:o(n),每次調整o(log n),故最好、最壞、平均情況下:o(n*logn);

五、冒泡排序

打個小guang告,搜索拼duoduo店鋪: Boush雜貨鋪
物美價廉,你值得擁有

1.基本思路

一次冒泡將序列中從頭到尾所有元素兩兩比較,將最大的放在最後面。

將剩餘序列中所有元素再次兩兩比較,將最大的放在最後面。

重複第二步,直到只剩下一個數。

2.代碼實現

/**
 * @author fupeng
 * 冒泡排序優化第二版
 * 第一版優化增加flag標記,沒有數字交換直接return,最優時間複雜度O(n)
 * 第二版優化,增加tempPostion記錄內循環最後一次交換的位置,來縮減內循環的次數
 */
public static void bubbleSort(int[] array) {
    int len = array.length - 1;
    int temp; // 開闢一個臨時空間, 存放交換的中間值
    int tempPostion = 0;  // 記錄最後一次交換的位置
    // 要遍歷的次數
    for (int i = 0; i < array.length - 1; i++) {
        int flag = 1; // 設置一個標誌位
        // 依次的比較相鄰兩個數的大小,遍歷一次後,把數組中第i小的數放在第i個位置上
        for (int j = 0; j < len; j++) {
            // 比較相鄰的元素,如果前面的數大於後面的數,交換
            if (array[j] > array[j + 1]) {
                temp = array[j + 1];
                array[j + 1] = array[j];
                array[j] = temp;
                flag = 0;  // 發生交換,標誌位置0
                tempPostion = j;  // 記錄交換的位置
            }
        }
        len = tempPostion; // 把最後一次交換的位置給len,來縮減內循環的次數
        if (flag == 1) {// 如果沒有交換過元素,則已經有序
            System.out.println(Arrays.toString(array));
            return;
        }
    }
    System.out.println(Arrays.toString(array));
}

3.時間複雜度和空間複雜度

冒泡排序的最好時間複雜度爲O(n),最壞時間複雜度爲O(n²),平均時間複雜度爲O(n²),空間複雜度爲O(1),它是一種穩定的排序算法。

六、快速排序

1.基本思路

快速排序使用分治策略來把一個序列(list)分爲兩個子序列(sub-lists)。步驟爲:

  • 1.從數列中挑出一個元素,稱爲"基準"(pivot)。
  • 2.重新排序數列,所有比基準值小的元素擺放在基準前面,所有比基準值大的元素擺在基準後面(相同的數可以到任一邊)。在這個分區結束之後,該基準就處於數列的中間位置。這個稱爲分區(partition)操作。
  • 3.遞歸地(recursively)把小於基準值元素的子數列和大於基準值元素的子數列排序。

遞歸到最底部時,數列的大小是零或一,也就是已經排序好了。這個算法一定會結束,因爲在每次的迭代(iteration)中,它至少會把一個元素擺到它最後的位置去。

2.代碼實現

public static void quickSort(int[] array) {
    sort(array, 0, array.length - 1);
    System.out.println(Arrays.toString(array));
}

private static void sort(int[] a, int low, int high) {
    int i = low;
    int j = high;
    if (a.length <= 1) {
        return;
    }
    if (i >= j) {
        return;
    }
    int index = a[i];
    while (i < j) {
        while (i < j && a[j] >= index)
            j--;
        if (a[j] < index)
            a[i++] = a[j];
        while (i < j && a[i] <= index)
            i++;
        if (a[i] > index)
            a[j--] = a[i];
    }
    a[i] = index;
    sort(a, low, i - 1);
    sort(a, i + 1, high);
}

3.時間複雜度和空間複雜度

雖然 快排的時間複雜度達到了 O(n²),但是在大多數情況下都比平均時間複雜度爲 O(n logn) 的排序算法表現要更好。

七、歸併排序

1.基本思路

歸併排序(MERGE-SORT)是利用歸併的思想實現的排序方法,該算法採用經典的分治(divide-and-conquer)策略(分治法將問題分(divide)成一些小的問題然後遞歸求解,而治(conquer)的階段則將分的階段得到的各答案"修補"在一起,即分而治之)。

  • 1.分而治之
    在這裏插入圖片描述

可以看到這種結構很像一棵完全二叉樹,本文的歸併排序我們採用遞歸去實現(也可採用迭代的方式去實現)。分階段可以理解爲就是遞歸拆分子序列的過程,遞歸深度爲log2n。

  • 2.合併相鄰有序子序列
    再來看看治階段,我們需要將兩個已經有序的子序列合併成一個有序序列,比如上圖中的最後一次合併,要將[4,5,7,8]和[1,2,3,6]兩個已經有序的子序列,合併爲最終序列[1,2,3,4,5,6,7,8],來看下實現步驟。

在這裏插入圖片描述
在這裏插入圖片描述

2.代碼實現

public static void mergeSort(int[] array) {
    int[] temp = new int[array.length];// 在排序前,先建好一個長度等於原數組長度的臨時數組,避免遞歸中頻繁開闢空間
    mergeSort(array, 0, array.length-1, temp);
    System.out.println(Arrays.toString(array));
}

private static void mergeSort(int[] arr, int left, int right, int []temp) {
    if(left < right) {
        int mid = (left+right) / 2;
        mergeSort(arr, left, mid, temp);// 左邊歸併排序,使得左子序列有序
        mergeSort(arr, mid+1, right, temp);// 右邊歸併排序,使得右子序列有序
        merge(arr, left, mid, right, temp);// 將兩個有序子數組合並操作
    }
}

private static void merge(int[] arr, int left, int mid, int right, int[] temp) {
    int i = left;// 左序列指針
    int j = mid+1;// 右序列指針
    int t = 0;// 臨時數組指針
    while (i <= mid && j <= right) {
        if(arr[i] <= arr[j]) {
            temp[t++] = arr[i++];
        } else {
            temp[t++] = arr[j++];
        }
    }
    while(i <= mid) {// 將左邊剩餘元素填充進temp中
        temp[t++] = arr[i++];
    }
    while(j <= right) {// 將右序列剩餘元素填充進temp中
        temp[t++] = arr[j++];
    }
    t = 0;
    // 將temp中的元素全部拷貝到原數組中
    while(left <= right) {
        arr[left++] = temp[t++];
    }
}

3.時間複雜度和空間複雜度

歸併排序是穩定排序,它也是一種十分高效的排序,能利用完全二叉樹特性的排序一般性能都不會太差。java中Arrays.sort()採用了一種名爲TimSort的排序算法,就是歸併排序的優化版本。從上文的圖中可看出,每次合併操作的平均時間複雜度爲O(n),而完全二叉樹的深度爲|log2n|。總的平均時間複雜度爲O(nlogn)。而且,歸併排序的最好,最壞,平均時間複雜度均爲O(nlogn)。

八、基數排序

1.基本思路

  • 1.基數排序的思想就是先排好各位,然後排好各位的基礎上排十位,以此類推,直到遍歷最高位 次,排序結束(仔細理解最後一句話)
  • 2.基數排序不是比較排序,而是通過分配和收集的過程來實現排序
  • 3.初始化10個桶(固定的),桶下標爲0-9
  • 4.通過得到待排序數字的個十百等位的數字,把這個數字對應的item放到對應的桶中
  • 5.基數排序有兩種排序方式:LSD和MSD,最小位優先(從右邊開始)和最大位優先(從左邊開始)

在這裏插入圖片描述

2.代碼實現

public static void radixSort(int[] array) {
    ArrayList<ArrayList<Integer>> queue = new ArrayList<>();
    for (int i = 0; i <10 ; i++) {
        queue.add(new ArrayList<>());// 創建一個基數從0---9 每個數字上都是一個list
    }
    // 找到最大值,並判斷最大值是幾位數
    int max = array[0];
    for (int i = 1; i < array.length; i++) {
        if (max < array[i]) {
            max = array[i];
        }
    }
    int time = 0;
    while (max > 0) {
        max /= 10;
        time++;
    }
    for (int i = 0; i < time; i++) {// 循環每一個位數(個位、十位、百位)
        for (int j = 0; j < array.length; j++) {// 循環數組,取每一個值
            int x = array[j] % (int) Math.pow(10, i + 1) / (int) Math.pow(10, i);
            ArrayList<Integer> queue3 = queue.get(x);

            queue3.add(array[j]);
            queue.set(x, queue3);
        }
        int count = 0;
        for (int k = 0; k < 10; k++) {
            while (queue.get(k).size() > 0) {
                ArrayList<Integer> queue4 = queue.get(k);
                array[count] = queue4.get(0);
                queue4.remove(0);
                count++;
            }
        }
    }
}

3.時間複雜度和空間複雜度

歸併排序是穩定排序,它也是一種十分高效的排序,能利用完全二叉樹特性的排序一般性能都不會太差。java中Arrays.sort()採用了一種名爲TimSort的排序算法,就是歸併排序的優化版本。從上文的圖中可看出,每次合併操作的平均時間複雜度爲O(n),而完全二叉樹的深度爲|log2n|。總的平均時間複雜度爲O(nlogn)。而且,歸併排序的最好,最壞,平均時間複雜度均爲O(nlogn)。

總結

在這裏插入圖片描述

引用:

https://www.cnblogs.com/mensan/p/10570050.html

https://www.cnblogs.com/jyroy/p/11248691.html

https://www.cnblogs.com/chengxiao/p/6194356.html

https://www.jianshu.com/p/8340dfaea3af

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章