前言
本博客为个人复习时总结用,无商业目的,其大多数内容皆为博主整理所得,并非原创。侵删。
常用算法操作:hash,预处理
树状数组:
数字的二进制形式中,从右往左数第一个1出现的位置
lowbit(x)=(−x)&x
因为树状数组的特殊性质,我们只需要修改所有包含这个元素的节点就行了。
假设本次对e[x]进行了操作,那么下一次就对e[x+lowbit(x)]进行操作就行了。
void add(int x,int v)
{
while(x<=len)
{
e[x]+=v;
x+=lowbit(x);
}
}
答案加上x对应节点的值,然后将x减去它的lowbit,继续进行这样的操作,直到x小于等于0。
int query(int x)
{
int sum=0;
while(x>0)
{
sum+=e[x];
x-=lowbit(x);
}
return sum;
}
快排优化
三种快排:
方法(1):固定位置;
方法(2):随机选取基准;
方法(3):三数取中(median-of-three)
最佳的划分是将待排序的序列分成等长的子序列,
最佳的状态我们可以使用序列的中间的值,也就是第N/2个数。可是,这很难算出来,并且会明显减慢快速排序的速度。这样的中值的估计可以通过随机选取三个元素并用它们的中值作为枢纽元而得到。事实上,随机性并没有多大的帮助,因此一般的做法是使用左端、右端和中心位置上的三个元素的中值作为枢纽元
。显然使用三数中值分割法消除了预排序输入的不好情形,并且减少快排大约14%的比较次数。
四种优化方式:
- 优化1:当待排序序列的长度分割到一定大小后,使用插入排序。
- 优化2:在一次分割结束后,可以把与Key相等的元素聚在一起,继续下次分割时,不用再对与key相等元素分割。
具体过程:在处理过程中,会有两个步骤
第一步,在划分过程中,把与key相等元素放入数组的两端
第二步,划分结束后,把与key相等的元素移到枢轴周围 - 优化3:优化递归操作
- 优化4:使用并行或多线程处理子序列(略)
最差情况:有序时相当于冒泡排序n^2,
快排优化:随机选择基值。
各种排序算法时间复杂度
各种常用排序算法 | ||||||||
类别 | 排序方法 | 时间复杂度 | 空间复杂度 | 稳定性 | 复杂性 | 特点 | ||
最好 | 平均 | 最坏 | 辅助存储 |
| 简单 |
| ||
插入 排序 | 直接插入 | O(N) | O(N2) | O(N2) | O(1) | 稳定 | 简单 |
|
希尔排序 | O(N) | O(N1.3) | O(N2) | O(1) | 不稳定 | 复杂 |
| |
选择 排序 | 直接选择 | O(N) | O(N2) | O(N2) | O(1) | 不稳定 |
|
|
堆排序 | O(N*log2N) | O(N*log2N) | O(N*log2N) | O(1) | 不稳定 | 复杂 |
| |
交换 排序 | 冒泡排序 | O(N) | O(N2) | O(N2) | O(1) | 稳定 | 简单 | 1、冒泡排序是一种用时间换空间的排序方法,n小时好 |
注:
- 归并排序每次递归都要用到一个辅助表,长度与待排序的表长度相同,虽然递归次数是O(log2n),
但每次递归都会释放掉所占的辅助空间
, - 快速排序空间复杂度只是在通常情况下才为O(log2n),如果是最坏情况的话,很显然就要O(n)的空间了。当然,可以通过随机化选择pivot来将空间复杂度降低到O(log2n)。
快排与归并选取问题:
不可描述的常量导致使用快排而不是归排。
算法的每一步实际上都需要一个固定时间量,被称为常量。我们平时考虑时间复杂度的时候并不考虑常量的影响,但有时候常量的影响不可忽略,比如在这个问题上。但是大多数时候考虑复杂度的时候,可能还是不需要考虑常量的影响的,嗯,我觉得是……由于算法的每一步都有一个常量,而快排的常量比归排小,因此虽然两者的复杂度相同,但是快排更快一些。
那第二个问题就来了,归排的复杂度一直都是O(nlog n),而快排的平均复杂度才是O(nlog n),最坏情况下快排的复杂度可以达到O(n^2),为什不怕快排的时候是最坏的情况呢?主要是因为绝大多数情况下,快排遇到的都是平均情况,也就是最佳情况,只有极个别的时候会是最坏情况,因此往往不考虑这种糟糕的情况。
建堆
- 我们现在有一个长度为n的数组a,里边的元素都没有顺序,但是每个元素都实现了内部比较器。
- 然后我们新建一个完全二叉树b,按数组下标0到n-1的顺序依次放入完全二叉树,也就是说现在b[0]=a[0],b[1]=a[1]…。
- 下一步我们需要找到最后一个非终端节点(最简单的找法就是从最后一个节点b[n-1],然后b[n-2]…依次往前找,直到找到一个节点,它有左节点,或者左右节点都有,这个节点就是最后一个非终端节点,也叫最后一个父节点)。
- 找到了这个父节点之后,我们来看他的左右节点,我们把左右节点进行比较,找出最大的那个节点,然后将这个最大节点(左或右节点)和父节点进行比较,如果最大节点大于父节点,那就交换,不然就跳过
- 从最后一个父节点,到倒数第二个父节点,再依次往上,直到根节点(也就是第一个父节点),对每一个父节点都执行第④步的比较操作。
- 注意!注意!注意!这一步和第五步是同步发生,它不是最后一步,也就是说一旦第五步发生了交换操作(左右节点中的一个和父节点交换了),这一步就要开始执行,这一步的名字叫做递归调整,当发生了交换操作时,我们把交换操作中的子节点(左或右节点)作为根节点c[0],然后找出这个根节点所代表的子树c,从第一个父节点c[0](也就是根节点),第二个父节点c[1],再依次往下,直到最后一个父节点,对每一个父节点都执行第④步的比较操作。
Dijkstra 算法的过程总结:
- 第一步:从起始顶点开始
- 第二步:对当前顶点进行标识
- 第三步:对当前顶点的所有相邻顶点依次进行松弛操作
- 第四步:更新列表
- 第五步:从列表的未标识顶点中找到当前最短距离最小的顶点,作为新的当前顶点
- 第六步:重复第二步至第五步,直到列表中不存在未标识顶点
汉诺塔问题:
=2时:
A->B递归
A->C输出
C->A递归
等于1时A->C
SSL与MD5
ssl:
SSL利用非对称密钥算法加密密钥的方法实现密钥交换,保证第三方无法获取该密钥。
https://www.cnblogs.com/bhlsheji/p/4586597.html
基于HTTP协议,通过SSL或TLS提供加密处理数据、验证对方身份以及数据完整性保护
md5:
-
数据填充
对消息进行数据填充,使消息的长度对512取模得448,设消息长度为X,即满足X mod 512=448。根据此公式得出需要填充的数据长度。
填充方法:在消息后面进行填充,填充第一位为1,其余为0。 -
添加消息长度
在第一步结果之后再填充上原消息的长度,可用来进行的存储长度为64位。如果消息长度大于264,则只使用其低64位的值,即(消息长度 对 264取模)。
在此步骤进行完毕后,最终消息长度就是512的整数倍。 -
数据处理
准备需要用到的数据:
4个常数: A = 0x67452301, B = 0x0EFCDAB89, C = 0x98BADCFE, D = 0x10325476;
4个函数:F(X,Y,Z)=(X & Y) | ((~X) & Z); G(X,Y,Z)=(X & Z) | (Y & (~Z)); H(X,Y,Z)=X ^ Y ^ Z; I(X,Y,Z)=Y ^ (X | (~Z));
把消息分以512位为一分组进行处理,每一个分组进行4轮变换,以上面所说4个常数为起始变量进行计算,重新输出4个变量,以这4个变量再进行下一分组的运算,如果已经是最后一个分组,则这4个变量为最后的结果,即MD5值。