各种排序算法java实现及总结

从小到大的排序

基于比较排序

所有比较算法用到的通用方法,

    private static void exch(int [a],int i, int j){
       int t = a[i]; a[i] = a[j]; a[j] = t;
    }
    //比较大小
    private static boolean less(int a, int b){
        is(a < b) return true;
        else      return false
    }

1 选择排序(selectesort)

基本思想,固定数组的第n个下标所对应的值就是要排序的数组中第n小的数。例如,先假设a[0]的值就是最小的数,依次遍历数组,进行比较,如果a[i]的值比a[0]小则记录i为最小值的下标,依次循环记录i,直到数组到达结尾,此时i代表的就是最小值的下标。最后交换a[0]与a[i].这样就找到了最小的值,并且放在第一位。

public class SelectSort{
    public static void sort(int[] a){
        int N = a.length;
        //最后一个不用比较,所以n-1
        for (int i = 0;i < N-1 ;i++ ) {
            int min = i;
            for (int j = i+1;j < N ;j++ ) {
                if(less(a[i], a[j]))
                    min = j;
            }
            exch(a, i, min);
        }
    }
}

选择排序一共比较了n-1轮,每轮分别比较了n-1,n-2,n-3…次,所以总的比较次数(n-1)+ (n-2)+(n-3)…+1=(n-1+1)(n-1)/2,由此可见改排序法的复杂度是O(n^2)的,并且与输入模型无关,时间复杂度总是O(n^2)。不需要额外的空间

2 冒泡排序(bubblesort)

冒泡排序的基本思想是大数往下沉,小数向上浮,不停的进行比较和交换

public class BubbleSort {
//less和exch和实现与选择排序一样
    public void sort(int [] a){
        for(int i = 0; i < a.length; i++){
            for(int j = 1; i < a.length; i++){
                if(less(a[j] < a[j - 1]))
                   exch(a, j, j - 1); 
            } 
        } 
    }
}

该方法进行了首先第一个for循环控制比较的轮数,每一轮左右比较,如果右边的数比左边的大,交换。这样使得第一轮比较后,最大的数下沉到最后一位,第二轮比较使得第二大的数下沉到倒数第二位,以此类推。n轮之后,排序完成。
双重for循环,时间复杂度O(n^2),并且与输入模型无关,不需要额外空间

3 插入排序(insertsort)

基本思想,类似于扑克牌的排序,手牌是已经排好序的牌,然后现在从牌堆里拿一张新牌A插入手牌,首先把A与手牌中的最后一张比较,如果A大,则停止比较,如果A小则交换A与改手牌,直至A比要比较的手牌大或者A到第一位停止。这里写图片描述
(图片来自百度),黑色括号里的牌就相当于你的手牌,是已经排好序的,括号外的牌就是牌堆里的牌。注意一开始你的手牌就只有1张,每次加进来一张牌,我们都进行排序保持手牌的有序性。

public class InsertSorted{
    public static void sort(int[] a){
        for (int i = 1;i < a.length ;i++ ) {//control the starting index;
            for(int j = i;j > 0&&less(a[j], a[j-1]);j--){
                exch(a, j, j-1);
            }
        }
    }

该方法的时间复杂度与上述两种方法稍微不同(选择和冒泡)。该方法的时间复杂度与输入模型有关。在最坏的情况(输入的数组是已经有序或近似有序,但是却是按照想反的排序方式这样使得每次插入一张牌的排序都需要比较到第一位然后终止)下该方法的复杂度为O(n^2),在最好的情况下(与最坏情况相反)该方法的复杂度是O(n)。该算法不需要额外空间

4 归并排序(mergesort)

基本思想,分治策略。把一个数组先分(分到只有一个元素),然后再从最小的子数组开始合并,其中在合并的时候进行比较把元素放在合适的位置上,实现排序。
这里写图片描述

public class MergeSort{
//这里用一个缓存数组aux来代替“分”的过程中所要的两个字数组,用下标来区分
    private int[] aux;
    public void sort(int [] a){
        aux = new int [a.length];
        sort(a, 0, a.length-1);
    }
    private void sort(int [] a, int lo, int hi){
        int mid = (lo+hi)/2;
        if(lo >= hi)   return;
        sort(a, lo, mid);
        sort(a, mid+1, hi);
        merge(a, lo, hi);
    }
    private void merge(int [] a, int lo, int mid, int hi){
        int i = lo; //相当于第一个子数组的起始下标
        int j = mid + 1;//第二个子数组的起始下标
        //把要归并的数组拷贝到aux中,相当于把数组a里数分到两个小数组中
        for(int k = lo; k <= lo; k++)
            aux[i] = a[i];
        //归并操作,在这个过程中进行比并病排序
        for(int k = lo; k < hi; k++){
        //判断第一个子数组是否到头
            if(i > mid)  a[k] = aux[j++];
        //判断第二个子数组是否到头
            else if(j > hi) a[k] = aux[i++];
            else if(k > ji) a[k] = aux[j++];
            else     a[k] = aux[i++]
    }
}

由于该方法使用了额外的子数组所以需要额外的空间。关于其时间复杂度,如上图所示,构成了一个“二叉树”,该二叉树的高度为lgN,需要遍历该树N次,所以其时间复杂度为O(NlgN).
也可以这样理解:假设有数组中有2^n = N个元素,那么该二叉树从上倒下第k层每个子数组长度就是2^(n-k),并且第k层有2^k个子数组,那么每层进行merge比较操作的次数就是2^(n-k)*2^k=2^n次,总共有n层,即n*2^n ==> NlgN。

5 希尔排序(shellsort)

基本思想,类似于插入排序,插入排序每次元素只能移动一位,效率较慢,希尔排序则可以使元素移动多位。将要排序的数组按h分成a[i] , a[i+h], [i+2*h]…的子数组进行排序,然后逐渐减少h重复(h最终必须减到1),这样就使得最终当h为1时数组已经大致排好顺序。
这里写图片描述

public class ShellSort{

