zoukankan      html  css  js  c++  java
  • 几种常见的排序算法分析学习

     

    本篇博客知识点 
    分别描述了 冒泡,选择,直接插入,二分插入,希尔,快速以及归并排序。同时还有Java实现代码,算法分析和示意图

    冒泡排序

    算法描述

    • 设待排序记录序列中的记录个数为n
    • 一般地,第i趟起泡排序从1到n-i+1
    • 依次比较相邻两个记录的关键字,如果发生逆序,则交换之。
    • 其结果是这n-i+1个记录中,关键字最大的记录被交换到第n-i+1的位置上,最多作n-1趟。

    算法实例 
    经过五趟可以将 21 25 49 25 16 08 排为由小到大的升序 
    这里写图片描述 
    其中,里面的每一趟的排序示意如下,以第一天49如何配到最后为例 
    这里写图片描述

    每一趟的结果都是把未排序的最大的那个数字排到最后。

    算法代码实现—Java代码实现 
    工具方法,交换数组中的两个位置

    private static void swap(int[] num, int i, int j) {
            int temp = num[i];
            num[i] = num[j];
            num[j] = temp;
    
        }
    //优化版---冒泡排序
        public void sort2(int [] num){
            for(int i=0;i<num.length;i++){
                boolean isOK = true; 
                for(int j=0;j<num.length-i-1;j++){
                    if(num[j]>num[j+1]){
                        //交换位子
                        swap(num, j, j+1);
                        isOK = false;
                    }
                }
                // 当某次没有交换时,说明已经是排好序了。后面就可以不用再比较了
                if(isOK){
                    System.out.println("isOk:"+isOK);
                    break;
                }
            }
        }
        //冒泡排序
        public void sort1(int [] num){
            for(int i=0;i<num.length-1;i++){
                for(int j=0;j<num.length-1-i;j++){
                    if(num[j]>num[j+1]){
                        //交换位子
                        swap(num,j,j+1);
                    }
                }
            }
        }

    算法评价

    • 时间复杂度:T(n)=O(n²)
    • 空间复杂度:S(n)=O(1) 
      这里写图片描述

    选择排序

    算法描述

    • 首先通过n-1次比较,从n个数中找出最小的, 将它与第一个数交换——第一趟选择排序,结果最小的数被安置在第一个元素位置上。
    • 再通过n-2次比较,从剩余的n-1个数中找出关键字次小的记录,将它与第二个数交换——第二趟选择排序。
    • 重复上述过程,共经过n-1趟排序后,排序结束。

    排序实例 
    找到最小的数,把他放到前面去 
    这里写图片描述

    算法代码实现—Java代码实现

    //选择排序:每一趟选出最小的数前面的数交换
        public void sort2(int a[]){
            int k = 0;// k用来比赛当前最小的数的坐标
            for(int i=0;i<a.length-1;i++){
                for(int j=i+1;j<a.length;j++){
                    if(a[j]<a[k]){
                         k = j;
                    }
                }
                //一轮比较完后,最小的数的位子为k  应该放的位子最前面的位子i 交换~
                swap(a, i, k);
            }
        }

    算法评价

    • 时间复杂度:T(n)=O(n²)
    • 空间复杂度:S(n)=O(1) 
      这里写图片描述

    3.1 直接插入排序

    算法分析

    • 关键字比较次数和记录移动次数与记录关键字的初始排列有关。
    • 最好情况下, 排序前记录已按关键字从小到大有序, 每趟只需与前面有序记录序列的最后一个记录比较1次, 移动2次记录, 总的关键字比较次数为 n-1, 记录移动次数为 2(n-1)。在平均情况下的关键字比较次数和记录移动次数约为n² /4。
    • 直接插入排序是一种稳定的排序方法
    • 直接插入排序最大的优点是简单,在记录数较少时,是比较好的办法。

    算法描述:

    记录存放在数组R[0….n-1]中,排序过程的某一中间时刻,R被划分成两个子区间R[0…i-1]和R[i….n-1],其中:前一个子区间是已排好序的有序区;后一个子区间则是当前未排序的部分。

    基本操作:

    将当前无序区的第1个记录R[i]插入到有序区R[0….i-1]中适当的位置,使R[0…i]变为新的有序区。

    操作细节:

      当插入第i(i≥1)个对象时, 前面的r[0], r[1], …, r[i-1]已经排好序。
      用r[i]的关键字与r[i-1], r[i-2], …的关键字顺序进行比较(和顺序查找类似),如果小于,则将r[x]向后移动(插入位置后的记录向后顺移);找到插入位置即将r[i]插入。
    • 1
    • 2

    算法实例: 21, 25, 49, 25*, 16, 08 
    这里写图片描述

    实现代码

    //3.1直接插入排序 ---原序列越有序排得越快 (逆序排得最慢)
        public void sort3_2(int a[]){
            //依次把每个元素拿来插入到 之前已经有序的子序列当中
            for(int i=0; i<a.length-1; i++){//趟数:n-1  ---除第1个元素,后面的每个元素都拿来插入一次
                //前面i个数已经排好序,现在是准备插入第i+1个数
    
                //待插入的数
                int temp = a[i+1];
    
                //找到j ----temp最终是坐在j+1的位置    j的情况:或者是-1,或者是从后往前找到的第一个没有比temp大的数
                int j=i;//从第i个位置开始从后往前依次边查找边移位置
                while(a[j]>temp){
                    a[j+1] = a[j];
                    j--;
                    if(j<0){
                        break;
                    }
                }
                //让temp坐在j+1的位置 
                a[j+1]=temp;
            }
        }
    

    3.1 二分查找插入排序

    算法描述:

    • 在直接插入排序的基础上,利用二分(折半)查找算法决策出当前元素所要插入的位置。
    • 在直接插入排序的基础上,利用二分(折半)查找算法决策出当前元素所要插入的位置。
    • 找到当前元素的插入位置i之后,把i和high之间的元素从后往前依次后移一个位置,然后再把当前元素放入位置i。

    算法实现:

    
        //3.2 加入二分查找的插入排序
            private static void binaryInsertSort(int[] a) {
                //依次把每个元素拿来插入到 之前已经有序的子序列当中
                for(int i=0; i<a.length-1; i++){//趟数:n-1  ---除第1个元素,后面的每个元素都拿来插入一次
                    //前面i个数已经排好序,现在是准备插入第i+1个数
    
                    //待插入的数
                    int temp = a[i+1];
    
                    //※※利用二分算法查找j ---j的定义同3.1
                    int low=0;
                    int high=i;
                    int mid;
                    while(low<=high){
                        //System.out.println(low+","+high);
                        mid =(low+high)/2;
                        if(a[mid]>temp){//左半区(所有右半区的数都会比temp大)
                            high=mid-1;
                        }else{//右半区
                            low = mid+1;
                        }
                    }
                    int j=high;//出循环后,high的位置即是我们想要找的j
    
                    //把[j,i]部分的元素全部往后移一个位置
                    for(int k=i;k>j;k--){
                        a[k+1]=a[k];
                    }
    
                    //让temp坐在j+1的位置 
                    a[j+1]=temp;
                }
    
            }

    希尔入排序

    1.希尔排序又称缩小增量排序,是1959年由D.L.Shell提出来的。

    算法描述

    1.先取定一个小于n的整数gap1作为第一个增量,把整个序列分成gap1组。所有距离为gap1的倍数的元素放在同一组中,在各组内分别进行排序(分组内采用直接插入排序或其它基本方式的排序)。
    2.然后取第二个增量gap2<gap1,重复上述的分组和排序。
    3.依此类推,直至增量gap=1,即所有元素放在同一组中进行排序为止。

    算法分析

    • 开始时 gap 的值较大, 子序列中的元素较少, 排序速度较快。
    • 随着排序进展, gap 值逐渐变小, 子序列中元素个数逐渐变多,由于前面大多数元素已基本有序, 所以排序速度仍然很快。
    • 分组后n值减小,n²更小,而T(n)=O(n²),所以T(n)从总体上看是减小了。
    • Gap的取法有多种。 shell 提出取 gap = n/2 ,gap = gap/2 ,…,直到gap = 1。gap若是奇,则gap=gap+1

    运用实例 
    这里写图片描述

    实现代码

    //希尔排序,最小增量排序
        public void sort4_1(int a[]){
            // gab---每次减半
            for( int gab=(a.length+1)/2;gab>0;gab=(gab+1)/2){
                //组内排序---简单冒泡排序
                for(int i=0;i<a.length-gab;i++){
                    for(int j=i;j<a.length-gab;j+=gab){
                        if(a[j]>a[j+gab]){
                            swap(a, j, j+gab);
                        }
                    }
                }
                if(gab==1){
                    break;
                }
            }
        }

    快速排序

    算法描述

    • 任取待排序记录序列中的某个记录(例如取第一个记录)作为基准(枢),按照该记录的关键字大小,将整个记录序列划分为左右两个子序列
    • 左侧子序列中所有记录的关键字都小于或等于基准记录的关键字
    • 右侧子序列中所有记录的关键字都大于基准记录的关键字
    • 基准记录则排在这两个子序列中间(这也是该记录最终应安放的位置)。
    • 然后分别对这两个子序列重复施行上述方法,直到所有的记录都排在相应位置上为止。
    基准记录也称为枢轴(或支点)记录。取序列第一个记录为枢轴记录,其关键字为Pivotkey。指针low指向序列第一个记录位置,指针high指向序列最后一个记录位置。
    • 1

    算法特点:

    • 以某个记录为界(该记录称为支点或枢轴),将待排序列分成两部分:
    • 一部分: 所有记录的关键字大于等于支点记录的关键字
    • 另一部分: 所有记录的关键字小于支点记录的关键字

    算法实例 
    这里写图片描述 
    这里写图片描述 
    这里写图片描述

    算法分析

    • 快速排序是一个递归过程,快速排序的趟数取决于递归树的高度。
    • 如果每次划分对一个记录定位后, 该记录的左侧子序列与右侧子序列的长度相同, 则下一步将是对两个长度减半的子序列进行排序, 这是最理想的情况 
      实现代码
    //快速排序
            private static void sort5_1(int[] a, int p, int r) {
                if(p<r){
                    int q = partition(a,p,r);//划分之后,a[q]的位置已经排好,a[p~q-1]中的元素全部比a[q]小,a[q+1~r]中的元素全部比a[q]大
                    sort5_1(a,p,q-1);//左子序列
                    sort5_1(a,q+1,r);//右子序列
                }
            }
            private static int partition(int[] a, int p, int r) {
                //优化,随机取一个数和第一个数交换
                int rand = (int)(Math.random()*(r-p));
                swap(a,p,p+rand);  //把随机选中的元素换到首元素(枢轴)
                //以下代码都是以第一个数为枢轴
                int x=a[p];
                int i=p+1;//把第一个元素定为枢轴
                int j=r+1;
                while(true){
                    /*
                    //i--在左区找比枢轴大的数(位置)
                    while(a[i]<x && i<r){
                        i++;
                    }
                    //j--在右区找比枢轴小的数(位置)
                    while(a[j]>x){
                        j--;
                    }
                    */
    
                    //i--在左区找比枢轴大的数(位置)
                    while(a[i]<x && i<r){
                        i++;
                    }
                    //j--在右区找比枢轴小的数(位置)
                    while(a[--j]>x);
    
                    if(i>=j){
                        break;
                    }
                    swap(a,i,j);
                }
                //把枢轴换中间位置
                swap(a,p,j);
    
                return j;
            }

    算法评价

    • 时间复杂度:

    最好情况(每次总是选到中间值作枢轴)T(n)=O(nlogn) 
    最坏情况(每次总是选到最小或最大元素作枢轴)T(n)=O(n²)

    • 空间复杂度:需栈空间以实现递归

    最坏情况:S(n)=O(n) 
    一般情况:S(n)=O(logn)

    快速排序 优化—前面实现版本

       1.可以证明,快速排序算法在平均情况下的时间复杂性和最好情况下一样,也是O(nlogn),这在基于比较的算法类中算是快速的,快速排序也因此而得名。
       2.快速排序算法的性能取决于划分的对称性。因此通过修改partition( )方法,可以设计出采用随机选择策略的快速排序算法,从而使期望划分更对称,更低概率出现最坏情况。

    归并排序

    算法描述:

    1.设初始序列含有n个记录,则可看成n个有序的子序列,每个子序列长度为1。
    2.两两合并,得到 n/2 个长度为2或1的有序子序列。
    3.再两两合并,……如此重复,直至得到一个长度为n的有序序列为止。

    算法实例 
    这里写图片描述

    这里写图片描述

    实现代码

    //6归并排序
        //6.1 归并方法(要求掌握):把数组a[]当中 [left,mid] 和  [mid+1,right] 两个子序列归并,结果放在b[]
        private static void merge(int[]a, int[]b, int left,int mid, int right ){
            int p=left; //子序列a[left,mid]的遍历游标
            int r=mid+1; //子序列a[mid+1,right]的遍历游标
            int k=left; //归并结果序列b[left,right]的遍历游标
    
            //该过程持续到其中一个子序列归并完为止
            while( (p<=mid) && (r<=right) ){
                if( a[p]<a[r]){
                    b[k++]=a[p++];
                }else{
                    b[k++]=a[r++];
                }
            }
            //把另一个序列中剩下的元素直接对拷到 b 中
            if(p>mid){//左子序列归并完。把右子序列中剩下的元素对拷到b
                for(int i=r;i<=right;i++){
                    b[k++]=a[i];
                }
            }else{//右子序列归并完。把左子序列中剩下的元素对拷到b
                for(int i=p;i<=mid;i++){
                    b[k++]=a[i];
                }
            }
        }
        //6.2 对一个乱序序列进行用归并排序(了解即可)
        private static void mergeSort(int[]a, int left, int right){
            if (left<right) {//至少有2个元素才进行排序
                //先分解
                int mid = (left + right) / 2;
                mergeSort(a,left,mid);
                mergeSort(a,mid+1,right);
    
                //对上面的两个子序列进行归并排序
                int b[] = new int[a.length];//新开一个临时数组b,用于存放归并结果
                merge(a,b,left,mid,right);//把当前分解层次的两个子序列归并到数组b中
                copyArray(a,b,left,right);//把临时数组b中的数据复制到a中,由后续动作对a继续进行归并
            }
        }
        private static void copyArray(int[] a, int[] b, int left, int right) {
            for(int i=left;i<=right;i++){
                a[i] = b[i];
            }
        }
    

    算法分析:

     1. 合并排序法主要是将两笔已排序的资料合并和进行排序。
     2.如果所读入的资料尚未排序,可以先利用其它的排序方式来处理这两笔资料,然后再将排序好的这两笔资料合并。 

    算法评价

    • 时间复杂度:T(n)=O(nlogn)
    • 空间复杂度:S(n)=O(n)

    总结

    这里写图片描述

  • 相关阅读:
    vue使用elementui合并table
    使用layui框架导出table表为excel
    vue使用elementui框架,导出table表格为excel格式
    前台传数据给后台的几种方式
    uni.app图片同比例缩放
    我的博客
    【C语言】取16进制的每一位
    SharePoint Solution 是如何部署的呢 ???
    无效的数据被用来用作更新列表项 Invalid data has been used to update the list item. The field you are trying to update may be read only.
    SharePoint 判断用户在文件夹上是否有权限的方法
  • 原文地址:https://www.cnblogs.com/charlas/p/7596350.html
Copyright © 2011-2022 走看看