数据结构笔记【基础篇】——排序

排序

  • 排序

    • 如何分析一个排序算法

      • 排序算法的执行效率

          1. 最好情况、最坏情况、平均情况时间复杂度

          2. 时间复杂度的系数、常数 、低阶

          3. 比较次数和交换(或移动)次数

      • 排序算法的内存消耗

        • 原地排序算法,就是特指空间复杂度是 O(1) 的排序算法。
      • 排序算法的稳定性

        • 这个概念是说,如果待排序的序列中存在值相等的元素,经过排序之后,相等元素之间原有的先后顺序不变。
    • 冒泡排序

    // 冒泡排序,a表示数组,n表示数组大小
    public void bubbleSort(int[] a, int n) {
           
      if (n <= 1) return;
    
     for (int i = 0; i < n; ++i) {
           
        // 提前退出冒泡循环的标志位
        boolean flag = false;
        for (int j = 0; j < n - i - 1; ++j) {
           
          if (a[j] > a[j+1]) {
            // 交换
            int tmp = a[j];
            a[j] = a[j+1];
            a[j+1] = tmp;
            flag = true;  // 表示有数据交换      
          }
        }
        if (!flag) break;  // 没有数据交换,提前退出
      }
    }
    
    • 插入排序
      • 希尔排序
