1.插入排序
①一般思路:
②(优化)减少交换次数
代码:
public class InsertionSort {//已优化
public static void insertionSort(int[] a,int left , int right){//下标是闭区间
for(int i=left+1; i<=right; i++){
int temp = a[i];
int j;
for(j=i; j>0&&a[j-1]>temp;j--)
a[j] = a[j-1];
a[j]= temp;
}
}
}
2.选择排序
思路:
代码:
public static void selectionSort(int[] a,int start,int end){//闭区间
for(int i=start; i<=end; i++){
int min = a[i];
int minIndex = i;
for(int j =i; j<=end; j++){
if(a[j]<min){
min = a[j];
minIndex = j;
}
}
swap(a,minIndex,i);
}
}
public static void swap(int[] a, int m, int n){
int temp = a[m];
a[m] = a[n];
a[n] = temp;
}
3.归并排序
①思路(自顶向下):
代码:
//划分数组并逐层排序,自顶向下
public static void mergeSort(int[] arr,int left,int right){//left和right皆为闭区间
if(left>=right)
return;
if(right-left<=15){//这种情况下用插入排序效果更好
InsertionSort.insertionSort(arr,left,right);
return;
}
int middle = (left+right)/2;
mergeSort(arr,left,middle);//保证了数组中,left到middle是有序的
mergeSort(arr,middle+1,right);//保证了数组中,middle+1到right是有序的
if(arr[middle]>arr[middle+1])//若中间的比右边的还大,则才有必要进行排序
merge(arr,left,middle,right);
}
/**
* 对数组arr[left...middle]和arr[middle+1...right]两部分进行排序
* @param arr
* @param left
* @param middle
* @param right
*/
public static void merge(int[] arr,int left , int middle , int right){
//1.复制一份数组
int[] arrCopy = new int[right-left+1];
for(int i=left ; i<=right; i++)
arrCopy[i-left] = arr[i];
//2.定义副本数组的两个索引
int i = left , j = middle+1;
for(int k = left; k<=right; k++){
//3.先判断下标有没有越界,再来判断3个值大小
if(i>middle){//左边数组弄完了
arr[k] = arrCopy[j-left];//-left是因为起始是left
j++;
}
else if(j>right){//右边数组弄完了
arr[k] = arrCopy[i-left];
i++;
}
else if(arrCopy[i-left] < arrCopy[j-left]){//左边数组的值小于右边数组的值
arr[k] = arrCopy[i-left];//将较小值赋值给原数组的值
i++;//左数组下标右移
}
else{
arr[k] = arrCopy[j-left];
j++;
}
}
}
②自底向上
代码:
public static void mergeSortBU(int[] arr , int left , int right){//自底向上
int n = arr.length;
for(int size = 1; size<=n; size=size*2)//遍历数组元素,从1个元素,到2个,到4,到8
for(int i = 0; i+size<n; i=i+2*size)//每一轮归并中起始的元素位置
//对arr[i到i+size-1]和arr[i+size到i+2*size-1]进行归并
merge(arr,i,i+size-1,Math.min(i+size+size-1,n-1));
}
/**
* 对数组arr[left...middle]和arr[middle+1...right]两部分进行排序
* @param arr
* @param left
* @param middle
* @param right
*/
public static void merge(int[] arr,int left , int middle , int right){
//1.复制一份数组
int[] arrCopy = new int[right-left+1];
for(int i=left ; i<=right; i++)
arrCopy[i-left] = arr[i];
//2.定义副本数组的两个索引
int i = left , j = middle+1;
for(int k = left; k<=right; k++){
//3.先判断下标有没有越界,再来判断3个值大小
if(i>middle){//左边数组弄完了
arr[k] = arrCopy[j-left];//-left是因为起始是left
j++;
}
else if(j>right){//右边数组弄完了
arr[k] = arrCopy[i-left];
i++;
}
else if(arrCopy[i-left] < arrCopy[j-left]){//左边数组的值小于右边数组的值
arr[k] = arrCopy[i-left];//将较小值赋值给原数组的值
i++;//左数组下标右移
}
else{
arr[k] = arrCopy[j-left];
j++;
}
}
}
4.快速排序
①一般思路:
代码:
public static void quickSort(int[] arr, int left, int right) {
if (right-left<=15){
InsertionSort.insertionSort(arr, left, right);
return;
}
int p = partition(arr, left, right);
quickSort(arr, left, p - 1);//排序,使得左边的数都比p小
quickSort(arr, p + 1, right);//排序,使得右边的数都比p大
}
/**
* 返回一个p,使得任何arr[left...p-1]都小于arr[p],任何arr[p+1...right]都大于arr[p]
*
* @param arr
* @param left
* @param right
* @return
*/
private static int partition(int[] arr, int left, int right) {
int v = arr[left];//第一个数
// arr[left+1...j]<v;arr[j+1...i)>v
int j = left;//小于当前访问值的区间的右边界
for(int i=left+1;i<=right;i++){
if(arr[i]<v){
swap(arr, i,j+1);
j++;
}
}
swap(arr,left,j);
return j;
}
private static void swap(int[] a,int i, int j) {
int temp= a[i];
a[i] = a[j];
a[j] = temp;
}
②(优化)两边一起来:
代码:
public static void quickSortOptimize(int[] arr,int l,int r){
if(r-l<=15){
InsertionSort.insertionSort(arr, l, r);
return;
}
int p = partition2(arr,l,r);
quickSortOptimize(arr,l,p-1);
quickSortOptimize(arr,p+1,r);
}
private static int partition2(int[] arr, int l, int r) {
int v = arr[l];
int i = l+1,j = r;
while(true){
while(i<=r && arr[i]<v) i++;//直到某个数大于v
while(j>=l+1 && arr[j]>v) j--;
if(i>j) break;
swap(arr,i,j);
i++;//右移
j--;//左移
}
swap(arr,l,j);
return j;
}
③(优化)递归的时候不去考虑相等的部分:
代码:
/**
* 三路快速排序,让相等的不参与递归
* @param a
* @param l
* @param r
*/
public static void quickSort3Ways(int[] a,int l,int r){
if(r-l<=15){
InsertionSort.insertionSort(a,l,r);
return;
}
int v = a[l];
int lt = l;//保证a[l+1...lt]<v
int gt = r+1;//保证a[gt...r]>v
int i= l+1;//保证a[lt+1...i)==v
while(i<gt){
if(a[i]<v){
swap(a,i,lt+1);
lt++;
i++;
}
else if(a[i]>v){
swap(a,i,gt-1);
gt--;
}
else{//a[i]==v
i++;
}
}
swap(a,l,lt);
//让相等的不参与递归了
quickSort3Ways(a, l, lt-1);//lt-1是因为前面已经交换了v和a[lt]
quickSort3Ways(a,gt,r);
}
测试耗时
代码:
GenerateArray generate = new GenerateArray();
int[] arr = generate.getArr();
@Test
public void testSelectionSort(){
long start = System.nanoTime();
SelectionSort.selectionSort(arr,0,99999);
long end = System.nanoTime();
System.out.println("SelectionSort耗时: "+ ((double)(end - start)/1000000)+"ms");
}
@Test
public void testInsertionSort(){
long start = System.nanoTime();
InsertionSort.insertionSort(arr,0,99999);
long end = System.nanoTime();
System.out.println("InsertionSort耗时: "+ ((double)(end - start)/1000000)+"ms");
}
@Test
public void testMergeSort(){
long start = System.nanoTime();
MergeSort.mergeSort(arr,0,99999);
long end = System.nanoTime();
System.out.println("MergeSort耗时: "+ ((double)(end - start)/1000000)+"ms");
}
@Test
public void testMergeSortBU(){
long start = System.nanoTime();
MergeSort.mergeSortBU(arr,0,99999);
long end = System.nanoTime();
System.out.println("MergeSortBU耗时: "+ ((double)(end - start)/1000000)+"ms");
}
@Test
public void testQuickSort(){
long start = System.nanoTime();
QuickSort.quickSort(arr,0,99999);
long end = System.nanoTime();
System.out.println("QuickSort耗时: "+ ((double)(end - start)/1000000)+"ms");
}
@Test
public void testQuickSortOptimize(){
long start = System.nanoTime();
QuickSort.quickSortOptimize(arr,0,99999);
long end = System.nanoTime();
System.out.println("QuickSortOptimize耗时: "+ ((double)(end - start)/1000000)+"ms");
}
@Test
public void testQuickSort3Ways(){
long start = System.nanoTime();
QuickSort.quickSort3Ways(arr,0,99999);
long end = System.nanoTime();
System.out.println("QuickSort3Ways耗时: "+ ((double)(end - start)/1000000)+"ms");
}
public class GenerateArray {
int[] arr = new int[100000];
public int[] getArr() {
return arr;
}
public GenerateArray() {
super();
this.arr = generateRandomArray(100000,1,100);
}
/**
* 生成从start到end的随机数组
* @param total 数组个数
* @param start 从哪里开始
* @param end 从哪里结束
* @return
*/
public static int[] generateRandomArray(int total , int start , int end){
int[] a = new int[total];
int minus = end - start;
if(minus<0)
try {
throw new Exception("end>start");
} catch (Exception e) {
e.printStackTrace();
}
for(int i=0; i<total; i++)
a[i] = (int) (Math.ceil(Math.random()*minus)+start);
return a;
}
}
结果:
总结
插入排序、选择排序 的平均时间复杂度都是O(n^2)
归并排序、快速排序 的平均时间复杂度都是O(nlogn)
访问数组的值、交换数组中的两个值都很耗时,尽量避免