插入排序
基本概念:
将未排序序列通过向有序序列从后向前扫描,找到合适的位置插入到有序序列里。
基本步骤:
- 从第二个元素开始。(第一个元素默认为是有序序列)
- 取出下一个元素,在已排序序列中从后向前扫描
- 该元素大于新元素就往后移动
- 重复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