数据结构与算法Java版——九大基本排序算法(1)

来源:互联网 发布:51牛股数据分析大师 编辑:程序博客网 时间:2024/06/06 09:53

  这次分享的主题是排序算法,排序是数据处理中经常用到的一种操作,主要目的就是为了查找,在数据大量时,不同的算法有不同的效果。

  排序板块是目前我的写的数据结构与算法Java版的最后一块内容,所以日后会开始学习其他的内容,有兴趣的伙伴可以继续关注我, 我的博客

  由于csdn中排版有一些问题,可能会导致看起来有些不直观,所以建议去我的个人网站看这篇文章我的个人博客
  首先,介绍两个基本的概念:

  • 排序:是将一个记录的任意序列重新排列成一个按关键码有序的序列。
  • 排序算法的稳定性:假定在待排序的记录序列中,存在多个具有相同关键码的记录,若经过排序后,这些记录相对次序保持不变,即若原序列中存在k1=k2,且k1在k2前面,排序过后,k1仍然在k2前面,则称这种排序算法稳定,否则成为不稳定。

      接下来就步入正题,一一介绍九大常用排序。

插入排序

  插入排序是一类借助“插入”进行排序的方法,主要思想:每次将一个待排序的记录按其关键码的大小插入到一个已经排好序的有序序列中,直到全部记录排好序。

直接插入排序

  直接插入排序是插入排序中最简单的排序方法,其基本思想是依次将待排序序列中的每一个记录插入到一个已排好序的序列中,直到全部记录都排好序。这里就是从第二个元素开始,按大小顺序插入到前面有序区中,具体过程如图所示:

