这是我参与「第三届青训营 -后端场」笔记创作活动的第三篇笔记
课程目录
1,为什么要学习数据结构与算法
引例
数据结构
数据结构是计算机存储、组织数据的方式,指相互之间存在一种或多种特定关系的数据元素的集合。
通常情况下,精心选择的数据结构可以带来更高的运行或者存储效率。数据结构往往同高效的检索算法和索引技术有关。
算法简单来说就是解决问题的步骤
数据结构,比如链表为啥插入、删除快,而查找慢,平衡的二叉树插入、删除、查找都快,这都是实现这些数据结构的算法所造成的。
算法的快慢要结合具体的场景。
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的位置。
算法分析:
空间复杂度: 因为直接插入排序并没有开第二个数组,它仅仅只是就着他自己的原来的数组进行排序,利用的存储空间是常数个空间,因而其空间复杂度为O ( 1 ) O(1)O(1),即原地工作。
时间复杂度: 在排序过程中,向有序区逐个的插入元素进行了n−1趟,每趟都分为两步:
- 和前一元素进行比较
- 将前一元素进行往后挪 其中比较的次数与待排序数组的初始状态有关,在最优情况下,表中元素已有序,此时每遍历一次,都不用移动元素,且只需比较一次,时间复杂度为O(n)。在最坏情况下,数组中元素与要排序顺序相反(在本博客中以初始时降序排列为反序),总的比较次数达到最大均为O()。平均情况下总的比较次数和移动次数约为,所以时间复杂度为O()。
稳定性:每次插入元素时,总是从后向前比较再移动,所以相同元素的相对位置不会发生变化,直接插入排序是一个稳定的排序算法。
适用性:直接插入排序适用于顺序存储和链式存储的线性表。
2.1.2 折半插入排序
排序思路: 思路同直接插入排序一致,但在进行元素比较时采用的是折半查找,在查找待插入位置时时间复杂度为O()。
2.1.3 希尔排序(shell sort)
排序思路: 先将待排序的元素分组,从初始表中每相隔距离d取一个元素,形如L[i,i+d,i+2d,i+3d,...,i+kd],首次可将n个元素划分成d个组(即从第1个元素到第d个元素直接的所有元素是每组的第一个元素,“头儿”),每次分别对这些组进行直接插入排序,当整个表中的元素已基本有序时,再对全体记录进行一次直接插入排序。希尔排序每趟并不产生有序区,在最后一趟排序结束前,所有元素并不一定归位了,但是在希尔排序每趟完成后数据越来越接近有序。
2.2 快速排序
算法思想 快速排序是通过多次比较和交换来实现排序,在一趟排序中把将要排序的数据分成两个独立的部分,对这两部分进行排序使得其中一部分所有数据比另一部分都要小,然后继续递归排序这两部分,最终实现所有数据有序。
大致步骤如下:
- 首先设置一个分界值也就是基准值又是也称为监视哨,通过该分界值将数据分割成两部分。
- 将大于或等于分界值的数据集中到右边,小于分界值的数据集中到左边。一趟排序过后,左边部分中各个数据元素都小于分界值,而右边部分中各数据元素都大于或等于分界值,且右边部分个数据元素皆大于左边所有数据元素。
- 然后,左边和右边的数据可以看成两组不同的部分,重复上述1和2步骤当左右两部分都有序时,整个数据就完成了排序。
最好情况
每次数据元素都能平均的分成两个部分。得到一个完全二叉树。如果有n个数据元素,那么数的深度为 + 1
时间复杂度为O(nlogn)
最坏情况
在最坏的情况下,这个数仅有右子树或左子树,比较次数为 (n-1)+(n-2) + (n-3) + … +1=n*(n-1)/2 ,因此时间复杂度为O(n^2),在待排序数据元素已经有序的情况下快速排序时间复杂度最高
空间复杂度为O(n) 快速排序是一种不稳定的排序算法,会改变数据元素的相对位置,也是内排序中平均效率最高的排序算法。
2.3 堆排序
堆排序的基本思想是: 1、将带排序的序列构造成一个大顶堆,根据大顶堆的性质,当前堆的根节点(堆顶)就是序列中最大的元素;2、将堆顶元素和最后一个元素交换,然后将剩下的节点重新构造成一个大顶堆;3、重复步骤2,如此反复,从第一次构建大顶堆开始,每一次构建,我们都能获得一个序列的最大值,然后把它放到大顶堆的尾部。最后,就得到一个有序的序列了。
复杂度分析
因为堆排序无关乎初始序列是否已经排序已经排序的状态,始终有两部分过程,构建初始的大顶堆的过程时间复杂度为O(n),交换及重建大顶堆的过程中,需要交换n-1次,重建大顶堆的过程根据完全二叉树的性质,[log2(n-1),log2(n-2)...1]逐步递减,近似为nlogn。所以它最好和最坏的情况时间复杂度都是O(nlogn),空间复杂度O(1)。
2.4 经典算法比较
在元素完全随机的场景中,有如下数据:
在有序的场景中:
3,从零开始打造pdqsort
3.1 pdqsort简介
pdqsort实质为一种混合排序算法,在不同情况下切换到不同的排序机制,该实现灵感来自C++和RUST的实现,是对C++标准库算法introsort的一种改进,其理想情况下的时间复杂度为 O(n),最坏情况下的时间复杂度为 O(n* logn),不需要额外的空间。
3.2 pdqsort-version1
3.2 pdqsort-version2
对version1进行修改
具体策略如下: