面试用算法复杂度总结

面试时被问到了很多算法复杂度的东西,现在做个总结。主要从树结构和排序的角度分析这些常用算法的时间和空间复杂度。

一、复杂度简介

1、空间复杂度

空间复杂度有很多影响因素,如指令空间、数据空间(动态数组、动态类实例、常量和简单变量的存储空间)、环境栈空间(常在递归时使用)。一般说的空间复杂度是指所需要的可变空间,也就是动态数据或栈所用所需的空间。

2、时间复杂度

主要关注运行时间,常用的方法是:找出一个或多个关键步骤所需要的执行时间,并确定程序的执行步数。

二、计算方式

因为计算过于复杂,所以通常分析最好、最坏和平均情况。并常用f = O(g)来表示算法复杂度,其表示为f渐进的小于等于g,即对于所有的n大于n0,有f(n) <= c*g(n)。g一般用的常数1、对数log、线性n、nlog、n^2、指数a^n、阶乘n!。一般只要分析算法复杂度是其中的哪一种即可。

三、算法复杂度分析

1、折半查找(二分查找)

二分查找要求元素必须是有序的,其算法复杂度为O(nlogn)。

设序列长度为n,因为每次查找过程都把当前序列分成两半,减少搜索的范围,循环最多执行O(logn)次,每次O(1)(上面两个的符号稍微有点错误应该是theta),所以最坏情况下需要比较log(n+1)次,也就是期望的时间复杂度为O(logn)。这个其实可以也看成是一种分治策略只不过少了合并过程。

利用T(n) = 2*T(n/2) + O(n)可以得到O(logn)的计算结果。

2、常用排序算法

下面是网上的一个算法复杂度的图,不过这里注意归并排序的空间复杂度应该是O(n)。这里的稳定性是指,如果有两个相同的值a和b,在原数据中的相对位置和排序后的相对位置不变,则称其为稳定排序。

《面试用算法复杂度总结》

从简单到复杂、从非分治到分治依次分析其复杂度。

(1)直接插入排序和冒泡排序

插入排序就是每次循环中把数据插入到前面已排序数据的适当位置。而冒泡排序就是每次冒泡过程中,找出最大值,并将其安排到最后一位。

这两个的共同点就是最坏时需要执行O(n^2)次循环(数组完全逆序,所有内循环都要执行),而每次循环都是一次数据交换或数据插入的过程,其复杂度为O(1),所以最坏情况下算法复杂度为O(n^2)。

最好情况是当数组有序,直接插入排序只用比较1次(也就是内循环只用一次),同理冒泡排序(但是冒泡需要加入一个flag,在第一次内循环全部执行证明有序后,外循环只执行一次)而这两种方法都不需要额外的空间。

(2)直接选择排序

思想是每次选择一个最小的值和第一个位置交换。这个缺点是每次都要遍历后面未排序部分找到其最小值。固定的需要O(n^2)次比较,没有最好和最坏情况。平均情况下,插入排序的速度比直接选择排序快。

(3)快速排序

这个被问的最多了,从这里可以利用一些分治的方法求解时间复杂度了。快排思想是每次希望能把一个序列分成两部分,一部分比a大,一部分比a小。实现方式是通过不断地交换数据并递归,实现这一目标。

空间复杂度:这里由于是递归实现的,则需要的栈空间为O(n),如果用栈来模拟递归,可以达到O(nlogn)的空间复杂度。

时间复杂度:快排是个很厉害的算法,其最坏情况为O(n),而最好情况和平均情况都为O(nlogn)。快排的最坏情况发生在选择的划分不平衡,也就是小的部分或大的部分中为空,即T(n) = T(n-1) + O(n),当输入数组完全有序的时候就是这样。最好情况是当左右数据段中元素数目大致相同时。这两种情况都不难分析,关键是为什么平均状况下也是O(nlogn)。

假设划分比例为9:1,在计算的时候T(n) = T(9n/10) + T(n/10) + cn。这个可以用递归树的方式进行求解,递归树的深度为log10/9(n) = O(logn),而每一层的时间代价都是O(n),所以时间复杂度为O(nlogn)。这只是一个大致的分析,对于每次不同比例的分支其实是类似的。

(4)归并排序

这个也是常考的,因为这是比较典型的稳定排序算法,且在最坏的情况下,时间复杂度也是O(nlogn)的。方法思想是对半分,递归的对每一段进行归并排序并合并起来。在时间上比快排好一点,但是缺点是需要临时数组。

(5)堆排序

这个也是O(nlogn)的,是基于优先级队列设计的排序算法,分为大根堆和小根堆,排序过程有建堆和调整堆两个过程。一个常用的场景是找出k个最大或最小值。

3、关于树的算法复杂度

这个在面试的时候被问蒙了,于是总结一下。

(1)普通的二叉查找树

二叉树最常用的操作不过是删除、查入、查找,其时间复杂度主要是与树的高度密切相关O(h)。对于一个普通的二叉查找树,其最差的算法复杂度为O(n)也就是单边树,最好的情况为O(logn)也就是平衡树。为了能够利用二叉查找树,一般需要建树也就是执行插入操作。

(2)AVL树

为了能够实现删加树节点并不会造成平衡树的结构改变,也就是树的高度一直保持为O(logn),引出了平衡树的概念(要求结点的左右子树高度差小于1),其中AVL树就是一种。AVL树和红黑树都是利用“旋转”来保持平衡,且实际的运行时间相似,但是AVL树每次删除最多需要O(logn)次旋转(插入也是可以在O(1)时间内解决),而红黑树只需要O(1)次旋转。

这里对AVL树的大致过程进行解释:

首先,AVL树的高度为最坏情况为一个斐波那契数列,高度近似为O(logn)。接着引入一个平衡因子,用于衡量左右子树的高度差,由定义知可能取值为-1,0,1。

然后从删除和插入两个过程描述一下。出现不平衡主要是平衡因子的值改变出现-2或2。对于插入过程只有从根节点到插入节点路径上的节点平衡因子会发生改变。设X是最后一个失衡点(用回溯进行判断),若平衡因子出现2一定是左子树插入了值,如果是-2一定是右节点插入了值。细分的话这种不平衡可以分为LL(新插入节点在失衡点左子树),LR(新插入点在失衡点右子树)、RR、RL。其中LL和RR用一次旋转就可以解决,而LR和RL则需要两次旋转,如LR需要左旋转加右旋转对于删除过程,因为不但要考虑最近的不平衡节点,还要不断向上回溯到根节点来调整所有节点的平衡性,旋转的次数会达到O(logn)量级。如果删除发生在左子树造成的不平衡称为L型,反之为R型。旋转的方式其实与插入是一样的也是可以看成是LL(左左失衡)、LR(左右失衡)、RR、RL。

关于AVL更细致的讲解:http://www.cnblogs.com/skywang12345/p/3576969.html

(3)红黑树

红黑树比较麻烦,但是在面试的时候被问到了,一脸懵逼,还回答错了。

红黑树改变了AVL树删除过程中多次旋转的问题。红黑树加入了红黑特性:(1)根节点是黑的,(2)外部节点是黑的(即空节点表示的外部节点),(3)相邻接点不能同时为红,(4)从根节点到外部节点的路径所有路径上包含的黑色节点数量相同。其中(4)保证了没有一条路径会比其他路径长出2倍,因而红黑树是接近平衡的树。红黑树最大高度为2log(n+1)比AVL要深,所以内部节点数相同时在最坏情况下,红黑树比较高,速度略慢一点点,但是也是O(logn)量级的。红黑树节点定义的时候需要加入一位表示其颜色,也加入一个指向父节点的指针。

现在从红黑树的插入操作开始:第一步,按照红色来插入节点,这是因为这时候违背的红黑树的特性最少,只是可能违背两个红色节点相连的问题。第二步,如果插入后违背了这一特性,则树的平衡被破坏。不平衡性的检查通过检查节点u和其父节点pu和其祖父节点gu确定,分为:LLr,LLb, LRr, LRb、RRb、RRr、RLb、RLr八中细分的情况。其中XYr只需要改变颜色就能处理,而XYb类型只能用旋转处理。修复情况1、父节点和叔叔节点都是红色XYr,解决方法是:将父节点和叔叔节点涂黑,如果祖父节点不是根节点就把祖父节点涂红,这时可能会引起进一步的变化,也可能完全稳定。修复情况2、父节点是红色,叔叔是黑色,且节点是父节点的右子节点XRb,解决方法:节点左旋,变成后面XLb的形式。修复情况3、父节点红色,叔叔黑色,当前节点是父节点左子节点XLb,解决方法:父节点变黑色,祖父变红色,以祖父为支点右旋。总之就是全部转换成直接变色解决或者XLb形式的变色解决问题。

红黑树的删除节点稍微复杂一点点。删除的时候是找到左子树中的最大值代替待当前节点的值,然后删除此节点。可以判断出:真正删除的节点只有一个红色孩子或是没有孩子(否则不能满足性质要求)、如果真正删除的节点是红色节点那么她必定是叶节点。所以可以分为以下几种情况:1、节点是红色节点(即叶节点)直接删除即可。2、节点是黑色节点,其子节点为红色节点(叶节点),直接用子节点值代替当前节点然后删除子节点即可(黑红)。3、若当前节点为黑色,代替他的子节点也是黑色(包括为外部节点的情况)并删除子节点后,则相对于其兄弟节点的子树少了一个黑节点,这样就需要调整,这时候就可以分为另外的4种情况(这个算法导论上用黑黑和红黑来描述比较有用, http://blog.csdn.net/v_JULY_v/article/details/6105630):1、兄弟节点为红色,则侄子节点全黑,父节点也是黑色:改变父节点和兄弟节点颜色,以兄弟为轴左旋转为后面的情况(兄弟节点为黑色)。2、兄弟节点为黑色,两个子节点也为黑色:也就是说要在兄弟节点中去掉黑色变为红色,将父节点当成当前节点(颜色为红黑或黑黑,如果为红黑就按上面说的停止循环变为黑色,如果是黑黑就继续循环)重新计算。3、兄弟节点为黑色,兄弟节点的左孩子是红色,右孩子是黑色:可以交换兄弟节点和左子节点的颜色,然后右旋(这样不改变任何性质)变为情况4。4、兄弟节点为黑色,且右节点是红色的。这时候吧兄弟节点变成父节点颜色,父节点变黑色,兄弟结点的右子节点变成黑色,然后以父节点为支点左旋,结束循环。

这个还是直接看代码比较直观,总的来说就是分为删除代码和调整两个部分,调整主要是针对删除节点为黑色节点进行的,分为上面的四种情况。


    原文作者:约瑟夫环问题
    原文地址: https://blog.csdn.net/woaidapaopao/article/details/72082640
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