    public void sort(Integer[] a){
        //the shellsort reduce the step of exchange;
        int N = a.length;
        int h = 1;
        while(h<N/3) h=3*h+1;//control the length of the enties skip
        while(h>=1){// the h = 1 is the neccessary for the last step
            for (int i = h;i<N ;i++ ) {
                for (int j = i;j >= h&&less(a[j], a[j-h]) ;j-=h ) {
                        exch(a,j,j-h);
                }
            }
            h = h/3;
        }
    }
//这里的h控制跳步的长度,也可以把一系列h的值放在数组里,这里对h进行了实时计算;

希尔排序的时间复杂度很大一部分跟h的取值有关(目前还不清楚有什么具体关系),在最坏的情况下其复杂度为O(n^2),最好的情况下为O(n),平均复杂度约为O(n^1.5)。(该算法很不稳定不推荐使用)

6 快速排序(quicksort)

基本思想,把无序数组a[0…n-1]变成 …..< v <…. 这样的形式,即用v把数组分成了大于v和小于v的两部分,然后分别对两端进行排序(在这里也可以使用递归对子数组是用同样的方法),实现排序。
这里写图片描述

public class QuickSort{
    private int partition(int[] a, int hi, int lo){
         //把第一个元素作为分割点v
         int v = a[hi];
         //从左侧出发的遍历下标
         int i = hi;
         //从右侧出发的遍历下标
         int j = lo + 1;
         //交换
         while(true){
             while(less(a[++i],v))    if(i==lo) break;
             while(less(v,a[--j]))      if(j==hi) break;
             if(i >= j) break;
             exch(a, i, j);
         }
         exch(a,hi,j);//把v换到中间
         return j;
    }
    public void sort(int[] a){
        sort(a, 0, a.lenght - 1);
    }
    private void sort(int[] a, int hi, int lo){
        if(hi >= lo)    return;
        int j = partition(a, hi, lo);
       sort(a, hi, j - 1);
       sort(a, j + 1, lo); 
    }
}

在最坏的情况下(已经排好序的数组,无论正序,反序)时间复杂度O(n^2),平均情况下O(nlgn)。
对于quicksort还有几种优化:
1. 通过media-of-three(选择三个数种间的数作为分割点),这样大大减少了最坏情况可能出现的可能性。
2. cutoff to insertsort,由于quicksort在处理长度较小的数组时排序效率没有insertsort效率高,所以当把数组分到一定长度的子数组时调用insetsort进行排序。
3. 当处理有大量相同值的数组时,参照,荷兰国旗问题的解决方法

非基于比较的排序(线性排序)

7 计数排序

基本思想,计算出数组中元素应该所在的位置,然后把填充到该位置,例如,A在所有元素中第12小,那么就放在第12的位置。关键就是如何得到A在所有元素中应该在的位置。

public class CounterSort2 {
//接受排序完成的数组
        int [] b = new int [a.length];
        int max = a[0]; 
        int min = a[0];
        for(int i = 0; i < a.length; i++){
            if(a[i] > max ) max = a[i];
            if(a[i] < min)  min = a[i];
        }
//算极差k
        int k = max - min + 1;
        int [] c = new int [k];
        for(int i = 0; i < a.length; i++){
            c[a[i] - min] +=1; 
        }

