《数据结构与算法》课堂笔记 | 青训营笔记

150 阅读7分钟

这是我参与「第三届青训营 -后端场」笔记创作活动的第三篇笔记

课程目录

01.png

1,为什么要学习数据结构与算法

引例

02.png

数据结构

数据结构是计算机存储、组织数据的方式,指相互之间存在一种或多种特定关系的数据元素的集合。

通常情况下,精心选择的数据结构可以带来更高的运行或者存储效率。数据结构往往同高效的检索算法和索引技术有关。

算法简单来说就是解决问题的步骤

数据结构,比如链表为啥插入、删除快,而查找慢,平衡的二叉树插入、删除、查找都快,这都是实现这些数据结构的算法所造成的。

03.png

算法的快慢要结合具体的场景。

04.png

2,经典排序算法

2.1 插入排序算法

插入排序分为直接插入排序折半插入排序希尔排序(shell sort),后两种是在直接插入排序的改进上而来。

2.1.1.直接插入排序

排序思路: 假设待排序的元素存放在数组A[1..n]中,在排序过程的某一时刻,A被划分为两个子区间A[1..mid]和A[mid+1..n],其中前一个子区间为排好序的有序区,而后一个子区间为未排序的子区间,暂称作无序区。该排序算法顾名思义,取出无序区的第一个元素A [mid+1]插入到有序区A[1..mid]中适当的位置,A[1..mid+1] 变成新的有序区(假设按照升序排序)。

对于第i趟排序,要将无序区的第一个元素A[mid+1] (设A[mid+1]的值为a) 插入到有序区的适当位置,此时便将a不断的与有序区的元素进行比较,当找到第一个比a小的元素时(假设是A[m] (1≤m<mid+1),在该元素的后面将a插入,当a是有序区中最小的元素时,将a放在有序区的第一个位置,此时数组中下标m+1到mid的所有元素均要往后移动一格,就像是腾出一个位置来让给a,然后把a放到数组中m的位置。

05.png

06.png

算法分析:

空间复杂度: 因为直接插入排序并没有开第二个数组,它仅仅只是就着他自己的原来的数组进行排序,利用的存储空间是常数个空间,因而其空间复杂度为O ( 1 ) O(1)O(1),即原地工作。

时间复杂度: 在排序过程中,向有序区逐个的插入元素进行了n−1趟,每趟都分为两步:

  • 和前一元素进行比较
  • 将前一元素进行往后挪 其中比较的次数与待排序数组的初始状态有关,在最优情况下,表中元素已有序,此时每遍历一次,都不用移动元素,且只需比较一次,时间复杂度为O(n)。在最坏情况下,数组中元素与要排序顺序相反(在本博客中以初始时降序排列为反序),总的比较次数达到最大均为O(n2n^2)。平均情况下总的比较次数和移动次数约为n24\frac{n^2}{4},所以时间复杂度为O(n2n^2)。

稳定性:每次插入元素时,总是从后向前比较再移动,所以相同元素的相对位置不会发生变化,直接插入排序是一个稳定的排序算法。

适用性:直接插入排序适用于顺序存储和链式存储的线性表。

2.1.2 折半插入排序

排序思路: 思路同直接插入排序一致,但在进行元素比较时采用的是折半查找,在查找待插入位置时时间复杂度为O(nlog2nnlog_2n)。

2.1.3 希尔排序(shell sort)

排序思路: 先将待排序的元素分组,从初始表中每相隔距离d取一个元素,形如L[i,i+d,i+2d,i+3d,...,i+kd],首次可将n个元素划分成d个组(即从第1个元素到第d个元素直接的所有元素是每组的第一个元素,“头儿”),每次分别对这些组进行直接插入排序,当整个表中的元素已基本有序时,再对全体记录进行一次直接插入排序。希尔排序每趟并不产生有序区,在最后一趟排序结束前,所有元素并不一定归位了,但是在希尔排序每趟完成后数据越来越接近有序。

2.2 快速排序

算法思想 快速排序是通过多次比较和交换来实现排序,在一趟排序中把将要排序的数据分成两个独立的部分,对这两部分进行排序使得其中一部分所有数据比另一部分都要小,然后继续递归排序这两部分,最终实现所有数据有序。

大致步骤如下:

  1. 首先设置一个分界值也就是基准值又是也称为监视哨,通过该分界值将数据分割成两部分。
  2. 将大于或等于分界值的数据集中到右边,小于分界值的数据集中到左边。一趟排序过后,左边部分中各个数据元素都小于分界值,而右边部分中各数据元素都大于或等于分界值,且右边部分个数据元素皆大于左边所有数据元素。
  3. 然后,左边和右边的数据可以看成两组不同的部分,重复上述1和2步骤当左右两部分都有序时,整个数据就完成了排序。

07.png

最好情况

每次数据元素都能平均的分成两个部分。得到一个完全二叉树。如果有n个数据元素,那么数的深度为log2n\lfloor log2^n \rfloor + 1

时间复杂度为O(nlogn)

最坏情况

在最坏的情况下,这个数仅有右子树或左子树,比较次数为 (n-1)+(n-2) + (n-3) + … +1=n*(n-1)/2 ,因此时间复杂度为O(n^2),在待排序数据元素已经有序的情况下快速排序时间复杂度最高

空间复杂度为O(n) 快速排序是一种不稳定的排序算法,会改变数据元素的相对位置,也是内排序中平均效率最高的排序算法。

08.png

2.3 堆排序

堆排序的基本思想是: 1、将带排序的序列构造成一个大顶堆,根据大顶堆的性质,当前堆的根节点(堆顶)就是序列中最大的元素;2、将堆顶元素和最后一个元素交换,然后将剩下的节点重新构造成一个大顶堆;3、重复步骤2,如此反复,从第一次构建大顶堆开始,每一次构建,我们都能获得一个序列的最大值,然后把它放到大顶堆的尾部。最后,就得到一个有序的序列了。

复杂度分析

因为堆排序无关乎初始序列是否已经排序已经排序的状态,始终有两部分过程,构建初始的大顶堆的过程时间复杂度为O(n),交换及重建大顶堆的过程中,需要交换n-1次,重建大顶堆的过程根据完全二叉树的性质,[log2(n-1),log2(n-2)...1]逐步递减,近似为nlogn。所以它最好和最坏的情况时间复杂度都是O(nlogn),空间复杂度O(1)。

2.4 经典算法比较

11.png

12.png

在元素完全随机的场景中,有如下数据:

13.png

在有序的场景中:

14.png

15.png

3,从零开始打造pdqsort

3.1 pdqsort简介

16.png

pdqsort实质为一种混合排序算法,在不同情况下切换到不同的排序机制,该实现灵感来自C++RUST的实现,是对C++标准库算法introsort的一种改进,其理想情况下的时间复杂度为 O(n),最坏情况下的时间复杂度为 O(n* logn),不需要额外的空间。

3.2 pdqsort-version1

17.png

18.png

19.png

3.2 pdqsort-version2

对version1进行修改

20.png

具体策略如下:

21.png

22.png

23.png

24.png

3.3 pdqsort-final version

25.png

26.png

27.png

28.png

总结

29.png