十大经典排序算法(java)实现及特点性能对比

1 冒泡排序

思想:每一轮两两比较,逆序就交换,类似水泡冒出,每一轮都能找到一个当前最大的。

public static void sort(int[] a){
    for(int i=0;i<a.length-1;i++){
        for(int j=0;j<a.length-1-i;j++){
            if(a[j]>a[j+1]){
                int temp=a[j];
                a[j]=a[j+1];
                a[j+1]=temp;
            }
        }
    }
}
  • 时间复杂度:最好 O ( n ) O(n) O(n),最坏 O ( n 2 ) O(n^2) O(n2),平均 O ( n 2 ) O(n^2) O(n2)。最好本来是 O ( n 2 ) O(n^2) O(n2)但是可以加一个判断上一趟是否进行了交换,如果不交换说明是有序的,直接返回。
  • 空间复杂度: O ( 1 ) O(1) O(1)
  • 是否稳定:是

2 简单选择排序

思想:每一轮都需要找到当前轮的最值元素,然后把最值元素和它交换位置。

public static void sort(int[] a) {
    int temp;
    for (int i = 0; i < a.length; i++) {
        int minPos = i;
        for (int j = i + 1; j < a.length; j++) {
            if (a[j] < a[minPos]) {
                minPos = j;
            }
        }
        //交换
        temp = a[minPos];
        a[minPos] = a[i];
        a[i] = temp;
    }
}
  • 时间复杂度:最好 O ( n 2 ) O(n^2) O(n2),最坏 O ( n 2 ) O(n^2) O(n2),平均 O ( n 2 ) O(n^2) O(n2)
  • 空间复杂度: O ( 1 ) O(1) O(1)
  • 是否稳定:否(可能在每一轮交换的时候打乱)

3 直接插入排序

思想:每一轮都把当前数字插入到有序数组的尾巴,然后和前面那个比较,依次两两换位,直到找到正确位置。

public static void sort(int[] a) {
    for (int i = 1; i < a.length; i++) {
        for (int j = i; j >= 1; j--) {
            if (a[j - 1] > a[j]) {
                int temp = a[j - 1];
                a[j - 1] = a[j];
                a[j] = temp;
            } else break;
        }
    }
}
  • 时间复杂度:最好 O ( n ) O(n) O(n),最坏 O ( n 2 ) O(n^2) O(n2),平均 O ( n 2 ) O(n^2) O(n2)
  • 空间复杂度: O ( 1 ) O(1) O(1)
  • 是否稳定:是

4 希尔排序(改进的直接插入排序)

思想:改进的直接插入排序,对数组进行分组,开始组多元素少,最后组少元素多,序列逐渐成形。

public static void sort(int[] a) {
        //最外层,增量(数组分成的组数),每次减半
        for (int gap = a.length / 2; gap > 0; gap /= 2) {
            //对gap分的每一组都进行直接插入排序
            for (int i = gap; i < a.length; i++) {
                //对每一组进行插入排序,注意,对比的上一个元素是:j-gap(而直接插入排序是j--)
                for (int j = i; j > 0; j -= gap) {
                    if (a[j - 1] > a[j]) {
                        int temp = a[j - 1];
                        a[j - 1] = a[j];
                        a[j] = temp;
                    } else break;
                }
            }

        }
    }
  • 时间复杂度:最好 O ( n ) O(n) O(n),最坏 O ( n 2 ) O(n^2) O(n2),平均 O ( n 1.3 ) O(n^{1.3}) O(n1.3)
  • 空间复杂度: O ( 1 ) O(1) O(1)
  • 是否稳定:否(可能在每一轮交换的时候打乱)

5 堆排序

借鉴:图解排序算法(三)之堆排序
:具有以下性质的完全二叉树:每个结点的值都大于或等于其左右孩子结点的值,称为大顶堆;或者每个结点的值都小于或等于其左右孩子结点的值,称为小顶堆。(注意左右孩子节点值没有明确要求谁大谁小);
堆排序思想:将待排序序列构造成一个大顶堆,此时,整个序列的最大值就是堆顶的根节点。将其与末尾元素进行交换,此时末尾就为最大值。然后将剩余n-1个元素重新构造成一个堆,这样会得到n个元素的次小值。如此反复执行,便能得到一个有序序列了;
注意:大(小)顶堆都是逻辑结构,其实现可以是树或者数组,由于完全二叉树的特性,采用数组实现较为方便。
数组实现完全二叉树性质:

  • 第一个非叶子节点位置: a r r a y . l e n g t h / 2 − 1 array.length / 2 - 1 array.length/21
  • 位置 i i i 的左右孩子节点位置: 2 i + 1 2i+1 2i+1 2 i + 2 2i+2 2i+2
