1. 選擇排序
選擇排序就是在遍歷數組的過程中,每次都選出從當前位置到數組末尾的最小值,並把選出來的最小值和當前位置交換。
//選擇排序
public static void select_sort(int[] arr){
if (arr == null || arr.length < 2) {
return;
}
int n = arr.length;
for(int i = 0; i < n; i++){
int minIndex = i;
for(int j = i + 1; j < n; j++){
min = arr[minIndex] > arr[j] ? j : minIndex;
}
if(minIndex != i) swap(arr, i, minIndex);
}
}
- 時間複雜度:O(n的平方)
- 空間複雜度:O(1)
- 可以做到穩定
2. 冒泡排序
冒泡排序就是在遍歷數組的時候,讓當前位置元素arr[j] 和後一個元素arr[j+1] 進行比較,將大的元素放在後面,最後遍歷完,最後一個元素就是最大值,下一次遍歷,只需到上一次最大值的位置(不包括這個位置),這樣,每次找到的都是每一段元素中的最大值。
//冒泡排序
public static void bubble_sort(int[] arr){
if (arr == null || arr.length < 2) {
return;
}
int n = arr.length;
for(int i = n - 1; i >= 0; i--){
//注意遍歷到i,不包括i,因爲i位置是最大值
for(int j = 0; j < i; j++){
if(arr[j] > arr[j + 1]) swap(arr, j, j + 1);
}
}
}
- 時間複雜度:O(n的平方)
- 空間複雜度:O(1)
- 可以做到穩定
3. 插入排序
插入排序就是遍歷數組的時候,假設當前位置之前的數組都是有序的,如果當前位置的數小於前一個數就交換,如果不小於,就結束插入,繼續遍歷數組中下一個位置
//插入排序
public static void insert_sort(int[] arr){
if (arr == null || arr.length < 2) {
return;
}
int n = arr.length;
//注意這裏是從1開始的
for(int i = 1; i < n; i++){
//若果arr[j] >= arr[j-1],這個循環就結束了
for(int j = i; j > 0 && arr[j] < arr[j - 1]; j--){
swap(arr, j, j - 1);
}
}
}
- 時間複雜度:O(n的平方)
- 空間複雜度:O(1)
- 可以做到穩定
4. 歸併排序
歸併排序就是利用分治思想,先將數組切成兩半,然後合併。利用遞歸,將數組切成兩半,再將每一半切成兩半,…,最後分成每一半隻有一個元素,然後,一半一半(這兩半是同一次拆分成的)的合併,合併的過程中排序。
//歸併排序
public static void merge_sort(int[] arr){
if (arr == null || arr.length < 2) {
return;
}
merge_sort(arr, 0, arr.length - 1);
}
private static void merge_sort(int[] arr, int left, int right){
//切到只有一個元素
if(left >= right) return;
int mid = left + (right - left) / 2;
merge_sort(arr, left, mid);
merge_sort(arr,mid + 1, right);
merge(arr, left, mid, right);
}
//合併,left爲左半部分開始的索引,right爲右半部分結束的索引
private static void merge(int[] arr, int left, int mid, int right){
//臨時數組,暫時存放數據
int[] help = new int[right - left + 1];
int count = 0;
//i爲左半部分的開始,j爲右半部分的開始
int i = left, j = mid + 1;
//將左半部分和右半部分進行比較,較小的數先放到數組
while(i <= mid && j <= right){
help[count++] = arr[i] > arr[j] ? arr[j++]:arr[i++];
}
//可能有左半部分或者右半部分的數沒有存到help數組
//判斷左半部分是否都已存到help數組,沒有的話,就存到數組
while(i <= mid){
help[count++] = arr[i++];
}
//判斷右半部分是否都已存到數組,沒有的話,就存到數組
while(j <= right){
help[count++] = arr[j++];
}
//將排好序的數組放到原數組中
for(int k = 0; k < help.length; k++){
arr[left++] = help[k];
}
}
- 時間複雜度:O(nlogn)
- 空間複雜度:O(n)
- 可以做到穩定
5. 快速排序
快速排序就是找出一個參照物,每次將大於參照物的放在右邊,小於參照物的放在左邊,之後,再分別從參照爲的兩邊找參照物,重複上述過程,整個過程也是遞歸的。
//快速排序
public static void quick_sort(int[] arr){
if (arr == null || arr.length < 2) {
return;
}
quick_sort(arr,0, arr.length - 1);
}
private static void quick_sort(int[] arr, int left, int right){
if(left >= right) return;
//快速排序的效率跟選的參照物有很大的關係,所以,這裏,隨機選一個,和數組
//首元素交換
swap(arr, left, left + (int)(Math.random() * (right - left + 1)));
int mid = partition(arr, left, right);
quick_sort(arr, left, mid - 1);
quick_sort(arr, mid + 1, right);
}
private static int partition(int[] arr, int left, int right){
int model = arr[left];
while(left < right){
//當arr[right]>model的時候,讓right一直左移
//注意這裏要判斷left<right,有可能在移動的時候,left = right
while(left < right && arr[right] > model){
right--;
}
//上面循環結束,可能是nums[right]<model,這樣的話,就交換left和right的值
//也可能是left == right,所以判斷一下
if(left < right){
arr[left++] = arr[right];
}
while(left < right && arr[left] < model){
left++;
}
if(left < right) {
arr[right--] = arr[left];
}
}
//當right=left的時候,這個位置是空,把model放進去
arr[left] = model;
return left;
}
- 時間複雜度:O(nlogn)
- 空間複雜度:O(logn)
- 常規實現不穩定
快速排序的時間複雜度主要與選取的參照物有關,可以隨機選出一個值作爲參照物,下面是快速排序的另一種寫法,這種寫法和上面哪一種寫法的partition過程不一樣。
//快速排序
public static void quick_sort(int[] arr){
if (arr == null || arr.length < 2) {
return;
}
quick_sort(arr,0, arr.length - 1);
}
private static void quick_sort(int[] arr, int left, int right){
if(left >= right) return;
//快速排序的效率跟選的參照物有很大的關係,所以,這裏,隨機選一個,和數組
//首元素交換
//swap(arr, left, left + (int)(Math.random() * (right - left + 1)));
//隨機選一個數,和數組最後一個元素交換,作爲參照物
swap(arr, right, left + (int)(Math.random() * (right - left + 1)));
//int mid = partition(arr, left, right);
int mid = partition2(arr, left, right);
quick_sort(arr, left, mid - 1);
quick_sort(arr, mid + 1, right);
}
private static int partition2(int[] arr, int left, int right) {
//小於等於target的邊界
int small = left - 1;
//大於target的邊界,注意最後一個數是target,最後交換
int big = right;
while(left < big){
if(arr[left] < arr[right]){
//注意left++
swap(arr, ++small, left++);
}else if(arr[left] > arr[right]){
swap(arr, --big, left);
}else{
left++;
}
}
//最後,將arr[big]和比較的值arr[right]交換
swap(arr, big, right);
//最後返回big的索引
return big;
}
6. 堆排序
數據結構中的堆是一個完全二叉樹,一個數組,可以看成是一個完全二叉樹,按層次遍歷的順序存放,一個元素(索引爲i)的父節點是 (i- 1)/ 2, 左孩子的索引爲(2 * i + 1)。如果堆中,每一個根節點(子樹中)都所在樹中的最大值,那麼這個堆就是最大堆,堆排序就是利用最大堆進行排序的。大致過程是,將數組構建成一個最大堆,將數組最後一個元素(n -1)和第一個元素交換,再將除最後一個元素之外的數組調整爲最大堆,重複上述過程。
//堆排序
public static void heap_sort(int[] arr){
if (arr == null || arr.length < 2) {
return;
}
//構造最大堆
for(int i = 0; i < arr.length; i++){
heap_insert(arr, i);
}
int size = arr.length;
//將第一個元素和最後一個元素交換
swap(arr, 0, --size);
while(size > 0){
//調整堆爲最大堆
heapify(arr, 0, size);
swap(arr, 0, --size);
}
}
//使用插入的方式構建最大堆
private static void heap_insert(int[] arr, int index){
//當孩子結點大於父節點的時候交換
while(arr[index] > arr[(index - 1) / 2]){
swap(arr, index, (index - 1) / 2);
//父節點和子節點交換完之後,將父節點的索引賦給index,
//再去判斷父節點和父節點的父節點的大小,所以這裏用的是while
index = (index - 1) / 2;
}
}
//調整堆,注意size是數組個數,不是索引
private static void heapify(int[] arr, int index,int size){
int left = index * 2 + 1;
while(left < size){
int largest = left;
//找出左右孩子中最大的一個
if(left + 1 < size && arr[left + 1] > arr[left]){
largest++;
}
//如果根節點大於左右孩子,那麼說明已經是最大堆了,
//因爲左右孩子是左右子樹的根節點,是子樹中的最大值
if(arr[index] > arr[largest]){
break;
}
swap(arr, index, largest);
//將左右子樹中最大值的索引賦給index,調整子樹爲最大堆
index = largest;
left = index * 2 + 1;
}
}
- 時間複雜度:O(nlogn)
- 空間複雜度:O(1)
- 不穩定
7. 計數排序
計數排序其實是一種特殊的桶排序,就是先求出數組中的最大值max和最小值min,然後,建max-min +1個桶,假入是數組,數組長度就是max-min+1,bucket數組中存的就是原數組中第i(i爲桶的索引,數組元素-min得到)小的數的個數,統計完之後,根據桶(bucket)中的個數,將min+i放到原數組中,對應的桶中記錄的有幾個就放幾個。計數排序和前面六中排序不同的是,計數排序不是基於比較的排序。
//計數排序
public static void count_sort(int [] arr){
if(arr == null || arr.length < 2) return;
int min = arr[0];
int max =0;
//找出數組中的最大值和最小值
for(int i = 0; i < arr.length; i++){
max = Math.max(max, arr[i]);
min = Math.min(min, arr[i]);
}
//bucket數組中存放的是第i小的個數
int[] bucket = new int[max - min + 1];
//統計元素出現的次數
for(int i = 0; i < arr.length; i++){
bucket[arr[i] - min]++;
}
int j = 0;
//將bucket中的數寫回原數組
for(int i = 0; i < bucket.length; i++){
while(bucket[i]-- > 0){
arr[j++] = min + i;
}
}
}
8. java中的Arrays.sort()
java1.8中,當數組長度小於47的時候,使用的是冒泡排序,當數組長度大於47,小於286的時候使用雙軸快速排序,並且當快速排序遞歸時,如果長度小於47,直接使用插入排序,大於286的時候,使用的是歸併排序,