zoukankan      html  css  js  c++  java
  • 数据结构与算法(八)——排序

    一、排序

    1、介绍

      影响排序算法性能的几个要素:时间性能、辅助空间、算法的复杂性。
      内部排序:将需要处理的所有数据都加载到内存中进行排序。包括交换式排序、选择式排序、插入式排序。
      外部排序:数据量过大,无法全部加载到内存中,需要借助外部存储进行排序。包括合并排序、直接合并排序。

    2、分类

      交换式排序:冒泡排序(Bubble sort)、快速排序(Quick sort)
      选择式排序:选择排序(Select sort)、堆排序(Heap sort)
      插入式排序:插入排序(Insert sort)、希尔排序(Shell sort)、二叉树排序(Binary-tree sort)
      归并式排序:归并排序()

    二、冒泡排序

    1、思想

      两个数比较大小,较大的数下沉,较小的数冒起来。每一趟至少有一个数排好,所以 n 个数至多需要 n - 1 趟。

    2、过程

      动图:

    3、代码

     1 // 冒泡排序
     2 public static void bubbleSort(int[] arr) {
     3     // 冒泡排序一趟至少可以确定一个数的位置
     4     boolean flag = false;
     5     for (int i = 0; i < arr.length - 1; i++) {
     6         flag = false;
     7         for (int j = 0; j < arr.length - 1 - i; j++) {
     8             if (arr[j] > arr[j + 1]) {
     9                 swap(arr, j, j + 1);
    10                 flag = true;
    11             }
    12         }
    13         // 没有执行交换,已经有序
    14         if (!flag) {
    15             break;
    16         }
    17     }
    18 }

    三、快速排序

    1、思想

      快速排序:是对冒泡排序的一种改进。
      (1)在数据集之中,选择一个元素作为"基准"(pivot)。
      (2)所有小于"基准"的元素,都移到"基准"的左边;所有大于"基准"的元素,都移到"基准"的右边。
      (3)对"基准"左边和右边的两个子集,不断重复第一步和第二步,直到所有子集只剩下一个元素为止。整个排序过程可以递归进行,以此达到整个数据有序。

    2、过程

    3、代码

     1 // 快速排序
     2 public static void quickSort(int[] arr, int left, int right) {
     3     if (left >= right) {
     4         return;
     5     }
     6     int l = left;
     7     int r = right;
     8     int pivot = arr[r];
     9 
    10     while (l < r) {
    11         while (l < r && arr[l] < pivot) {
    12             l++;
    13         }
    14         if (l < r) {
    15             arr[r] = arr[l];
    16             r--;
    17         }
    18 
    19         while (l < r && arr[r] > pivot) {
    20             r--;
    21         }
    22         if (l < r) {
    23             arr[l] = arr[r];
    24             l++;
    25         }
    26     }
    27     // 此时 l == r
    28     arr[l] = pivot;
    29     quickSort(arr, left, r - 1);
    30     quickSort(arr, l + 1, right);
    31 }

    四、选择排序

    1、思想

      在长度为 n 的无序数组中,
      第一次遍历 n 个数,找到最小的数值与第一个元素交换;
      第二次遍历n - 1个数,找到最小的数值与第二个元素交换;
      。。。
      第n - 1次遍历2个数,找到最小的数值与第n - 1个元素交换,排序完成。

    2、过程

      动图:

    3、代码

     1 // 选择排序.方式一、少交换
     2 public static void selectSort(int[] arr) {
     3     int min = 0, index = 0;
     4     for (int i = 0; i < arr.length - 1; i++) {
     5         min = arr[i];
     6         index = i;
     7         // 找最小值
     8         for (int j = i + 1; j < arr.length; j++) {
     9             if (arr[j] < min) {
    10                 min = arr[j];
    11                 index = j;
    12             }
    13         }
    14         // 将最小值与第 i 位交换
    15         swap(arr, i, index);
    16     }
    17 }
    18 
    19 // 选择排序.方式二、多交换
    20 public static void selectSort2(int[] arr) {
    21     for (int i = 0; i < arr.length - 1; i++) {
    22         for (int j = i + 1; j < arr.length; j++) {
    23             if (arr[i] > arr[j]) {
    24                 swap(arr, i, j);
    25             }
    26         }
    27     }
    28 }

    五、插入排序

    1、思想

      将一个数插入到已经排好序的有序表中,从而得到一个新的、记录数增加1的有序表。

    2、过程

     

      动图:

    3、代码

     1 // 插入排序
     2 public static void insertSort(int[] arr) {
     3     for (int i = 1; i < arr.length; i++) {
     4         // 待插入数
     5         int insertVal = arr[i];
     6         int index = i - 1;
     7         while (index >= 0 && insertVal < arr[index]) {
     8             arr[index + 1] = arr[index];
     9             index--;
    10         }
    11         arr[index + 1] = insertVal;
    12     }
    13 }

    六、希尔排序

      插入排序可能存在的问题,数组 arr = {2,3,4,5,6,1} 这时需要插入数 1,这样的过程是:

      {2,3,4,5,6,6}
      {2,3,4,5,5,6}
      {2,3,4,4,5,6}
      {2,3,3,4,5,6}
      {2,2,3,4,5,6}
      {1,2,3,4,5,6}

      结论:当需要插入的数是较小的数时,后移的次数明显增多,对效率有影响。

    1、思想

      希尔排序:是对插入排序的一种改进。也称为缩小增量排序。
      在要排序的一组数中,根据某一增量分为若干子序列,并对子序列分别进行插入排序。然后逐渐将增量减小,并重复上述过程,直至增量为1,此时数据序列基本有序,最后进行插入排序。

    2、过程

      动图:

    3、代码

     1 // 希尔排序.方式一、移位法
     2 public static void shellSort(int[] arr) {
     3     // 增量 gap,并逐步缩小增量
     4     for (int gap = arr.length / 2; gap > 0; gap = gap / 2) {
     5         // 从第gap个元素,逐个对其所在的组进行直接插入排序
     6         for (int i = gap; i < arr.length; i++) {
     7             // 待插入数
     8             int insertVal = arr[i];
     9             int index = i - gap;
    10             while (index >= 0 && insertVal < arr[index]) {
    11                 arr[index + gap] = arr[index];
    12                 index = index - gap;
    13             }
    14             arr[index + gap] = insertVal;
    15         }
    16     }
    17 }
    18 
    19 // 希尔排序.方式二、交换法
    20 public static void shellSort2(int[] arr) {
    21     // 分组.个人感觉这种方式组内排序更像是冒泡
    22     for (int gap = arr.length / 2; gap > 0; gap = gap / 2) {
    23         for (int i = gap; i < arr.length; i++) {
    24 
    25             for (int j = i - gap; j >= 0; j = j - gap) {
    26                 if (arr[j] > arr[j + gap]) {
    27                     swap(arr, j, j + gap);
    28                 }
    29             }
    30         }
    31     }
    32 }

    七、归并排序

    1、思想

      该算法采用经典的分治(divide-and-conquer)策略。分治法将问题分(divide)成一些小的问题然后递归求解,而治(conquer)的阶段则将分的阶段得到的各答案"修补"在一起,即分而治之。

    2、过程

      分阶段:可以理解为就是递归拆分子序列的过程。

      治阶段:将两个已经有序的子序列合并成一个有序序列,比如上图中的最后一次合并,要将[4,5,7,8]和[1,2,3,6]两个已经有序的子序列,合并为最终序列[1,2,3,4,5,6,7,8]。

      动图:

    3、代码

     1 // 归并排序.分+合方法
     2 public static void mergeSort(int[] arr, int left, int right, int[] temp) {
     3     if (left < right) {
     4         int mid = (left + right) / 2;
     5         // 向左递归进行分解
     6         mergeSort(arr, left, mid, temp);
     7         // 向右递归进行分解
     8         mergeSort(arr, mid + 1, right, temp);
     9         // 合并
    10         merge(arr, left, mid, right, temp);
    11     }
    12 }
    13 
    14 // 合并的方法
    15 private static void merge(int[] arr, int left, int mid, int right, int[] temp) {
    16     int i = left;
    17     int j = mid + 1;
    18     int tempIndex = 0;
    19 
    20     // 1.合并两个有序序列到temp
    21     while (i <= mid && j <= right) {
    22         if (arr[i] <= arr[j]) {
    23             temp[tempIndex] = arr[i];
    24             i++;
    25         } else {
    26             temp[tempIndex] = arr[j];
    27             j++;
    28         }
    29         tempIndex++;
    30 
    31     }
    32 
    33     while (i <= mid) {
    34         temp[tempIndex] = arr[i];
    35         i++;
    36         tempIndex++;
    37     }
    38     while (j <= right) {
    39         temp[tempIndex] = arr[j];
    40         j++;
    41         tempIndex++;
    42     }
    43 
    44     // 2.将temp数组的元素拷贝到arr
    45     tempIndex = 0;
    46     int tempLeft = left;
    47 
    48     while (tempLeft <= right) {
    49         arr[tempLeft] = temp[tempIndex];
    50         tempLeft++;
    51         tempIndex++;
    52     }
    53 }

    八、基数排序

    1、思想

      基数排序(radix sort)属于"分配式排序"(distribution sort),又称"桶子法"(bucket sort)。顾名思义,将关键字按位数切割成不同的数字,然后按每个位数分别比较,这样从最低位排序一直到最高位排序完成以后, 数列就变成一个有序序列。

    2、过程

      将数组{53,3,542,748,14,214}使用基数排序,进行升序排序。

      动图:

      说明:
      基数排序是对传统桶排序的扩展,速度很快。
      基数排序是经典的空间换时间的方式,占用内存很大。当对海量数据排序时,容易造成 OutOfMemoryError。
      有负数的数组,我们不用基数排序来进行排序。如果要支持负数,参考:https://code.i-harness.com/zh-CN/q/e98fa9

    3、代码

     1 // 基数排序
     2 public static void radixSort(int[] arr) {
     3     // 定义桶的个数.0~9 一共10个
     4     final int bucketCount = 10;
     5 
     6     // 由于每一个桶都是一个一维数组,所以不难想到定义一个二维数组来表示
     7     int[][] buckets = new int[bucketCount][arr.length];
     8 
     9     // 为每一个桶定义一个指针,表示当前桶中实际放入的元素个数
    10     int[] indexs = new int[bucketCount];
    11 
    12     final int maxValueLength = getMaxValueLength(arr);
    13     for (int i = 0; i < maxValueLength; i++) {
    14 
    15         // 1.arr -> 放入桶
    16         for (int j : arr) {
    17             // 依次取个位、十位、百位……上的数字.比如取十位: 748/10=74,74%10=4
    18             int digit = j / (int) Math.pow(10, i) % 10;
    19 
    20             // 放入到对应的桶中
    21             buckets[digit][indexs[digit]] = j;
    22             // 对应桶的个数 +1
    23             indexs[digit]++;
    24         }
    25 
    26         // 2.从桶取 -> arr
    27         int temp = 0;
    28         // 遍历这 10 个桶
    29         for (int j = 0; j < bucketCount; j++) {
    30             if (indexs[j] > 0) {
    31                 for (int k = 0; k < indexs[j]; k++) {
    32                     arr[temp] = buckets[j][k];
    33                     temp++;
    34                 }
    35             }
    36 
    37             // 取完 j 号桶后,将指针归零
    38             indexs[j] = 0;
    39         }
    40     }
    41 }
    42 
    43 // 获取最大数是几位数
    44 private static int getMaxValueLength(int[] arr) {
    45     int max = arr[0];
    46     for (int i : arr) {
    47         if (i > max) {
    48             max = i;
    49         }
    50     }
    51 
    52     return String.valueOf(max).length();
    53 }

    九、常用排序算法对比

      相关术语解释:
      1)稳定:如果a原本在b前面,而a=b,排序之后a仍然在b的前面;
      2)不稳定:如果a原本在b的前面,而a=b,排序之后a可能会出现在b的后面;
      3)内排序:所有排序操作都在内存中完成;
      4)外排序:由于数据太大,因此把数据放在磁盘中,而排序通过磁盘和内存的数据传输才能进行;
      5)时间复杂度: 一个算法执行所耗费的时间。
      6)空间复杂度:运行完一个程序所需内存的大小。
      7)n:数据规模
      8)k:"桶"的个数
      9)In-place:不占用额外内存
      10)Out-place:占用额外内存

    作者:Craftsman-L

    本博客所有文章仅用于学习、研究和交流目的,版权归作者所有,欢迎非商业性质转载。

    如果本篇博客给您带来帮助,请作者喝杯咖啡吧!点击下面打赏,您的支持是我最大的动力!

  • 相关阅读:
    JavaScrip--JS面向对象
    JavaScrip-Screen对象
    JavaScrip--Location对象
    安卓奇葩问题之:运行OK,打包安装崩溃(原因是:代码不规范导致编译出错)
    安卓奇葩问题之.so库加载不了
    Fresco简单的使用—SimpleDraweeView
    安卓奇葩问题之SQLite条件查找不到数据
    打包时动态指定一些值
    安卓开发:DateUtils
    SAX解析xml文件
  • 原文地址:https://www.cnblogs.com/originator/p/14351747.html
Copyright © 2011-2022 走看看