天天看點

java排序算法(二)-歸并排序、快速排序前言歸并排序快速排序

排序算法-歸并排序

  • 前言
  • 歸并排序
  • 快速排序

前言

此文内容純手寫,為了記錄學習中的一些算法邏輯,如有錯誤請指出。

歸并排序

歸并排序,顧名思義就是先拆分,再合并。

歸并排序的要點:先拆分,然後合并。

(下面的left right mid 都為下标位置。)

拆分:每次将數組拆分為左右兩部分,一直拆分到左邊的下标等于右邊的下标時就不再拆分,開始傳回。

合并:将左右兩邊的數組合并,合并時依次按左右兩邊下标的首個位置開始比較,數值小的按順序指派到temp數組。

一直到左右兩邊下标達到mid或right的位置則停止,然後将沒到達mid或right的下标重新周遊,然後依次加入到

temp數組(這裡加入時肯定是有序的,因為左右兩邊的下标表示的區間内的數肯定是有序的),都周遊進temp後,

再依次将temp中的數一一指派進原數組arr對應的當次遞歸的left到right的位置,則此次遞歸的left到right位置就排好序了。

例如:

4,2,3,1

拆分:

4,2 3,1

拆分:

4 2 3 1

合并:

2<4 temp = {2}

左邊還剩4,直接加入到temp = {2,4}

1<3 temp = {1}

左邊還剩3,直接加入到temp = {1,3}

2,4 1,3

合并:

1<2則 temp = {1}

2,4 3 temp = {1}

2<3則 temp = {1,2}

4 3 temp = {1,2}

3<4則 temp = {1,2,3}

右邊都加入完了,左邊的2,4中還剩一個4,那麼直接加入到temp = {1,2,3,4}

這樣就排序完了。

下面見代碼,會比文字描述好了解一些。

package com.example.demo;

public class SortTest {

    public static void main(String[] args) {
//        int[] arr = {1, 2, 3, 4, 5, 6, 7, 8};
        int[] arr = {8, 7, 6, 5, 4, 3, 2, 1};
//        int[] arr = {7, 4, 1, 5, 2, 8, 4,0};
        gbSort(arr);
//        quickSort(arr);
    }
	/*
	 * 歸并排序
	 * 時間複雜度:O(n*logN)
     * 空間複雜度:O(n)
     * 原地排序算法:否
     * 穩定:是
     * @param arr 要排序的數組
     */
	public static void gbSort(int[] arr) {
        int left = 0;
        int right = arr.length - 1;
        int[] temp = new int[arr.length];
        splitArr(arr, left, right, temp);
        for (int i = 0; i < arr.length; i++) {
            System.out.println(arr[i]);
        }
    }

    /**
     * 拆分邏輯
     *
     * @param arr   原數組
     * @param left  拆分成兩份後的左下标
     * @param right 拆分成兩份後的右下标
     * @param temp  用于臨時存儲資料的數組
     */
    public static void splitArr(int[] arr, int left, int right, int[] temp) {
        // 遞歸的終止條件。
        if (left >= right) {
            return;
        }
        int mid = (left + right) / 2;
        splitArr(arr, left, mid, temp);
        splitArr(arr, mid + 1, right, temp);
        mergeArr(arr, left, mid, right, temp);
    }
    
	/**
     * 合并邏輯
     * 遞歸到最深時,第一次合并的必定是數組中隻有一個值的數組。
     * 這樣就是直接開始比較大小然後合并
     *
     * @param arr   原數組
     * @param left  拆分成兩份後的左下标
     * @param mid   中間位置下标
     * @param right 拆分成兩份後的右下标
     * @param temp  用于臨時存儲資料的數組
     */
    public static void mergeArr(int[] arr, int left, int mid, int right, int[] temp) {
        int i = left;
        int j = mid + 1;
        int k = 0;
        // 從left和right的最左邊開始比較。更小的放入temp
        while (i <= mid && j <= right) {
            if (arr[i] < arr[j]) {
                temp[k++] = arr[i++];
            } else {
                temp[k++] = arr[j++];
            }
        }
        // 上面沒完全放入temp的,這裡直接放入
        while (i <= mid) {
            temp[k++] = arr[i++];
        }

        // 上面沒完全放入temp的,這裡直接放入
        while (j <= right) {
            temp[k++] = arr[j++];
        }
        k = 0;
        // 将temp裡面排好序的直接放到arr的left到right的下标位置
        // 增加temp是為了臨時存放合并的數值,最後直接插入arr的位置,
        // 隻改變了arr中需要合并的數值,對其他下标位置不影響,降低了空間複雜度
        while (left <= right) {
            arr[left++] = temp[k++];
        }
    }
}
           

