以下是O(n2)级别的算法
选择排序
一共n-1轮循环,第一轮将第一个数与它后面所有数比较,若它大于后面某个数则交换,然后继续拿这个新的索引为0的数与后面的数继续比较,直到这一轮循环结束。第二轮循环则拿索引为1的数与它后面的数继续比较(重复上一轮的操作)。
public static void selectsort(int[] arr){
for(int i=0;i<arr.length-1;i++){ //控制排序的轮数同时指定下标范围
for(int j=i+1;j<arr.length;j++){ //被比较数的下表范围
if(arr[i]>arr[j]){
//交换
huan(arr,i,j);
}
}
}
}
插入排序(对于近乎有序的数组,其时间复杂度会降至O(N))
从数组第二个元素开始,比较第二个与第一个元素,后面的更小则交换,就像玩扑克牌一样,抽完牌后将牌插入到前面合适的位置排序
//插入排序(稳定排序:相等值的相对位置不变)
public static void insertsort(int[] arr,int n){
for(int i=1;i<n;i++){
//寻找arr[i]在前面是否有合适的插入位置
for(int j=i;j>0&&arr[j]<arr[j-1];j--){
huan(arr,j,j-1);
}
}
}
优化版插入排序
没有交换操作,大大减少计算时间。首先复制一份待插入的arr[i](int a=arr[i]),若a比它前一个位置(i-1)的数小则将前一个数赋值给后一个数(即a[i]),再比较a和a[i-2],若a小于a[i-2]则a[i-2]赋值给a[i-1],再比较a和a[i-3],,,直到a要比较的数不存在(即a[0]前一个数)或a大于它比较的数,则将a赋值给a[0]或它比较的数的后一个数。
public static void Betterinsertsort(int[] arr,int n){
for(int i=1;i<n;i++){
//寻找arr[i]在前面是否有合适的插入位置
int a=arr[i];
int j;//保存元素a应该插入的位置
for(j=i;j>0&&arr[j-1]>a;j--){
arr[j]=arr[j-1];
}
arr[j]=a;
}
}
以下是O(nlogn)级别的算法
归并排序
//自顶向下归并排序 (稳定排序:相等值的相对位置不变)
public static void mergesort(int[] arr,int n){
__mergesort(arr,0,n-1);
}
//递归使用归并排序,对arr[l,r]的范围进行排序
public static void __mergesort(int[] arr,int l,int r){
// if(l>=r) //这是递归倒底时的一般处理
// return;
if(r-l<10){ //当递归过程中的数组的长度已缩小到一定程度,有序性的概率就大了,用插入排序更快
insertsort2(arr,l,r);
return;
}
int mid=(l+r)/2;
__mergesort(arr,l,mid);
__mergesort(arr,mid+1,r);
if(arr[mid]>arr[mid+1]) //若arr[mid]<arr[mid+1],则数组已经有序,if判断下减少了归并步骤
__merge(arr,l,mid,r);
}
//将arr[l,mid]和arr[mid+1,r]两个部分进行归并
public static void __merge(int[] arr,int l,int mid,int r){
int aux[]=new int[r-l+1];
for(int i=l;i<=r;i++){
aux[i-l]=arr[i]; //i-l:减去偏移量
}
int i=l,j=mid+1; //分别指向两个数组的开头
for(int k=l;k<=r;k++){
if(i>mid){ //若aux[]左半边的元素都访问完了,则将aux右半边剩余元素赋值回arr[]
arr[k]=aux[j-l];
j++;
}
else if(j>r){ //若aux[]右半边的元素都访问完了,则将aux左半边剩余元素赋值回arr[]
arr[k]=aux[i-l];
i++;
}
else if(aux[i-l]<aux[j-l]){ //在aux[]中比较后,将正确结果赋值回arr[]
arr[k]=aux[i-l];
i++;
}else{
arr[k]=aux[j-l];
j++;
}
}
}
快速排序
然而这样写的快速排序有个弊端,当数组是近乎有序的,那选定的最左边用来参照的数"v"极有可能是最小的,则并不能将数组分成小于"v"和大于"v"的两部分,就退化成了O(N2)的算法。不过改进一下,避免这种情况还是简单的,arr[l,r]中随机获得一个数将它和arr[l]交换,再将arr[l]赋值给v。
public static void quicksort(int[] arr,int n){
__quicksort(arr,0,n-1);
}
public static void __quicksort(int arr[],int l,int r){
// if(l>=r)
// return;
if(r-l<10){ //当递归过程中的数组的长度已缩小到一定程度,有序性的概率就大了,用插入排序更快
insertsort2(arr,l,r);
return;
}
int p=__partition(arr,l,r);
__quicksort(arr,l,p-1);
__quicksort(arr,p+1,r);
}
//对arr[l,r]进行__partition操作
//返回p,使得arr[l,p-1]<arr[p];arr[p+1,r]>arr[p]
public static int __partition(int arr[],int l,int r){
int v=arr[l];
//使得arr[l+1...j]<v;arr[j+1..r)>v
int j=l; //j指向小于v的左侧数据最后一个元素
for(int i=l+1;i<=r;i++){
if(arr[i]<v){
huan(arr,j+1,i);
j++;
}
}
huan(arr,l,j);
return j;
}
三路快速排序
当数组中存在大量等于v(参照数)的数,同样导致"<v"和">v"的两部分严重不平衡,因此把整个数组分成"<v"和"=v"和">v"三部分,然后继续对"<v"和“>v”部分进行三路快速排序。
//三路快速排序(将arr[l,r]分为<v,=v,>v三部分)适合重复数据多的数组
//之后递归<v;>v两部分继续进行三路递归排序
public static void quicksort3ways(int[] arr,int n){
__quicksort3ways(arr,0,n-1);
}
public static void __quicksort3ways(int arr[],int l,int r){
if(r-l<10){ //当递归过程中的数组的长度已缩小到一定程度,有序性的概率就大了,用插入排序更快
insertsort2(arr,l,r);
return;
}
//partition
huan(arr,l,new Random().nextInt(r-l+1)+l);
int v=arr[l];
int lt=l; //arr[l+1,lt]<v
int gt=r+1; //arr[gt,r]>v
int i=l+1; //arr[lt+1,i)==v
while(i<gt){
if(arr[i]<v){
huan(arr,i,lt+1);
lt++;
i++;
}else if(arr[i]>v){
huan(arr,i,gt-1);
gt--;
}else{ //arr[i]==v
i++;
}
}
huan(arr,l,lt);
__quicksort3ways(arr,l,lt-1);
__quicksort3ways(arr,gt,r);
}
PS:发现了一个BUG修复了一下,就是在归并、快速、三路快速排序中的以下程序段:
if(r-l<10){ //当递归过程中的数组的长度已缩小到一定程度,有序性的概率就大了,用插入排序更快
insertsort2(arr,l,r);
return;
}
应修改成这样,新写了一个插入排序,之前传入的是arr,r-l+1,这样导致插入排序排序的部分跟我想让它排序的不是一个部分,,,,