这里写图片描述

  代码如下:

    public void insertSort(int[] a) {        for (int i = 1; i < a.length; i++)         {            int cos = a[i]; //用于交换元素            int j = i - 1;            //不要用a[i]代替cos,a[i]会偷偷摸摸改变            for (; j >= 0 && cos < a[j]; j--)             {                //数据后移                a[j + 1] = a[j];            }            //将值插入            a[j + 1] = cos;        }    }

  直接插入排序由两层for循环组成,外层循环执行n-1次,内层循环执行次数则看数据情况。平均情况下,这种排序时间复杂度为O(n^2)。直接插入排序只需要一个记录的辅助空间,用来作为待插入记录的暂存单元。也就是上述代码中 的cos。

  直接插入排序是一种稳定的排序算法,容易实现,当记录少时或者记录基本有序时,它是最佳算法,但是记录过多大量比较和移动就会让其效率大大降低。

希尔排序

  希尔排序是对直接插入排序的一种改进,其基本思想是:先将整个待排序记录序列分割成若干个子序列,在子序列内分别进行直接插入排序,待整个序列基本有序,再对全体记录进行一次直接插入排序。

  基本实现算法:

第一趟以d=n/2(n为数组长度,每次取整)为间隔,将数组分为若干子序列,如下图所示,将同一序列的数进行直接插入排序。(4插到9前面,3插到5前面……)

第二趟以d=d/2(取整)为间隔,将数组分为若干子序列,将同一序列的数进行直接插入排序。(2插到4前面,第一个子序列最后一个5插到8前面……)

…….

最后当d=1时再进行一次直接插入排序即可。

这里写图片描述

  代码如下:

    // 以d=n/2为间距(n为数组长),将数组分为几个序列,对每个序列进行插入排序,直到d=1    public void shellSort(int[] a){        // 以数组长度的一半为d,每次缩小一半,直到d=1        for(int d=a.length/2;d>=1;d=d/2)        {            for (int m = 0; m <= a.length/2; m++) {                //将每个序列进行插入排序,从数组第一个开始,每次i加间隔d                for(int i=m;i<a.length;i=i+d)                {                    int cos=a[i];                    int j=i-d;                    for(;j>=0&&cos<a[j];j=j-d)                    {                        a[j+d]=a[j];                    }                    //因为上面最后执行了j=j-d,所以此处j+d                    a[j+d]=cos;                }            }        }    }

  希尔排序算法时间复杂度很难分析,不过书上说某人做了大量实验基础上指出,希尔排序时间性能在O(n^2)和O(nlog2 n)之间,当n在某个特定范围时,希尔排序的时间性能约为O(n^1.3)。

  希尔排序只需要一个记录的辅助空间,用于暂存当前插入记录,上述代码中的cos。希尔排序是一种不稳定的排序算法。

交换排序

  交换排序是一类借助”交换”进行排序的方法,主要思想:在待排序序列中选两个记录,将它们的关键码进行比较,如果反序则交换它们的位置。

冒泡排序

  冒泡排序是交换排序中最简单的排序方法,及基本思想:两两比较相邻记录的关键码,如果反序则交换,直到没有反序记录为止。

  代码如下:

    //冒泡排序,两两比较,再相互交换位置    public void BubbleSort(int[] a) {        for (int i = 0; i < a.length; i++)        {            for (int j = i+1; j < a.length; j++)             {                if (a[i] > a[j])                 {                    // 交换位置                    int cos = a[i];                    a[i] = a[j];                    a[j] = cos;                }            }        }    }

  冒泡排序平均时间复杂度为O(n^2),冒泡排序只需要一个记录的辅助空间,用来交换数组值,冒泡排序是一种稳定的排序方法。

快速排序

  快速排序(Quicksort)是对冒泡排序的一种改进。一趟快速排序的算法是:

  1. 设置两个变量i、j,排序开始的时候:i=0,j=N-1;
  2. 以第一个数组元素作为关键数据,赋值给key,即key=A[0];
  3. 从j开始向前搜索,即由后开始向前搜索(j–),找到第一个小于key的值A[j],将A[j]和A[i]互换;
  4. 从i开始向后搜索,即由前开始向后搜索(i++),找到第一个大于key的A[i],将A[i]和A[j]互换;
  5. 重复第3、4步,直到i=j; 3,4步中,没找到符合条件的值,即3中A[j]不小于key,4中A[i]不大于key的时候改变j、i的值,使得j=j-1,i=i+1,直至找到为止。找到符合条件的值,进行交换的时候i, j指针位置不变。  

      示例:(此处为百度的结果,个人认为解释的很仔细)

      假设用户输入了如下数组:

    下标 0 1 2 3 4 5
    数据 6 2 7 3 8 9

      创建变量i=0(指向第一个数据), j=5(指向最后一个数据), k=6(赋值为第一个数据的值)。

      我们要把所有比k小的数移动到k的左面,所以我们可以开始寻找比6小的数,从j开始,从右往左找,不断递减变量j的值,我们找到第一个下标3的数据比6小,于是把数据3移到下标0的位置,把下标0的数据6移到下标3,完成第一次比较:

    下标 0 1 2 3 4 5
    数据 3 2 7 6 8 9

      i=0 j=3 k=6

      接着,开始第二次比较,这次要变成找比k大的了,而且要从前往后找了。递加变量i,发现下标2的数据是第一个比k大的,于是用下标2的数据7和j指向的下标3的数据的6做交换,数据状态变成下表:

    下标 0 1 2 3 4 5
    数据 3 2 6 7 8 9

      i=2 j=3 k=6

      称上面两次比较为一个循环。

      接着,再递减变量j,不断重复进行上面的循环比较。 

      在本例中,我们进行一次循环,就发现i和j“碰头”了:他们都指向了下标2。于是,第一遍比较结束。得到结果如下,凡是k(=6)左边的数都比它小,凡是k右边的数都比它大:

    下标 0 1 2 3 4 5
    数据 3 2 6 7 8 9

      如果i和j没有碰头的话,就递加i找大的,还没有,就再递减j找小的,如此反复,不断循环。注意判断和寻找是同时进行的。

      然后,对k两边的数据,再分组分别进行上述的过程,直到不能再分组为止。

      注意:第一遍快速排序不会直接得到最终结果,只会把比k大和比k小的数分到k的两边。为了得到最后结果,需要再次对下标2两边的数组分别执行此步骤,然后再分解数组,直到数组不能再分解为止(只有一个数据),才能得到正确结果。

      如下图所示:

这里写图片描述

  代码如下:

    /**     * 返回轴值的下标i     * @param a 数组     * @param i 轴值,也是每个分区的第一个元素     * @param j 数组下标的最大值     */    private int quickSort1(int [] a, int i,int j){        int key=a[i];   //轴值        outer:while (i < j) {            for (;; j--) {                if (i >= j) {                    //当i=j时结束while循环                    break outer;                    }                //从前向后查找,找到第一个小于轴值的数并且交换a[i]和a[j]                if (a[j] < key) {                    int cos = a[i];                    a[i] = a[j];                    a[j] = cos;                    break;                }            }            for (;; i++) {                if (i >= j) {                    //当i=j时结束while循环                    break outer;                }                //从前向后查找,找到第一个小于轴值的数并且交换a[i]和a[j]                if (a[i] > key) {                       int cos = a[i];                    a[i] = a[j];                    a[j] = cos;                    break;                }            }        }        //返回轴值下标i        return i;    }    /**快速排序     * @param a 数组     * @param i 轴值,也是数组第一个元素     * @param j 数组下标的最大值     */    public void quickSort(int[] a ,int i,int j) {        //执行区间大于1时,执行划分数组,否则结束递归        if(i<j){            //接受上一次轴值下标            int middle=quickSort1(a, i, j);            quickSort(a, i, middle-1);            quickSort(a, middle+1, j);        }    }

  以上就实现了快速排序,快速排序是一种不稳定的排序方法,快速排序适用于待排序记录个数很大且原始记录随机排列的情况。
  今天就暂时分享到这里,明天继续剩下的排序。如有不对之处,欢迎指出。

原创粉丝点击