排序

  • 冒泡排序:
static void MP_sort(int[]array){
    for(int i=0;i<array.length;i++)
        for(int j=i+1;j<array.length;j++)
            if(array[j]<array[i]){
                int temp=array[i];
                array[i]=array[j];
                array[j]=temp;
            }
}

 

  • 插入排序:将数组分为两个部分,一边排好序的,一边是乱的,将乱一个个插入到有序数组合适的位置
    • 性能与数组中的逆序对数量息息相关
    • 通过交换相邻元素进行排序的任何算法平均都需要N^2
void insert_sort( int array[]){
    int j;
    for(int i=1;i<array.length;i++){
        int temp=array[i];
        for (j=i;j>0&&temp<array[j-1];j--)
            array[j]=array[j-1];
        array[j]=temp;
    }
}
 

 

  • 希尔排序:缩小增量排序,是对插入排序的一种改良
    • 一趟一个增量,用增量进行分组,组内进行插入排序
    • 可以理解为插入排序是增量1的希尔排序
    • 因为插入排序的性能取决于逆序对数量,但每次交换只能抵消一个逆序对,希尔排序提高一次交换抵消逆序对的数量
    • 至于增量大小的选择:array.length/2是作者shell建议的序列,但是不好
    • 运行时间更依赖增量的选择:
      • shell的最坏是n^2;(1,2,4,8)
      • Hibbard的最坏是n^(3/2);(1,3,7,15,2^k-1))
      • Sedgewick的最坏是n^(4/3),平均是n^8/7;(1,5,19,41,109)或者(9*4^i-9*2^i+1)或(4^i-3*2^i+1)
    • 编程简单特点使得他成为对适度地大量的输入数据经常选用
void shell_sort(int array[])
{
        for(int mid=array.length/2;mid>0;mid/=2)      //mid表示增量的大小,每一次整除与2,
                 for(int i=mid;i<array.length;i++)//分组,后一部分
                 {              
                 int temp=array[i];
                 int j=i;
                 while(j>=0&&temp<a[j])        //对组内元素进行插入排序
                  {
                    array[j]=array[j+mid];
                    j-=mid;
                  }
                 aarray[j]=temp;   
         }
}

  

  • 堆排序:建立堆的时间是logN,每次删除堆顶需要n次,所以为N*logN
    • 为了避免使用新数组存储被删除的数,我们将数插入在堆后面
    • 当然为了方便,我们把堆建成大顶堆,这样容易得到从小到大的数组
    • 稳定,它使用的比较平均只比最坏情形界指出的略少,最坏(2N*logN-O(N))
void heap_sort(int array[]){
    for(int i= array.length/2-1;i>=0;i--)//建立堆
            percDown(array,i, array.length);
    for(int i=array.length-1;i>0;i--){
        int temp=array[0];
        array[0]=array[i];
        array[i]=temp;  //交换,使得最大在最后
        percDown(array,0, i);//调整
    }
}
 
 
void percDown(int array[],int i,int n){
    //i表示从该节点为根进行下滤调整,n表示堆的大小
    int child=2*i+1;//左树
    int temp=array[i];
    while(child<n)//左子树边界,否则就是叶子节点
    {
        if(child!=n-1&&array[child]<array[child+1])
            child++;//比较左右子树那个大,大的位置在child
        if(array[child]>temp)
            array[i]=array[child];
        else break;
        i=child;//由于是这个儿子升上去;所以下滤寻找temp适合的位置
        child=2*i+1;
    }
    array[i]=temp;
}

 

 
  • 归并排序:核心操作就是合并两个排好序的数组
    • 最坏时间O(nlogn),需要开辟辅助数组
    • 分治法的经典策略
    • 归并排序在其他流行排序算法中最少的比较次数,在java标准库中泛型排序所使用的算法
