排序算法之冒泡、插入、选择、归并、快排、桶排序、计数排序、基数排序

数组打印方式:
Arrays.toString() 打印一维数组
Arrays.deepToString()打印多维数组

public int[] initArray(int n){
       int[] array = new int[n];
       for (int i = 0; i < n; i++) {
           array[i] = (int) (Math.random()*10);
       }
       return array;
   }
 public void printArray(String str, int[] arrays) {
        System.out.println(str + " " + Arrays.toString(arrays));
    }

在这里插入图片描述
排序算法的稳定性: 如果待排序的序列中存在值相等的元素,经过排序之后,相等元素之间原有的先后顺序不变,即是稳定的排序算法。
平均时间复杂度: 平均时间复杂度就是加权平均期望时间复杂度。
有序度: 有序度是数组中具有有序关系的元素对的个数,即有序元素对:a[i] <= a[j], 如果i < j
逆有序度: 逆有序度是数组中无序关系的元素对的个数,即逆序元素对:a[i] > a[j], 如果i < j
逆序度 = 满有序度 - 有序度
对于一个倒序排列的数组,比如 6,5,4,3,2,1,有序度是 0。
对于一个完全有序的数组,比如 1,2,3,4,5,6,有序度就是 n*(n-1)/2,我们把这种完全有序的数组的有序度叫作满有序度
在这里插入图片描述

冒泡排序:相邻两个元素进行比较,看是否满足大小关系,不满足就交换,一次能比较出一个最小或最大的元素,一共n个元素,重复n次,完成n个数据的排序工作。

  1. 时间复杂度 O(n^2)
  2. 是原地排序算法
  3. 是稳定的排序算法

我们要对一组数据 4,5,6,3,2,1,从小到大进行排序。第一次冒泡操作的详细过程就是这样:
在这里插入图片描述
优化: 冒泡过程还可以优化。当某次冒泡操作已经没有数据交换时,说明已经达到完全有序,不用再继续执行后续的冒泡操作。

  @Test
    public void bubbleTest() {
        int n = 10;
        int[] array = initArray(n);
        System.out.println(Arrays.toString(array));
        int count = 0;
        for (int i = 0; i < n; ++i) {
            // 提前退出冒泡循环的标志位
            boolean flag = false;
            System.out.println(Arrays.toString(array));
            for (int j = 0; j < n - i - 1; ++j) {
                count++;
                if (array[j] > array[j + 1]) { // 交换
                    int tmp = array[j];
                    array[j] = array[j + 1];
                    array[j + 1] = tmp;
                    flag = true;  // 表示有数据交换
                }
            }
            if (!flag)
                break;  // 没有数据交换,提前退出
        }
        System.out.println("执行次数" + count);
        System.out.println(Arrays.toString(array));
    }

在这里插入图片描述

插入排序:从一个数组的第二个元素开始向后遍历每个即将插入的数据,逐个向前比较,找到即将插入的位置(即比较前一个元素是否满足比较关系,不满足,向后移动,腾出位置,找到满足比较关系的元素位置),放入数据。

  1. 时间复杂度 O(n^2)
  2. 是原地排序算法
  3. 是稳定的排序算法
    在这里插入图片描述
   @Test
    public void insertSortTest() {
        int n = 10;
        int[] array = initArrays(n);
        System.out.println(Arrays.toString(array));
        for (int i = 1; i < n; i++) {
            int insertValue = array[i];
            int j = i - 1;
            for (; j >= 0; j--) {
                if (array[j] > insertValue) {
                    array[j + 1] = array[j];
                } else {
                    break;
                }
            }
            array[j + 1] = insertValue;
        }
        System.out.println(Arrays.toString(array));
    }