        for(int i = 1; i < c.length; i++){
            c[i] = c[i] + c[i - 1]; 
        }

        for(int i = a.length - 1; i >= 0; i--){
            b[--c[a[i] - min]] = a[i]; 
        }
    }
}

计数排序的时间复杂度是线性的,但是同时跟极差K有关,当K较大时复杂度增加,同时该排序不是in place的,是一种牺牲空间来换取时间的排序方法,一般当数据不是很大,极差不是很大的话可以考虑使用

8 桶排序

桶排序的思想是,把数组 arr 划分为n个大小相同子区间(桶),每个子区间各自排序,一般在区间中使用其他的排序方法,例如快速排序最后合并。计数排序是桶排序的一种特殊情况,可以把计数排序当成每个桶里只有一个元素的情况。

首先要找到数组的最大值和最小值,例如min = 0, max = 100。然后将min ~max 划分成若干个等长区间(可以用Arraylist),[0,10),[10,20), ….然后把元素放到桶里(跟区间端点比较),然后对每个区间进行排序,排序完成后按次序连接每个桶即可。

public static void bucketSort(int[] a){

    int max = Integer.MIN_VALUE;
    int min = Integer.MAX_VALUE;
    for(int i = 0; i < a.length; i++){
        max = Math.max(max, a[i]);
        min = Math.min(min, a[i]);
    }
    //桶数。
    int bucketNum = (max - min) / a.length + 1;
    ArrayList<ArrayList<Integer>> bucketA = new ArrayList<>(bucketNum);
    //用 ArrayList 做桶
    for(int i = 0; i < bucketNum; i++){
        bucketA.add(new ArrayList<Integer>());
    }

    //将每个元素放入桶
    for(int i = 0; i < a.length; i++){
        int num = (arr[i] - min) / (a.length);
        bucketA.get(num).add(a[i]);
    }

    //对每个桶进行排序
    for(int i = 0; i < bucketA.size(); i++){
        new QuickSort().sort(buketA.get(i));
    }

9 基数排序

排序思想,首先对数据按个位数的大小进行排序,然后对数据的十位数按大小排序,以此类推直到最高位数,注意排序过程保持数据的“鲁棒性”
例如
73, 22, 93, 43, 55, 14, 28, 65, 39, 81
首先根据个位数的数值,在走访数值时将它们分配至编号0到9的桶子中:
0
1 81
2 22
3 73 93 43
4 14
5 55 65
6
7
8 28
9 39
接下来将这些桶子中的数值重新串接起来,成为以下的数列:
81, 22, 73, 93, 43, 14, 55, 65, 28, 39
接着再进行一次分配,这次是根据十位数来分配:
0
1 14
2 22 28
3 39
4 43
5 55
6 65
7 73
8 81
9 93
接下来将这些桶子中的数值重新串接起来,成为以下的数列:
14, 22, 28, 39, 43, 55, 65, 73, 81, 93
注意该排序过程必须保持数据的“鲁棒性

public class RadixSort
{
    public static void sort(int[] number, int d) //d表示最大的数有多少位
    {
        intk = 0;
        intn = 1;
        intm = 1; //控制键值排序依据在哪一位
        int[][]temp = newint[10][number.length]; //数组的第一维表示可能的余数0-9
        int[]order = newint[10]; //数组orderp[i]用来表示该位是i的数的个数
        while(m <= d)
        {
            for(inti = 0; i < number.length; i++)
            {
                intlsd = ((number[i] / n) % 10);
                temp[lsd][order[lsd]] = number[i];
                order[lsd]++;
            }
            for(inti = 0; i < 10; i++)
            {
                if(order[i] != 0)
                    for(intj = 0; j < order[i]; j++)
                    {
                        number[k] = temp[i][j];
                        k++;
                    }
                order[i] = 0;
            }
            n *= 10;
            k = 0;
            m++;
        }
    }
}

(基数排序来自百度百科)

参考:http://www.cnblogs.com/zer0Black/p/6169858.html
http://baike.baidu.com/link?url=9XoIra4_moXkohAWHxFgtptiKczr5FY-lHgsgbO3V4ETJGJVBPMxT9UIeoXcLkNTsap9DCduG_PS1hbc5GXs6TtTHKOsHBenprhrIuBIEOyfRcfzMpiWeBliugD3s_hi
(还有一些其他的但是时间久远。。。就不一一写了)
(未经同意可以随便转载)。。。。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值