//初始化,开辟辅助空间
static void merge_sort(int array[]){
    int []B=new int[array.length];
    merge_sort(array,B,0,array.length-1);
}
//分割
static void merge_sort(int array[],int B[],int left,int right){
    if(left<right){
        int center=(left+right)>>1;
        merge_sort(array,B,left,center);
        merge_sort(array,B,center+1,right);
        merge(array,B,left,center+1,right);;//合并
    }
}
//合并
static void merge(int array [],int B[],int left_p,int right_p,int right_end){
        int left_end=right_p-1;
        int B_p=left_p;//辅助空间起始位置
        int numElements=right_end-left_p+1;//辅助空间大小
        //比较大小
        while (left_p<=left_end&&right_p<=right_end){
            if(array[left_p]<=array[right_p])
                B[B_p++]=array[left_p++];
           else
                B[B_p++]=array[right_p++];
        }
        
        //如果有一个先没了,那剩下就是另一个的
        while (left_p<=left_end){
            B[B_p++]=array[left_p++];
        }
        while (right_p<=right_end){
            B[B_p++]=array[right_p++];
        }
        //更新原数组
        while(numElements!=0){
            array[right_end]=B[right_end];
            right_end--;
            numElements--;
        }
}
 

 

  • 快速排序:平均时间复杂度O(N logN)
    • 选定枢纽元,将比它小的放在左边,比他大的放在右边
    • 然后分成两部分,进行递归操作
    • 选取枢纽元:
      • 选择第一个数虽然简单,但不是好主意(错误)
      • 随机在数组中抽取,大大减低接连不断产生劣质分割的风险(安全,但生成随机数的算法开销很大)
      • 三数中值分割法:对比数组中,开头,中间,尾巴的三个值,选中中间的作为枢纽元
    • 对于小数组,快速排序的效率可能没插入排序好
    • 在快速排序的基础上可以改成快速选择,在类似寻找第k大元素的需求可以应用
    • (分治法选择问题):五数中值取中 分割法,基本的枢纽元选择算法如下
      • 把N分为5个元素的组组,一共N/5个
      • 找出每个组的中值项,得到N/5个项存在M表中
      • 求M的中值项,将其作为枢纽元
//快速排序入口,带优化
     void  quick_sort(int array[]){
        quick_sort(array,0, array.length-1);
    }
// 交换   
     void swap(int array[],int x,int y){
        int temp=array[x];
        array[x]=array[y];
        array[y]=temp;
    }
//  快排主元选取的优化
     int median3( int array[],int left,int right){
        //三数中值分割法处理主元,使得left比主元小,right比主元大
        //最后把主元放在后面,注意,是right-1位置,因为确定right比主元大了
        int center=(left+right)>>1;
 
        if(array[center]<array[left])
            swap(array,center,left);
        if(array[right]<array[left])
            swap(array,right,left);
        if(array[right]<array[center])
            swap(array,center,right);
        swap(array,center,right-1);
        return array[right-1];
    }
//  如果是小数组,那么就使用插入排序
     void insert_sort( int array[],int left,int right){
        int j;
        for(int i=left+1;i<=right;i++){
            int temp=array[i];
            for (j=i;j>0&&temp<array[j-1];j--)
                array[j]=array[j-1];
            array[j]=temp;
        }
    }
//    现实中快排核心主体,由于包含在实际中的优化;如果想纯净的快排,不能只是把CUTOFF修改0
//    因为如此操作会导致数组溢出,因为在优化主元时候少于上三个
     void quick_sort(int array[],int left, int right){
        //为了优化效率,我们设置CUTOFF大小比如20;
        //意思是在实际中数组少于20个就没必要用快排,插入排序更快
        int CUTOFF=10;
        if(left+CUTOFF<=right){
            int pivot=median3(array,left,right);
            int i=left,j=right-1;
             while(true){
                while (array[++i]<pivot){ }
                while (array[--j]>pivot){ }
                if(i<j)
                    swap(array,i,j);
                else break;
            }
            swap(array,i,right-1);//交换主元,数组分割
            quick_sort(array,left,i-1);
            quick_sort(array,i+1,right);
        }
        else{
            insert_sort(array,left,right);
        }
    }

 

    • 不带优化的快速排序
