今天读了《算法导论》,趁晚上有些许空闲时间做一下读书笔记。
插入排序
排序问题是算法题中非常重要又基础的部分,完成排序有许多算法,冒泡排序,桶排序,插入排序,快速排序等等。以下我写一些我对插入排序的一些理解和提供伪代码。
插入排序思想
对于少量元素的排序,插入排序是一个有效的算法,插入排序的工作方式,像许多人排序一手扑克牌。开始时,我们的左手为空并且桌子上的牌面向下。然后,我们每次从桌子上拿走一张牌并将它插入左手中正确的位置。为了找到一张牌的正确位置,我们从右到左将它与已在手中的每张牌进行比较,拿在左手上的牌总是排序好的,原来这些牌时桌子上牌堆中顶部的牌。
插入排序伪代码
INSERTION-SORT(A)
1 for j = 2 to A.length
2 key = A[j]
3 //Insert A[j] into the sorted sequence A[1...j-1].
4 i = j -1
5 while i > 0 and A[i] > key
6 A[i+1] = A[j]
7 i = i -1
8 A[i + 1] = key
在上面的伪代码中,我们并没有完全依照“从桌子上拿牌放到左手上进行排序”的步骤,而是“所有未排序的牌都在手上”。其中,A表示输入数组,在初始时,A中的所有元素均未排序。A.length表示A的长度,key表示本次循环中选中需要插入排序的元素。
在第2行代码,我们将 A[j] 中的元素放入到 key 中。因为 A 数组是一个变化的数组,在不断插入排序的过程中,数组的每一个元素都有可能发生改变,一旦我们没有存放当前元素,在下面的步骤中,我们移动元素时可能就会将当前元素覆盖掉,我们就丢失了当前元素是什么了。
在第4行代码,我们将 i = j - 1,目的是让当前需要排序的元素与之前已经排序好的元素进行对比,即 key (= A[j] )与 A[i] 进行比较大小,然后按照升序降序的需要来完成特定的插入操作。
在5~7行代码,我们先来看 while 循环的条件,while 循环中有 2 个条件,一个是 i > 0 ,另一个是 A[i] > key ,并且为与关系。A[i]>key 很好理解,因为我们是升序,所以当前一个元素比我当前元素要大,那么我就要把我的元素插入到前面去,那为什么需要 i > 0呢?因为我们是将当前元素与前面元素进行对比,但我们的循环并不能一直往前走,当一直比较到第一个元素(即目前已排序好的序列中的最小元素)时,就无法再往前比较了。A[i+1] = A[j] ,当确认我的当前元素比前一个元素小时,那么就需要把前一个元素往后移,这样我的当前元素才能插入。 i = i -1,下一次循环对比更前面一个元素。
在第8行代码,A[i+1] = key 在空出来的位置中放入当前元素。
如果还是不太理解的话,可以看一下下面这副图片,用图片的方式形式化地表现了插入排序的过程。
分析算法
我们在考察一个算法的优劣程度时,有一个重要的度量——计算时间。我们往往集中于只求最坏情况运行时间,即对规模为n的任何输入,算法的最长运行时间。
在上面的插入排序算法的伪代码中,我们很容易看出,算法包含了两个循环,第一个 for 循环一共需要循环 n-1 次(循环从 j=2 开始),但这个 for 循环中还包含了一个 while 循环,当输入情况是最坏情况(即反序)时,我们每次 while 都需要将当前位置的元素调到最前面。此时,我们可以列出最坏情况下的运行时间:
显然,它是一个 n 的二次函数,在这里的系数c是指计算机在做每一步指令时所需要花费的时间,我们一般把它看成一个常数。
增长量级
我们很多时候在分析算法的时间复杂度时,一般只分析最坏情况下的运行时间,除此之外,我们一般指用一种更简化的抽象,即我们真正感兴趣的运行时间的增长率或增长量级。所以我们只考虑公式中最重要的项,在上述公式中,最重要的项就是指数最大的项,因为当 n 十分巨大时,相比于指数最大的项,其它项对于时间消耗的影响就没有那么大了。其中,该项的系数对时间消耗的影响也很小,也可以省略。所以,我们记插入排序具有最坏运行时间