(简单、希尔、堆、归并、快速、基数)排序算法实现及比较

为了简单起见,只讨论基于比较的从小到大的整数排序

简单排序

冒泡排序

算法思想

类似于水中冒泡,假设从小到大排序,即为较大的数慢慢往后排,较小的数慢慢往前排。依次比较两个相邻的元素,如果顺序错误就把他们交换过来,每一趟遍历,将一个最大的数移到序列末尾。重复进行直到没有相邻元素需要交换为止。
算法描述

算法描述

  1. 比较相邻的元素,如果前一个比后一个大,交换之。
  2. 第一趟排序第1个和第2个元素,比较与交换,随后第2个和第3个比较交换,这样直到倒数第2个和最后1个,将最大的数移动到最后一位。
  3. 第二趟将第二大的数移动至倒数第二位
    。。。
    需要比较n-1趟。

算法实现

public static void bubbleSort(int[] a) {
    int tmp;
    for(int p=a.length-1; p>=0; p--){
        for(int i=0; i<p; i++ ) {
            /* 一趟冒泡*/
            if( a[i] > a[i+1] ) {
                tmp = a[i];
                a[i] = a[i+1];
                a[i+1] = tmp;
            }
        }
    }
}

最好情况:顺序T= O( N)
最坏情况:逆序T= O( N2)

插入排序

算法思想

它的基本思想是将一个记录插入到已经排好序的有序表中,从而生成一个新的、记录数增1的有序表。在其实现过程使用双层循环,外层循环对除了第一个元素之外的所有元素,内层循环对当前元素前面有序表进行待插入位置查找,并进行移动。

算法描述

  1. 从数组的第二个数据开始往前比较,即一开始用第二个数和他前面的一个比较,如果第二个数小于第一个数,则让他们交换位置。
  2. 然后再用第三个数和第二个比较,如果小于则交换,继续往前比较,当遇到大于时停止比较。
  3. 重复步骤二,一直到数据全都排完。

算法实现

public static void insertionSort(int[] a) {
    for(int p=1; p<a.length; p++) {
        int tmp = a[p];
        /* 摸下一张牌*/
        int i;
        for(i=p; i>0 && a[i-1]>tmp; i--)
            /* 移出空位*/
            a[i] = a[i-1];
        /* 新牌落位*/
        a[i] = tmp;
    }
}

最好情况:顺序T= O( N)
最坏情况:逆序T= O( N2)
如果序列基本有序,则插入排序简单且高效

希尔排序

定理:任何仅以交换相邻两元素来排序的算法,其平均时间复杂度为( N2)。
而交换2个相邻元素正好消去1个逆序对! 
这意味着:要提高算法效率,我们必须每次消去不止1个逆序对,每次交换相隔较远的2个元素!

算法思想

定义一个增量序列DM> DM-1> …> D1= 1,对每个Dk进行“Dk-间隔”排序( k = M, M-1, …1 )
注意:“Dk-间隔”有序的序列,在执行“Dk-1-间隔”排序后,仍然是“Dk间隔”有序的。

算法实现

public static void shellSort(int[] a) {
    /* 希尔增量序列*/
    for(int d=a.length/2; d>0; d/=2) {
        /* 插入排序*/
        for(int p=d; p<a.length; p++) {
            int tmp= a[p];
            int i;
            for(i=p; i>=d && a[i-d]>tmp; i-=d)
                a[i] = a[i-d];
            a[i] = tmp;
        }
    }
}

最坏情况:T= Θ( N2)
增量元素不互质,则小增量可能根本不起作用。

更多增量序列

  1. Hibbard 增量序列:Dk= 2k–1 相邻元素互质 
    最坏情况:T = Θ( N3/2) 
    猜想:Tavg= O ( N5/4) 
  2. Sedgewick增量序列 {1, 5, 19, 41, 109, …}
    猜想:Tavg= O ( N7/6),Tworst= O ( N4/3)

选择排序

算法思想

