那些年數組的排序

今天來回顧一下簡單的排序思想,留做從此的複習和備份用。本篇是很是很是基礎的,甚至都不會講實際項目真正能用的排序方法,譬如雙軸快速排序 。寫的很差請多多諒解。java

想要解鎖更多新姿式?請訪問http://blog.tengshe789.tech/算法

準備階段

相關功能函數

爲了保持代碼的整潔,先創造好對數器和相關功能性函數。api

交換器

兩個數組中的元素比較排序過程當中,必定會有元素的交換操做。爲了保持代碼的整潔,先寫出交換操做的函數。數組

public static void swap(int[] arr, int i, int j) {
		arr[i] = arr[i] ^ arr[j];
		arr[j] = arr[i] ^ arr[j];
		arr[i] = arr[i] ^ arr[j];
	}
複製代碼

隨機樣本產生器

本身編數組太麻煩了,讓他本身生產吧緩存

public static int[] generateRandomArray(int maxSize, int maxValue) {
		int[] arr = new int[(int) ((maxSize + 1) * Math.random())];
		for (int i = 0; i < arr.length; i++) {
			arr[i] = (int) ((maxValue + 1) * Math.random()) - (int) (maxValue * Math.random());
		}
		return arr;
	}
複製代碼

對數器

對數器其實就是一個絕對正確可是複雜度很差的方法。less

public static void comparator(int[] arr) {
		Arrays.sort(arr);
	}
複製代碼

說說Arrays.sort()的邏輯吧。數組進入方法,先判斷。若是數組的長度小於QUICKSORT_THRESHOLD(默認值是286)的話,再判斷,若是數組長度小於INSERTION_SORT_THRESHOLD(值爲47)的話,那麼就會用插入排序 ,不然就會使用雙軸快速排序。dom

若是大於286呢,它就會堅持數組的連續升序和連續降序性好很差,若是好的話就用歸併排序,很差的話就用快速排序。函數

比較器

比較兩個數組一不同~測試

public static boolean isEqual(int[] arr1, int[] arr2) {
		if ((arr1 == null && arr2 != null) || (arr1 != null && arr2 == null)) {
			return false;
		}
		if (arr1 == null && arr2 == null) {
			return true;
		}
		if (arr1.length != arr2.length) {
			return false;
		}
		for (int i = 0; i < arr1.length; i++) {
			if (arr1[i] != arr2[i]) {
				return false;
			}
		}
		return true;
	}
複製代碼

打印器

public static void printArray(int[] arr) {
   if (arr == null) {
      return;
   }
   for (int i = 0; i < arr.length; i++) {
      System.out.print(arr[i] + " ");
   }
   System.out.println();
}
複製代碼

複製器

public static int[] copyArray(int[] arr) {
		if (arr == null) {
			return null;
		}
		int[] res = new int[arr.length];
		for (int i = 0; i < arr.length; i++) {
			res[i] = arr[i];
		}
		return res;
	}
複製代碼

主函數

public static void main(String[] args) {
   int testTime = 500000;
   int maxSize = 100;
   int maxValue = 100;
   boolean succeed = true;
   for (int i = 0; i < testTime; i++) {
      int[] arr1 = generateRandomArray(maxSize, maxValue);
      int[] arr2 = copyArray(arr1);
      bubbleSort(arr1);
      comparator(arr2);
      if (!isEqual(arr1, arr2)) {
         succeed = false;
         break;
      }
   }
   System.out.println(succeed ? "牛逼,算法對了!" : "❌!");

   int[] arr = generateRandomArray(maxSize, maxValue);
   printArray(arr);
   bubbleSort(arr);//測試的算法
   printArray(arr);
}
複製代碼

腦子

腦闊疼ui

正篇

基於比較的排序

冒泡排序

原理

冒泡排序算法的原理以下:

  1. 比較相鄰的元素。若是第一個比第二個大,就交換他們兩個。此時這兩個數,永遠是後面的數大。
  2. 第一回合將每一對相鄰元素作一樣的工做。回合結束後,最後的元素是整個數組最大的數。
  3. 第二回合...第n回合過程當中,對除了最後一個元素重複以上的步驟。

實現

public static void bubbleSort(int[] arr) {
		if (arr == null || arr.length < 2) {
			return;
		}
		for (int end = arr.length - 1; end > 0; end--) {//end最後的數
			for (int i = 0; i < e; i++) {
				if (arr[i] > arr[i + 1]) {
					swap(arr, i, i + 1);//交換
				}
			}
		}
	}
複製代碼

複雜度

時間複雜度:O(N²)

額外空間複雜度:O(1)

選擇排序

原理

1.第一回合,將指針指向第一個元素,將第一個元素和剩餘的元素比較,最小的元素放到一號位置。

2.第二回合...第n回合過程當中,指針加一。對除了第一個元素重複以上的步驟。

實現