//堆排序
public static void sort(int[] arr) {
    //1.构建大顶堆
    for (int i = arr.length / 2 - 1; i >= 0; i--) {
        //从第一个非叶子结点从下至上,从右至左调整结构
        adjustHeap(arr, i, arr.length);
    }

    //2.调整堆结构+交换堆顶元素与末尾元素
    for (int j = arr.length - 1; j > 0; j--) {
        //顶和尾元素交换
        int temp = arr[0];
        arr[0] = arr[j];
        arr[j] = temp;
        //只需要把顶元素下沉,调整顶堆
        adjustHeap(arr, 0, j);
    }
}


//调整大顶推,目的是找到arr[i]这个元素在新的大顶堆(length长度数组)的位置(大于左右子节点值即可)
public static void adjustHeap(int[] arr, int i, int length) {
    int temp = arr[i];//先取出当前元素i
    for (int k = i * 2 + 1; k < length; k = k * 2 + 1) {//从i结点的左子结点开始,也就是2i+1处开始
        if (k + 1 < length && arr[k] < arr[k + 1]) {//如果左子结点小于右子结点,k指向右子结点
            k++;
        }
        if (arr[k] > temp) {//如果子节点大于父节点,将子节点值赋给父节点
            arr[i] = arr[k];
            i = k;//k就变成了下一个父节点
        } else {//两个子节点都小于等于父节点i,满足要求,break出去
            break;
        }
    }
    arr[i] = temp;//将temp值放到最终的位置
}
  • 时间复杂度:最好 O ( n l o g 2 n ) O(nlog_{2}n) O(nlog2n),最坏 O ( n l o g 2 n ) O(nlog_{2}n) O(nlog2n),平均 O ( n l o g 2 n ) O(nlog_{2}n) O(nlog2n)
  • 空间复杂度: O ( 1 ) O(1) O(1)
  • 是否稳定:否(可能顶堆调整的时候打乱)

6 快速排序

思想:利用分治策略,首先找到某个元素的最终位置(元素左边比它小,右边比它大),然后再对元素左边和右边的数组进行同样的处理。

治思想,把数组low到high排序好
private static void sort(int[] arr, int low, int high) {
    //注意边界条件
    if (low < high) {
        int index = getIndex(arr, low, high);//把arr[low]元素放到最终的位置并返回index
        sort(arr, low, index - 1);//分治左边
        sort(arr, index + 1, high);//分治右边
    }
}

//把arr[low]元素放到最终的位置并返回index
private static int getIndex(int[] arr, int low, int high) {
    // arr[low]需要处理的元素
    int tmp = arr[low];
    while (low < high) {
        // 从数组尾开始,要是满足就继续向首部看
        while (low < high && arr[high] >= tmp) {
            high--;
        }
        // 不满足,就要把这个元素移到low
        arr[low] = arr[high];
        // 然后从前往后看,满足就继续向后
        while (low < high && arr[low] <= tmp) {
            low++;
        }
        // 不满足,就把这个元素移动到high
        arr[high] = arr[low];

    }
    // 跳出循环时low和high相等,把tmp放到这个位置,并返回该位置
    arr[low] = tmp;
    return low;
}
  • 时间复杂度:最好 O ( n l o g 2 n ) O(nlog_{2}n) O(nlog2n),最坏 O ( n 2 ) O(n^2) O(n2),平均 O ( n l o g 2 n ) O(nlog_{2}n) O(nlog2n)
  • 空间复杂度: O ( n l o g 2 n ) O(nlog_2n) O(nlog2n)
  • 是否稳定:否(可能在获取某个元素排序时打乱)

7 归并排序

思想:标准的分治策略,把任务拆成两个排序子任务,然后合并两个有序数组。

//把从L到R的数组排好序
public static void sort(int[] arr, int L, int R) {
    //递归结束条件,
    if (L == R) {
        return;
    }
    int mid = (L + R) / 2;//中间位置
    sort(arr, L, mid);
    sort(arr, mid + 1, R);
    merge(arr, L, mid, R);
}

//合并L到mid 和mid到R 两个排好序的数组
public static void merge(int[] arr, int L, int mid, int R) {
    int[] temp = new int[R - L + 1];//开辟空间存放合并后的结果
    int i = 0;//合并的index
    int p1 = L;//第一个数组的首指针
    int p2 = mid + 1;//第二个数组的首指正
    // 比较左右两部分的元素,哪个小,把那个元素填入temp中
    while (p1 <= mid && p2 <= R) {
        if(arr[p1]<arr[p2]){
            temp[i++]=arr[p1++];
        }
        else {
            temp[i++]=arr[p2++];
        }
    }
    // 上面的循环退出后,把剩余的元素依次填入到temp中
    while (p1 <= mid) {
        temp[i++] = arr[p1++];
    }
    while (p2 <= R) {
        temp[i++] = arr[p2++];
    }
    // 把最终的排序的结果复制给原数组
    for (i = 0; i < temp.length; i++) {
        arr[L + i] = temp[i];
    }
}
  • 时间复杂度:最好 O ( n l o g 2 n ) O(nlog_{2}n) O(nlog2n),最坏 O ( n l o g 2 n ) O(nlog_{2}n) O(nlog2n),平均 O ( n l o g 2 n ) O(nlog_{2}n) O(nlog2n)
  • 空间复杂度: O ( n ) O(n) O(n)
  • 是否稳定:是(合并的时候,可以考虑相同元素的合并原则)