static void quick_2(int array[], int q, int n)//q为起始位置,n为终点
{
    if(q>=n)return;
    int inter=array[q];//把起始位置作为主元
    int L=q+1;
    int R=n;
 
 
    while(L<=R)///数组都遍历完了即R与L交错时停止循环
    {
        while(L<=R&&inter>=array[L])   L++;
        while(L<=R&&inter<array[R])    R--;
 
 
        if(L<R)///交换位置,把比主元大的放后面 ,比主元小的放前面
        {
            int temp=array[L];
            array[L]=array[R];
            array[R]=temp;
        }
    }
 
 
    array[q]=array[R];
    array[R]=inter; ///交换位置把主元放进去作为间隔
 
 
    quick_2(array,q,R-1);
    quick_2(array,R+1,n);
}
 

 

  • 线性时间的排序:桶排序和基数排
  • 桶排序:将待排序元素划分到不同的桶。
    • 先扫描一遍序列求出最大值 maxV 和最小值 minV ,
    • 设桶的个数为 k ,则把区间 [minV, maxV] 均匀划分成 k 个区间,每个区间就是一个桶。将序列中的元素分配到各自的桶。
    • 对每个桶内的元素进行排序。可以选择任意一种排序算法。
    • 将各个桶中的元素合并成一个大的有序序列。
  • 基数排序
    • 得出最大数的位数
    • 由于每一位的取值范围为0-9;所以定义长度为10的数组B ,B[i]又引出一个ArrayList,就像二维表
      • 从个位开始,遍历原数组,我们将原数组值得对应位加入对应数组,比如456;我们把他加入B[6];231加入B[1];
      • 然后根据B[i]顺序将值覆盖原数组;对同一个B[i]的ArrayList有多个值,一定要按头到尾输出,保留顺序,就是按插入的顺序
    • 接下来就是十位了,我们重复上面那两个步奏就行
    • 最大数有多少位就重复弄几次
    • 应用:将字符串排序
static  void radix_sortA(String [] str ,int stringlen){
    //假设所有字符串有相同的长度
    int BUCKETS=256; //Unicode编码的前256位
    ArrayList<String>[] array=new ArrayList[BUCKETS];  //创建256空间的数组
    for(int i=0;i<BUCKETS;i++) //根据下标创建链表
        array[i]=new ArrayList<>();
    for(int pos=stringlen-1;pos>=0;pos--)// 从低位开始到高位
    {
        for(String s:str) //遍历原字符串数组的每一个字符串,将他插在合适的位置上
            array[s.charAt(pos)].add(s);
        int idx=0;
        for(ArrayList<String> thisArr : array){ //复原原数组,就是把新的顺序覆盖原数组
            for(String s:thisArr)
                str[idx++]=s;
            thisArr.clear();// 清空,以便下一位的进去
        }
    }
}   

 

                                                                                                                                                                                                                                                 
  • 计数排序:
    • 计数排序是一种稳定的线性时间排序算法。
    • 假设数组中的所有数据为小于M的正整数
    • 使用一个M大小的count数组,初始化为0;遍历原数组,读取array[i]时候;count[array[i]]++;最后扫描count数组进行输出
    • 时间复杂度O(N),需要额外空间M
  • 外部排序:
    • 对于各种海量的数据,以上的排序方法就不适合了,因为我们的内存有限,上面那些算法称为内部排序
    • 基本的外部排序算法使用的是归并排序中的合并算法
    • 简单算法:以下代码不完整,但思路清晰,需要额外三块磁盘
