一、插入排序
算法思想:
從第二個數開始,往前找,如果前面的數比自己大,就交換,最終找到合適的位置就結束了。(這個交換可以理解爲向後移位,你玩撲克牌的時候,按大小排序,前面插一張,後面的就要往後挪一點)
時間複雜度:
最好:O(n)
最壞:O(n^2)
平均:O(n)
空間複雜度: O(1)
穩定性:穩定
public static void InserSort(int[] arr){
int tmp;
for(int i=0;i<arr.length-1;i++){
for(int j=i+1;j>0;j--){
if(arr[j] < arr[j-1]){
tmp = arr[j];
arr[j] = arr[j-1];
arr[j-1] = tmp;
} else{
break;
}
}
}
}
換個版本
public static void InsertSort(int[] arr){
for(int i=1;i<arr.length;i++){
int key = arr[i];
int j = i-1;
for(;j>=0 && arr[j] > v;j--){
arr[j+1] = arr[j];
}
arr[j+1] = key;
}
}
二、希爾排序
算法思想:基於直接插入方法的升級版本,目的就是爲了先讓數組變得更有序,有序了插入排序的時間複雜度就會降低。
時間複雜度:
最好:O(n)
最壞:O(n^2)
平均:O(n^1.3)
空間複雜度: O(1)
穩定性: 不穩定
public static void ShellSort(int[] arr,int length){
int pos = length;
int tmp;
while(true){
pos = pos/2;
for(int k=0;k<pos;k++){
for(int i= k+pos;i<arr.length;i+=pos){
for(int j=i;j>k;j-=pos){
if(arr[j] < arr[j-pos]){
tmp = arr[j];
arr[j] = arr[j-pos];
arr[j-pos] = tmp;
} else {
break;
}
}
}
}
if(pos == 1){
break;
}
}
}
換個版本:
public static void shellSort(int[] array) {
int gap = array.length;
while (gap > 1) {
insertSortGap(array, gap);
gap = (gap / 3) + 1; // OR gap = gap / 2;
}
insertSortGap(array, 1);//就是直接插入排序了
}
private static void insertSortGap(int[] array, int gap) {
for (int i = 1; i < array.length; i++) {
int v = array[i];
int j = i - gap;
for (; j >= 0 && array[j] > v; j -= gap) {
array[j + gap] = array[j];
}
array[j + gap] = v;
}
}
三、冒泡排序
算法思想:每次讓最大的冒出來;這麼說吧,從最後一個往前找,發現左邊的比後面的大,就交換,然後第一趟,把最小的放到了前面,第二趟,把次小的放到了前面。
時間複雜度:
最好:O(n)
最壞:O(n^2)
平均:O(n^2)
空間複雜度: O(1)
穩定性: 穩定
public static void BubbleSort(int[] arr){
int tmp;
for(int i=0;i<arr.length;i++){
for(int j= arr.length-1;j>i;j--){
if(arr[j] < arr[j-1]){
tmp = arr[j];
arr[j] = arr[j-1];
arr[j-1] = tmp;
}
}
}
}
升級版:主要是爲了解決沒有交換,說明已經有序,直接跳出。
public static void BubbleSort(int[] arr){
int tmp;
boolean flag = false;
for(int i=0;i<arr.length;i++){
flag = false;
for(int j= arr.length-1;j>i;j--){
if(arr[j] < arr[j-1]){
tmp = arr[j];
arr[j] = arr[j-1];
arr[j-1] = tmp;
flag = true;
}
}
if(!flag){
break;
}
}
}
四、選擇排序
算法思想:打擂臺方法,若爲升序,第一個肯定最小,然後我找後面有比它還小的嗎,如果有更改最小標誌位,直到找到最小的,然後交換。
時間複雜度: O(n^2)
空間複雜度: O(1)
穩定性: 不穩定
public static void SelectSort(int arr[]){
int tmp=0;
for(int i=0;i<arr.length-1;i--){
int minIndex = i;
for(int j=i+1;j<arr.length;j++){
if(arr[minIndex] > arr[j]){
minIndex = j;
}
}
if(minIndex != i){
tmp = arr[i];
arr[i] = arr[minIndex];
arr[minIndex] = tmp;
}
}
}
五、快速排序
算法思想:先找出一個基準值,找到之後,比他小的放左邊,比他大的放右邊,排好序後,把臨界點的值給返回來。(看代碼說話),分治法的思想。
時間複雜度:
最好:O(nlog(n))
最壞:O(n^2)
平均:O(nlog(n))
空間複雜度: 最好平均都是O(log(n)),最壞O(n)
穩定性: 不穩定
遞歸版本
public static void QuickSort(int[] arr){
QuickSortHelper(arr,0,arr.length-1);
}
private static void QuickSortHelper(int arr[],int left,int right){
if(left >= rigth){ //遞歸結束標誌
return;
}
int partIndex = partition(arr,left,right);//找出臨界值
QuickSortHelper(arr,left,partIndex-1);//排序左半部分
QuickSortHelper(arr,partIndex+1,right);//排序右半部分
}
private static void partition(int[] arr,int left,int right){
int BaseIndex = right; //以最右邊爲基準點
int BaseValue = arr[right];
while(left < BaseIndex){
while(left < BaseIndex && arr[left] <= BaseValue ){
left++; //從左往右找到第一個大於基準值的
}
while(left < BaseIndex && BaseIndex <= arr[BaseIndex]){
BaseIndex--; //從右邊往左找第一個小於基準值的
}
swap(arr,left,BaseIndex);//把兩個交換
}
swap(arr,left,right);//把臨界點的那個值放到最右邊去
return left;//返回臨界值的座標
]
private static void swap(int[] arr,int left,int right){
int tmp = arr[left];
arr[left] = arr[right];
arr[rigth] = tmp;
}
非遞歸版本:
public static void quickSort(int[] array) {
Stack<Integer> stack = new Stack<>();
stack.push(array.length - 1);
stack.push(0);
while (!stack.isEmpty()) {
int left = stack.pop();
int right = stack.pop();
if (left >= right) {
continue;
}
int pivotIndex = partition(array, left, right);
stack.push(right);
stack.push(pivotIndex + 1);
stack.push(pivotIndex - 1);
stack.push(left);
}
}
private static void partition(int[] arr,int left,int right){
int BaseIndex = right;
int BaseValue = arr[right];
while(left < BaseIndex){
while(left < BaseIndex && arr[left] <= BaseValue ){
left++;
}
while(left < BaseIndex && BaseIndex <= arr[BaseIndex]){
BaseIndex--;
}
swap(arr,left,BaseIndex);
}
swap(arr,left,right);
return left;
]
private static void swap(int[] arr,int left,int right){
int tmp = arr[left];
arr[left] = arr[right];
arr[rigth] = tmp;
}
六、歸併排序
算法思想:是分治法的一個非常典型的應用,將已有序的子序列合併,得到完全有序的序列;即先使每個子序列有序,再使子序列段間有序。若將兩個有序表合併成一個有序表,稱爲二路歸併
時間複雜度: O(n*log(n))
空間複雜度: O(n)
穩定性: 穩定
public static void MergeSort(int arr[]){
MergeSortHelper(arr,0,arr.length);
}
private static void MergeSort(int[] arr,int left,int right){
if(left >= right || rigth-left == 1){
return;
}
int mid = (left+right)/2;
MergeSort(arr,left,mid);//排序左邊
MergeSort(arr,mid,right);//排序右邊
merge(arr,left,mid,right);//合併
}
private static void merge(int[] arr, int left,int mid ,int right){
int length = right-left;
int[] Output = new int[length];//搞個臨時數組,就排序的
int OutputIndex = 0;
int i=left;
int j=mid;
while (i < mid && j < right){
if(arr[i] <= arr[j]){
Output[OutputIndex++] = arr[i++];插入排序
} else {
Output[OutputIndex++] = arr[j++];
}
}
while(i < mid){
Output[OutputIndex++] = arr[i++];//看看哪個子串沒空,補上
}
while(j < right){
Output[OutputIndex++] = arr[j++];
}
for(int k=0;k<OutputIndex;k++){
arr[left+k] = Output[k]; //賦值回原數組
}
}
七、堆排序
算法思想:基本原理也是選擇排序,只是不在使用遍歷的方式查找無序區間的最大的數,而是通過堆來選擇無序區間的最大的數。
堆:其實有一個類似於完全二叉樹的結構,並同時滿足堆積的性質:即子結點的鍵值或索引總是小於(或者大於)它的父節點。
注意: 排升序要建大堆;排降序要建小堆
時間複雜度: O(n*log(n))
空間複雜度: O(1)
穩定性: 不穩定
public class HeapSort {
/**
* 選擇排序-堆排序
* @param array 待排序數組
* @return 已排序數組
*/
public static int[] heapSort(int[] array) {
//這裏元素的索引是從0開始的,所以最後一個非葉子結點array.length/2 - 1
for (int i = array.length / 2 - 1; i >= 0; i--) {
adjustHeap(array, i, array.length); //調整堆
}
// 上述邏輯,建堆結束
// 下面,開始排序邏輯
for (int j = array.length - 1; j > 0; j--) {
// 元素交換,作用是去掉大頂堆
// 把大頂堆的根元素,放到數組的最後;換句話說,就是每一次的堆調整之後,都會有一個元素到達自己的最終位置
swap(array, 0, j);
// 元素交換之後,毫無疑問,最後一個元素無需再考慮排序問題了。
// 接下來我們需要排序的,就是已經去掉了部分元素的堆了,這也是爲什麼此方法放在循環裏的原因
// 而這裏,實質上是自上而下,自左向右進行調整的
adjustHeap(array, 0, j);
}
return array;
}
/**
* 整個堆排序最關鍵的地方
* @param array 待組堆
* @param i 起始結點
* @param length 堆的長度
*/
public static void adjustHeap(int[] array, int i, int length) {
// 先把當前元素取出來,因爲當前元素可能要一直移動
int temp = array[i];
for (int k = 2 * i + 1; k < length; k = 2 * k + 1) { //2*i+1爲左子樹i的左子樹(因爲i是從0開始的),2*k+1爲k的左子樹
// 讓k先指向子節點中最大的節點
if (k + 1 < length && array[k] < array[k + 1]) { //如果有右子樹,並且右子樹大於左子樹
k++;
}
//如果發現結點(左右子結點)大於根結點,則進行值的交換
if (array[k] > temp) {
swap(array, i, k);
// 如果子節點更換了,那麼,以子節點爲根的子樹會受到影響,所以,循環對子節點所在的樹繼續進行判斷
i = k;
} else { //不用交換,直接終止循環
break;
}
}
}
/**
* 交換元素
* @param arr
* @param a 元素的下標
* @param b 元素的下標
*/
public static void swap(int[] arr, int a, int b) {
int temp = arr[a];
arr[a] = arr[b];
arr[b] = temp;
}
}
【理解多少,全憑個人造化。】
補充
其他非主流排序,就說說算法吧,當做興趣看一看。
1.基數排序
就是建立一個長數組,數字是多少就放在對應下標,然後裏面的數字就是元素的個數,然後遍歷一遍數組就得到了排序的結果。優劣性,想想就知道了,數字特別大豈不是涼涼,算是一種空間換時間的做法。(PS:數字大也是可以解決的,選擇一個基數,除以這個數來放,有的哈希的味道)
2.睡眠排序
這個算好玩吧,這個是多線程方面的,就是進來一個數字,我就創建一個線程,然後讓線程休眠,休眠的時間就是這個數字,到了這個時間,線程顯示出這個數字就銷燬。這排序可以自己寫着玩玩,缺點一樣的,數字大休眠到天荒地老。這好像算時間換空間把
3.猴子排序
這純粹是搞笑來的,據說只要猴子夠多,讓猴子打字,猴子就能寫出一本《哈姆雷特》,而猴子排序也是這樣的。就是隨機,把數組所有的數字隨機排序一下,檢查是否有序,沒有的話就再來一次。我前面寫過C語言版本的