选择排序:每次选择排序列表中最小的元素,与有序后的第一个无序数据进行交换位置,当有序数组为全数组时,排序完成。

  1. 时间复杂度O(n^2)
  2. 是原地排序算法
  3. 不是稳定的排序算法。
    由于每次在剩余的元素查找到最小的元素,和之前的元素进行比较并交换,所以,位置就会进行变化,比如:5,8,5,2,9 这样一组数据,使用选择排序算法来排序的话,第一次找到最小元素 2,与第一个 5 交换位置,那第一个 5 和中间的 5 顺序就变了,所以就不稳定了。正是因此,相对于冒泡排序和插入排序,选择排序就稍微逊色了。

在这里插入图片描述

  @Test
    public void selectSortTest() {
        int n = 10;
        int[] array = initArray(n);
        System.out.println(Arrays.toString(array));
        for (int i = 0; i < n; i++) {
            int min = array[i];
            for (int j = i + 1; j < n; j++) {
                if (array[j] < min) {
                    int temp = array[j];
                    array[j] = min;
                    min = temp;
                }
            }
            array[i] = min;
        }
        System.out.println(Arrays.toString(array));
    }

总结: 插入排序要比冒泡排序更受欢迎.
原因:从代码实现上来看,冒泡排序的数据交换要比插入排序的数据移动要复杂,冒泡排序需要 3 个赋值操作,而插入排序只需要 1 个。


冒泡排序中数据的交换操作:
if (a[j] > a[j+1]) { // 交换
   int tmp = a[j];
   a[j] = a[j+1];
   a[j+1] = tmp;
   flag = true;
}

插入排序中数据的移动操作:
if (a[j] > value) {
  a[j+1] = a[j];  // 数据移动
} else {
  break;
}

在这里插入图片描述
归并排序:递归到只有两个元素时,进行排序,再向上返回三个/四个元素进行排序,直到全数组进行排序,即排序完成。
1.是稳定的排序算法
2.时间复杂度:O(nlogn)
3.空间复杂度:O(n)
4.稳定的排序算法
5.不是原地排序

  public void mergeSort(int[] original) {
        int arrayLength = original.length;
        int middle = arrayLength / 2;
        if (middle < 1) {
            return;
        }
        int[] partitionA = Arrays.copyOfRange(original, 0, middle);
        int[] partitionB = Arrays.copyOfRange(original, middle, arrayLength);
        mergeSort(partitionA);
        mergeSort(partitionB);
        sort(partitionA, partitionB, original);
    }

    public void sort(int[] partA, int[] partB, int[] original) {
        int i = 0;
        int j = 0;
        int k = 0;
        while (i < partA.length && j < partB.length) {
            if (partA[i] <= partB[j]) {
                original[k++] = partA[i++];
            } else {
                original[k++] = partB[j++];
            }
        }
        if (i == partA.length) {
            while (j < partB.length) {
                original[k++] = partB[j++];
            }
        } else if (j == partB.length) {
            while (i < partA.length) {
                original[k++] = partA[i++];
            }
        }
    }


  • 添加哨兵优化版:
  
package sorts;

/**
 * Created by wangzheng on 2018/10/16.
 */
public class MergeSort {

  // 归并排序算法, a是数组,n表示数组大小
  public static void mergeSort(int[] a, int n) {
    mergeSortInternally(a, 0, n-1);
  }

  // 递归调用函数
  private static void mergeSortInternally(int[] a, int p, int r) {
    // 递归终止条件
    if (p >= r) return;

    // 取p到r之间的中间位置q,防止(p+r)的和超过int类型最大值
    int q = p + (r - p)/2;
    // 分治递归
    mergeSortInternally(a, p, q);
    mergeSortInternally(a, q+1, r);

    // 将A[p...q]和A[q+1...r]合并为A[p...r]
    merge(a, p, q, r);
  }

