Arrays.sort() 采用了2種排序算法 -- 基本類型數據使用快速排序法,對象數組使用歸並排序.
java的Collections.sort算法調用的是歸並排序,它是穩定排序
方法一:直接插入
1.基本思路:
在要排序的一組數中,假設前面(n-1) [n>=2] 個數已經是排好順序的,現在要把第n個數插到前面的有序數中,使得這n個數也是排好順序的。如此反復循環,直到全部排好順序。
2.代碼實現:
(1)首先設定插入次數,即循環次數,for(int i=1;i<length;i++),從第二個數字開始插入,排序好。再依次插入第三個。。。
(2)設定插入數和得到已經排好序列的最后一個數的位數。temp和j=i-1。
(3)從最后一個數開始向前循環,如果插入數小於當前數,就將當前數向后移動一位。
(4)將當前數放置到空着的位置,即j+1。
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.定義:
針對直接插入排序的下效率問題,有人對次進行了改進與升級,這就是現在的希爾排序。希爾排序,也稱遞減增量排序算法,是插入排序的一種更高效的改進版本。希爾排序是非穩定排序算法。
2.基本思路:
數的個數為length,i=length/2,將下標差值為i的數分為一組,構成有序序列。
再取i=i/2 ,將下標差值為i的數分為一組,構成有序序列。
重復第二步,直到k=1執行簡單插入排序。
思路
(1)希爾排序(shell sort)這個排序方法又稱為縮小增量排序,是1959年D·L·Shell提出來的。該方法的基本思想是:設待排序元素序列有n個元素,首先取一個整數increment(小於n)作為間隔將全部元素分為increment個子序列,所有距離為increment的元素放在同一個子序列中,在每一個子序列中分別實行直接插入排序。然后縮小間隔increment,重復上述子序列划分和排序工作。直到最后取increment=1,將所有元素放在同一個子序列中排序為止。
(2)由於開始時,increment的取值較大,每個子序列中的元素較少,排序速度較快,到排序后期increment取值逐漸變小,子序列中元素個數逐漸增多,但由於前面工作的基礎,大多數元素已經基本有序,所以排序速度仍然很快。
(3)希爾排序舉例:
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)); }
4.時間復雜度和空間復雜度:
希爾排序的平均時間復雜度為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; } } }
3.時間復雜度:
簡單選擇排序時間復雜度:O(n^2)
方法四:堆排序
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.代碼實現:
public static int[] buildMaxHeap(int[] array,int length){//截取數組從0到length,構建大根堆 for (int i = (length-2)/2; i >=0 ; i--) {//從最后一個有子結點的結點開始調整,(減一)一直調節到根節點 adjustDownToUp(array,i,length); } return array; } private static void adjustDownToUp(int[] array, int i, int length) {//i要調整的結點 int temp = array[i]; //int j = i*2+1;//取得左孩子 for (int j=i*2+1;j<length;j=j*2+1){//從該節點一直調節到葉子結點,因為上層調節改變之后,會影響下層結構,所以一直循環到葉子結點。 if(j+1<length && array[j]<array[j+1]){//有又孩子,且右孩子數值更大 j++;//取更大結點的索引 } if(temp<array[j]){//根節點比最大的值小 array[i] = array[j];//調整根節點的值為最大值 i=j;//把空余的位置給i //array[j] = temp; } } array[i] = temp;//找到最后空余的位置,填上向下調整的值 } public static void heapSort(int[] array){ int len = array.length; int temp; for (int i = len; i >0; i--) { array=buildMaxHeap(array,i); temp = array[0]; array[0] = array[i-1]; array[i-1]=temp; } System.out.println(Arrays.toString(array)); }
3.時間復雜度:
時間復雜度:建堆:o(n),每次調整o(log n),故最好、最壞、平均情況下:o(n*logn);
方法五:冒泡排序
1.基本思路:
一次冒泡將序列中從頭到尾所有元素兩兩比較,將最大的放在最后面。
將剩余序列中所有元素再次兩兩比較,將最大的放在最后面。
重復第二步,直到只剩下一個數。
2.代碼實現:
public static void bubbleSort(int[] array){ for (int i = 0; i <array.length ; i++) {//第i冒泡,一次冒泡,會確定一個最大值 for (int j = 0; j <array.length-i-1 ; j++) {//從頭一直到已經確定的位置前,兩兩比較 int temp = array[j]; if(array[j]>array[j+1]){ array[j]=array[j+1]; array[j+1]=temp; } } } }
3.時間復雜度:
冒泡排序的時間復雜度為O(n^2),空間復雜度為O(1),它是一種穩定的排序算法。
方法六:快排
1.基本思路:
快速排序使用分治策略來把一個序列(list)分為兩個子序列(sub-lists)。步驟為:
- 從數列中挑出一個元素,稱為"基准"(pivot)。
- 重新排序數列,所有比基准值小的元素擺放在基准前面,所有比基准值大的元素擺在基准后面(相同的數可以到任一邊)。在這個分區結束之后,該基准就處於數列的中間位置。這個稱為分區(partition)操作。
- 遞歸地(recursively)把小於基准值元素的子數列和大於基准值元素的子數列排序。
遞歸到最底部時,數列的大小是零或一,也就是已經排序好了。這個算法一定會結束,因為在每次的迭代(iteration)中,它至少會把一個元素擺到它最后的位置去。
2.代碼實現:
public static void quickSprt(int[] array,int low,int high){ if(low>=high) return; int left = low; int right = high; int pivot = array[left];//設立基准點 while (left<right){ while (left<right && array[right]>pivot)//從右向左,大數位置不變 right--; array[left] = array[right];//把小數移到左邊 while (left < right && array[left]<pivot) //從左向右,小數位置不變 left++; array[right] = array[left];//把大數移到右邊 } array[left]=pivot; quickSprt(array,low,left-1); quickSprt(array,left+1,high); }
3.時間復雜度:
雖然 快排的時間復雜度達到了 O(n²),但是在大多數情況下都比平均時間復雜度為 O(n logn) 的排序算法表現要更好。
方法七:歸並排序
1.基本思路:
對於給定的一組記錄,利用遞歸與分治技術將數據序列划分成為越來越小的半子表(直到剩余一個數字),在對半子表排序,最后再用遞歸方法將排好序的半子表合並成為越來越大的有序序列。
(1)申請空間,使其大小為兩個已經排序序列之和,該空間用來存放合並后的序列
(2)設定兩個指針,最初位置分別為兩個已經排序序列的起始位置
(3)比較兩個指針所指向的元素,選擇相對小的元素放入到合並空間,並移動指針到下一位置
(4)重復步驟3直到某一指針達到序列尾
(5)將另一序列剩下的所有元素直接復制到合並序列尾
2.代碼實現:
//歸並排序 public static int[] mergeSort(int[] array,int low,int high){ if(low<high){ int mid = (low+high)/2; mergeSort(array,low,mid); mergeSort(array,mid+1,high); merge(array,low,mid,high);//歸並 } return array; } private static void merge(int[] array, int low, int mid, int high) { int i = low;//指針,前一個序列的頭指針 int j = mid+1;//指針,后一個序列的頭指針 int[] temp = new int[high-low+1]; int k=0; while (i<=mid && j<= high){ if (array[i]<array[j]){//從頭比較兩個序列,小的放入臨時數組temp temp[k++] = array[i++];//前一個序列指針后移一位 }else { temp[k++] = array[j++];//后一個序列指針后移一位 } } //最后只會剩下一組序列 while (i<=mid){ temp[k++] = array[i++];//把前一個指針剩余的數字放入臨時數組 } while (j<=high){ temp[k++] = array[j++];//把后一個指針剩余的數字放入臨時數組 } for (int m = 0; m <high-low+1 ; m++) { array[low+m] = temp[m]; } }
3.時間復雜度:
時間復雜度:O(nlog2n)
方法八:基數排序
1.基本思路:
(1)按照所有的數的個位數,在0---9 的基數序列中,把各個數字排入,每一個數字基數上也是一個序列。
(2)將新序列取出按基數順序取出,形成新數組。
(3)再按照十位數字進行排序,插入到基數序列,循環(1)(2)
2.代碼實現:
public static void baseSort(int[] array){ ArrayList<ArrayList<Integer>> queue = new ArrayList<ArrayList<Integer>>(); for (int i = 0; i <10 ; i++) { ArrayList<Integer> queue2 = new ArrayList<Integer>(); queue.add(queue2);//創建一個基數從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.時間復雜度:
時間復雜度:O(nlog2n)
總結:
排序算法 | 思路 | 排序算法 | 最好時間復雜度 | 平均時間復雜度 | 最壞時間復雜度 | 空間復雜度 (輔助存儲) |
是否穩定 |
插入排序 | 將一個數字插入已經排列好的序列中 | 直接插入法 | O(N) | O(N2) | O(N2) | O(1) | 穩定 |
希爾排序 | O(N) | O(N1.3) | O(N2) | O(1) | 不穩定 | ||
選擇排序 | 每一次循環都是選擇出最小(最大)的數字放到數組最前面(最后面)的位置 | 簡單選擇 | O(N) | O(N2) | O(N2) | O(1) | 不穩定 |
堆排序 | O(N*log2N) | O(N*log2N) | O(N*log2N) | O(1) | 不穩定 | ||
交換排序 | (冒泡)兩兩交換,大的后移,再次兩兩交換 (快速)和基准交換,比基准大排右邊,比基准小排左邊 |
冒泡排序 | O(N) | O(N2) | O(N2) | O(1) | 穩定 |
快速排序 | O(N*log2N) | O(N*log2N) | O(N2) | O(log2n)~O(n) | 不穩定 | ||
歸並排序 | O(N*log2N) | O(N*log2N) | O(N*log2N) | O(n) | 穩定 | ||
基數排序 | O(d(r+n)) | O(d(r+n)) | O(d(r+n)) | O(rd+n) | 穩定 |