//假设你电脑内存有限,你有数据在磁盘上,数据量远超你的内存,请问你如何排序
//内存大小N,磁盘A大小M,N<<M;
void sort1(int A[]){
    int N=10;//假设内存最多一次放10个元素
    int M=A.length-1;
    //三块辅助磁盘,大小和A一样
    int[] B= new int[M];
    int[] C= new int[M];
    int[] D= new int[M];
    int k=1;
    //由于内存排序的速度高于外存
    //所以第一步操作,将磁盘A每次取N的元素放在内存排序后
    //分奇偶次分别存进,数组D,C
    int c1=0;
    int d1=0;
    while(true){
        if(k*N-1<M) {
            insert_sort(A, (k - 1) * N, k * N-1);
            if(k%2==0){//存在C
                for(int i=(k-1)*N;i<=N*k-1;i++,c1++)
                    C[c1]=A[i];
            }else//存在D
                for(int i=(k-1)*N;i<=N*k-1;i++,d1++)
                    D[d1]=A[i];
        }
        else {
            insert_sort(A, (k - 1) * N, M);
            if(k%2==0){//存在C
                for(int i=(k-1)*N;i<=M;i++,c1++)
                    C[c1]=A[i];
            }else//存在D
                for(int i=(k-1)*N;i<=M;i++,d1++)
                    D[d1]=A[i];
             break;//退出
        }
        k++;
    }
    //接下来就是归并排序中的合并算法
    //将CD合并到AB
    A=sort2(C,D,A,B,N);
 
 
}
int [] sort2(int A[],int B[],int C[],int D[],int k){
    //将AB合并到CD,
    //k表示AB中有序串的长度
    //检查AB是否有一个为空,有就返回另一个
    if (){
        
    }
    int alen=A.length-1;
    int blen=B.length-1;
    int t=1;
    int c1=0;
    int d1=0;
    while((t-1)*k<=alen||(t-1)*k<=blen){
        int a1=(t-1)*k;
        int b1=(t-1)*k;
        if(t*k-1<=alen&&t*k-1<=blen){
            if(t%2==0){//AB中两个串合并到C
                andsort(A,B,C,a1,t*k-1,b1,t*k-1,(t-1)*k);
            }
            else
                andsort(A,B,D,a1,t*k-1,b1,t*k-1,(t-1)*k);
        }
        else if(t*k-1<=blen){
            if(t%2==0){//AB中两个串合并到C
                andsort(A,B,C,a1,alen,b1,t*k-1,(t-1)*k);
            }
            else
                andsort(A,B,D,a1,alen,b1,t*k-1,(t-1)*k);
        }
        else if(t*k-1<=alen){
            if(t%2==0){//AB中两个串合并到C
                andsort(A,B,C,a1,t*k-1,b1,blen,(t-1)*k);
            }
            else
                andsort(A,B,D,a1,t*k-1,b1,blen,(t-1)*k);
        }
        t++;
    }
     //必须清空数组AB
    A  B.clear();
   return  A=sort2(C,D,A,B,2*k);
 
 
}
 
//用于合并两个数组AB到C
void  andsort(int a[],int b[],int c[],int abe,int aend,int bbe,int bend,int cbe){
    while(true){
        if(abe<=aend&&bbe<=bend){
            if(a[abe]<b[bbe])
                c[cbe++]=a[abe++];
            else c[cbe++]=b[bbe++];
        }
        else if(abe>aend&&bbe<=bend){
            c[cbe++]=b[bbe++];
        }else{
           while(abe<=aend)
               c[cbe++]=a[abe++];
            break;
        }
    }
 
}
//插入排序,用于内存内部排序
void insert_sort( int array[],int left,int right){
    int j;
    for(int i=left+1;i<=right;i++){
        int temp=array[i];
        for (j=i;j>0&&temp<array[j-1];j--)
            array[j]=array[j-1];
        array[j]=temp;
    }
}
 

 

    • 多路合并:如果可以使用更多的磁盘,那我们可以扩充为多路合并,减少数据排序所需要的趟数,但是对于合并排序中寻找小元素插入新磁盘就比较麻烦;k路合并需要2k个磁盘;
    • 多相合并:k路合并需要k+1个磁盘;k个磁盘合并到一个后,这一个再把数据串分配下去各个磁盘,上面的是合并包分配
    • 替换选择:对顺串的优化
 
 
posted @ 2020-07-12 23:36  浪波激泥  阅读(161)  评论(0编辑  收藏  举报