  private static void merge(int[] a, int p, int q, int r) {
    int i = p;
    int j = q+1;
    int k = 0; // 初始化变量i, j, k
    int[] tmp = new int[r-p+1]; // 申请一个大小跟a[p...r]一样的临时数组
    while (i<=q && j<=r) {
      if (a[i] <= a[j]) {
        tmp[k++] = a[i++]; // i++等于i:=i+1
      } else {
        tmp[k++] = a[j++];
      }
    }

    // 判断哪个子数组中有剩余的数据
    int start = i;
    int end = q;
    if (j <= r) {
      start = j;
      end = r;
    }

    // 将剩余的数据拷贝到临时数组tmp
    while (start <= end) {
      tmp[k++] = a[start++];
    }

    // 将tmp中的数组拷贝回a[p...r]
    for (i = 0; i <= r-p; ++i) {
      a[p+i] = tmp[i];
    }
  }

  /**
   * 合并(哨兵)
   *
   * @param arr
   * @param p
   * @param q
   * @param r
   */
  private static void mergeBySentry(int[] arr, int p, int q, int r) {
    int[] leftArr = new int[q - p + 2];
    int[] rightArr = new int[r - q + 1];

    for (int i = 0; i <= q - p; i++) {
      leftArr[i] = arr[p + i];
    }
    // 第一个数组添加哨兵(最大值)
    leftArr[q - p + 1] = Integer.MAX_VALUE;

    for (int i = 0; i < r - q; i++) {
      rightArr[i] = arr[q + 1 + i];
    }
    // 第二个数组添加哨兵(最大值)
    rightArr[r-q] = Integer.MAX_VALUE;

    int i = 0;
    int j = 0;
    int k = p;
    while (k <= r) {
      // 当左边数组到达哨兵值时,i不再增加,直到右边数组读取完剩余值,同理右边数组也一样
      if (leftArr[i] <= rightArr[j]) {
        arr[k++] = leftArr[i++];
      } else {
        arr[k++] = rightArr[j++];
      }
    }
  }
}
 

快速排序 :找一个任意数据作为分区点pivote(案例是将最后一个元素作为pivote),将数组分为大于和小于pivot的两部分,直至分组区间缩小为1,说明数据是有序的了。
1.时间复杂度:O(nlogn)
2.最坏:O(n2)
3.原地排序算法
4.不稳定的排序算法

  • 优化:
  • pivot 设值为 三数取中法/五数取中法/十数取中法,这样可以避免pivot选取不均,数据全部一边倒,导致两边数据区间过大或过小
  • pivote 设值为 随机法取数,避免每次选择最后一个,防止该序列是倒序序列等,每次分的区间会导致严重不均匀。

递推公式:
quick_sort(p…r) = quick_sort(p…q-1) + quick_sort(q+1… r)

终止条件:
p >= r

在这里插入图片描述
在这里插入图片描述

 @Test
    public void quickSortTest() {
        int[] arrays = initArrays(22);
        printArray("排序前:", arrays);
        quickSort(arrays, arrays.length);
        printArray("排序后:", arrays);

    }

    public void quickSort(int[] array, int n) {
        quickSort(array, 0, n - 1);
    }

    public void quickSort(int[] array, int p, int r) {
        if (p >= r)
            return;
        int q = partition(array, p, r);
        quickSort(array, p, q - 1);
        quickSort(array, q + 1, r);
    }

    public int partition(int[] array, int p, int r) {
        int pivot = array[r];
        int i = p;
        for (int j = p; j < r - 1; j++) {
            if (array[j] < pivot) {
                int temp = array[j];
                array[j] = array[i];
                array[i] = temp;
                i++;
            }
        }
        int temp = array[r];
        array[r] = array[i];
        array[i] = temp;
        return i;
    }

**线性排序:**桶排序、计数排序、基数排序 时间复杂度:O(n)
桶排序:将要排序的数据分到几个有序的桶里,每个桶里的数据再单独进行排序。

  • 应用场景:适用于数据量较大,且内存不足的情况。
    在这里插入图片描述
    计数排序:当要排序的 n 个数据,把数据划分成 k 个桶,再将其取出,便是有序的了。
  • 应用场景:考生分数的查询及人数
    基数排序:对数据的要求是可以分隔的,对分隔后的数据进行排序,排好序后,整体数据便是有序的了
  • 应用场景:1-万手机号的排序
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值