public static void selectionSort(int[] arr) {
   if (arr == null || arr.length < 2) {
      return;
   }
   for (int i = 0; i < arr.length - 1; i++) {
      int minIndex = i;
      for (int j = i + 1; j < arr.length; j++) {
         minIndex = arr[j] < arr[minIndex] ? j : minIndex;
      }
      swap(arr, i, minIndex);
   }
}
複製代碼

複雜度

時間複雜度:O(N²)

額外空間複雜度:O(1)

插入排序的

原理

1.第一回合,比較第一個元素和第二個元素大小,大的放在第二個位置上

2.第二回合,將第三個元素與第2、第一個元素比較,大的放在第三個位置上

3.輪迴

實現

public static void insertionSort(int[] arr) {
   if (arr == null || arr.length < 2) {
      return;
   }
   for (int i = 1; i < arr.length; i++) {
      for (int j = i - 1; j >= 0 && arr[j] > arr[j + 1]; j--) {
         swap(arr, j, j + 1);
      }
   }
}
複製代碼

複雜度

時間複雜度:O(N²)

額外空間複雜度:O(1)

堆排序

堆其實就是徹底二叉樹,看堆要首先知道大頂堆、小頂堆。

每一個結點的值都大於或等於其左右孩子結點的值,稱爲大頂堆;或者每一個結點的值都小於或等於其左右孩子結點的值,稱爲小頂堆。

大頂堆:arr[i] >= arr[2i+1] && arr[i] >= arr[2i+2]

小頂堆:arr[i] <= arr[2i+1] && arr[i] <= arr[2i+2]

原理

將待排序序列構形成一個大頂堆(升序採用大頂堆,降序採用小頂堆),此時,整個序列的最大值就是堆頂的根節點。將其與末尾元素進行交換,此時末尾就爲最大值。而後將剩餘n-1個元素從新構形成一個堆,這樣會獲得n個元素的次小值。如此反覆執行,便能獲得一個有序序列了

代碼

public static void heapSort(int[] arr) {
		if (arr == null || arr.length < 2) {
			return;
		}
		//0-i之間生成大根堆這種結構
		for (int i = 0; i < arr.length; i++) {
			heapInsert(arr, i);
		}
		int size = arr.length;//定義數組大小,能夠判斷是否越界
		swap(arr, 0, --size);
		while (size > 0) {
			heapify(arr, 0, size);
			swap(arr, 0, --size);
		}
	}
	//生成大根堆這種結構
	public static void heapInsert(int[] arr, int index) {
		while (arr[index] > arr[(index - 1) / 2]) {//若是我這個節點比父節點大
			swap(arr, index, (index - 1) / 2);//交換
			index = (index - 1) / 2;//回到父位置繼續
		}
	}
	//將數值小的元素往下沉
	public static void heapify(int[] arr, int index, int size) {
		int left = index * 2 + 1;
		while (left < size) {//左孩子在堆上,沒越界
			int largest = left + 1 < size && arr[left + 1] > arr[left] ? left + 1 : left;//找出左右孩子中最大的數
			largest = arr[largest] > arr[index] ? largest : index;//和父比較
			if (largest == index) {
				break;
			}
			swap(arr, largest, index);
			index = largest;//回到較大節點
			left = index * 2 + 1;
		}
	}
複製代碼

複雜度

若是隻是創建堆的過程,時間複雜度爲O(N)

時間複雜度O(N*logN)

額外空間複雜度O(1)

快速排序

快速排序不是一種穩定的排序算法,也就是說,多個相同的值的相對位置也許會在算法結束時產生變更。

假定在待排序的記錄序列中,存在多個具備相同的關鍵字的記錄,若通過排序,這些記錄的相對次序保持不變,即在原序列中,r[i]=r[j],且r[i]在r[j]以前,而在排序後的序列中,r[i]仍在r[j]以前,則稱這種排序算法是穩定的;不然稱爲不穩定的。

原理

我先說說經典快排的思路吧。

將數組分紅兩部分,一部分是小於等於某個數的,一部分是大於等於某個數的。這兩部分初始指針在數組的左(L)右(R)兩頭,此時L和R分別是一個邊界點。

1.先定義less區域和more區域,表明比數組中某一個數更小更大的區域。初始less區域是L-1以左的部分,more區域是R以右的區域

2.第一回合,從數組左邊開始。若L指針指的節點值小於某個數值,less區域向右移動一個位置 swap(arr,++less,L++);,L節點位置+1準備下一個回合;若它大於這個數值,more區域向左擴張一格,而後將這個節點放到more區域swap(arr,--more,L++);,L節點位置+1準備下一個回合;若他等於這個數值,什麼也無論,只是L節點位置+1準備下一個回合。

3.重複上述過程,獲得了一個數組,他的L指針右邊時小於某個數的,R的右邊時大於某個數的。[L,R]這個區間是等於某個數的。

