插入排序
基本概念:
將未排序序列通過向有序序列從後向前掃描,找到合適的位置插入到有序序列裏。
基本步驟:
- 從第二個元素開始。(第一個元素默認爲是有序序列)
- 取出下一個元素,在已排序序列中從後向前掃描
- 該元素大於新元素就往後移動
- 重複3步驟
- 找到合適的位置,將新元素插入對應位置
- 重複1-5
例如:int[] arrays = {2,3,9,17,4,0,7,8};
- 有序序列:2,未排序序列 3,9,17,4,0,7,8
- 取出3,掃描有序序列 2,比較 3->2,3比2大,放在2,之後。
- 取出9,與3比較,比3大,放在3之後
- 取出17,與9比較,比9大,放在9之後
- 取出4,與17比較,比17小,將17往後移動一位
- 與9比較,比9小,將9往後移動一位
- 與3比較,比3大,將4插入3之後
- ……重複以上步驟………
代碼實現:
/**
* 插入排序 :將未排序序列的元素逐一和有序序列比較,並插入到有序序列中,默認第一個是有序序列
* @param array
* @return
*/
private static int[] insertSort(int[] array){
if(array == null) return null;
int len = array.length;
int i,j;
// 循環取出未排序序列元素
for(i = 1;i < len;i++){ // c1:執行n次
// 取出新元素
int newElement = array[i]; // c2: n-1
// 將新元素與有序序列比較
for(j = i;j>0 && array[j-1]>newElement;j--){ c3:
// 元素後移
array[j] = array[j-1]; // c4:
}
// 插入新元素
array[j] = newElement; // c5:n-1
}
return array;
}
時間複雜度:
- 最好的情況是正序有序時,只需要比較一遍,而不需要移動元素。執行n次。
- 最壞的情況是逆序,需要比較一遍且每個元素都需要移動一次。
插入排序的時間複雜度是O(n^2).
二分法查找
基本概念:
將數組折半查找,比較需要查找值與中間值比較,循環縮小查找範圍,知道 找到最終數據。
/**
* 二分法查找: 將數組分成兩半,在其中一半比較需要查找的數據,依次縮小查找範圍,知道找到需要的值
* @param arrary
* @param findValue
* @return
*/
// 非遞歸方式
private static int binarySearch(int[] array,int findValue){
if(array == null || array.lenth <= 0) return -1;
int left = 0;
int right = array.length - 1;
while(left < right){
int mid = (left+right)/2;
if(findValue == array[mid]){
return mid;
}else if(array[mid] > findValue){
right = mid-1;
}else{
left = mid +1;
}
}
return -1;
}
// 遞歸方式
private static int binarySearch2(int[] arrary,int findValue,int left,int right){
if (arrary == null || arrary.length <= 0 || left > right || left <0 || right < 0) return -1;
int mid = (left+right)/2;
if (findValue > arrary[mid]){
binarySearch2(arrary,findValue,mid,right);
}else if (findValue < arrary[mid]){
binarySearch2(arrary,findValue,left,mid);
}else {
return mid;
}
return -1;
}
時間複雜度:logn.
二分法插入排序
插入排序是循環將未排序序列插入到排序序列中,在排序序列中使用二分法查找需插入的位置會比較快。
代碼實現:
/**
* 通過二分法查找進行插入排序,主要對有序序列進行二分法查找到插入的位置
* @param array
* @return
*/
private static int[] insertSortByBinary(int[] array){
if (array == null || array.length <= 0) return null;
int len = array.length
for(int i =1;i< len;i++){
// 取出新元素
int newElement = array[i];
int left = 0;
int right = i-1;
// 查找插入的位置,最終left就是插入的位置
while(left <= right){
int mid = (left + right)/2;
if(array[mid]<array[i]){
left = mid +1;
}eles{
right = mid -1;
}
}
// 元素後移
for(int j = i-1;j>=left;j--){
array[j+1] = array[j];
}
// 插入元素
array[left] = newElement;
}
return array;
}
測試代碼:
private static int[] testArr = new int[500];
public static void main(String[] agrs){
Random random = new Random();
int length = testArr.length;
for (int i = 0; i < length; i++) {
testArr[i] = random.nextInt(100)+3;
}
System.out.println();
long startTime = System.currentTimeMillis();
int[] ints = selectSort(testArr);
long endTime = System.currentTimeMillis();
System.out.println("選擇排序時間:"+String.valueOf(endTime - startTime));
sprintResult(ints);
System.out.println();
long startTime1 = System.currentTimeMillis();
int[] insertSort = insertSort2(testArr);
long endTime1 = System.currentTimeMillis();
System.out.println("直接插入法時間:"+String.valueOf(endTime1 - startTime1));
sprintResult(insertSort);
System.out.println();
long startTime2 = System.currentTimeMillis();
int[] insertSort1 = insertByBinarySort(testArr);
long endTime2 = System.currentTimeMillis();
System.out.println("二分插入法時間:"+String.valueOf(endTime2 - startTime2));
sprintResult(insertSort1);
}
private static void sprintResult(int[] ints) {
if (ints == null || ints.length <= 0) return;
System.out.println();
int length = ints.length;
System.out.print(" 排序後: ");
for (int i = 0; i < length; i++) {
System.out.print(" "+ints[i]);
}
}
選擇排序
基本概念:
默認第一個元素是已排序並最小(大)元素,在剩餘元素中查找最小(大)的元素,與第一個交換位置;
在剩餘的元素中查找最小(大)的元素,與已經排好序的第二個元素交換位置;
重複以上步驟;
代碼實現:
/**
* 選擇排序:默認第一個是最小的,查找剩餘的最小的元素,交換進行排序,重複以上步驟
* @param array
* @return
*/
private static int[] selectSort(int[] array){
if (array == null ) return null;
int len = array.length;
int tmep;
for(int i = 0;i< len-1;i++){ // 執行n-1次
//設定最小的元素
int min = i;
// 查找剩餘元素的最小元素
for(int j = min+1;j<len;j++){
if(array[min]>array[j]){
min = j;
}
}
// 交換位置,可以在這裏控制是否是穩定排序
if(i!=min){
temp = array[i];
array[i]= array[min];
array[min]= temp;
}
}
return array;
}
時間複雜度:
- 最好的情況是正序時,不需要交換位置,但是每次都需要查找一遍最小元素,時間複雜度O(n^2);
- 最壞的情況是逆序是,不但每次需要查找最小元素的位置還需要作出交換,時間複雜度O(n^2);
結論:
但需排序的數據量越大,二分法插入排序與直接插入排序時間差更明顯。
當數據量大時,選擇排序遠比插入排序耗時。
時間精準到ms。不考慮小數點。
數據量 | 直接插入排序耗時 | 二分法插入排序耗時 | 選擇排序耗時 |
---|---|---|---|
100 | 0 | 0 | 0ms |
1000 | 3ms | 0ms | 4ms |
10000 | 12ms | 5ms | 57ms |
合併四個有序數組
拆分爲合併兩個有序數組,再將合併好的有序數組與其他數組合並。
/**
* 合併2個有序數組,
* 1、先合併兩個,然後將合併好的數組與第三個合併,然後與第四個合併
* 2、可以使用二維數組;之後使用
* 3、將a1和a2的每個元素進行比較,小的放進新數組裏
* 4、將剩餘的每合併完的數組拷貝到新數組(因爲是有序數組,之前也比較過,剩餘的都是可以直接拷貝,順序不會發生變化)
*/
private static int[] mergerSorted(int[] a1,int[] a2){
if(a1== null || a2 == null) return null;
int i = 0,j = 0,k = 0;
int len1 = a1.length;
int len2 = a2.length;
int[] merge = new int[len1+len2];
// 比較兩個不同的數組
while(i<len1 && j<len2){
if(a1[i]<a2[j]){
merge[k++] = a1[i++];
}else{
merge[k++] = a2[j++];
}
}
// 將剩餘的複製到新的數組裏
while(i < len1){
merge[k++] = a1[i++];
}
while(j<len){
merger[k++] = a2[j++];
}
return merge;
}
private static int[] mergeFour(int[] a1,int[] a2,int[] a3,int[] a4){
return mergerSorted(mergeSorted(a1,a2),mergeSorted(a3,a4));
}
測試:
int[] a1 = {1,2,4,5,7,8,9};
int[] a2 = {5,8,10,11};
int[] a3 = {9,12,14,15};
int[] a4 = {7,9,11,16,20};
int[] merge = mergeFour(a1,a2,a3,a4);
System.out.println();
sprintResult(hebin);
測試結果:
排序後: 1 2 4 5 5 7 7 8 8 9 9 9 10 11 11 12 14 15 16 20