第一次从待排序的数据元素中选出最小的元素,存放在序列的起始位置,然后再从剩余的未排序元素中寻找到最小元素,然后放到已排序的序列的末尾。以此类推,直到全部待排序的数据元素的个数为零。选择排序是不稳定的排序方法。

算法实现

public void selectionSort(int[] a) {
    for(int i = 0; i < a.length; i ++ ) {
        /* 从A[i]到A[N–1]中找最小元,并将其位置赋给MinPosition*/
        int minPosition= scanForMin(a, i, a.length);
        /* 将未排序部分的最小元换到有序部分的最后位置*/
        int tmp = a[i];
        a[i] = a[minPosition];
        a[minPosition] = tmp;
    }
}

private int scanForMin(int[] a, int i, int l) {
    int minPosition = i;
    for (int j = i + 1; j < l; j++){
        if(a[j] < a[minPosition]) minPosition = j;
    }
    return minPosition;
}

无论如何:T= Θ(N2)
如何快速找到最小元素?

堆排序

算法思想

创建最小堆存储所有元素,每次弹出的元素就是最小元素。

算法实现

算法1
public int[] heapSort(int[] a){
    PriorityQueue<Integer> pq = new PriorityQueue<>(new Comparator<Integer>() {
        @Override
        public int compare(Integer o1, Integer o2) {
            return o1 - o2;
        }
    });
    for (int i:a) {
        pq.add(i);
    }
    int[] tmp = new int[a.length];
    for(int i=0; i<a.length; i++ )
        tmp[i] = pq.poll(); /*O(logN)*/
    return tmp;
}

T( N ) = O ( Nlog N)

算法2

快速排序

算法思想

通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。
注意:基准有不同的取法,对算法速度有影响。常见的选法有:选取左边或右边、选取左右中间三个数的中间值、随机选取。

算法实现

public void quick_Sort(int[] a) {
    quicksort(a,0,a.length-1);
}

public void quicksort(int[] a, int left, int right) {
    if(left < right){
        //以数组中右边的元素为基准
        int pivot = a[right];
        int i = left;
        int j = right-1;
        int tmp;
        while (i < j){
            while(i<=right-1 && a[i] <= pivot)i++;
            while(j>=left && a[j] >= pivot)j--;
            if(i < j){
                //交换元素
                tmp = a[i];
                a[i] = a[j];
                a[j] = tmp;
            }
        }
        //交换元素
        tmp = a[i];
        a[i] = a[right];
        a[right] = tmp;
        quicksort(a,left,i-1);
        quicksort(a,i+1,right);
    }
}

每次正好中分:T(N)=O(NlogN)
最坏情况,数组本身就是有序的,每次选取左边或右边的元素为基准:T(N) = O(N2)

归并排序

算法思想

该算法是采用分治法的一个非常典型的应用,先使每个子序列有序,再将已有序的子序列合并,最终得到完全有序的序列。

算法实现

递归实现
public void mergeSort(int[] a) {
    int[] tmp = new int[a.length+1];
    mSort(a, tmp, 0,a.length-1);
}

private void mSort(int[] a, int[] tmp, int l, int rightEnd) {
    int center;
    if( l < rightEnd) {
        center = (l + rightEnd) / 2;
        mSort(a, tmp, l, center);
        mSort(a, tmp, center+1, rightEnd);
        merge(a, tmp, l, center+1, rightEnd);
    }
}

private void merge(int[] a, int[] tmp, int l, int r, int rightEnd) {
    int leftEnd = r - 1; /*左边终点位置。假设左右两列挨着*/
    int tmpl = l;/*存放结果的数组的初始位置*/
    int num = rightEnd - l + 1;
    while(l <= leftEnd && r <= rightEnd) {
        if(a[l] <= a[r])
            tmp[tmpl++] = a[l++];
        else
            tmp[tmpl++] = a[r++];
    }
    while(l <= leftEnd) /* 直接复制左边剩下的*/
        tmp[tmpl++] = a[l++];
    while(r <= rightEnd) /*直接复制右边剩下的*/
        tmp[tmpl++] = a[r++];
    for(int i = 0; i < num; i++, rightEnd--)
        a[rightEnd] = tmp[rightEnd];
}

