diff --git a/thu_dsa/chp11/kmp.md b/thu_dsa/chp11/kmp.md index 98ccb27..7b9a969 100644 --- a/thu_dsa/chp11/kmp.md +++ b/thu_dsa/chp11/kmp.md @@ -1,6 +1,20 @@ kmp conclusion ============== +## 知识脉络 + +这里主要讨论串匹配的多个有效算法。各个算法的历史与逻辑在对应的`md`文件里面已经说得非常清楚了,这里主要针对考题来给出总结,即给定一个模式串`P`,要求写出它的`next`数组,改进的`next`数组,`bc`表,`ss`表和`gs`表。 + +对于`next`数组,一种方案是把`makeNext`函数手动运行一遍,不过我觉得太慢了,不太喜欢。比较好的办法是,对于当前字符`P[j]`,`next[j]`即是它的前缀中最长的自匹配长度。不过需要注意的是,这里自匹配的前缀和后缀一定要是`P[0, j)`的真前缀和真后缀,否则`next[j] = j`也没有意义啊。此外还有一种验证的方法,即`next[j+1]`至多比`next[j]`大`1`。 + +构造改进的`next`数组,则应该建立在`next`数组的基础上进行。即对于当前字符`P[j]`,首先比较是否满足`P[j] == P[next[j]]`,如果不满足的话直接更新`P[j] = next[next[j]]`。 + +`bc`表的构造过于简单了,这里就不再赘述,按照定义来写就好。 + +`ss`表的构造也可以直接通过定义得到,为了得到`gs`表,我觉得跑一次`buildGS`几乎没有可能,所以还是用`gs`表的定义做吧。需要注意的是,对于`gs[m-1]`,此时`好后缀`的长度为零,`gs[m-1]`应该等于移动到第一个不等于`P[m-1]`的字符所需要移动的距离。 + +## 串匹配问题 + 字符串匹配问题是算法中的常见问题,即对于一个较长的文本串`T`,以及一个较短的模式串`P`,返回模式串`P`在文本串`T`中是否出现,或者首先出现的位置,或者所有出现的位置。实际上,在实际生活中,具有大量的字符匹配问题的应用场景,例如一般的编辑器软件,都具有的查找替换功能,还有像是`google`这种软件,本质上就是从整个因特网的文本数据中,去查找用户搜索的字符串。 以下主要讨论如何实现串匹配问题。 diff --git a/thu_dsa/chp12/quicksort.md b/thu_dsa/chp12/quicksort.md index 7048881..55d8f9a 100644 --- a/thu_dsa/chp12/quicksort.md +++ b/thu_dsa/chp12/quicksort.md @@ -1,6 +1,10 @@ QuickSort总结 ============ +## 关于轴点 + +在原始序列中,轴点未必存在。根据轴点的定义,轴点的必要条件是轴点必然是已经就位的点。所以,在有序序列中,所有元素皆为轴点。这样,快速排序的本质,其实就是将所有元素逐个转换成轴点的过程。 + ## QuickSort基本思路 + 分而治之的策略,类似于Mergesort diff --git a/thu_dsa/chp12/sort.md b/thu_dsa/chp12/sort.md index b37d9d1..50288f7 100644 --- a/thu_dsa/chp12/sort.md +++ b/thu_dsa/chp12/sort.md @@ -1,6 +1,19 @@ Conclusions on Sorting Algorithm ================================ -## 快速排序 -> 关于轴点 -+ 在原始序列中,轴点未必存在。根据轴点的定义,轴点的必要条件是轴点必然是已经就位的点。所以,在有序序列中,所有元素皆为轴点。这样,快速排序的本质,其实就是将所有元素逐个转换成轴点的过程。 +## 知识脉络总结 + +`快速排序`的思想是简单而深刻的,即`分而治之`的策略,需要注意它和`归并排序`在思想上的异同。`快速排序`的关键在于`partition`算法,本章给出了`partition`算法的三种实现,需要领会这三种不同的实现各自的特点是什么,是为了解决什么问题而出现的。需要指出的是,尽管在一般情况下`快速排序`运行的平均时间最短(这也是它被称作`快速排序`的原因),可是在最坏情况下`快速排序`仍然需要`O(n^2)`的时间,可以通过改造`轴点`的选择算法,在一定程度上减小最坏情况出现的概率。在平均情况下,`快速排序`仅需要`O(nlogn)`的时间复杂度,要会证明这个结论。 + +`希尔排序`的正确性和有穷性不言而喻,其时间复杂度主要取决于`增量序列`的选择,例如`希尔序列`就是一个比较糟糕的序列,需要能够领会为什么`希尔序列`的性能较差。为了分析`希尔排序`的时间复杂度,首先需要理解`邮资问题`和`g-有序`的序列经过`h-排序`后仍然保持`g-有序`的证明,在此基础上,即可针对不同的`增量序列`给出复杂度的证明。 + +本章的剩余部分主要讨论算法层面的问题,集中讨论了`k选取问题`,`中位数选取`问题,`众数选取`问题以及`归并向量的中位数`定位问题。 + +对于`k选取问题`,可以基于`堆`结构给出若干算法,这些算法在`k`很大或者很小时都具有较为优化的性能(`O(n)`),但是在`k = O(n)`时,算法的时间复杂度将退化为`O(nlogn)`。基于`快速排序`的思想,可以构造出`快速选择`(quickSelect)算法,它的平均时间复杂度为`O(n)`,证明方法和`快速排序`相仿,然而在最坏情况下,它的时间复杂度可以达到`O(n^2)`。最后教材上给出了一个最坏情况下仍然可以保持`O(n)`的`k选取`算法,它的基本思想是将整个序列划分成三个子序列,并且使得其中小于`轴点`的子序列(记作`L`)以及大于`轴点`的子序列(记作`G`)的规模都不小于`n / 4`,从而一次迭代后问题的规模至少可以缩减为原来的`3 / 4`;为此,需要合理选择`轴点`,具体的做法是首先将整个序列平均划分为若干很小的子序列,每个子序列的规模为`Q`,并且求出这些子序列的`中位数`,再递归地计算这些`中位数`组成的`中位数序列`的`中位数`,将结果作为`轴点`。`中位数选取`问题是`k选取`问题的一个特例,这里不再赘述。 + +对于`众数的选取`问题,关键在于明确`众数的定义`乃是序列中出现频数多于一半的元素,从而可以证明教材所给算法的正确性。 + +关于`归并向量的中位数`选择,首先可以采用蛮力策略,假想地对两个有序向量进行归并,该策略的时间复杂度为`O((m + n) / 2)`;若两个向量等长,则可以分别取两个向量的`中位数`以及`逆向中位数`进行比较,从而将问题的规模缩小一半;若允许向量不等长,可以对问题进行分类讨论: + ++ 若两个向量的规模差距巨大,则可以对更长的向量进行'裁剪',截除等长的两翼,从而问题归入到下一种情况。 ++ 两个向量规模相当,则以较短的向量的中位数`mid1`为基础,分别构造出较长向量的前后两个中间数`mid2a, mid2b`,它们分别满足到向量的首尾的长度是`mid1`,通过将两者与`mid1`进行比较,从而可以截除接近一半的数据,问题的规模从而减少大约一半。这里不直接选择较长向量的中位数进行比较的原因,乃是需要保证两端截除的长度要相等,只有这样新问题的中位数才和原先的问题的中位数一致。