说在前面
个人读书笔记
起泡排序
在由一组整数组成的序列中,满足的相邻元素称作顺序的,否则是逆序的。不难看出,有序序列中每一对相邻元素都是顺序的,亦即,对任意都有;反之,所有相邻元素均顺序的序列,也必然整体有序。
由有序序列的上述特征,我们可以通过不断改善局部的有序性实现整体的有序:从前向后依次检查每一对相邻元素,一旦发现逆序即交换二者的位置。对于长度为的序列,共需做次比较和不超过次交换,这一过程称作一趟扫描交换。
可见,经过这样的一趟扫描,序列未必达到整体有序。果真如此,则可对该序列再做一趟扫描交换。事实上,很有可能需
要反复进行多次扫描交换,直到在序列中不再含有任何逆序的相邻元素。多数的这类交换操作,都会使得越小(大)的元素朝上(下)方移动,直至它们抵达各自应处的位置。
排序过程中,所有元素朝各自最终位置亦步亦趋的移动过程,犹如气泡在水中的上下沉浮,起泡排序(bubblesort)算法也因此得名。
经过趟扫描交换之后,最大的前个元素必然就位;经过k趟扫描交换之后,待求解问题的有效规模将缩减至
时间复杂度
随着输入规模的扩大,算法的执行时间将如何增长?执行时间的这一变化趋势可表示为输入规模的一个函数,称作该算法的时间复杂度(time complexity)。具体地,特定算法处理规模为n的问题所需的时间可记作。
大记号
同样地出于保守的估计,我们首先关注的渐进上界。为此可引入所谓“大记号”(big-O notation)。具体地,若存在正的常数和函数,使得对任何,都有。则可认为在足够大之后,给出了增长速度的一个渐进上界。此时,记之为:
由这一定义,可导出大记号的以下性质:
- 对于任一常数,有
- 对于任意常数,有
前一性质意味着,在大记号的意义下,函数各项正的常系数可以忽略并等同于1。后一性质则意味着,多项式中的低次项均可忽略,只需保留最高次项。可以看出,大O记号的这些性质的确体现了对函数总体渐进增长趋势的关注和刻画。
以大记号形式表示的时间复杂度,实质上是对算法执行时间的一种保守估计,称作最坏实例或最坏情况。
起泡排序的时间复杂度
bubblesort1A()算法由内、外两层循环组成。内循环从前向后,依次比较各对相邻元素,如有必要则将其交换。故在每一轮内循环中,需要扫描和比较n - 1对元素,至多需要交换n - 1对元素。元素的比较和交换,都属于基本操作,故每一轮内循环至多需要执行2(n - 1)次基本操作。另外,外循环至多执行n - 1轮。因此,总共需要执行的基本操作不会超过次。若以此来度量该算法的时间复杂度,则有
根据大记号的性质,可进一步简化和整理为:
复杂度分析
常数时间复杂度
一般地,仅含一次或常数次基本操作的算法均属此类。此类算法通常不含循环、分支、子程序调用等。
对数时间复杂度
考查如下问题:对于任意非负整数,统计其二进制展开中,数位1的总数。
根据右移运算的性质,每右移一位,n都至少缩减一半(n是输入的十进制整数)。也就是说,至多经过次循环,n必然缩减至0,从而算法终止。实际上从另一角度来看,恰为n二进制展开的总位数,每次循环都将其右移一位,总的循环次数自然也应是。
无论是该循环体之前、之内还是之后,均只涉及常数次(逻辑判断、位与运算、加法、右移等)基本操作。因此,countOnes()算法的执行时间主要由循环的次数决定,亦即:
由大记号定义,在用函数界定渐进复杂度时,常底数的具体取值无所谓,故通常不予专门标出而笼统地记作。
线性时间复杂度
对于输入的每一单元,此类算法平均消耗常数时间。就大多数问题而言,在对输入的每一单元均至少访问一次之前,不可能得出解答。以数组求和为例,在尚未得知每一元素的具体数值之前,绝不可能确定其总和。
递归
以数组求和问题为例。易见,若n = 0则总和必为0,这也是最终的平凡情况;否则一般地,总和可理解为前n - 1个整数之和,再加上末元素。
按这一思路,可基于线性递归模式,设计出另一sum()算法如下图所示。
由此实例,可以看出保证递归算法有穷性的基本技巧:
首先判断并处理n = 0之类的平凡情况,以免因无限递归而导致系统溢出。这类平凡情况统称“递归基”(base case of recursion)。平凡情况可能有多种,但至少要有一种(比如此处),且迟早必然会出现。
线性递归的模式,往往对应于所谓减而治之(decrease-and-conquer)的算法策略:
递归每深入一层,待求解问题的规模都缩减一个常数,直至最终蜕化为平凡的小(简单)问题。
按照减而治之策略,此处随着递归的深入,调用参数将单调地线性递减。因此无论最初输入的n有多大,递归调用的总次数都是有限的,故算法的执行迟早会终止,即满足有穷性。当抵达递归基时,算法将执行非递归的计算(这里是返回0)。
为保证有穷性,递归算法都必须设置递归基,且确保总能执行到。为此,针对每一类可能出现的平凡情况,都需设置对应的递归基,故同一算法的递归基可能(显式或隐式地)不止一个。
递归算法所消耗的空间量主要取决于递归深度
递归要保证子问题与原问题在接口形式上的一致
结语
如果您有修改意见或问题,欢迎留言或者通过邮箱和我联系。
手打很辛苦,如果我的文章对您有帮助,转载请注明出处。