// 插入排序,a表示数组,n表示数组大小
public void insertionSort(int[] a, int n) {
     
  if (n <= 1) return;

  for (int i = 1; i < n; ++i) {
     
    int value = a[i];
    int j = i - 1;
    // 查找插入的位置
    for (; j >= 0; --j) {
     
      if (a[j] > value) {
     
        a[j+1] = a[j];  // 数据移动
      } else {
     
        break;
      }
    }
    a[j+1] = value; // 插入数据
  }
}
	
  • 选择排序(Selection Sort)

    • 数据结构笔记【基础篇】——排序_第1张图片

      • 小结

      • 数据结构笔记【基础篇】——排序_第2张图片

  • 归并排序

    • 数据结构笔记【基础篇】——排序_第3张图片
  • 快速排序

    • 数据结构笔记【基础篇】——排序_第4张图片
    • 数据结构笔记【基础篇】——排序_第5张图片
    • 对于上图的解释 i与j首先居于左侧 no swap的时候j向右走 如果swap 则i与j均向右走
  • 归并、快排小结

    • 归并排序算法是一种在任何情况下时间复杂度都比较稳定的排序算法,这也使它存在致命的缺点,即归并排序不是原地排序算法,空间复杂度比较高,是 O(n)。正因为此,它也没有快排应用广泛。
    • 快速排序算法虽然最坏情况下的时间复杂度是 O(n2),但是平均情况下时间复杂度都是 O(nlogn)。不仅如此,快速排序算法时间复杂度退化到 O(n2) 的概率非常小,我们可以通过合理地选择 pivot 来避免这种情况。
  • 课后思考

    • 现在你有 10 个接口访问日志文件,每个日志文件大小约 300MB,每个文件里的日志都是按照时间戳从小到大排序的。你希望将这 10 个较小的日志文件,合并为 1 个日志文件,合并之后的日志仍然按照时间戳从小到大排列。如果处理上述排序任务的机器内存只有 1GB,你有什么好的解决思路,能“快速”地将这 10 个日志文件合并吗?
    • 先构建十条io流,分别指向十个文件,每条io流读取对应文件的第一条数据,然后比较时间戳,选择出时间戳最小的那条数据,将其写入一个新的文件,然后指向该时间戳的io流读取下一行数据,然后继续刚才的操作,比较选出最小的时间戳数据,写入新文件,io流读取下一行数据,以此类推,完成文件的合并, 这种处理方式,日志文件有n个数据就要比较n次,每次比较选出一条数据来写入,时间复杂度是O(n),空间复杂度是O(1),几乎不占用内存
  • 线性排序

    • 桶排序、计数排序、基数排序
    • 桶排序(Bucket sort)
      • 数据结构笔记【基础篇】——排序_第6张图片
      • 桶排序比较适合用在外部排序中。所谓的外部排序就是数据存储在外部磁盘中,数据量比较大,内存有限,无法将数据全部加载到内存中。
    • 计数排序(Counting sort)
      • 数据结构笔记【基础篇】——排序_第7张图片
      • 上图中先对A数组统计最大值 max 开一样大的数组C,并统计相关信息记录在C数组中,依次累加,随后从后往前取A中数字并根据C数组中所示插入R数组中
      • 计数排序只能用在数据范围不大的场景中,如果数据范围 k 比要排序的数据 n 大很多,就不适合用计数排序了。而且,计数排序只能给非负整数排序,如果要排序的数据是其他类型的,要将其在不改变相对大小的情况下,转化为非负整数。
    • 基数排序(Radix sort)
      • 数据结构笔记【基础篇】——排序_第8张图片
      • 基数排序对要排序的数据是有要求的,需要可以分割出独立的“位”来比较,而且位之间有递进的关系,如果 a 数据的高位比 b 数据大,那剩下的低位就不用比较了。除此之外,每一位的数据范围不能太大,要可以用线性排序算法来排序,否则,基数排序的时间复杂度就无法做到 O(n) 了。
  • 课后思考

    • 假设我们现在需要对 D,a,F,B,c,A,z 这个字符串进行排序,要求将其中所有小写字母都排在大写字母的前面,但小写字母内部和大写字母内部不要求有序。比如经过排序之后为 a,c,z,D,F,B,A,这个如何来实现呢?如果字符串中存储的不仅有大小写字母,还有数字。要将小写字母的放到前面,大写字母放在最后,数字放在中间,不用排序算法,又该怎么解决呢?
    • 用两个指针a、b:a指针从头开始往后遍历,遇到大写字母就停下,b从后往前遍历,遇到小写字母就停下,交换a、b指针对应的元素;重复如上过程,直到a、b指针相交。
      对于小写字母放前面,数字放中间,大写字母放后面,可以先将数据分为小写字母和非小写字母两大类,进行如上交换后再在非小写字母区间内分为数字和大写字母做同样处理
  • 排序算法特点汇总

  • 数据结构笔记【基础篇】——排序_第9张图片

  • 如果对小规模数据进行排序,可以选择时间复杂度是 O(n2) 的算法;如果对大规模数据进行排序,时间复杂度是 O(nlogn) 的算法更加高效。所以,为了兼顾任意规模数据的排序,一般都会首选时间复杂度是 O(nlogn) 的排序算法来实现排序函数。

  • 如何优化快速排序?

    • 最理想的分区点是:被分区点分开的两个分区中,数据的数量差不多。
    • 比较常用、比较简单的分区算法
      • 三数取中法
        • 我们从区间的首、尾、中间,分别取出一个数,然后对比大小,取这 3 个数的中间值作为分区点。这样每间隔某个固定的长度,取数据出来比较,将中间值作为分区点的分区算法,肯定要比单纯取某一个数据更好。但是,如果要排序的数组比较大,那“三数取中”可能就不够了,可能要“五数取中”或者“十数取中”。
      • 随机法
        • 随机法就是每次从要排序的区间中,随机选择一个元素作为分区点。这种方法并不能保证每次分区点都选的比较好,但是从概率的角度来看,也不大可能会出现每次分区点都选的很差的情况,所以平均情况下,这样选的分区点是比较好的。时间复杂度退化为最糟糕的 O(n2) 的情况,出现的可能性不大。
  • qsort() 会优先使用归并排序来排序输入数据,要排序的数据量比较大的时候,qsort() 会改为用快速排序算法来排序。

  • 对于小规模数据的排序,O(n2) 的排序算法并不一定比 O(nlogn) 排序算法执行的时间长。对于小数据量的排序,我们选择比较简单、不需要递归的插入排序算法。

  • Arrays.sort的源码使用了Timsort算法

    • 1 元素个数 < 32, 采用二分查找插入排序(Binary Sort)
      2 元素个数 >= 32, 采用归并排序,归并的核心是分区(Run)
      3 找连续升或降的序列作为分区,分区最终被调整为升序后压入栈
      4 如果分区长度太小,通过二分插入排序扩充分区长度到分区最小阙值
      5 每次压入栈,都要检查栈内已存在的分区是否满足合并条件,满足则进行合并
      6 最终栈内的分区被全部合并,得到一个排序好的数组

      Timsort的合并算法非常巧妙:

      1 找出左分区最后一个元素(最大)及在右分区的位置
      2 找出右分区第一个元素(最小)及在左分区的位置
      3 仅对这两个位置之间的元素进行合并,之外的元素本身就是有序的

你可能感兴趣的:(数据结构/算法,算法,数据结构,排序算法)