4.返回這個都是相同數的數組的左邊界、右邊界

5.不斷遞歸

經典快排有一個弊端。**左部分和右部分的規模不同或者有一個部分規模特別大,算法效率會變差。**舉個栗子,若是我有個數組[1,1,3,4,7,6,1,2,1,5,1,7],我指定的某個數字是7,那麼那麼排序後就變成了[1,1,1,1,1,2,3,4,5,6,7],經典快排結束後只搞定了一個一個區間(<7的區間),複雜度就從理想狀態下的O(N)變成了O(N²)

而後就有了改進後的隨機快排。

隨機快排比經典快排多了一個選隨機數的過程 swap(arr, L + (int) (Math.random() * (R - L + 1)), R);。就是隨機生成某個數,這樣生成的區間雖然也會出現上述經典快排的惡劣狀況,可是此時的複雜度就從原來的惡劣事件變成了有機率惡劣事件,但整體指望是好的。這就變成了一個機率問題。

代碼

如下爲隨機快排

//隨機快速排序
    public static void quickSort(int[] arr) {
        if (arr == null || arr.length < 2) {
            return;
        }
        quickSort(arr, 0, arr.length - 1);
    }

    public static void quickSort(int[] arr,int L,int R){
        if (L<R){//將邏輯套在 L與R中
            swap(arr, L + (int) (Math.random() * (R - L + 1)), R);//隨機數
            int[] p = partiton(arr,L,R);
            quickSort(arr, L, p[0] - 1);
            quickSort(arr, p[1] + 1, R);
        }
    }
    //返回等於區域的範圍
    private static int[] partiton(int[] arr,int L,int R){
        int less = L-1;
        int more = R;
        while (arr[L] < R){
            if (arr[L] < more){
                swap(arr,++less,L++);
            }
            if (arr[L] > arr[R]){
                swap(arr,--more,L++);
            }else{
                L++;
            }
        }
        swap(arr,more,R);
        return new int[]{less+1,more-1};
    }
複製代碼

複雜度

科學家數學證實,長期指望的時間複雜度爲O(logN*N)

快速排序能夠作到穩定性問題,很是難,要知道的能夠谷歌「01 stable sort」 ,反正我不會。

歸併排序

原理

1.和上題同樣,先定義左邊界L右邊界R數組中,而後定義一箇中間值mid = (r-l)/2

2.遞歸,在邊界內部不斷的找中間值mid

實現

//歸併排序
public static void mergeSort(int[] arr){
    if (arr==null || arr.length <2){
        return;
    }
    mergeSort(arr,0,arr.length-1);
}

private static void mergeSort(int[] arr, int l, int r) {
    if (l == r){
        return;
    }
    int mid = l + ((r - l) >> 1); //(r-l)/2
    mergeSort(arr,l,mid);
    mergeSort(arr,mid+1,r);
    merge(arr, l, mid, r);
}
複製代碼

複雜度

時間複雜度O(N*logN)

額外空間複雜度O(N),歸併排序的額外空間複雜度能夠變成O(1),可是很是難,我沒花時間研究,要知道的能夠谷歌「歸併排序 內部緩存法」

這裏的時間複雜度怎麼算出來的呢?有一個master定理

T(N) = a*T(N/b) + O(N^d)

其中 a >= 1 and b > 1 是常量,其表示的意義是n表示問題的規模,a表示遞歸的次數也就是生成的子問題數,b表示每次遞歸是原來的1/b之一個規模。 以下:

  1. log(b,a) > d -> 複雜度爲O(N^log(b,a))

  2. log(b,a) = d -> 複雜度爲O(N^d * logN)

  3. log(b,a) < d -> 複雜度爲O(N^d)

這裏,歸併排序中b=2,a=2.

非基於比較的排序

非基於比較的排序,與被排序的樣本的實際數據情況頗有關係,因此實際中並不常用

桶排序

原理

1.找到一個數組中最大數的值

2.定義(最大數+1)個桶

3.將數組的數放到對應編號相同的桶中,每放進一個數,桶裏面的數值加一

4.依次從小輸出這個桶,桶裏的元素出現幾回就輸出幾個桶的編號

代碼

// only for 0~200 value
	public static void bucketSort(int[] arr) {
		if (arr == null || arr.length < 2) {
			return;
		}
		int max = Integer.MIN_VALUE;
		for (int i = 0; i < arr.length; i++) {
			max = Math.max(max, arr[i]);
		}
		int[] bucket = new int[max + 1];
		for (int i = 0; i < arr.length; i++) {
			bucket[arr[i]]++;
		}
		int i = 0;
		for (int j = 0; j < bucket.length; j++) {
			while (bucket[j]-- > 0) {
				arr[i++] = j;
			}
		}
	}
複製代碼

複雜度

時間複雜度O(N)

額外空間複雜度O(N)

相關文章
相關標籤/搜索