----------------------------------------常见算法之排序(基础部分)_01-----------------------------------------------
排序 | 时间复杂度 | 空间复杂度 |
归并排序(Merge Sort) | O(N*logN) | O(N) |
快速排序(Quick Sort) | O(N*logN) | O(logN) |
堆排序(Heap Sort) | O(N*logN) | O(1) |
用master method方法求递归程序的时间复杂度如下:
若log a / log b > d | 则T( n ) = O( n ^ ( log a / log b)) |
若log a / log b > d | 则T( n ) = O( n ^ d ) |
若log a / log b > d | 则T( n ) = O( N ^ d * log N ) |
调用Array.sort()函数对数组排序,其实用的是综合排序。当子数组的内容小于60时,直接用插入排序,因为插入排序的时间复杂度的常数项小,当n小时优势就体现出来了。当子数组的内容大于60时,会调用归并排序或者快速排序。当数组的内容为基础类型时,会调用快速排序,快速排序里面的partition过程会将数组分成子数组,然后继续判断是否大于60;当数组的内容为类类型时(包括自定义类),调用归并排序,归并排序中每次递归都会将数组分为子数组,然后继续判断是否大于60。采用综合排序的原因是考虑稳定性,基础类型不用考虑稳定性,所以用快速排序 (比如整型,一堆数字3是一样的)。
------------------------------------------------------归并排序--------------------------------------------------
public class Code_01_Sort
{
public static void main(String[] args)
{
//初始化数组
int len = (int)(Math.random()*20);//随机生成20以内的数组长度
int[] arr = new int[len];
for(int i = 0; i < len; i++)//在数组内随机生成1000内的值
{
arr[i] = (int)(Math.random()*1000) - (int)(Math.random()*1000);
}
mergeSort(arr);//调用归并排序
for(int i = 0; i < len; i++)//格式化输出数组
System.out.printf("%02d:%-+4d\n",i,arr[i]);
}
public static void mergeSort(int[] arr)
{//这个函数主要用来统一函数接口,sortProcess才是归并排序
System.out.println("归并排序:");
if(arr == null)
return;
sortProcess(arr,0,arr.length-1);
}
public static void sortProcess(int[] arr, int L, int R)
{//归并排序:用递归思想处理排序问题,归并指的是先递归,再合并
if(L == R)//基础条件:如果子数组只有一个元素,就不再继续递归。
return;
int mid = L + ((R-L)/2);//中点位置,直接用(L+R)/2可能会溢出
sortProcess(arr,0,mid);//递归的排序数组的左半部分
sortProcess(arr,mid+1,R);//递归的排序数组的右半部分
merge(arr,L,mid,R);//调用合并函数,将有序的左半部分和有序的右半部分排成总体有序
}
public static void merge(int[] arr, int l, int mid, int r)//L,mid,R分别指数组的开始位置,中间位置,结束位置
{//合并函数
int[] help = new int[r-l+1];//辅助数组
int ptr_h = 0;//指针pointer_help,指向help数组
int ptr_l = l;//指针pointer_left,指向左半部分数组
int ptr_r = mid+1;//指针pointer_right,指向右半部分数组
while(ptr_l <= mid && ptr_r <= r)//只要两边还有元素没比完,就一直比下去,直到其中一边元素都执行过对比的动作。
{
if(arr[ptr_l] < arr[ptr_r])//比较左边的有序数组和右边的游戏数组
{
help[ptr_h] = arr[ptr_l];//哪边的最小值小,就把该边最小值赋给help辅助数组,
ptr_l++;//并把最小值小的那边的指针向右移一位
}
else
{
help[ptr_h] = arr[ptr_r];
ptr_r++;
}
ptr_h++;//将help数组的指针向右移动一位
}
while(ptr_l <= mid)//左边元素还没对比完,则把左边的剩余元素直接填到help辅助数组去
{
help[ptr_h] = arr[ptr_l];
ptr_h++;
ptr_l++;
}
while(ptr_r <= r)//右边元素还没对比完,则把右边的剩余元素直接填到help辅助数组去
{
help[ptr_h] = arr[ptr_r];
ptr_h++;
ptr_r++;
}
for(int i = 0; i < help.length; i++)//将已排好序的辅助数组复制给元数组
{
arr[l+i] = help[i];
}
}
}
练习:求小和问题
//求小和问题:给定一个数字数组,将每个数左边比它小的数的值都累加起来,最终和是多少?
//思路:某个数的左边部分其实有无序都不影响该数的小和。可以用归并排序,边排序边求小和。
//在合并过程中返回给已排好序的两个区间进行整体排序时产生的小和。
//直接看代码会好懂一些,其实就是稍微改动了下归并排序。
public class Code01_ZuiXiaoHe
{
public static void main(String[] args)
{
int[] arr = {4,1,3,5,0,6};
int result = smallSum(arr);
System.out.println(result);
}
public static int smallSum(int[] arr)
{
if(arr == null || arr.length < 2)
return 0;
return mergeSort(arr, 0, arr.length-1);
}
public static int mergeSort(int[] arr, int l, int r)
{
if(l == r)
return 0;
int mid = (l +(r-l)/2);
return mergeSort(arr, l ,mid) + mergeSort(arr,mid+1,r) + merge(arr,l,mid,r);
}
public static int merge(int[] arr, int l, int m, int r)
{
int[] help = new int[r-l+1];
int point_h = 0;
int point_l = l;
int point_r = m+1;
int result = 0;
while(point_l <= m && point_r <= r)
{
if(arr[point_l] < arr[point_r])
{
result += arr[point_l]*(r - point_r + 1);//有所改动的部分(某个数*右边比它大的个数)
help[point_h] = arr[point_l];
point_l++;
}
else
{
result += 0;
help[point_h] = arr[point_r];
point_r++;
}
point_h++;
}
while(point_l <= m)
{
help[point_h] = arr[point_l];
point_h++;
point_l++;
}
while(point_r <= m)
{
help[point_h] = arr[point_r];
point_h++;
point_r++;
}
for(int i = 0; i < help.length; i++)
{
arr[l+i] = help[i];
}
return result;
}
}