T(N) = O(NlogN)

非递归实现
public void mergePass(int[] a, int[] tmp, int n, int length)
{
    //两两归并相邻有序子列
    int i, j;

    for (i=0; i <= n-2*length; i += 2*length)
        merge( a, tmp, i, i+length, i+2*length-1);
    //归并最后2个子列
    if (i+length < n )
        merge( a, tmp, i, i+length, n-1);
        //最后只剩1个子列,什么也不做
}

public void mergeSort1(int[] a)
{
    int[] tmp = new int[a.length];
    int length = 1; /* 初始化子序列长度*/
    while(length < a.length) {
        mergePass(a, tmp, a.length, length);
        length *= 2;
    }
}

基数排序

分为两类

  1. 低位优先法,简称LSD法:先从最低位开始排序,再对次低位排序,直到对最高位排序后得到一个有序序列。
  2. 最高位优先法,简称MSD法:先从最高位开始排序,再逐个对各分组按次高位进行子排序,循环直到最低位。

算法实现

LSD
public static void radixSort(int[] a){
    int n = a.length;
    //创建10个桶
    List<LinkedList<Integer>> t = new ArrayList<LinkedList<Integer>>();
    for(int i = 0 ;i < 10;i++){
        LinkedList<Integer> list = new LinkedList<>();
        t.add(list);
    }
    //得到最大数的基数
    int max = a[0];
    for(int i = 1 ;i < n;i++){
        if(max < a[i])
            max = a[i];
    }
    double d = Math.pow(10, String.valueOf(max).length());

    int k = 1;
    while(k < d){
        for(int i : a){
            int m = (i / k) % 10;
            t.get(m).add(i);
        }
        int c = 0;
        for(int i = 0; i < 10; i++){
            while(!t.get(i).isEmpty()){
                a[c++] = t.get(i).pollFirst();
            }
        }
        k = k*10;
    }
}

参考:https://blog.csdn.net/u011948899/article/details/78027838

MSD
//得到元素特定位置上的数据
public static int GetNumInPos(int num, int pos) {
    int temp = 1;
    for (int i = 0; i < pos - 1; i++) {
        temp *= 10;
    }
    return (num / temp) % 10;
}

//MSD,调用时指定最高位数
public static void sort(LinkedList<Integer> A, int d) {
    int len = A.size();
    //分为0~9的序列空间
    List<LinkedList<Integer>> radixArray = new ArrayList<LinkedList<Integer>>();
    for (int i = 0; i < 10; i++){
        radixArray.add(new LinkedList<Integer>());
    }
    //位数大于0,且数组长度大于1
    if (d >= 1 && len > 1) {
        while(!A.isEmpty()){
            int num = GetNumInPos(A.peekFirst(), d);
            radixArray.get(num).add(A.pollFirst());
        }

        for (int i = 0, j = 0; i < 10; i++) {
            if(radixArray.get(i).size() == 1){
                A.add(radixArray.get(i).pollFirst());
            } else if(radixArray.get(i).size() > 1){
                sort(radixArray.get(i), d-1);//递归,对每个子桶从次高位开始分配
                while (!radixArray.get(i).isEmpty())
                    A.add(radixArray.get(i).pollFirst());
            }
        }
    }
}

public static LinkedList<Integer> MSDSort(int[] n){
    LinkedList<Integer> list = new LinkedList<>();
    int max = n[0];
    for(int i = 0 ;i < n.length;i++){
        list.add(n[i]);
        if(max < n[i])
            max = n[i];
    }
    int maxL = String.valueOf(max).length();  //获取数组中最长元素长度
    sort(list, maxL);
    return list;
}

各算法比较

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值