8 计数排序

原理:根据数组元素的值,把这个值放到新开辟空间进行计数。(哈希思想)

public static void sort(int[] array) {
    //1.得到数列的最大、最小值
    int max = array[0];
    int min = array[0];
    for (int i = 1; i < array.length; i++) {
        if (array[i] > max)
            max = array[i];
        if (array[i] < min)
            min = array[i];
    }

    //2.根据数列的最大值确定统计数组的长度
    int[] coutArray = new int[max - min + 1];

    //3.遍历数列,填充统计数组
    for (int i = 0; i < array.length; i++)
        coutArray[array[i] - min]++;

    System.out.println(Arrays.toString(coutArray));
    //4.根据统计表把排序后的值返回给数组
    int index = 0;
    for (int i = 0; i < coutArray.length; i++) {
        for (int j = 0; j < coutArray[i]; j++) {
            array[index] = i + min;
            index++;
        }
    }
}
  • 时间复杂度:最好 O ( n + k ) O(n+k) O(n+k),最坏 O ( n + k ) O(n+k) O(n+k),平均 O ( n + k ) O(n+k) O(n+k)
  • 空间复杂度: O ( n + k ) O(n+k) O(n+k)
  • 是否稳定:是(相同的数,有先后的加入计数数组的顺序)

9 桶排序

思想:桶排序是计数排序的扩展,把数据按照某个规则进行分桶操作,然后在桶内使用其他排序算法,最后遍历每个桶和桶内元素,就可以得到一个有序的数组了。

 static void sort(int[] arr){
    // 计算最大值与最小值
    int max = Integer.MIN_VALUE;
    int min = Integer.MAX_VALUE;
    for(int i = 0; i < arr.length; i++){
        max = Math.max(max, arr[i]);
        min = Math.min(min, arr[i]);
    }

    // 计算桶的数量
    int bucketNum = (max - min) / arr.length + 1;
    ArrayList<ArrayList<Integer>> bucketArr = new ArrayList<>(bucketNum);
    for(int i = 0; i < bucketNum; i++){
        bucketArr.add(new ArrayList<Integer>());
    }

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

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

    // 将桶中的元素赋值到原序列
    int index = 0;
    for(int i = 0; i < bucketArr.size(); i++){
        for(int j = 0; j < bucketArr.get(i).size(); j++){
            arr[index++] = bucketArr.get(i).get(j);
        }
    }
}
  • 时间复杂度:最好 O ( n ) O(n) O(n),最坏 O ( n 2 ) O(n^2) O(n2),平均 O ( n + k ) O(n+k) O(n+k)
  • 空间复杂度: O ( n + k ) O(n+k) O(n+k)
  • 是否稳定:是(相同的数加入同一个桶,可以记录顺序)

10 基数排序

思想:也是计数排序的一种扩展,比如对一些1000以内的数进行排序,先将其按照个位分桶,然后展开,再按照十位分桶,然后展开,最后按照百位分桶,最后展开,就可以得到一个有序的数组。

public static void sort(int[] number, int d) {
    int k = 0;
    int n = 1;
    int m = 1; //控制键值排序依据在哪一位
    int[][]temp = new int[10][number.length]; //数组的第一维表示可能的余数0-9
    int[]order = new int[10]; //数组orderp[i]用来表示该位是i的数的个数
    while(m <= d) {
        for(int i = 0; i < number.length; i++) {
            int lsd = ((number[i] / n) % 10);
            temp[lsd][order[lsd]] = number[i];
            order[lsd]++;
        }
        for(int i = 0; i < 10; i++) {
            if(order[i] != 0)
                for(int j = 0; j < order[i]; j++)
                {
                    number[k] = temp[i][j];
                    k++;
                }
            order[i] = 0;
        }
        n *= 10;
        k = 0;
        m++;
    }
}
  • 时间复杂度:最好 O ( n ∗ k ) O(n*k) O(nk),最坏 O ( n ∗ k ) O(n*k) O(nk),平均 O ( n ∗ k ) O(n*k) O(nk)
  • 空间复杂度: O ( n + k ) O(n+k) O(n+k)
  • 是否稳定:是(相同的数加入同一个桶,可以记录顺序)

总结(图片来自网络)

sorry,忘记了是哪里粘的了,作者请见谅。
十大排序算法总结

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值