快速排序

快速排序了解起來還是稍微有點難度的,不過如果不在原地交換處理的話,那麼了解難度會大大降低,下面的做法是原地交換處理的做法,具體的分析邏輯都寫在了注釋上,可以進行檢視。

這裡再分析一下非原地的做法:

找到端點point後,将左右兩邊的數分别用臨時數組存起來,然後再依次複制到原數組到位置,這樣也可以達到排序到效果,不過會增加一些空間複雜度。

例如:7,4,2,4,6,8,2,5

第一輪拆分後分别存在2個數組中 [4,2,4,2] 與 [7,6,8],5為端點值然後将2個數組的值分别複制到原數組中,先複制小于端點的,再複制端點,再複大于端點的。

public class HelloWorld {

    public static void main(String []args) {
		int[] arr = {7,4,2,4,6,8,2,5};
		// 以下數組經過第一次分區就會展現出是一個不穩定的排序算法。
		// 因為第一個6會與3交換位置,這樣就改變了順序。
		int[] arr = {6,7,9,7,6,8,3,5};
        quickSort(arr);
		for(int i : arr){
			System.out.println(i);	
		}
    }
	
	/**
     * 快速排序
	 * 核心是:
	 * 先确定一個端點(以數組中任意一個下标都行),這裡以目前數組範圍最後一個下标為端點。
	 * 然後依次用數組中除開端點下标都值與端點下标都值比較,小于端點的放在左邊,大于端點的放在右邊
	 * 下面使用的是交換的方式,例如:
	 * 6,2,4,6,3,5
	 * 交換後為2,4,3,5,6,6
	 * 然後再讓端點左右兩邊的數再次進行此操作
	 * 左邊:2,3,4  右邊6,6 這樣就排序完成了
	 *
     * 時間複雜度:O(n*logN)
     * 空間複雜度:O(n)
     * 原地排序算法:是
     * 穩定:否
     * @param arr 原數組
     */
	public static void quickSort(int[] arr){
			chaifen(arr,0,arr.length-1);
	}
	
	/**
	 * 将數組以point位置拆分分組,這裡point預設取right位置。
	 * 拆分後找到比point大與比point小大數,分别又數2部分,再進行遞歸拆分調用。
	 * 最後拆分到left>=right時, 就把最後到數組通過patition排好序了。
	 * @param arr 原數組
 	 * @param left 數組首個下标
 	 * @param right 數組末尾下标
	 */
	public static void chaifen(int[] arr,int left,int right){
			if(left >= right){
				return;
			}
		int q = patition(arr,left,right);
		chaifen(arr,left,q-1);
		chaifen(arr,q+1,right);
	}
	
	public static int patition(int[] arr,int left,int right){
		// 預設以最後到下标作為比較到端點point
		int point = arr[right];
		// i 是比point小的數要放入的位置,放入方式為與j交換位置,這樣保證了是原地排序算法。
		int i = left;
		// j 是需要用來循環與point比較的下标位置,除了最後一個right下标的數不需要比較以外,
		// 所有大于left、小于right的數都需要進行依次比較。
		int j = left;
		for(;j<right;j++){
			if(arr[j]<point){
				// 要比較都j下标的值如果小于point說明是要交換到i到位置。
				// 那麼讓i和j交換,交換後i已經被插入小于point到數了,那麼i需要++,
				// 移動到下一個待要交換待位置
				int temp = arr[i];
				arr[i] = arr[j];
				arr[j] = temp;
				i++;
			}
		}
		// 經過上面待循環,所有的小于point的數都交換到了<i下标到位置,
		// 目前i的位置肯定數大于或等于point的值,是以讓i與point位置的值交換。
		// 交換後point左邊的數小于point,point右邊的數大于point,這樣就進行完了一輪排序。
		int t = point;
		arr[right] = arr[i];
		arr[i] = point;
		return i;
	}
	
}