COMP2119A 问题集
与 OI 中间接接触到的各种 DS&A 知识相比,这门课的优势在于比较正统,严谨的理论分析;因此还是有必要记录一下的。这篇笔记将采用 problem-based 的形式,比较随性的收集一些 insights。
This article is a self-administered course note.
It will NOT cover any exam or assignment related content.
Recursion Problem
关于 Recursion Problem 有大概两种题型:
- 根据 recurrence equation 求 closed-form。
- 同时给出 recurrence equation 与 closed-form,证明它们是对应的。
第一种:比较容易的高中数列题。一般来说有两种做法:
- 根据 recurrence equation,不断将
拆成 ,寻找一系列表达式之间的规律。 - 从 base case
开始向 累加,累加的值可用等差/等比数列求和简化。
Ex1. perm(A, n, n)
is a function outputting all
permutations of array swap()
function get called. Prove that for
1 | void perm(int* A, int k, int n) { |
Ex2. Let
第二种:使用 mathematical induction (M.I.) 进行证明。
- Base case:证明在
的最小值 处成立。 - Induction hypothesis:列出归纳假设,即对于
with 假设成立,则对于 同样成立。(一般来说直接归纳证明 成立) - Induction step:用等号连接
的两种表示,证明 。
Complexity Analysis
is bounded by : s.t. . 's time complexity is . 's order of growth is . , is a constant.
在比较两算法 execution time
, 在渐进意义下比 增长的更快。 for some constant , 与 的渐进增长速度一致。 , 渐进意义下比 增长的更快。
除
notations | description |
---|---|
Little-Oh |
|
Big-Oh |
|
Big-Theta |
|
Big-Omega |
|
Little-Omega |
关于 tractable / computationally hard problems:
- tractable: 存在 worst-case polynomial-time 的算法,i.e.,
for some constant . - computationally hard: worst-case complexity is
for some .
有关时间复杂度分析的几种题型:
- 比较一系列 total execution time 之间的渐进增长速度。
- 分析 recursive function 的时间复杂度。
第一种:相除后取极限进行比较。
- Ex1. 比较
与 . (Ans ) - Ex2. 比较
与 . (Ans , ) - Ex3. (midterm)
第二种:使用 Master Theorem 进行分析。关于 Master Theorem 见 貴方が私の Master Theorem か。
Amortized/Average Analysis
以 hash table 为例,讲下如何进行时间复杂度的均摊分析。
若 hash table 的容量为
- chaining (open hashing): # of trials
. - probing (open addressing): # of trials
.
可以发现,对于 open addressing 实现的哈希表,我们需要控制
- 当 # of elements
时,单次插入/查找的平均时间复杂度为 。 - 当 # of elements reach
,重建一个容量为 的哈希表,时间复杂度为 。
在所有插入操作中,只有极小部分会 trigger
哈希表的重建:我们考虑将代价较大的哈希表的重建操作均摊到所有插入操作的代价中。设插入一次的时间代价为
为了方便讨论,我们规定从
目前
,再插入 次就将触发哈希表重建;因此这 次操作将承担重建的代价。重建哈希表所需的时间代价为
.求出被均摊分配的时间代价
.
因此单次插入的均摊时间代价为
Insight: Building & Egg Problem
站在复杂度分析的角度,我们定义
base case:
时, . 时, .
1 | int T(int n, int k) { |
其中,每个 optimal_a
就是在该情况下鸡蛋尝试的 optimal position。
一般来说,根据不同的情况,我们采取不同的策略:
:别无选择,只能采用 linear search。 。 :由于有不止一个鸡蛋,可以采取 jump search 的策略;而其中最优的是 square-root search,即我们熟悉的分块策略。 。 :此时鸡蛋的数量支持我们进行 binary search。 。
以上的程序也能显示这一点。(issue: 并没有!准备去问一下 Hubert)
Radix Sort and Counting Sort
题目来自 2019 COMP2119 Final Exam Q4D (已解禁)。
Given an array of
elements, where each element is an integer in for some small constant . The task is to sort the elements with asymptotic running time strictly better than . (You may use extra space.)
- 条件
: 优于 ,考虑 counting sort。 - 条件
: Keys range from 但仅有 extra space。考虑 counting sort 的进阶 - radix sort。
如何选取 radix?时刻记住,key range
- Key range
决定了键值数组的大小。 - Radix
决定了桶数组的大小。 - # of digits
决定了进行几轮 counting sort。
回到这道题,
- Key range:
- Radix:
- # of digits:
复杂度
Binary Search Tree
关于 BST 一些容易忘记的小细节。
Successor
BST 中某节点的后继:
- the minimum of
. 不存在:locate an ancestor that is a left child. the successor is 's parent.
1 | node* successor(node* x) { |
前驱类似:
- the maximum of
. 不存在:locate an ancestor that is a right child. the successor is 's parent.
Deletion
删除节点
has no children: delete . has one child: splice out . has two children: replace by its immediate successor , and splice out ( is the minimum of for sure, which means it has at most one child)
定义 splice out 操作为将某点的 children 连到其 parent 上。splice out 只适用于删除只有一个孩子的节点。
1 | void delete(root* T, node* z) { |
Tree Height Analysis
Complexity of insertion and deletion:
保证树高为
朴素的删除会影响树的结构。在执行若干次删除操作后树会逐渐左倾。
Self-Balanced Tree: Height Analysis
题目来自 2019 COMP2119 Final Exam Q3C (已解禁)。
Suppose in a binary tree with n nodes, at every node, the difference of heights of the left and the right sub-tree is at most
, where is some parameter that is at least . Prove that the height of the tree is at most .
我们熟悉的 AVL 树就是
证明来自 MIT algorithm 6.006-spring-2014.
, the minimum number of nodes in the left subtree of , the minimum number of nodes in the right subtree of
Since
Insight: Convert Linked List to BST
根据 sorted array 建 BST 很简单,这是因为它支持随机访问:
朴素算法:模仿 sorted array
的建立方法,在链表中暴力遍历到根的位置。满足
随着 head
指针在给定链表中迭代,我们还原出以该链表为中序遍历的一颗二叉树。该二叉树的结构又被二分放所保证,其高度一定是接近于
1 | struct ListNode { |
Bubble Sort: Average # of Swapping
一个小证明:冒泡排序平均会调用 swap()
函数多少次呢?
1 | for (int i = n; i >= 2; --i) { |
对于长为
这个式子很难算。换个角度,考虑每一对逆序对对答案作出的贡献:选取两个数
每一个这样的逆序对会做出多少贡献?考虑一个逆序对
那么长为 swap()
函数。
Heap: Linear Heap Building
众所周知,堆有两种建法:
- 向空堆中插入
次,复杂度 - heapify 方法,复杂度
这里主要讲下线性建堆方法的复杂度证明 (以大根堆为例)。
1 | void sink(int x) { |
若堆高为
因此总 swap 次数为:
Randomized Quicksort Analysis
众所周知,快排在序列 already sorted 或 reverse-sorted
时有最坏时间复杂度
当然是取平均!由于随机化,partition 产生的两个子序列的长度可能是
- base case
(提示我们 应该是一个 large enough constant)
- induction hypothesis: assume for
,
进行 induction:
我们尝试进行更加细致的放缩。由于
Huffman Tree: Key lemma
Key lemma of Huffman tree.
Reference
This article is a self-administered course note.
References in the article are from corresponding course materials if not specified.
Course info. Code: COMP2119, Lecturer: Hubert T.H. Chan.