├── .gitignore
├── LICENSE
├── README.md
├── _config.yml
├── package-lock.json
├── package.json
├── scaffolds
├── draft.md
├── page.md
└── post.md
├── source
├── _posts
│ ├── solution1
│ │ ├── exe1-1.md
│ │ ├── exe1-2.md
│ │ └── hw1.md
│ ├── solution2
│ │ ├── exe2-1.md
│ │ ├── exe2-2.md
│ │ └── hw2.md
│ ├── solution3
│ │ ├── exe3-1.md
│ │ ├── exe3-2.md
│ │ └── hw3.md
│ ├── solution4
│ │ ├── exe4-1.md
│ │ ├── exe4-2.md
│ │ └── hw4.md
│ ├── solution5
│ │ ├── exe5-1.md
│ │ ├── exe5-2.md
│ │ └── hw5.md
│ ├── solution6
│ │ ├── exe6.md
│ │ ├── hw6-1.md
│ │ └── hw6-2.md
│ ├── solution7
│ │ ├── exe7-1.md
│ │ ├── exe7-2.md
│ │ ├── hw7-1.md
│ │ └── hw7-2.md
│ ├── solution8
│ │ ├── exe8-1.md
│ │ ├── exe8-2.md
│ │ └── hw8.md
│ ├── solution9
│ │ ├── exe9-1.md
│ │ ├── exe9-2.md
│ │ └── hw9.md
│ └── syllabus.md
├── about
│ ├── cover.png
│ └── index.md
├── assets
│ ├── build-max-heap-prime.png
│ ├── column-sort.png
│ ├── compact-list-search-prime.png
│ ├── compact-list-search.png
│ ├── compare-exchange.png
│ ├── count-sort-modify.png
│ ├── hoare-partition.png
│ ├── insertion-sort.png
│ ├── oil-pipeline.png
│ └── tail-recursive-quicksort.png
├── book
│ ├── introduction-to-algorithm-fourth-edition.pdf
│ └── introduction-to-algorithm-third-edition.pdf
├── errata
│ └── index.md
├── exercise
│ ├── exe1-1.pdf
│ ├── exe1-2.pdf
│ ├── exe2-1.pdf
│ ├── exe2-2.pdf
│ ├── exe3-1.pdf
│ ├── exe3-2.pdf
│ ├── exe4-1.pdf
│ ├── exe4-2.pdf
│ ├── exe5-1.pdf
│ ├── exe5-2.pdf
│ ├── exe6.pdf
│ ├── exe7-1.pdf
│ ├── exe7-2.pdf
│ ├── exe8-1.pdf
│ ├── exe8-2.pdf
│ ├── exe9-1.pdf
│ └── exe9-2.pdf
├── homework
│ ├── hw1.pdf
│ ├── hw2.pdf
│ ├── hw3.pdf
│ ├── hw4.pdf
│ ├── hw5.pdf
│ ├── hw6-1.pdf
│ ├── hw6-2.pdf
│ ├── hw7-1.pdf
│ ├── hw7-2.pdf
│ ├── hw8.pdf
│ └── hw9.pdf
├── pseudocode
│ ├── katex.js
│ ├── lec1
│ │ ├── binary-addition.html
│ │ ├── binary-search.html
│ │ ├── bubble-sort.html
│ │ ├── count-inversions.html
│ │ ├── horner.html
│ │ ├── linear-search.html
│ │ ├── poly.html
│ │ ├── selection-sort.html
│ │ └── two-sum.html
│ ├── lec3
│ │ ├── linear-time-maximum-subarray.html
│ │ └── strassen.html
│ ├── lec4
│ │ ├── permute-by-cyclic.html
│ │ ├── permute-with-all.html
│ │ ├── permute-without-identity.html
│ │ ├── random-sample.html
│ │ ├── random-search.html
│ │ ├── random.html
│ │ ├── randomize-in-place.html
│ │ └── unbiased-random.html
│ ├── lec5
│ │ ├── d-ary-extract-max.html
│ │ ├── d-ary-heap.html
│ │ ├── d-ary-insert.html
│ │ ├── heap-increase-key.html
│ │ ├── max-heap-delete.html
│ │ ├── merge-sorted-lists.html
│ │ ├── min-heapify.html
│ │ ├── minimum-priority-queue.html
│ │ ├── young-extract-min.html
│ │ ├── young-find.html
│ │ ├── young-insert.html
│ │ └── young-sort.html
│ ├── lec6
│ │ ├── fuzzy-quicksort.html
│ │ ├── hoare-quicksort.html
│ │ ├── modified-tail-recursive-quicksort.html
│ │ ├── partition-with-equal.html
│ │ └── quicksort-with-equal.html
│ ├── lec7
│ │ ├── interval-counting.html
│ │ └── modified-counting-sort.html
│ ├── lec8
│ │ ├── iterative-randomized-select.html
│ │ └── two-array-median.html
│ ├── lec9
│ │ ├── binary-tree-traversal-nonrecursive-with-constant-space.html
│ │ ├── binary-tree-traversal-nonrecursive.html
│ │ ├── binary-tree-traversal-recursive.html
│ │ ├── compactify-list.html
│ │ ├── deque.html
│ │ ├── mergable-heap-sorted.html
│ │ ├── mergable-heap-unsorted.html
│ │ ├── parent.html
│ │ ├── queue-operation-handling-overflow-underflow.html
│ │ ├── reverse-singly-linked-list.html
│ │ ├── single-array-allocate-free.html
│ │ ├── singly-circular-linked-list.html
│ │ ├── tree-traversal-recursive.html
│ │ ├── two-queue-stack.html
│ │ ├── two-stack-queue.html
│ │ ├── union-of-circular-doubly-linked-list.html
│ │ └── xor-linked-list.html
│ ├── pseudocode.css
│ └── pseudocode.js
└── slides
│ ├── lec01-getting-started.pdf
│ ├── lec02-growth-of-functions.pdf
│ ├── lec03-divide-and-conquer.pdf
│ ├── lec04-probabilistic-analysis-and-randomized-algorithms.pdf
│ ├── lec05-heapsort.pdf
│ ├── lec06-quicksort.pdf
│ ├── lec07-sorting-in-linear-time.pdf
│ ├── lec08-medians-and-order-statistics.pdf
│ └── lec09-elementary-data-structures.pdf
└── themes
└── one-paper
├── _config.yml
├── layout
├── _partial
│ ├── footer.ejs
│ ├── head.ejs
│ ├── header.ejs
│ ├── paginator.ejs
│ └── post-header.ejs
├── archive.ejs
├── index.ejs
├── layout.ejs
└── post.ejs
└── source
├── css
├── a11y-dark.min.css
├── fonts.css
├── markdown.css
├── reset.css
└── style.css
├── fonts
├── montserrat-v23-latin-600.woff
├── montserrat-v23-latin-600.woff2
├── montserrat-v23-latin-600italic.woff
├── montserrat-v23-latin-600italic.woff2
├── montserrat-v23-latin-italic.woff
├── montserrat-v23-latin-italic.woff2
├── montserrat-v23-latin-regular.woff
└── montserrat-v23-latin-regular.woff2
├── img
└── favicon.png
└── js
├── highlight.min.js
└── highlightjs-line-numbers.js
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | Thumbs.db
3 | db.json
4 | *.log
5 | node_modules/
6 | public/
7 | .deploy*/
8 | _multiconfig.yml
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # 《算法导论》学习网站
2 |
3 | 网站地址:https://algorithm.cuijiacai.com
4 |
5 | > 在计算机出现之前,就有了算法。现在有了计算机,就需要更多的算法,算法是计算的核心。 ——《算法导论》
6 |
7 | 本站是一个《算法导论》的学习网站,笔者不定期更新视频、PPT、习题以及习题解答等学习资料。如果你想要系统学习“算法”本身,并且不满足于知其然,想要触摸“其所以然”的世界,那么这是一个适合你的网站。
8 |
9 | 
10 |
--------------------------------------------------------------------------------
/_config.yml:
--------------------------------------------------------------------------------
1 | # Hexo Configuration
2 | ## Docs: https://hexo.io/docs/configuration.html
3 | ## Source: https://github.com/hexojs/hexo/
4 |
5 | # Site
6 | title: 算法导论
7 | subtitle: 在计算机出现之前,就有了算法
8 | description: ''
9 | keywords:
10 | author: 崔家才
11 | language: zh-CN
12 | timezone: ''
13 |
14 | # URL
15 | ## Set your site url here. For example, if you use GitHub Page, set url as 'https://username.github.io/project'
16 | url: https://algorithm.cuijiacai.com/
17 | permalink: :title/
18 | permalink_defaults:
19 | pretty_urls:
20 | trailing_index: false # Set to false to remove trailing 'index.html' from permalinks
21 | trailing_html: false # Set to false to remove trailing '.html' from permalinks
22 |
23 | # Directory
24 | source_dir: source
25 | public_dir: public
26 | tag_dir: tags
27 | archive_dir: archives
28 | category_dir: categories
29 | code_dir: pseudocode
30 | i18n_dir: :lang
31 | skip_render: pseudocode/**
32 |
33 | # Writing
34 | new_post_name: :title.md # File name of new posts
35 | default_layout: post
36 | titlecase: false # Transform title into titlecase
37 | external_link:
38 | enable: true # Open external links in new tab
39 | field: site # Apply to the whole site
40 | exclude: ''
41 | filename_case: 0
42 | render_drafts: false
43 | post_asset_folder: false
44 | relative_link: false
45 | future: true
46 | highlight:
47 | enable: true
48 | line_number: true
49 | auto_detect: false
50 | tab_replace: ''
51 | wrap: true
52 | hljs: false
53 | prismjs:
54 | enable: false
55 | preprocess: true
56 | line_number: true
57 | tab_replace: ''
58 |
59 | # Home page setting
60 | # path: Root path for your blogs index page. (default = '')
61 | # per_page: Posts displayed per page. (0 = disable pagination)
62 | # order_by: Posts order. (Order by date descending by default)
63 | index_generator:
64 | path: ''
65 | per_page: 10
66 | order_by: -date
67 |
68 | # Category & Tag
69 | default_category: uncategorized
70 | category_map:
71 | tag_map:
72 |
73 | # Metadata elements
74 | ## https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta
75 | meta_generator: true
76 |
77 | # Date / Time format
78 | ## Hexo uses Moment.js to parse and display date
79 | ## You can customize the date format as defined in
80 | ## http://momentjs.com/docs/#/displaying/format/
81 | date_format: YYYY-MM-DD
82 | time_format: HH:mm:ss
83 | ## updated_option supports 'mtime', 'date', 'empty'
84 | updated_option: 'mtime'
85 |
86 | # Pagination
87 | ## Set per_page to 0 to disable pagination
88 | per_page: 10
89 | pagination_dir: page
90 |
91 | # Include / Exclude file(s)
92 | ## include:/exclude: options only apply to the 'source/' folder
93 | include:
94 | exclude:
95 | ignore:
96 |
97 | # Extensions
98 | ## Plugins: https://hexo.io/plugins/
99 | ## Themes: https://hexo.io/themes/
100 | theme: one-paper
101 |
102 | # Deployment
103 | ## Docs: https://hexo.io/docs/one-command-deployment
104 | deploy:
105 | type: ''
106 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "hexo-site",
3 | "version": "0.0.0",
4 | "private": true,
5 | "scripts": {
6 | "build": "hexo generate",
7 | "clean": "hexo clean",
8 | "deploy": "hexo deploy",
9 | "server": "hexo server",
10 | "netlify": "npm run clean && npm run build"
11 | },
12 | "hexo": {
13 | "version": "6.3.0"
14 | },
15 | "dependencies": {
16 | "hexo": "^6.3.0",
17 | "hexo-generator-archive": "^2.0.0",
18 | "hexo-generator-category": "^2.0.0",
19 | "hexo-generator-index-pin-top": "^0.2.2",
20 | "hexo-generator-tag": "^2.0.0",
21 | "hexo-katexify": "^1.1.0",
22 | "hexo-renderer-ejs": "^2.0.0",
23 | "hexo-renderer-marked": "^6.0.0",
24 | "hexo-renderer-stylus": "^2.1.0",
25 | "hexo-server": "^3.0.0",
26 | "hexo-theme-landscape": "^0.0.3"
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/scaffolds/draft.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: {{ title }}
3 | ---
4 |
--------------------------------------------------------------------------------
/scaffolds/page.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: {{ title }}
3 | date: {{ date }}
4 | ---
5 |
--------------------------------------------------------------------------------
/scaffolds/post.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: {{ title }}
3 | date: {{ date }}
4 | ---
5 |
6 |
7 |
--------------------------------------------------------------------------------
/source/_posts/solution1/exe1-1.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 练习 1-1 解答
3 | date: 2022-12-15 21:17:00
4 | description: 插入排序以及基础增量算法分析相关的习题解答
5 | ---
6 |
7 | #### Problem 1 (教材习题 2.1-3)
8 |
9 | 考虑以下 **查找问题** :
10 |
11 | **输入** : $n$ 个数的一个序列 $A = \langle a_1, a_2, \cdots, a_n\rangle$ 和一个值 $v$ 。
12 |
13 | **输出** :下标 $i$ 使得 $v = A[i]$ 或者当 $v$ 不在 $A$ 中出现时, $v$ 为特殊值 $NIL$ 。
14 |
15 | 写出 **线性查找** 的伪代码,它扫描整个序列来查找 $v$ 。使用一个循环不变式来证明你的算法是正确的。确保你的循环不变式满足三条必要的性质。
16 |
17 | ##### Solution
18 |
19 |
20 |
21 | 循环不变式:第1-5行循环的每次迭代开始前,不存在下标 $1 \le j < i$ 使得 $A[j] = v$ 。
22 |
23 | 初始化:第一次迭代开始前, $i = 1$ ,不变式显然成立。
24 |
25 | 保持:迭代过程中,算法2-4行检查是否 $A[i] = v$ ,
26 |
27 | - 如果是,则返回 $i$ ,我们找到了使得 $v = A[i]$ 的下标 $i$ 并以正确的结果停止了算法。
28 |
29 | - 如果不是,在 $A[1], A[2], ..., A[i - 1]$ 都不等于 $v$ 的基础上,我们又知道了 $A[i]$ 不等于 $v$ ,于是下一次迭代时,不存在下标 $1 \le j < i + 1$ 使得 $A[j] = v$ ,我们保持了循环不变式。
30 |
31 | 终止:迭代终止时, $i = n + 1$ ,根据不变式,不存在下标 $1 \le j \le n$ 使得 $A[j] = v$ ,我们正确地返回了 $NIL$ 。
32 |
33 | 综上,算法1正确。
34 |
35 | #### Problem 2 (教材习题 2.1-4)
36 |
37 | 考虑把两个 $n$ 位二进制整数加起来的问题,这两个整数分别存储在两个 $n$ 元数组 $A$ 和 $B$ 中。这两个整数的和应该按二进制形式形式存储在一个 $n + 1$ 元的数组 $C$ 中。请给出该问题的形式化描述,并写出伪代码。
38 |
39 | ##### Solution
40 |
41 |
42 |
43 | > 这里默认数组元素从左往右排列,表示数字时左边为高位,右边为低位。如果你和我的方向相反,但是意思一样的话也是可以的。
44 |
45 | 这个算法的循环不变式是:第2-9行循环的每次迭代开始前,$\{carry, C[i+2..n+1]\} = A[i+1..n] + B[i+1..n]$ ,其中 $\{carry, C[i+2..n+1]\}$ 表示将 $carry$ 拼接在 $C[i+1]$ 的位置得到的结果。
46 |
47 | 可以通过这个不变式来证明算法的正确性,这里不再赘述。
48 |
49 | #### Problem 3 (教材习题 2.2-2)
50 |
51 | 考虑排序存储在数组 $A$ 中的 $n$ 个数:首先找出 $A$ 中的最小元素,并将其与 $A[1]$ 中的元素进行交换。接着,找出 $A$ 中的次最小元素并将其与 $A[2]$ 中的元素进行交换。对 $A$ 中前 $n - 1$ 个元素按该方式继续,该算法称为 **选择排序** ,写出其伪代码。该算法维持的循环不变式是什么?为什么它只需要对前 $n - 1$ 个元素,而不是对所有 $n$ 个元素运行?用 $\Theta$ 记号给出选择排序的最好情况与最坏情况运行时间。
52 |
53 | ##### Solution
54 |
55 |
56 |
57 | 该算法维持的循环不变式是:第1-8行循环的每次迭代开始前,$A[1..i-1]$ 按照从小到大的顺序包含了 $A$ 中前 $i - 1$ 小的元素。(不变式的证明读者可自行完成)
58 |
59 | $n - 1$ 此循环结束后,$i = n$ ,由不变式可知, $A[1..n-1]$ 按照从小到大的顺序包含了 $A$ 中前 $n - 1$ 小的元素,于是此时的 $A[n]$ 必定是 $A$ 中最大的元素,因而没必要继续迭代了。
60 |
61 | 选择排序的最好情况与最坏情况的运行时间都是 $\Theta(n^2)$ 。因为无论在什么情况下,算法第3-8行的循环都会运行 $n - i$ 次以找出剩余元素当中最小的那一个,从而产生了如下的时间代价:
62 |
63 | $$
64 | \sum_{i = 1}^{n-1}(n - i) = n(n - 1) - \sum_{i = 1}^{n-1}i = \frac{n(n-1)}{2} = \Theta(n^2)
65 | $$
66 |
67 | #### Problem 4 (教材习题 2.2-3)
68 |
69 | 再次考虑线性查找问题(参见练习1-1问题1)。假定要查找的元素等可能地为数组中的任意元素,平均需要检查输入序列的多少元素?最坏情况又如何呢?用 $\Theta$ 记号给出平均情况和最坏情况的运行时间。证明你的答案。
70 |
71 | ##### Solution
72 |
73 | 本题假设 $v$ 能够在 $A[1..n]$ 中找到。考虑 $P\{A[i] = v\} = \frac{1}{n}$ ,且当 $A[i] = v$ 时,需要检查输入序列的 $i$ 个元素(前 $i - 1$ 个检查后拒绝,最后第 $i$ 个检查后接受)。记检查次数为 $X$ ,则:
74 |
75 | $$
76 | E(X) = \sum_{i = 1}^{n} i \cdot P\{A[i] = v\} = \sum_{i = 1}^{n} i \cdot \frac{1}{n} = \frac{n + 1}{2}
77 | $$
78 |
79 | 所以平均情况下需要比较 $\frac{n+1}2$ 次。
80 |
81 | 最坏情况就是只有 $A[n] = v$ 的情况,需要比较 $n$ 次。
82 |
83 | 因此,平均情况和最坏情况的最坏时间都是 $\Theta(n)$ 。
84 |
85 |
86 |
--------------------------------------------------------------------------------
/source/_posts/solution1/exe1-2.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 练习 1-2 解答
3 | date: 2022-12-16 11:46:00
4 | description: 归并排序以及基础分治算法分析相关的习题解答
5 | ---
6 |
7 | #### Problem 1 (教材习题 2.3-3)
8 |
9 | 使用数学归纳法证明:当 $n$ 刚好是 $2$ 的幂时,以下递归式的解是 $T(n) = n\log n$。
10 |
11 | $$
12 | T(n) = \begin{cases}
13 | 2 & \text{if } n = 2 \\
14 | 2T(n / 2) + n & \text{if } n = 2^k, k > 1
15 | \end{cases}
16 | $$
17 |
18 | ##### Solution
19 |
20 | 基础情况:$n = 2$ 时,$T(2) = 2 = 2 \cdot \log 2$ 显然成立。
21 |
22 | > 注:算法分析当中的 $\log$ 默认表示以 $2$ 为底的对数。
23 |
24 | 归纳步骤:假设 $n / 2$ 时,结论成立。考虑 $n$ 的情况。
25 |
26 | $$
27 | \begin{aligned}
28 | T(n) & = 2T(n / 2) + n = 2 \cdot \frac{n}2 \log {\frac{n}2} + n \\
29 | & = n(\log n - 1) + n \\
30 | & = n \log n
31 | \end{aligned}
32 | $$
33 |
34 | 综上,在 $n$ 为 $2$ 的幂时, $T(n) = n\log n$ 。
35 |
36 | #### Problem 2 (教材习题 2.3-5)
37 |
38 | 回顾查找问题(参见练习1-1问题1),注意到,如果序列 $A$ 已经排好序,就可以将该序列的中点与 $v$ 进行比较。根据比较的结果,原序列中有一半就可以不用再做进一步的考虑了。 **二分查找** 算法重复这个过程,每次都将序列剩余部分的规模减半。为二分查找写出迭代或递归的伪代码。证明:二分查找的最坏情况运行时间为 $\Theta(\log n)$ 。
39 |
40 | ##### Solution
41 |
42 |
43 |
44 | > 上述伪代码是二分查找的递归版本,但如果知道“尾递归”这个概念的话,你应该能够很容易写出和它等价的迭代版本。实际编程的时候更推荐迭代版本,因为函数调用的开销要比单纯控制流转移来地大。不过递归版本更容易证明正确性。
45 |
46 | 算法1是一个典型的分治算法,记输入规模 $n$ 时的运行时间为 $T(n)$ ,分析代价如下:
47 |
48 | - 分:计算中点并比较,代价为常数 。
49 |
50 | - 治:递归调用,递归地处理左半边或者又半边,代价为 $T(n/2)$ 。
51 |
52 | - 合:“治”的结果即时最终结果,代价为常数 。
53 |
54 | 于是, $T(n) = T(n / 2) + c$ , $c$ 为常数,不难得出 $T(n) = \Theta(\log n)$ 。
55 |
56 | #### Problem 3 (教材习题 2.3-6)
57 |
58 | 注意到[第1讲PPT第9页](/slides/lec01-getting-started.pdf#page=9)(教材2.1节)中过程 $INSERTION-SORT$ 的第5-7行的 **while** 循环采用一种线性查找来(反向)扫描已排好序的子数组 $A[1..j-1]$ 。我们可以使用二分查找(参见练习1-2问题2)来把插入排序的最坏情况总运行时间改进到 $\Theta(n\log n)$ 吗?
59 |
60 | ##### Solution
61 |
62 | 二分查找并不能改进插入排序的最坏情况运行时间。
63 |
64 | 二分查找可以帮助我们以对数时间找到需要插入的位置,但我们插入的时候,依旧需要把插入位置后面的元素整体右移一位,这个操作仍然是线性时间的。因此总体的最坏情况运行时间受到了拷贝操作的制约,依旧是 $\Theta(n^2)$ ,提升查找操作的速度并没有撼动拷贝操作的数量级。
65 |
66 | #### Problem 4 (教材习题 2.3-7)
67 |
68 | 描述一个运行时间为 $\Theta(n\log n)$ 的算法,给定 $n$ 个整数的集合 $S$ 和另一个整数 $x$ ,该算法能确定 $S$ 中是否存在两个其和刚好为 $x$ 的元素。
69 |
70 | #### Solution
71 |
72 | > 其实这一题可以用哈希表的方法得到 $\Theta(n)$ 复杂度的算法,不过这里为了教学目的,给出紧密结合第一讲内容的 $\Theta(n\log n)$ 的算法。
73 |
74 |
75 |
76 | 算法第1行排序的运行时间为 $\Theta(n\log n)$ ,第4-12行循环的运行时间为 $\Theta(n)$ ,整体时间依旧是 $\Theta(n \log n)$ (低阶项不影响数量级)。
77 |
78 | 算法的正确性可以通过循环不变式来证明。第4-12行 **while** 循环保持的不变式是:每次迭代开始前, **只可能** 存在 $i \le p < q \le j$ 使得 $S[p] + S[q] = x$ 。
79 |
80 | - 初始化:第一次迭代开始前, $i = 1, j = n$ ,不变式显然成立。
81 |
82 | - 保持:迭代开始前,只可能存在 $i \le p < q \le j$ 使得 $S[p] + S[q] = x$ 。算法第5-11行分如下三种情况讨论:
83 |
84 | 1. 如果 $S[i] + S[j] < x$ ,那么 $\forall i = p < q \le j, S[p] + S[q] < x$ ,于是只可能存在 $i + 1 \le p < q \le j$ 使得 $S[p] + S[q] = x$ ,从而 $i = i + 1$ 保持了不变式;
85 |
86 | 2. 如果 $S[i] + S[j] > x$ ,那么 $\forall i \le p < q = j, S[p] + S[q] > x$ ,于是只可能存在 $i \le p < q \le j - 1$ 使得 $S[p] + S[q] = x$ ,从而 $j = j - 1$ 保持了不变式;
87 |
88 | 3. 如果 $S[i] + S[j] = x$ ,迭代终止。
89 |
90 | - 终止:当迭代终止的时候,有两种情况:
91 |
92 | 1. 通过第10行的返回真终止,由于我们找到了 $S[i] + S[j] = x$ ,因此算法结果显然正确;
93 |
94 | 2. 通过第4行循环条件不满足而终止,此时 $i = j$ ,根据循环不变式, **只可能** 存在 $i \le p < q \le j$ 使得 $S[p] + S[q] = x$ ,但是现在 $i = j$ ,从而根本不存在这样的 $p$ 和 $q$ 。因此算法第13行返回假也是正确的。
95 |
96 | 综上,我们借助循环不变式证明了算法2的正确性。
97 |
98 | > 这里补充一下[两数之和问题的力扣链接](https://leetcode.cn/problems/two-sum/),读者可以自行编程并提交评测。
99 |
100 |
--------------------------------------------------------------------------------
/source/_posts/solution2/exe2-1.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 练习 2-1 解答
3 | date: 2022-12-17 21:17:00
4 | description: 渐近记号相关的习题解答
5 | ---
6 |
7 | #### Problem 1 (教材习题 3.1-1)
8 |
9 | 假设 $f(n)$ 与 $g(n)$ 都是渐近非负函数。使用 $\Theta$ 记号的基本定义来证明
10 |
11 | $$
12 | \max(f(n), g(n)) = \Theta(f(n) + g(n))
13 | $$
14 |
15 | ##### Solution
16 |
17 | $$
18 | \max(f(n), g(n)) = \frac{f(n) + g(n)}2 + \frac{|f(n) - g(n)|}2 \ge \frac12(f(n) + g(n))
19 | $$
20 |
21 | 且显然有
22 |
23 | $$
24 | \max(f(n), g(n)) \le f(n) + g(n)
25 | $$
26 |
27 | 所以 $\max(f(n), g(n)) = \Theta(f(n) + g(n))$ 。
28 |
29 | #### Problem 2 (教材习题 3.1-5)
30 |
31 | 证明定理3.1(见[第2讲PPT第6页](/slides/lec02-growth-of-functions.pdf#page=6)或者教材3.1节)。
32 |
33 | ##### Solution
34 |
35 | 证明:先证充分性。由 $f(n) = \Theta(g(n))$ ,有
36 |
37 | $$
38 | \exists c_1 > 0, c_2 > 0, n_0 > 0, \forall n \ge n_0, 0 \le c_1g(n) \le f(n) \le c_2g(n)
39 | $$
40 |
41 | 从而有 $\exists c_2 > 0, n_0 > 0, \forall n \ge n_0, 0 \le f(n) \le c_2g(n)$ ,即 $f(n) = O(g(n))$ ;
42 |
43 | 且 $\exists c_1 > 0, n_0 > 0, \forall n \ge n_0, 0 \le c_1g(n) \le f(n)$ ,即 $f(n) = \Omega(g(n))$ 。
44 |
45 | 再证必要性。
46 |
47 | 由 $f(n) = \Omega(g(n))$ 有 $\exists c_1 > 0, n_1 > 0, \forall n \ge n_1, 0 \le c_1g(n) \le f(n)$ 。
48 |
49 | 由 $f(n) = O(g(n))$ 有 $\exists c_2 > 0, n_2 > 0, \forall n \ge n_2, 0 \le f(n) \le c_2g(n)$ 。
50 |
51 | 取 $n_0 = \max(n_1, n_2) > 0$ ,则
52 |
53 | $$
54 | \exists c_1 > 0, c_2 > 0, n_0 > 0, \forall n \ge n_0, 0 \le c_1g(n) \le f(n) \le c_2g(n)
55 | $$
56 |
57 | 从而 $f(n) = \Theta(g(n))$ 。
58 |
59 | #### Problem 3 (教材习题 3.1-7)
60 |
61 | 证明: $o(g(n)) \cap \omega(g(n))$ 为空集。
62 |
63 | ##### Solution
64 |
65 | 证明:反证法,假设存在 $f(n)$ , $f(n) \in o(g(n)) \cap \omega(g(n))$ ,则
66 |
67 | $$
68 | f(n) \in o(g(n)) \Rightarrow \lim_{n\to\infty} \frac{f(n)}{g(n)} = 0
69 | $$
70 |
71 | $$
72 | f(n) \in \omega(g(n)) \Rightarrow \lim_{n\to\infty} \frac{f(n)}{g(n)} = \infty
73 | $$
74 |
75 | 从而 $0 = \infty$ ,矛盾。所以不存在这样的 $f(n)$ ,即 $o(g(n)) \cap \omega(g(n))$ 为空集。
76 |
77 | #### Problem 4 (教材习题 3.1-8)
78 |
79 | 可以扩展我们的记号到有两个参数 $n$ 和 $m$ 的情形,其中的 $n$ 和 $m$ 可以按不同速率独立地趋于无穷。对于给定的函数 $g(n, m)$ ,用 $O(g(n, m))$ 来表示以下函数集:
80 |
81 | $$
82 | O(g(n, m)) = \{f(n, m) | \exists c > 0, n_0 > 0 , m_0 > 0, \\
83 | \forall n \ge n_0 \vee m \ge m_0, 0\le f(n, m) \le cg(n, m)\}
84 | $$
85 |
86 | 对 $\Omega(g(n, m))$ 和 $\Theta(g(n, m))$ 给出相应的定义。
87 |
88 | ##### Solution
89 |
90 | $$
91 | \Omega(g(n, m)) = \{f(n, m) | \exists c > 0, n_0 > 0 , m_0 > 0, \\
92 | \forall n \ge n_0 \vee m \ge m_0, 0\le cg(n, m) \le f(n, m) \}
93 | $$
94 |
95 | $$
96 | \Theta(g(n, m)) = \{f(n, m) | \exists c_1 > 0, c_2 > 0, n_0 > 0 , m_0 > 0, \\
97 | \forall n \ge n_0 \vee m \ge m_0, 0\le c_1g(n, m) \le f(n, m) \le c_2g(n, m)\}
98 | $$
99 |
100 |
101 |
102 |
--------------------------------------------------------------------------------
/source/_posts/solution2/exe2-2.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 练习 2-2 解答
3 | date: 2022-12-17 22:17:00
4 | description: 标准记号与常用函数相关的习题解答
5 | ---
6 |
7 | #### Problem 1 (教材习题 3.2-2)
8 |
9 | 证明:$a^{\log_b c} = c^{\log_b a}$ 。
10 |
11 | ##### Solution
12 |
13 | 证明:因为
14 |
15 | $$
16 | \begin{aligned}
17 | \ln{a^{\log_b c}} & = \log_b c\ln a = \frac{\ln c}{\ln b} \ln a \\
18 | & = \frac{\ln a}{\ln b} \ln c = \log_b a\ln c\\
19 | & = \ln{c^{\log_b a}}
20 | \end{aligned}
21 | $$
22 |
23 | 所以 $a^{\log_b c} = c^{\log_b a}$ 。
24 |
25 | #### Problem 2 (教材习题 3.2-3)
26 |
27 | 证明 $\log(n!) = \Theta(n\log n)$ ,并证明 $n! = \omega(2^n)$ 且 $n! = o(n^n)$ 。
28 |
29 | ##### Solution
30 |
31 | 证明:根据斯特林近似公式(见[第2讲PPT第10页](/slides/lec02-growth-of-functions.pdf#page=10),教材3.2节),有:
32 |
33 | $$
34 | \begin{aligned}
35 | \log(n!) &= \log(\sqrt{2\pi n}(\frac{n}{e})^n(1 + \Theta(\frac{1}{n})))\\
36 | &= \frac12\log(2\pi n) + n\log n - n \log e + \log(1 + \Theta(\frac1n))
37 | \end{aligned}
38 | $$
39 |
40 | 其中最高阶项是 $n \log n$ ,所以 $\log(n!) = \Theta(n\log n)$ 。
41 |
42 | $$
43 | \begin{aligned}
44 | \lim_{n \to \infty}\frac{2^n}{n!} &= \lim_{n \to \infty} \frac{1}{\sqrt{2\pi n}(1+\Theta(\frac1n))} (\frac{2e}{n})^n \le \lim_{n \to \infty} (\frac{2e}{n})^n\\
45 | &\le \lim_{n \to \infty} (\frac12)^n ,n \ge 4e \\
46 | &= 0
47 | \end{aligned}
48 | $$
49 |
50 | 于是 $\lim\limits_{n \to \infty}\frac{2^n}{n!} = 0$ ,则 $n! = \omega(2^n)$ 。
51 |
52 | $$
53 | \begin{aligned}
54 | \lim_{n \to \infty}\frac{n^n}{n!} &= \lim_{n \to \infty} \frac{1}{\sqrt{2\pi n}(1+\Theta(\frac1n))} e^n \\
55 | &\ge \lim_{n\to\infty} \frac{e^n}{c\cdot n}, c \text{ is a constant}\\
56 | &= \infty
57 | \end{aligned}
58 | $$
59 |
60 | 于是 $\lim\limits_{n \to \infty}\frac{n^n}{n^!} = \infty$ ,则 $n! = o(n^n)$ 。
61 |
62 | #### Problem 3 (教材习题 3.2-4)
63 |
64 | 函数 $\lceil\log n\rceil!$ 多项式有界吗?函数 $\lceil\log\log n\rceil!$ 多项式有界吗?
65 |
66 | ##### Solution
67 |
68 | 1. 函数 $\lceil\log n\rceil!$ 不是多项式有界的。
69 |
70 | 证明:反证法。假设 $\lceil\log n\rceil!$ 是多项式有界的,则存在常数 $c, a, n_0$ ,使得当 $n \ge n_0$ 时, $\lceil\log n\rceil! \le cn^a$ 恒成立。特别地,考虑 $n = 2^k, k\in\mathbb{N}$ 的情况,有 $\lceil k\rceil! \le c(2^a)^k$ ,这与阶乘并不是指数有界是矛盾的。
71 |
72 | > 阶乘不是指数有界的这件事情可以通过问题2中类似的方式证明: $n! = \omega(a^n)$ 。
73 |
74 | 2. 函数 $\lceil\log\log n\rceil!$ 是多项式有界的。
75 |
76 | 证明:不失一般性,令 $n = 2^{2^k}$ ,则有
77 |
78 | $$
79 | \lceil\log\log n\rceil! = k! = \prod_{i = 1}^{k} i \le \prod_{i = 1}^{k} 2^{2^{i - 1}} = 2^{\sum\limits_{i = 1}^{k} 2^{i - 1}} = 2^{2^k - 1} \le 2^{2^k} = n
80 | $$
81 |
82 | 于是, $\lceil\log\log n\rceil!$ 是多项式有界的。
83 |
84 |
85 | #### Problem 4 (教材习题 3.2-5)
86 |
87 | 如下两个函数中,哪一个渐近更大些:$\log(\log^* n)$ 还是 $\log^*(\log n)$ ?
88 |
89 | ##### Solution
90 |
91 | 注意到 $\log^*(2^n) = 1 + \log^* n$ ,则:
92 |
93 | $$
94 | \begin{aligned}
95 | \lim_{n\to\infty} \frac{\log(\log^* n)}{\log^*(\log n)} &= \lim_{n\to\infty} \frac{\log(\log^* (2^n))}{\log^*(\log (2^n))} (\text{换元:} n \to 2^n ) \\
96 | &= \lim_{n\to\infty} \frac{\log(\log^*n + 1)}{\log^* n} \\
97 | &= \lim_{n\to\infty} \frac{\log(n + 1)}{n} (\text{换元:} \log^* n \to n)
98 | &= \lim_{n\to\infty} \frac{1}{n + 1} (\text{洛必达法则}) \\
99 | &= 0
100 | \end{aligned}
101 | $$
102 |
103 | 从而 $\log(\log^* n) = o(\log^*(\log n))$ , $\log^*(\log n)$ 渐近更大些。
104 |
105 |
106 | #### Problem 5 (教材习题 3.2-8)
107 |
108 | 证明: $k\ln k = \Theta(n) \Rightarrow k = \Theta(n / \ln n)$ 。
109 |
110 | ##### Solution
111 |
112 | 证明:下面的讨论都是在 $n$ 和 $k$ 充分大的情况下。
113 |
114 | 由 $k\ln k = \Theta(n)$ 有 $c_1 n \le k\ln k \le c_2 n$ 。
115 |
116 | 于是 $\frac{k\ln k}{c_2}\le n\le \frac{k\ln k}{c_1}$ ,
117 |
118 | 从而 $\ln k + \ln^{(2)} k - \ln c_2 \le \ln n \le \ln k + \ln^{(2)} k - \ln c_1$ 。
119 |
120 | 不难发现 $\ln n = \Theta(\ln k)$ ,不妨设 $c_3 \ln k \le \ln n \le c_4 \ln k$ ,于是
121 |
122 | $$
123 | \frac{\frac{k\ln k}{c_2}}{c_4 \ln k} \le \frac{n}{\ln n} \le \frac{\frac{k\ln k}{c_1}}{c_3 \ln k}
124 | $$
125 |
126 | 即 $\frac{k}{c_2c_4} \le n / \ln n \le \frac{k}{c_1c_3}$ ,简单变形有:
127 |
128 | $$
129 | c_1c_3 (n /\ln n) \le k \le c_2c_4 (n / \ln n)
130 | $$
131 |
132 | 因此 $k = \Theta(n / \ln n)$ 。
133 |
134 |
135 |
--------------------------------------------------------------------------------
/source/_posts/solution2/hw2.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 作业 2 解答
3 | date: 2022-12-18 12:17:00
4 | description: 第2讲函数的增长课后作业题解答
5 | ---
6 |
7 | #### Problem 1 (教材习题 3-4)
8 |
9 | **(渐近记号的性质)** 假设 $f(n)$ 和 $g(n)$ 为渐近正函数。证明或反驳下面的每个猜测。
10 |
11 | 1. $f(n) = O(g(n)) \Rightarrow g(n) = O(f(n))$ 。
12 |
13 | 2. $f(n) + g(n) = \Theta(\min(f(n), g(n)))$ 。
14 |
15 | 3. $f(n) = O(g(n))\Rightarrow \log(f(n)) = O(\log(g(n)))$ ,其中对所有足够大的 $n$ ,有 $\log(g(n)) \ge 1$ 且 $f(n) \ge 1$ 。
16 |
17 | 4. $f(n) = O(g(n)) \Rightarrow 2^{f(n)} = O(2^{g(n)})$ 。
18 |
19 | 5. $f(n) = O((f(n))^2)$ 。
20 |
21 | 6. $f(n) = O(g(n)) \Rightarrow g(n) = \Omega(f(n))$ 。
22 |
23 | 7. $f(n) = \Theta(f(n/2))$ 。
24 |
25 | 8. $f(n) + o(f(n)) = \Theta(f(n))$ 。
26 |
27 | ##### Solution
28 |
29 | 1. 假。反例: $n = O(n^2)$ 但 $n^2 \ne O(n)$ 。
30 |
31 | 2. 假。反例: $n + n^2 \ne \Theta(n)$ 。
32 |
33 | 3. 真。$f(n) = O(g(n)) \Rightarrow f(n) \le cg(n) \Rightarrow \log(f(n)) \le \log(cg(n)) = \log(g(n)) + \log(c) \Rightarrow \log(f(n)) = O(\log(g(n)))$ 。
34 |
35 | 4. 假。反例: $2n = O(n)$ ,但是 $2^{2n} = 4^n \ne O(2^n)$ 。
36 |
37 | 5. 假。反例: $\frac1n \ne O(\frac1{n^2})$ 。
38 |
39 | 6. 真。$f(n) = O(g(n)) \Rightarrow f(n) \le cg(n) \Rightarrow g(n) \ge \frac1cf(n) \Rightarrow g(n) = \Omega(f(n))$ 。
40 |
41 | 7. 假。反例: $4^n \ne \Theta(2^n) = \Theta(4^{n/2})$ 。
42 |
43 | 8. 真。
44 |
45 | $$
46 | \lim_{n \to \infty}\frac{f(n) + o(f(n))}{f(n)} = \lim_{n\to\infty} 1 + \frac{o(f(n))}{f(n)} = 1 + 0 = 1 \in (0, +\infty)
47 | $$
48 |
49 | 所以 $f(n) + o(f(n)) = \Theta(f(n))$ 。
50 |
51 | #### Problem 2 (教材习题 3-5)
52 |
53 | **( $O$ 与 $\Omega$ 的一些变形)** 某些作者用一种与我们稍微不同的方式来定义 $\Omega$ :假设我们使用 $\stackrel{\infty}{\Omega}$ (读作“ $\Omega$ 无穷”)来表示这种可选的定义。若存在常量 $c$ ,使得对无穷多个整数 $n$ ,有 $f(n)\ge cg(n) \ge 0$ ,则称 $f(n) = \stackrel{\infty}{\Omega}(g(n))$ 。
54 |
55 | 1. 证明:对渐近非负的任意两个函数 $f(n)$ 和 $g(n)$ ,或者 $f(n) = O(g(n))$ 或者 $f(n) = \stackrel{\infty}{\Omega}(g(n))$ 或者二者均成立,然而,如果使用 $\Omega$ 来代替 $\stackrel{\infty}{\Omega}$ ,那么该命题并不为真。
56 |
57 | 2. 描述用 $\stackrel{\infty}{\Omega}$ 代替 $\Omega$ 来刻画程序运行时间的潜在优点与缺点。
58 |
59 | 某些作者也用一种稍微不同的方式来定义 $O$ ;假设使用 $O'$ 来表示这种可选的定义。我们称 $f(n) = O'(g(n))$ 当且仅当 $|f(n)| = O(g(n))$ 。
60 |
61 | 3. 如果使用 $O'$ 代替 $O$ 但仍然使用 $\Omega$ ,定理3.1(见[PPT第2讲第6页](/slides/lec02-growth-of-functions.pdf#page=6)或者教材3.1节)中的“当且仅当”的每个方向将出现什么情况?
62 |
63 | 有些作者定义 $\stackrel{\sim}{O}$ (读作“软O“)来意指忽略对数因子的 $O$ :
64 |
65 | $$
66 | \stackrel{\sim}{O} = \{f(n) | \exists c > 0, k > 0, n_0 > 0, \forall n \ge n_0, 0 \le f(n) \le cg(n)\log^k(n)\}
67 | $$
68 |
69 | 4. 用一种类似的方式定义 $\stackrel{\sim}{\Omega}$ 和 $\stackrel{\sim}{\Theta}$ 。证明与定理3.1(见[PPT第2讲第6页](/slides/lec02-growth-of-functions.pdf#page=10)或者教材3.1节)相对应的类似结论。
70 |
71 | ##### Solution
72 |
73 | 1. 反证法。假设存在两个函数 $f(n), g(n)$ ,$f(n) = O(g(n))$ 与 $f(n) = \stackrel{\infty}{\Omega}(g(n))$ 都不成立。
74 |
75 | - 由 $f(n) = O(g(n))$ 不成立可知,$\forall c > 0, n_0 > 0, \exists n \ge n_0, f(n) > cg(n)$ 。
76 |
77 | - 任取一个正常数 $c$ ,考虑 $n_0 = 1$ 的时候,即 $\exists n \ge n_0, f(n) > cg(n)$ 中对应的 $n$ 为 $a_1$ ;再考虑 $n_0 = a_1 + 1$ 的时候,记对应的 $n$ 为 $a_2$ 。
78 |
79 | - 更一般地,记 $n_0 = a_i + 1$ 时,使得 $f(n) > cg(n)$ 的 $n$ 的值为 $a_{i+1}$ ,于是我们得到了一个无限集 $\{a_1, a_2, a_3, \cdots\}$ ,对于这个无限集上的整数 $n$ ,均有 $f(n) > cg(n)$ 。
80 |
81 | - 于是有 $f(n) = \stackrel{\infty}{\Omega}(g(n))$ 成立,这与假设矛盾,故而原命题成立。
82 |
83 | 如果使用 $\Omega$ 来代替 $\stackrel{\infty}{\Omega}$ ,则命题为假,反例:取 $f(n) = n^{\sin n}$ , $g(n) = n^{0.5}$ , $f(n) = O(g(n))$ 与 $f(n) = \Omega(g(n))$ 均不成立。
84 |
85 | 2. 优点是我们得到了一个很漂亮的性质,任意两个函数都可以通过这个新的符号体系进行比较了;缺点是得到的结论不够“强”,对于这个无限集以外的世界一无所知。
86 |
87 | - $\stackrel{\infty}{\Omega}$ 所描述的比较关系时很暧昧的,意思是我有无限多的时刻比你大,但是并不是所有时刻,也就是说 $f(n)$ 和 $g(n)$ 在这样的比较标准下并没有完全区分开,有可能是相互纠缠的。
88 |
89 | - 比如说奇数的时候 $f(n)$ 大,偶数的时候 $g(n)$ 大,此时既可以说 $f(n) = \stackrel{\infty}{\Omega}(g(n))$ ,也可以说 $g(n) = \stackrel{\infty}{\Omega}(f(n))$ 。
90 |
91 | 3. 没有发生变化。因为一个函数能够使用 $\Theta$ 记号就已经蕴含着它在 $n$ 充分大的渐近意义下是非负的,而我们讨论渐近复杂度的时候又不关心 $n$ 不充分大的情况,所以给一个渐近意义下非负的东西加绝对值并不能在渐近意义下改变什么。
92 |
93 | 4. 定义 $\stackrel{\sim}{\Omega}$ 和 $\stackrel{\sim}{\Theta}$ 如下:
94 |
95 | $$
96 | \stackrel{\sim}{\Omega} = \{f(n) | \exists c > 0, k > 0, n_0 > 0, \forall n \ge n_0, 0 \le \frac{cg(n)}{\log^k(n)} \le f(n)\}
97 | $$
98 |
99 | $$
100 | \stackrel{\sim}{\Theta} = \{f(n) | \exists c_1 > 0, c_2 > 0, k_1 > 0, k_2 > 0, n_0 > 0,\\
101 | \forall n \ge n_0, 0 \le \frac{c_1g(n)}{\log^{k_1}(n)} \le f(n) \le c_2g(n)\log^{k_2}(n)\}
102 | $$
103 |
104 | 与定理3.1类似的结论为:$f(n) = \stackrel{\sim}{\Theta}(g(n)) \Leftrightarrow f(n) = \stackrel{\sim}{O}(g(n)) \wedge f(n) = \stackrel{\sim}{\Omega}(g(n))$ ,证明过程类似于[练习2-1](/solution2/exe2-1/)问题2。
105 |
106 |
--------------------------------------------------------------------------------
/source/_posts/solution3/exe3-1.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 练习 3-1 解答
3 | date: 2023-1-1 23:48:00
4 | description: 最大子数组问题和矩阵乘法相关的习题解答
5 | ---
6 |
7 | #### Problem 1 (教材习题 4.1-4)
8 |
9 | 假定修改最大子数组问题的定义,允许结果为空数组,其和为 $0$ 。你应该如何修改现有算法,使它们能允许空数组为最终结果?
10 |
11 | ##### Solution
12 |
13 | 算法描述如下:
14 |
15 | - 首先,对于输入数组作一次线性扫描,看看它是否包含正数。
16 |
17 | - 如果全都是负数,则算法返回空数组,其和为 $0$ ,并终止算法。
18 |
19 | - 否则,正常运行现有的寻找最大子数组的算法即可。
20 |
21 |
22 | #### Problem 2 (教材习题 4.1-5)
23 |
24 | 使用如下思想为最大子数组问题设计一个非递归的、线性时间的算法。从数组的左边界开始,由左至右处理,记录到目前为止已经处理过的最大子数组。若已知 $A[1..j]$ 的最大子数组,基于如下性质将解扩展为 $A[1..j+1]$ 的最大子数组: $A[1..j+1]$ 的最大子数组要么是 $A[1..j]$ 的最大子数组,要么是某个子数组 $A[i..j+1] (1\le i\le j+1)$ 。在已知 $A[1..j]$ 的最大子数组的情况下,可以在线性时间内找出形如 $A[i..j+1]$ 的最大子数组。
25 |
26 | ##### Solution
27 |
28 | 算法伪代码如下:
29 |
30 |
31 |
32 | 从第 5 到 15 行的 **for** 循环不难看出,该算法的运行时间为 $\Theta(n)$ ,这是一个线性时间的算法。
33 |
34 | 下面使用 **循环不变式** 来证明该算法的正确性,第 5 到 15 行的 **for** 循环的不变式为:每次循环开始前, $A[p..q]$ 是 $A[1..j-1]$ 的最大子数组, $A[i..j-1]$ 是 $A[1..j - 1]$ 的最大后缀子数组。
35 |
36 | - 初始化:在第一次迭代开始前,$p = q = i = 1, j = 2$ ,$A[1]$ 显然是 $A[1]$ 的最大子数组和最大后缀子数组,不变式显然成立。
37 |
38 | - 保持:若迭代开始前,$A[p..q]$ 是 $A[1..j-1]$ 的最大子数组, $A[i..j-1]$ 是 $A[1..j-1]$ 的最大后缀子数组,
39 |
40 | - 算法第 6 到 11 行使得 $A[i..j]$ 是 $A[1..j]$ 的最大后缀子数组。
41 |
42 | - 因为 $A[1..j]$ 所有的后缀子数组都包含 $A[j]$ ,我们只需要让 $A[k..j-1]$(即 $A[1..j]$ 后缀子数组中不含最后一个元素的部分) 的部分尽可能大(非负的就要最大,负的直接不要——相当于0)就行。
43 |
44 | - 如果 $A[1..j-1]$ 的最大后缀子数组是负的,那么 $A[1..j]$ 的最大后缀子数组就是 $A[j]$ (算法第 6 到 8 行);
45 |
46 | - 如果 $A[1..j-1]$ 的最大后缀子数组 $A[i..j-1]$ 是非负的,那么 $A[1..j]$ 的最大后缀子数组就是 $A[i..j]$ (算法第 9 到 11 行);
47 |
48 | - 算法第 12 到 14 行使得 $A[p..q]$ 是 $A[1..j]$ 的最大子数组。
49 |
50 | - $A[1..j]$ 的最大子数组是 $A[1..j-1]$ 的最大子数组和 $A[1..j]$ 的最大后缀子数组中较大的那一个(算法第 12 到 14 行);
51 |
52 | - 因为 $A[1..j]$ 的最大子数组要么不含 $A[j]$ ,即上述的前一种情况;要么包含 $A[j]$ ,即上述的后一种情况,故而两种情况取最大即可。
53 |
54 | 于是,下一次迭代开始前,$A[p..q]$ 是 $A[1..j]$ 的最大子数组, $A[i..j]$ 是 $A[1..j]$ 的最大后缀子数组。循环保持了不变式。
55 |
56 | - 终止:最后一次迭代结束后, $j = n + 1$ ,根据循环不变式, $A[p, q]$ 是 $A[1..n+1-1] = A[1..n]$ 的最大子数组。因此,算法 1 是正确的。
57 |
58 |
59 | > 补充本题的洛谷模版题[链接](https://www.luogu.com.cn/problem/P1115),读者可以自行编程并提交评测。
60 |
61 |
62 | #### Problem 3 (教材习题 4.2-2)
63 |
64 | 为 Strassen 算法编写伪代码。
65 |
66 | ##### Solution
67 |
68 |
69 |
70 |
71 | #### Problem 4 (教材习题 4.2-6)
72 |
73 | 用 Strassen 算法作为子过程来进行一个 $kn\times n$ 矩阵和一个 $n \times kn$ 矩阵的乘法,最快需要花费多长时间? 对于两个输入矩阵规模互换的情况,回答相同的问题。
74 |
75 | ##### Solution
76 |
77 | 考虑分块矩阵的乘法。记
78 |
79 | $$
80 | A = \left[\begin{matrix}A_1 \\ A_2 \\ \vdots \\ A_k\end{matrix}\right],
81 | B = \left[\begin{matrix}B_1 & B_2 & \ldots & B_k\end{matrix}\right]
82 | $$
83 |
84 | 其中, $A_i$ 和 $B_j$ 都是 $n \times n$ 的矩阵。
85 |
86 | $$
87 | A \cdot B = \left[\begin{matrix}A_1B_1 & A_1B_2 & \ldots & A_1B_k\\
88 | A_2B_1 & A_2B_2 & \ldots & A_2B_k\\
89 | \vdots & \vdots & \ddots & \vdots\\
90 | A_kB_1 & A_kB_2 & \ldots & A_kB_k\\
91 | \end{matrix}\right],
92 | B \cdot A = \left[\sum_{i = 1}^{k} A_iB_i\right]
93 | $$
94 |
95 | 其中,每次乘法需要 $\Theta(n^{\log 7})$ (Strassen 算法),每次加法需要 $\Theta(n^2)$ (平凡算法)。
96 |
97 | 所以不难看出 $A\cdot B$ 的运行时间为 $\Theta(k^2 n^{\log 7})$ ,$B\cdot A$ 的时间为 $\Theta(k n^{\log 7})$ 。
98 |
99 |
100 | #### Problem 5 (教材习题 4.2-7)
101 |
102 | 设计算法,仅使用三次实数乘法即可完成复数 $a + bi$ 和 $c + di$ 相乘。算法需接收 $a, b, c, d$ 作为输入,分别生成实部 $ac - bd$ 和虚部 $ad + bc$ 。
103 |
104 | ##### Solution
105 |
106 | 考虑如下两个等式:
107 |
108 | $$
109 | ac - bd = ac + bc - bc - bd = (a+b)c - b(c+d)\\
110 | ad + bc = ad - bd + bd + bc = (a-b)d + b(c+d)
111 | $$
112 |
113 | 我们只需要计算三次乘积 $P_1 = (a+b)c, P_2 = b(c+d), P_3 = (a-b)d$ 即可分别生成实部 $ac-bd = P_1 - P_2$ 和虚部 $ad+bc = P_2 + P_3$ 。
114 |
115 |
--------------------------------------------------------------------------------
/source/_posts/solution5/exe5-1.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 练习 5-1 解答
3 | date: 2023-1-22 11:12:00
4 | description: 堆和堆排序相关的习题解答
5 | ---
6 |
7 | #### Problem 1 (教材习题 6.1-1 和 6.1-2)
8 |
9 | 证明:含 $n$ 个元素的堆的高度为 $\lfloor\log n\rfloor$ 。
10 |
11 | ##### Solution
12 |
13 | 证明:设含 $n$ 个元素的堆的高度为 $h$ 。
14 |
15 | 考虑高度为 $h$ 的堆,它的结点数
16 |
17 | - 最多是一个满二叉树的情况,有 $1 + 2 + \cdots + 2^h = 2^{h + 1} - 1$ 个结点;
18 |
19 | - 最少是一个高度为 $h - 1$ 的满二叉树再多 $1$ 个元素在第 $h$ 层的情况,有 $1 + 2 + \cdots + 2^{h-1} + 1 = 2^{h}$ 个结点。
20 |
21 | 因此,我们找出了结点数和高度之间的不等式关系:
22 |
23 | $$
24 | n \le 2^{h+1} - 1 \Rightarrow h \ge \log(n+1) - 1 > \log n - 1
25 | $$
26 |
27 | $$
28 | n \ge 2^{h} \Rightarrow h \le \log n
29 | $$
30 |
31 | 即 $\log n - 1 < h \le \log n$ ,也就是 $h = \lfloor\log n\rfloor$ 。
32 |
33 |
34 | #### Problem 2 (教材习题 6.1-7)
35 |
36 | 证明:当用数组表示存储 $n$ 个元素的堆时,叶结点下标分别是
37 |
38 | $$
39 | \lfloor n/2\rfloor + 1, \lfloor n/2\rfloor + 2, \cdots, n
40 | $$
41 |
42 | ##### Solution
43 |
44 | 证明:根据完全二叉树的定义,叶结点的下标连续是显然的。我们只需要证明叶结点的下标最小是 $\lfloor n/2\rfloor + 1$ 即可。
45 |
46 | 假设最小的叶结点的下标是 $k$ 则
47 |
48 | - 由于这是叶结点——没有子结点,我们有 $LEFT(k) \ge n + 1$ ,即
49 |
50 | $$
51 | k \ge (n + 1) / 2 > n / 2
52 | $$
53 |
54 | - 由于这是最小的叶结点,则 $k - 1$ 不是叶结点,因此 $LEFT(k - 1) \le n$ ,即
55 |
56 | $$
57 | k \le n / 2 + 1
58 | $$
59 |
60 | 综上,我们有 $n / 2 - 1 < k - 1 \le n / 2$ ,即 $k - 1 = \lfloor n / 2 \rfloor$ ,也就是最小的叶结点下标为 $k = \lfloor n / 2 \rfloor + 1$ 。
61 |
62 |
63 | #### Problem 3 (教材习题 6.2-2)
64 |
65 | 参考过程 $\text{MAX-HEAPIFY}$ (见[第3讲PPT第13页](/slides/lec05-heapsort.pdf#page=13)),写出能够维护相应最小堆的 $\text{MIN-HEAPIFY}(A, i)$ 的伪代码,并比较 $\text{MIN-HEAPIFY}$ 与 $\text{MAX-HEAPIFY}$ 的运行时间。
66 |
67 | ##### Solution
68 |
69 |
70 |
71 | 不难看出, $\text{MIN-HEAPIFY}$ 的运行时间和 $\text{MAX-HEAPIFY}$ 是一样的,都是 $O(h) = O(\log n)$ ,其中 $h$ 是以 $A[i]$ 为根结点的子树高度。
72 |
73 |
74 | #### Problem 4 (教材习题 6.2-6)
75 |
76 | 证明:对一个大小为 $n$ 的堆, $\text{MAX-HEAPIFY}$ 的最坏情况运行时间为 $\Omega(\log n)$ 。(提示:对于 $n$ 个结点的堆,可以通过对每个结点设定恰当的值,使得从根结点到叶结点路径上的每个结点都会递归调用 $\text{MAX-HEAPIFY}$ 。)
77 |
78 | ##### Solution
79 |
80 | 证明:考虑情况 $A[1] = 0$ , $A[i] = 1, 2 \le i \le n$ 。
81 |
82 | 由于 $0$ 是 $A[1..n]$ 中最小的元素,所以调用 $\text{MAX-HEAPIFY}(A, 1)$ 时,它需要和堆的每一层都交换,从而到达一个叶结点(这也是满足最大堆性质的情况下它应该在的位置)。
83 |
84 | 而堆的高度为 $\lfloor \log n \rfloor$ ,因此 $\text{MAX-HEAPIFY}$ 在最坏情况下的运行时间为 $\Omega(\log n)$ 。
85 |
86 | > 因此,我们在[第5讲PPT第13页](/slides/lec05-heapsort.pdf#page=13)给出的 $\text{MAX-HEAPIFY}(A, 1)$ 的上界 $O(\log n)$ 是一个紧确的界。
87 |
88 |
89 | #### Problem 5 (教材习题 6.3-2)
90 |
91 | 对于 $\text{BUILD-MAX}$ 中第 2 行的循环控制变量 $i$ 来说,为什么我们要求它是从 $\lfloor A.length / 2\rfloor$ 到 $1$ 递减,而不是从 $1$ 到 $\lfloor A.length / 2 \rfloor$ 递增呢?
92 |
93 | ##### Solution
94 |
95 | 这是因为 $\text{MAX-HEAPIFY}$ 算法的输入 $i$ 能够维护最大堆性质的前提是 $LEFT(i)$ 和 $RIGHT(i)$ 已经满足最大堆性质。从后往前迭代是因为最后的叶结点本身就是平凡的最大堆,能够满足让 $\text{MAX-HEAPIFY}$ 成立的输入条件。
96 |
97 | 基于循环不变式的算法正确性证明详见[第5讲PPT第18页](/slides/lec05-heapsort.pdf#page=18)。
98 |
99 |
100 | #### Problem 6 (教材习题 6.3-3)
101 |
102 | 证明:对于任一包含 $n$ 个元素的堆中,至多有 $\lceil n / 2^{h+1}\rfloor$ 个高度为 $h$ 的结点。
103 |
104 | ##### Solution
105 |
106 | 证明:记高度为 $h$ 的结点有 $n_h$ 个,下面证明 $n_h \le \lceil n / 2^{h+1}\rfloor$ 。对 $h$ 进行归纳。
107 |
108 | 基础情况: $h = 0$ 时显然成立,根据[练习5-1](/solution5/exe5-1/)问题2,一个包含 $n$ 个元素的堆有 $\lceil n / 2\rceil$ 个叶结点,也就是 $n_0 \le \lceil n / 2^{0 + 1}\rceil$ 。
109 |
110 | 归纳假设:假设高度为 $n_{h-1} \lceil n / 2^h\rceil$ 。
111 |
112 | 归纳步骤:下面证明 $n_h \le \lceil n / 2^{h+1}\rceil$ 。
113 |
114 | - 如果 $n_{h-1}$ 是偶数,则 $n_h = n_{h-1} / 2 = \lceil n_{h-1} / 2 \rceil$ ;
115 |
116 | - 高度为 $h$ 的结点中,每个结点都有两个孩子。
117 |
118 | - 如果 $n_{h-1}$ 是奇数,则 $n_h = \lfloor n_{h-1} / 2 \rfloor + 1 = \lceil n_{h-1} / 2\rceil$ ;
119 |
120 | - 高度为 $h$ 的结点中 $n_h - 1$ 个结点有两个孩子, $1$ 个结点有一个孩子。
121 |
122 | 结合上述两种情况,我们有 $n_h = \lceil n_{h-1} / 2\rceil$ ,于是
123 |
124 | $$
125 | n_h = \lceil \frac{n_{h-1}}{2} \rceil \le \lceil \frac{1}{2}\cdot\lceil\frac{n}{2^{h}}\rceil \rceil = \lceil\lceil \frac{n}{2^{h + 1}} \rceil\rceil = \lceil \frac{n}{2^{h + 1}} \rceil
126 | $$
127 |
128 | 综上, $n_h \le \lceil n / 2^{h+1}\rceil$ 。
129 |
130 | #### Problem 7 (教材习题 6.4-2)
131 |
132 | 试分析在使用下列循环不变式时, $\text{HEAPSORT}$ (详见[第5讲PPT第21页](/slides/lec05-heapsort.pdf#page=21))的正确性:
133 |
134 | 在算法的第 2 到 5 行 **for** 循环每次迭代开始时,子数组 $A[1..i]$ 是一个包含了数组 $A[1..n]$ 中前 $i$ 小元素的最大堆,而子数组 $A[i+1..n]$ 包含了数组 $A[1..n]$ 中已排好序的前 $n - i$ 大元素。
135 |
136 | ##### Solution
137 |
138 | 证明:采用“初始化-保持-终止”的方法来证明不变式,并说明堆排序的正确性。
139 |
140 | 初始化:第一次迭代之前,$i = n$ ,根据算法第 1 行 $\text{BUILD-MAX-HEAP}(A)$ 的正确性,显然有子数组 $A[1..n]$ 包含了数组 $A[1..n]$ 中前 $n$ 小元素的最大堆,而子数组 $A[n+1..n]$ 包含了数组 $A[1..n]$ 中已经排好序的前 $n - n = 0$ 大元素。
141 |
142 | 保持:假设迭代开始前,子数组 $A[1..i]$ 是一个包含了数组 $A[1..n]$ 中前 $i$ 小元素的最大堆,而子数组 $A[i+1..n]$ 包含了数组 $A[1..n]$ 中已排好序的前 $n - i$ 大元素。
143 |
144 | - $A[1]$ 是 $A[1..i]$ 中的最大元素,但是比 $A[i+1..n]$ 都小,算法第3行将其与 $A[i]$ 交换,此时 $A[i]$ 比 $A[i+1..n]$ 都小且是 $A[1..i]$ 中的最大元素。而 $A[i+1..n]$ 本身是排好序的,因此,在前面多缀了一个更小元素 $A[i]$ 的 $A[i..n]$ 依旧是排好序的,且是前 $n - i + 1$ 大元素。
145 |
146 | - 算法第 4 行将堆的大小减 1 ,得到了新的堆 $A[1..i-1]$ 。之前交换 $A[1]$ 和 $A[i]$ 的操作并没有破坏 $A[2]$ 和 $A[3]$ 在 $A[1..i-1]$ 中对应子树的最大堆性质,因此算法第 5 行的 $\text{MAX-HEAPIFY}(A, 1)$ 能够维护 $A[1..i-1]$ 的最大堆性质。同时,由于我们踢出堆的是原本前 $i$ 小的元素 $A[1..i]$ 中的最大元素,因此现在的 $A[1..i-1]$ 中是前 $i-1$ 小元素。
147 |
148 | - 综上,下一次迭代开始前,子数组 $A[1..i-1]$ 是一个包含了数组 $A[1..n]$ 中前 $i-1$ 小元素的最大堆,而子数组 $A[i..n]$ 包含了数组 $A[1..n]$ 中已排好序的前 $n - i + 1$ 大元素。循环保持了不变式。
149 |
150 | 终止:最后一次迭代结束后,$i = 1$ ,根据循环不变式,我们有 $A[2..n]$ 包含了数组 $A[1..n]$ 中已经排好序的前 $n - 1$ 大元素,$A[1]$ 是 $A[1..n]$ 中第 1 小,也就是最小的元素。
151 |
152 | 综上,算法结束时 $A[1..n]$ 已经排好了序,堆排序算法的正确性得证。
153 |
154 |
--------------------------------------------------------------------------------
/source/_posts/solution5/exe5-2.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 练习 5-2 解答
3 | date: 2023-1-22 23:50:00
4 | description: 优先队列相关的习题解答
5 | ---
6 |
7 | #### Problem 1 (教材习题 6.5-3)
8 |
9 | 要求用最小堆实现最小优先队列,请写出 HEAP-MINIMUM 、 HEAP-EXTRACT-MIN 、 HEAP-DECREASE-KEY 、 MIN-HEAP-INSERT 的伪代码。其中,你可以直接调用 MIN-HEAPIFY (详见[练习5-1](/solution5/exe5-1/)算法1)来维护最小堆的性质。
10 |
11 | ##### Solution
12 |
13 |
14 |
15 |
16 | #### Problem 2 (教材习题 6.5-5)
17 |
18 | 试分析在使用下列循环不变式时,HEAP-INCREASE-KEY(详见[第5讲PPT第42页](/slides/lec05-heapsort.pdf#page=42))的正确性:
19 |
20 | 在算法的第 4 到 6 行 **while** 循环每次迭代开始的时候,子数组 $A[1..A.\text{heap-size}]$ 要满足最大堆的性质。如果有违背,只有一个可能: $A[i] > A[PARENT(i)]$ 。
21 |
22 | 这里,你可以假定在调用 HEAP-INCREASE-KEY 时, $A[1...A.\text{heap-size}]$ 是满足最大堆性质的。
23 |
24 | ##### Solution
25 |
26 | 证明:采用“初始化-保持-终止”的方法来证明不变式,并说明 HEAP-INCREASE-KEY 的正确性。
27 |
28 | 初始化:第一次迭代开始前, $A$ 是一个最大堆,除非 $A[i] > A[PARENT(i)]$ ,因为算法第1-3行只是在一个原本的最大堆基础上增大了 $A[i]$ 的值,所以 $A[i]$ 依旧会比它的孩子们大,唯一可能破坏最大堆性质的就是 $A[i]$ 过于大,使得 $A[i] > A[PARENT(i)]$ 了。
29 |
30 | 保持:如果迭代开始前,$A$ 是一个最大堆,除非 $A[i] > A[PARENT(i)]$ ,根据算法第 4 行的判断条件,
31 |
32 | - 如果 $A$ 是一个最大堆,有 $A[i] \le A[PARENT(i)]$ ,则循环终止,我们已经得到了一个最大堆。
33 |
34 | - 如果 $A[i] > A[PARENT(i)]$ ,则进入循环体,算法第 5 行交换了 $A[i]$ 与 $A[PARENT(i)]$ 的值,此时 $A[i] < A[PARENT(i)]$ 满足最大堆性质,只不过 $A[PARENT(i)]$ 的增大可能会使得
35 |
36 | $$
37 | A[PARENT(i)] > A[PARENT(PARENT(i))]
38 | $$
39 |
40 | - 所以 $A$ 是最大堆,除非 $A[PARENT(i)] > A[PARENT(PARENT(i))]$ 。循环保持了不变式,因为根据算法第 6 行,下一次循环的 $i = PARENT(i)$ 。
41 |
42 | 终止:最后一次迭代结束时,根据不变式,$A$ 是一个最大堆,除非 $A[i] > A[PARENT(i)]$ 。
43 |
44 | - 如果是因为 $A[i] \le A[PARENT(i)]$ 结束的迭代,根据循环不变式,我们有 $A$ 是一个最大堆;
45 |
46 | - 如果是因为 $i = 1$ 结束的不变式,由于不存在 $PARENT(1)$ ,所以不可能 $A[1] > A[PARENT(1)]$ ,因此 $A$ 是一个最大堆。
47 |
48 | 综上,首先算法第 1 到 3 行显然增加了 $A[i]$ 的关键字值到 $key$ ,其次我们已经论证了算法的第 4 到 6 行保持了最大堆性质,因此,HEAP-INCREASE-KEY 算法在保持了最大堆性质的前提下增加了 $A[i]$ 的关键字值到 $key$ ,它是正确的。
49 |
50 |
51 | #### Problem 3 (教材习题 6.5-6)
52 |
53 | 在 HEAP-INCREASE-KEY 的第5行的交换操作中,一般需要通过三次赋值来完成。想一想如何利用 INSERTION-SORT (见[第1讲PPT第9页](/slides/lec01-getting-started.pdf#page=9))内循环部分的思想,只用一次赋值就完成这一交换操作?
54 |
55 | ##### Solution
56 |
57 | 和插入排序类似的思路:先腾出一个关键字为 $key$ 的元素的位置,然后再赋 $A[i] = key$ 。
58 |
59 |
60 |
61 |
62 | #### Problem 4 (教材习题 6.5-7)
63 |
64 | 试说明如何使用优先队列来实现一个先进先出队列,以及如何使用优先队列来实现栈?(先进先出队列和栈的定义见教材10.1节)
65 |
66 | ##### Solution
67 |
68 | 实现思路如下:
69 |
70 | - 将元素按照优先级递减的顺序插入最大优先队列就可以实现先进先出队列了;
71 |
72 | - 将元素按照优先级递增的顺序插入最大优先队列就可以实现栈了。
73 |
74 | 不过这种实现先进先出队列和栈的方式是不高效的(因为所有操作都是 $O(\log n)$ 的,而我们完全可以很轻松地实现 $O(1)$ 操作的先进先出队列和栈);并且,如果优先级可以上溢或者下溢的话,我们可能还需要在优先级溢出的时候为所有元素重新分配优先级。
75 |
76 |
77 | #### Problem 5 (教材习题 6.5-8)
78 |
79 | $\text{HEAP-DELETE}(A, i)$ 操作能够将结点 $i$ 从堆 $A$ 中删除。对于一个包含 $n$ 个元素的最大堆,请设计一个能够在 $O(\log n)$ 时间内完成的 $\text{HEAP-DELETE}$ 操作。
80 |
81 | ##### Solution
82 |
83 | 下面给出两种可能的实现方式,复杂度都是 $O(\log n)$ 。区别是,
84 |
85 | - 第一种直接复用已有的算法,不需要什么额外的代码,但是复杂度中的常数系数会更大一些;
86 |
87 | - 第二种需要多写一些代码和判断逻辑,但是复杂度中的常数系数会小一些。
88 |
89 |
90 |
91 |
92 | #### Problem 6 (教材习题 6.5-9)
93 |
94 | 请设计一个时间复杂度为 $O(n\log k)$ 的算法,它能够将 $k$ 个有序链表合并为一个有序链表,这里 $n$ 是所有输入链表包含的总的元素个数。(提示:使用最小堆来完成 $k$ 路归并。)
95 |
96 | ##### Solution
97 |
98 |
99 |
100 | 算法思路:
101 |
102 | 1. 用所有有序链表的第 1 个元素建立大小为 $k$ 的最小堆,需要 $O(k)$ 的时间;
103 |
104 | 2. 从最小堆中取出最小元素放入结果链表,需要 $O(\log k)$ 的时间;如果这个最小元素的来源链表非空,则将该链表现在的头部元素插入最小堆,需要 $O(\log k)$ 的时间;
105 |
106 | 3. 直到最小堆为空为止,一共需要重复第 2 步 $n$ 次。
107 |
108 | 综上,该算法的运行时间为 $O(n\log k)$ 。
109 |
110 | > 其实这里不一定非得是链表,顺序表(数组的数据结构叫法)也行。如果是顺序表的话可以用下标操作来表示取走元素的操作,能够在常数项时间内完成。
111 |
112 |
113 |
114 |
--------------------------------------------------------------------------------
/source/_posts/solution6/exe6.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 练习 6 解答
3 | date: 2023-1-25 23:50:00
4 | description: 快速排序相关的习题解答
5 | ---
6 |
7 | #### Problem 1 (教材习题 7.2-5)
8 |
9 | 假设快速排序的每一层所做的划分的比例都是 $1-\alpha : \alpha$ ,其中 $0 < \alpha \le 1/2$ 且是一个常数。试证明:在相应的递归树中,叶结点的最小深度大约是 $- \log n / \log\alpha$ ,最大深度大约是 $-\log n / \log(1-\alpha)$ (无需考虑舍入问题)。
10 |
11 | ##### Solution
12 |
13 | 证明:递归树的最小深度相当于:每次都选择最小的子问题,需要多少次才能到达基础情况,假设需要 $k$ 次,由于 $0 < \alpha \le 1/2$ ,即 $\alpha < 1 - \alpha$ ,我们每次都应该选择规模为 $\alpha\cdot n$ 的子问题,才能更快到达基础情况,具体地: $\alpha^k\cdot n \approx 1$,即
14 |
15 | $$
16 | k \approx \log_{\alpha}(1 / n) = - \log n / \log\alpha
17 | $$
18 |
19 | 递归树的最大深度相当于:每次都选择最大的子问题,需要多少次才能到达基础情况,假设需要 $k$ 次,和之前类似,我们很容易得到 $(1 - \alpha)^k \cdot n \approx 1$ ,即
20 |
21 | $$
22 | k \approx \log_{1-\alpha}(1/n) = -\log n / \log(1-\alpha)
23 | $$
24 |
25 |
26 | #### Problem 2 (教材习题 7.2-6)
27 |
28 | 试证明:在一个随机输入的数组上,对于任何常数 $0 < \alpha \le 1/2$ ,PARTITION 产生比 $1 - \alpha : \alpha$ 更平衡的划分的概率约为 $1 - 2\alpha$ 。
29 |
30 | ##### Solution
31 |
32 | 证明:考虑产生一个更糟糕的划分的概率,为了产生一个比 $1 - \alpha : \alpha$ 更糟糕的划分, PARTITION 算法必须选择前 $\alpha n$ 小的或者前 $\alpha n$ 大的元素作为主元,这两者的概率都约为 $\alpha n / n = \alpha$ ,于是产生一个更糟糕的划分的概率是 $\alpha + \alpha = 2\alpha$ 。从而,产生一个更平衡的划分的概率约为 $1 - 2 \alpha$ 。
33 |
34 |
35 | #### Problem 3 (教材习题 7.4-1改)
36 |
37 | 证明:在递归式
38 |
39 | $$
40 | T(n) = \max_{0 \le q \le n - 1}(T(q) + T(n - q - 1)) + \Theta(n)
41 | $$
42 |
43 | 中, $T(n) = \Theta(n^2)$ 。
44 |
45 | ##### Solution
46 |
47 | 证明:不失一般性,将递归式改写为 $T(n) = \max_{0 \le q \le n - 1}(T(q) + T(n - q - 1)) + dn$ ,下面使用代入法证明 $T(n) = \Theta(n^2)$ 。
48 |
49 | 先证 $T(n) = O(n^2)$ 。假设 $T(n) \le cn^2$ ,代入递归式有:
50 |
51 | $$
52 | \begin{aligned}
53 | T(n) &\le \max_{0 \le q \le n - 1}(cq^2 + c(n - q - 1)^2) + dn\\
54 | &= c\max_{0 \le q \le n - 1}(q^2 + (n - q - 1)^2) + dn\\
55 | &= c(n-1)^2 + dn\\
56 | &= cn^2 + (d - 2c)n + c\\
57 | &\le cn^2
58 | \end{aligned}
59 | $$
60 |
61 | 其中,倒数第 3 步是二次函数的区间最值,最后一步取 $c \ge d/2, n \ge c / (2c-d)$ 即可。
62 |
63 | 再证 $T(n) = \Omega(n^2)$ 。假设 $T(n) \ge cn^2$ ,代入递归式有:
64 |
65 | $$
66 | \begin{aligned}
67 | T(n) &\ge \max_{0 \le q \le n - 1}(cq^2 + c(n - q - 1)^2) + dn\\
68 | &= c\max_{0 \le q \le n - 1}(q^2 + (n - q - 1)^2) + dn\\
69 | &= c(n-1)^2 + dn\\
70 | &= cn^2 + (d - 2c)n + c\\
71 | &> cn^2
72 | \end{aligned}
73 | $$
74 |
75 | 其中,最后一步取 $0 < c < d/2$ 即可。
76 |
77 | 综上, $T(n) = \Theta(n^2)$ 。
78 |
79 |
80 | #### Problem 4 (教材习题 7.4-5)
81 |
82 | 当输入数据已经“几乎有序”时,插入排序速度很快。在实际应用中,我们可以利用这一特点来提高快速排序的速度。当对一个长度小于 $k$ 的子数组调用快速排序时,让它不做任何排序就返回。当上层的快速排序调用返回后,对整个数组运行插入排序来完成排序过程。试证明:这一排序算法的期望时间复杂度为 $O(nk + n\log(n/k))$ ;并说明我们应该如何选择 $k$ 。
83 |
84 | ##### Solution
85 |
86 | 证明:快速排序的期望时间复杂度和每次划分都平衡的最好时间复杂度是同阶的,因此我们不妨设每次划分都是平衡的,求得的复杂度就是期望复杂度。记运行时间为 $T(n)$ ,则题设算法的递归式为
87 |
88 | $$
89 | T(n) = \begin{cases}
90 | 2T(n/2) + O(n), & n > k\\
91 | O(n^2), & n <= k
92 | \end{cases}
93 | $$
94 |
95 | 用递归树法不难发现,递归树一共有 $\log(n/k) + 1$ 层,内部结点每层的代价为 $O(n)$ ;有 $n/k$ 个叶子结点,每个叶子结点的代价为 $O(k^2)$ ,则总代价为递归树所有内部结点和叶子结点的代价和,为
96 |
97 | $$
98 | \log(n / k)\cdot O(n) + (n / k)\cdot O(k^2) = O(nk + n\log(n/k))
99 | $$
100 |
101 | 对于 $k$ 的选择,我们的原则是复杂度优于直接快排的 $O(n\log n)$ 即可,不过这里由于渐近记号隐藏了系数,会导致不等式无解,我们记插入排序的复杂度系数为 $c_i$ ,快速排序的复杂度系数为 $c_q$ ,则需要
102 |
103 | $$
104 | c_q n\log n > c_i nk + c_q n\log(n/k) \Leftrightarrow \frac{\log k}{k} > \frac{c_i}{c_q}
105 | $$
106 |
107 | 取满足上述不等式条件的 $k$ 即可。不过在编程实践中,如果要这样处理,我们通常是根据具体的实验数据来选取一个合适的 $k$ 的。
108 |
109 |
110 | #### Problem 5 (教材习题 7.4-6)
111 |
112 | 考虑对 PARTITION 过程做这样的修改:从数组 $A$ 中随机选出三个元素,并用这三个元素的中位数(即这三个元素按大小排在中间的值)对数组进行划分。求以 $a$ 的函数形式表示的、最坏划分比例为 $\alpha : (1-\alpha)$ 的近似概率,其中 $0 < \alpha < 1$ 。
113 |
114 | ##### Solution
115 |
116 | 不失一般性,假设数组 $A$ 中的元素是 $1, 2,\cdots, n$ 的一个排列。令 $k$ 是随机选出三个数字中的中位数,不妨设 $\alpha \le 1 / 2$ ,则最坏划分比例为 $\alpha : (1-\alpha)$ 的概率为 $\alpha n \le k \le (1-\alpha)n$ 的概率。
117 |
118 | 考虑相反事件,即 $k < \alpha n$ 或者 $k > (1-\alpha)n$ ,其概率相当于随机选出三个元素,至少有两个在 $[1, \alpha n]$ 中或者至少有两个在 $[(1-\alpha)n, n]$ 中的概率,并且这两件事情概率是相等的,都是
119 |
120 | $$
121 | 3\alpha^2(1-\alpha) + \alpha^3 = 3\alpha^2 - 2\alpha^3
122 | $$
123 |
124 | 于是,最坏划分比例为 $\alpha : (1-\alpha)$ 的概率为 $1 - 2(3\alpha^2 - 2\alpha^3) = 1 - 6\alpha^2 + 4\alpha^3$ 。
125 |
126 |
127 |
--------------------------------------------------------------------------------
/source/_posts/solution6/hw6-2.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 作业 6-2 解答
3 | date: 2023-1-28 02:02:00
4 | description: 快速排序课后作业题解答
5 | ---
6 |
7 | #### Problem 1 (教材习题 7-4)
8 |
9 | **(快速排序的栈深度)** [第6讲PPT第5页](/slides/lec06-quicksort.pdf#page=5)中的 QUICKSORT 算法包含了两个对其自身的递归调用。在调用 PARTITION 后, QUICKSORT 分别递归调用了左边的子数组和右边的子数组。 QUICKSORT 中的第二个递归调用并不是必须的。我们可以用一个循环控制结构来代替它。这一技术称为 **尾递归** (tail recursion),好的编译器都提供这一功能。考虑下面这个版本的快速排序,它模拟了尾递归情况:
10 |
11 |
12 |
13 | 1. 证明:TAIL-RECURSIVE-QUICKSORT($A, 1, A.length$) 能正确地对数组 $A$ 进行排序。
14 |
15 | 编译器通常使用 **栈** 来存储递归执行过程中的相关信息,包括每一次递归调用的参数等。最新调用的信息存在栈的顶部,而第一次调用的信息存在栈的底部。当一个过程被调用时,其相关信息被 **压入** 栈中;当它结束时,其信息则被 **弹出** 。因为我们假设数组参数是用指针来表示的,所以每次过程调用只需要 $O(1)$ 的栈空间。 **栈深度** 是在一次计算中会用到的栈空间的最大值。
16 |
17 | 2. 请描述一种场景,使得针对一个包含 $n$ 个元素数组的 TAIL-RECURSIVE-QUICKSORT 的栈深度是 $\Theta(n)$ 。
18 |
19 | 3. 修改 TAIL-RECURSIVE-QUICKSORT 的代码,使其最坏情况下的栈深度是 $\Theta(\log n)$ ,并且能够保持 $O(n\log n)$ 的期望时间复杂度。
20 |
21 | ##### Solution
22 |
23 | 1. 用数学归纳法证明。
24 |
25 | 基础情况:若数组 $A$ 只有一个元素,即 $p = r$ ,算法第 1 行循环条件为否,数组保持不动,排序显然正确。
26 |
27 | 归纳假设:假设 TAIL-RECURSIVE-QUICKSORT 对于长度小于 $n$ 的数组都能够正确排序。
28 |
29 | 归纳步骤:算法第 3 行我们调用 PARTITION 来划分数组并得到主元下标 $q$ ,根据归纳假设,算法第 4 行的递归调用正确地将 $A[p..q-1]$ 排好了序。算法第 5 行将 $p$ 更新为 $q + 1$ ,然后重复上述步骤,对剩下的数组排序,根据归纳假设,每次循环的第 4 行总是能够正确地对一个子数组进行排序。循环直到不再剩下未排序的数组为止。因此, TAIL-RECURSIVE-QUICKSORT 能够对于长度为 $n$ 的数组正确排序。
30 |
31 | > 归纳步骤中,关于算法第 1 到 5 行的 **while** 循环的作用,严谨的书写应当使用循环不变式来证明,这里因为问题比较简单,为了节省笔墨,仅作了简要描述。
32 |
33 | 综上所述,TAIL-RECURSIVE-QUICKSORT($A, 1, A.length$) 能正确地对数组 $A$ 进行排序。
34 |
35 | 2. 比如说输入已经排好序的时候,栈深度为 $\Theta(n)$ 。因为在输入排好序的情况下,每次划分后右数组的长度都为 $0$ ,左数组的长度为 $n - 1$ ,所以,在 $p < r$ 条件违反之前会有 $n - 1$ 次递归调用。而每次调用需要 $O(1)$ 的空间,所以栈深度为 $\Theta(n)$ 。
36 |
37 | 3. 算法的基本思路是,每次迭代递归地解决划分的两个子数组中较小的那个,较大的那个子数组留待下一次迭代重新进行划分。修改后的算法伪代码如下:
38 |
39 |
40 |
41 | 这样修改是为了能够尽可能的少在栈中储存数据,较小规模的递归调用意味着较少的栈空间消耗。
42 |
43 |
44 | #### Problem 2 (教材习题 7-5)
45 |
46 | **(三数取中划分)** 一种改进 RANDOMIZED-QUICKSORT 的方法是在划分时,要从子数组中更细致地选择作为主元的元素(而不是简单地随机选择)。常用的做法是三数取中法:从子数组中随机挑选出三个元素,取其中位数作为主元(见[练习6问题5](/solution6/exe6/))。对于这个问题的分析,我们不妨假设数组 $A[1..n]$ 的元素是互异的且有 $n \ge 3$ 。我们用 $A'[1..n]$ 来表示已排好序的数组。用三数取中法选择主元 $x$ ,并定义 $p_i = Pr\{x = A'[i]\}$ 。
47 |
48 | 1. 对于 $i = 2, 3, \cdots, n-1$ ,请给出以 $n$ 和 $i$ 表示的 $p_i$ 的准确表达式(注意 $p_1 = p_n = 0$)。
49 |
50 | 2. 与平凡实现相比,在这种实现中,选择 $x = A'[\lfloor(n+1)/2\rfloor]$ (即 $A[1..n]$ 的中位数)的值作为主元的概率增加了多少?假设 $n \to \infty$ ,请给出两种实现下主元去到中位数的概率比值的极限值。
51 |
52 | 3. 如果我们定义一个“好”划分意味着主元选择 $x = A'[i]$ ,其中 $n / 3 \le i \le 2n/3$ 。与平凡实现相比,这种实现中得到一个好划分的概率增加了多少?(提示:用积分来近似累加和。)
53 |
54 | 4. 证明:对快速排序而言,三数取中法只影响其时间复杂度 $\Omega(n\log n)$ 的常数项因子。
55 |
56 | ##### Solution
57 |
58 | 1. $p_i$ 表示从 $n$ 个书中随机取 $3$ 个数,其中位数为 $A'[i]$ (数组 $A$ 中第 $i$ 小的元素)的概率。考虑古典概型,从 $n$ 个元素中随机取三个数,一共有 $C_n^3$ 种情况;从 $n$ 个元素中取三个数,使得 $A'[i]$ 为中位数,一共有 $(i-1)(n-i)$ 种情况——即先取一个比 $A'[i]$ 小的数字,有 $i - 1$ 种可能;再取一个比 $A'[i]$ 大的数字,有 $n - i$ 种可能。根据古典概型,有
59 | $$
60 | p_i = \frac{(i-1)(n-i)}{C_n^3} = \frac{6(n-i)(i-1)}{n(n-1)(n-2)}
61 | $$
62 |
63 | 2. 令 $i = \lfloor(n+1)/2\rfloor$ ,原本直接随机选择,$x = A'[i]$ 的概率为 $1 / n$ ,第 1 问中求出的三数取中法选择 $x = A'[i]$ 的概率为 $p_i$ ,概率增长了
64 | $$
65 | \begin{aligned}
66 | p_i - \frac{1}{n} &= \frac{(i-1)(n-i)}{C_n^3}\\
67 | &= \frac{6(n-i)(i-1)}{n(n-1)(n-2)} - \frac{1}{n}\\
68 | &= \frac{6(n-\lfloor(n+1)/2\rfloor)(\lfloor(n+1)/2\rfloor-1)}{n(n-1)(n-2)} - \frac{1}{n}
69 | \end{aligned}
70 | $$
71 | 概率比值的极限为:
72 | $$
73 | \lim_{n\to\infty}\frac{\frac{6(n-\lfloor(n+1)/2\rfloor)(\lfloor(n+1)/2\rfloor-1)}{n(n-1)(n-2)}}{\frac{1}{n}} = \frac{3}{2}
74 | $$
75 |
76 | 3. 因为取整对于求和影响不大,我们不妨假设 $n$ 是 $3$ 的倍数,我们可以通过积分来近似求和,得到“好”划分的概率为
77 | $$
78 | \begin{aligned}
79 | \sum_{i = n/3}^{2n/3} p_i
80 | &= \frac{6(n-i)(i-1)}{n(n-1)(n-2)}\\
81 | &\approx \int_{n/3}^{2n/3}\frac{6(n-x)(x-1)}{n(n-1)(n-2)}\text{d}x\\
82 | &=\frac{(\frac{13}{27}n-1)n}{(n-1)(n-2)}\\
83 | \end{aligned}
84 | $$
85 | 而原来得到一个好划分的概率为 $(2n/3 - n/3)/(n) = 1/3$ ,为了比较方便,我们可以考虑 $n$ 特别大的情况,对上述积分结果求极限,得到三数取中法在极限情况下得出“好”划分的概率为 $13/27$ 。 $13 / 27 > 1/3$ ,因此三数取中法增加了取到“好”划分的概率,具体地,在极限情况下增加了 $13/27 - 1/3 = 4/27$。
86 |
87 | 4. 即使说我们每次都能够极其幸运地取到整个待划分数组的中位数作为划分的主元,该递归算法的复杂度依旧是 $\Omega(n\log n)$ ,因为递归树至少 $\log n$ 层,每层的总代价都为 $n$ 。因此该算法只是增加了取到“好”划分的概率,但是并不会让算法突破它的最好情况运行时间的下界。
88 |
89 |
90 | #### Problem 3 (教材习题 7-6)
91 |
92 | **(对区间的模糊排序)** 考虑这样的一种排序问题:我们无法准确知道待排序的数字是什么。但对于每一个数,我们知道它属于实数轴上的某个区间。也就是说,我们得到了 $n$ 个形如 $[a_i, b_i]$ 的闭区间,其中 $a_i \le b_i$ 。我们的目标是实现这些区间的 **模糊排序** ,即对 $j = 1, 2, \cdots, n$ ,生成一个区间的排列 $\langle i_1, i_2, \cdots, i_n\rangle$ ,且存在 $c_j \in [a_{i_j}, b_{i_j}]$ ,满足 $c_1\le c_2\le \cdots \le c_n$ 。
93 |
94 | 1. 为 $n$ 个区间的模糊排序设计一个随机算法。你的算法应该具有算法的一般结构,它可以对左端点(即 $a_i$ 的值)进行快速排序,同时它也能利用区间的重叠性质来改善时间性能。(当区间重叠越来越多的时候,区间的模糊排序问题会变得越来越容易。你的算法应能充分利用这一重叠性质。)
95 |
96 | 2. 证明:在一般情况下,你的算法的期望运行时间为 $\Theta(n \log n)$ 。但是,当所有的区间都有重叠的时候,算法的期望运行时间为 $\Theta(n)$ (也就是说,存在一个值 $x$ ,对所有的 $i$ ,都有 $x \in [a_i, b_i]$ 。)你的算法不必显式地检查这种情况,而是随着重叠情况的增加,算法能自然地提高。
97 |
98 | ##### Solution
99 |
100 | 1. 这里区间排序和数字排序的整体逻辑是一样的,只是区间之间的大小比较规则和数字有所不同罢了。
101 |
102 | 根据题设定义,我们可以将区间无重叠的左右关系视为大小关系(左侧的区间小于右侧的区间),将区间的重叠关系视为“相等”关系。由于这里存在区间“相等”的情况,我们可以借鉴[作业6-1](/solution6/hw6-1/)问题2的做法,和“主元”“相等”的元素就没必要参与下一次递归了。
103 |
104 |
105 |
106 | 2. 一般情况下,上述算法的复杂度和普通的快速排序一样,都是 $O(n\log n)$ 。当输入序列的所有区间都有重叠的时候,FUZZY-PARTITION($A, 1, n$) 会返回 $(1, n)$ ,因为所有元素和主元都是“相等”的 ,从而算法第 6 到 7 行的两次递归调用都会在其第 4 行的检查处直接返回,运行时间为 $\Theta(1)$ 。因此,整个算法的运行时间就是 FUZZY-PARTITION的运行时间,不难得出为 $\Theta(n)$ 。
107 |
108 | 不难发现,重叠的区间越多,递归调用时的子问题规模就越小,算法的运行时间也会随之降低。
109 |
110 |
--------------------------------------------------------------------------------
/source/_posts/solution7/exe7-1.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 练习 7-1 解答
3 | date: 2023-1-30 13:44:00
4 | description: 决策树和计数排序相关的习题解答
5 | ---
6 |
7 | #### Problem 1 (教材习题 8.1-1)
8 |
9 | 在一棵比较排序算法的决策树中,一个叶结点可能的最小深度是多少?
10 |
11 | ##### Solution
12 |
13 | 为了得到 $a_1 \le a_2 \le \cdots \le a_n$ 的比较结果,我们至少需要 $n - 1$ 次比较,并且这 $n - 1$ 次比较能够恰好串成一个形如 $a_1 \le a_2 \le \cdots \le a_n$ 的不等式链。因此,一个叶结点可能的最小深度为 $n - 1$ 。
14 |
15 |
16 | #### Problem 2 (教材习题 8.1-2)
17 |
18 | 不用斯特林公式,给出 $\log n!$ 的渐进紧确界。利用[作业6-1](/solution6/hw6-1/)问题3第4问中涉及的技术来求累加和 $\sum\limits_{k=1}^{n} \log k$ 。
19 |
20 | ##### Solution
21 |
22 | 直接放缩求上界:
23 |
24 | $$
25 | \log n! = \sum_{k=1}^{n} \log k \le \sum_{k=1}^{n} \log n = n\log n
26 | $$
27 |
28 | 折半放缩求下界:
29 |
30 | $$
31 | \begin{aligned}
32 | \log n! &= \sum_{k=1}^{n} \log k\\
33 | &= \sum_{k=1}^{n/2} \log k + \sum_{k=n/2 + 1}^{n} \log k\\
34 | &\ge \sum_{k=1}^{n/2} 1 + \sum_{k=n/2 + 1}^{n} \log(n/2)\\
35 | &= \frac{n}{2} + \frac{n}{2}(\log n - 1)\\
36 | &= \frac{n}{2}\log n
37 | \end{aligned}
38 | $$
39 |
40 | 综上, $\frac{1}{2} n\log n \le \log n! \le n\log n$ ,所以 $\log n! = \Theta(n\log n)$ 。
41 |
42 |
43 | #### Problem 3 (教材习题 8.1-3)
44 |
45 | 证明:对于 $n!$ 种长度为 $n$ 的输入中的至少一半,不存在能达到线性运行时间的比较算法。如果只要求对 $1/n$ 的输入达到线性时间呢?$1 / 2^n$ 呢?
46 |
47 | ##### Solution
48 |
49 | 证明:作最保守的估计,假设决策树只有 $n! / 2$ 个叶子,即只能对于其中 $n!/2$ 种输入排序,剩余的输入情况直接不考虑。如果在这种假设下还不能够达到线性时间,那么无论如何也不能够达到线性时间了。
50 |
51 | 考虑其树的高度为 $h$ ,它最多有 $2 ^ h$ 个叶子,从而
52 |
53 | $$
54 | 2^h \ge \frac{n!}{2} \Rightarrow h \ge \log\frac{n!}{2} = \log n! - 2 = \Theta(n \log n) = \omega(n)
55 | $$
56 |
57 | 因此,不可能存在线性时间的算法。
58 |
59 | 类似的,对于 $1/n$ 的输入:
60 |
61 | $$
62 | 2^h \ge \frac{n!}{n} \Rightarrow h \ge \log\frac{n!}{n} = \log n! - \log n = \Theta(n \log n) = \omega(n)
63 | $$
64 |
65 | $$
66 | 2^h \ge \frac{n!}{2^n} \Rightarrow h \ge \log\frac{n!}{2^n} = \log n! - n = \Theta(n \log n) = \omega(n)
67 | $$
68 |
69 | 都不可能达到线性复杂度。
70 |
71 | > 其实这道题想说的就是 $n!$ 增长的超级快,即使你只需要其中的 $1 /2^n$ 种情况能拍好序,也必须要 $\Omega(n\log n)$ 的时间才行。
72 |
73 |
74 | #### Problem 4 (教材习题 8.1-4)
75 |
76 | 假设现在有一个包含 $n$ 个元素的待排序序列。该序列由 $n / k$ 个子序列组成,每个子序列包含 $k$ 个元素。一个给定子序列中的每个元素都小于其后继子序列中的所有元素,且大于其前驱子序列中的每个元素。因此,这个长度为 $n$ 的序列的排序转化为对 $n / k$ 个子序列中的 $k$ 个元素的排序。试证明:这个排序问题中所需比较次数的下界是 $\Omega(n\log k)$ 。(提示:简单地将每个子序列的下界进行合并是不严谨的。)
77 |
78 | ##### Solution
79 |
80 | 证明:考虑这个排序问题的任意算法的决策树,这个序列有 $(k!)^{n/k}$ 种可能的排列情况,也就是说如果算法能够正确地排序,那么它对应的决策树至少有 $(k!)^{n/k}$ 个叶子结点。记其高度为 $h$ ,则最多有 $2^h$ 个叶子结点,于是我们有
81 |
82 | $$
83 | 2^h \ge (k!)^{n/k} \Rightarrow h \ge \frac{n}{k} \log (k!) = \frac{n}{k} \Theta(k\log k) = \Theta(n\log k)
84 | $$
85 |
86 | 从而, $h = \Omega(n\log k)$ 。
87 |
88 |
89 | #### Problem 5 (教材习题 8.2-2)
90 |
91 | 试证明 COUNTING-SORT (详见[第7讲PPT第11页](/slides/lec07-sorting-in-linear-time.pdf#page=11))是稳定的(详见[第7讲PPT第15页](/slides/lec07-sorting-in-linear-time.pdf#page=15))。
92 |
93 | ##### Solution
94 |
95 | 证明:考虑任意的 $A[i] = A[j] = k$ ,其中 $i < j$ 。
96 |
97 | 下面证明 COUNTING-SORT 运行结束后, $A[i]$ 和 $A[j]$ 所对应的 $B[i']$ 和 $B[j']$ 依旧满足 $i' < j'$ 。
98 |
99 | 考虑算法第 10 到 12 行的 **for** 循环,在这个循环中,我们借助计数数组 $C$ ,利用输入数组 $A$ 中的元素构造了输出数组 $B$ 。
100 |
101 | 由于 $j > i$ ,循环会先处理 $A[j]$ ,后处理 $A[i]$ 。不妨设 $C[k] = m$ ,则算法第 11 行会将 $B[m]$ 赋值为 $A[j]$ ,即 $j' = m$ 。
102 |
103 | 之后算法第 12 行会将 $C[k]$ 赋值为 $m - 1$ ,并且循环只会减少 $C$ 中的值,因此后续迭代中 $A[i]$ 的时候,必然有 $C[k] \le m - 1$ ,从而 $i' \le m - 1 < m = j'$ 。
104 |
105 | 综上, $i' < j'$ 得证, COUNTING-SORT 是稳定的。
106 |
107 |
108 | #### Problem 6 (教材习题 8.2-3)
109 |
110 | 假设我们在 COUNTING-SORT (详见[第7讲PPT第11页](/slides/lec07-sorting-in-linear-time.pdf#page=11))的第 10 行循环的开始部分,将代码改写为
111 |
112 |
113 |
114 | 试证明该算法仍然是正确的。它还稳定吗?
115 |
116 | ##### Solution
117 |
118 | 证明:算法依旧正确。算法第 11 到 12 行从 $C$ 中取出下标,把对应 $A$ 中元素放入 $B$ 中对应位置,这个过程中是根据 $A$ 中元素从后往前的顺序进行还是从前往后的顺序进行并不会影响同一样大小的元素在结果数组 $B$ 中的整体位置。
119 |
120 | 具体地,大小为 $k$ 的元素,依旧会放置在 $B[C[k-1] + 1 .. C[k]]$ 这个区间中。只不过这个时候,同样大小为 $k$ 的元素,在 $A$ 中靠前的会被放到 $B[C[k-1] + 1 .. C[k]]$ 靠后的位置上,也就是稳定性被破坏了,并且在一个“值区间”内,输入数组和输出数组中等值元素的顺序是刚好相反的。
121 |
122 |
123 | #### Problem 7 (教材习题 8.2-4)
124 |
125 | 设计一个算法,它能够对于任何给定的介于 $0$ 到 $k$ 之间的 $n$ 个整数先进行预处理,然后在 $O(1)$ 时间内回答输入的 $n$ 个整数中有多少个落在区间 $[a..b]$ 内。你设计的算法与处理时间应为 $\Theta(n + k)$ 。
126 |
127 | ##### Solution
128 |
129 |
130 |
131 | 预先处理阶段和 COUNTING-SORT (详见[第7讲PPT第11页](/slides/lec07-sorting-in-linear-time.pdf#page=11)) 算法的第 1 到 9 行完全相同,这样的话 $C[i]$ 就包含了小于等于 $i$ 的元素个数。
132 |
133 | 要求 $[a..b]$ 内的元素个数的话,只需要计算 $C[b] - C[a-1]$ 即可。
134 |
135 |
--------------------------------------------------------------------------------
/source/_posts/solution7/exe7-2.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 练习 7-2 解答
3 | date: 2023-1-30 20:34:00
4 | description: 基数排序和桶排序相关的习题解答
5 | ---
6 |
7 | #### Problem 1 (教材习题 8.3-3)
8 |
9 | 利用循环不变式来证明基数排序(详见[第7讲PPT第17页](/slides/lec07-sorting-in-linear-time.pdf#page=17))是正确的。在你所给出的证明中,在哪里需要假设所用的底层排序算法是稳定的?
10 |
11 | ##### Solution
12 |
13 | 证明:循环不变式:在算法第 1 到 2 行的 **for** 循环每次迭代开始前,数组 $A$ 已经根据低 $i-1$ 位排好序了。
14 |
15 | 初始化:第一次迭代开始前,$i = 1$ ,数组已经 $A$ 平凡地根据低 $1 - 1 = 0$ 位排好序了,不变式显然成立。
16 |
17 | 保持:假设迭代开始前,数组 $A$ 已经根据低 $i - 1$ 位排好序了。在算法第 2 行运行结束后,数组会根据从低往高第 $i$ 位排好序。显然,第 $i$ 位不同的数字之间已经根据低 $i$ 位大小有序了,因为第 $i$ 位不同的数字之间根据低 $i$ 位比较大小只需要看最高的第 $i$ 位即可。
18 |
19 | 在第 $i$ 位相同的一组数字当中,由于第 $i$ 位相同时,根据低 $i$ 位比较大小只需要看低 $i - 1$ 位。而迭代开始前,低 $i-1$ 位已经有序,并且我们使用的是 **稳定** 排序,所以现在第 $i$ 位相同的这一组数字当中,他们的低 $i-1$ 位依旧是有序的,从而这组数字本身是根据低 $i$ 位有序的。
20 |
21 | 综上,下一次迭代开始前,数组 $A$ 已经根据低 $i$ 位排好序了,循环保持了不变式。
22 |
23 | 终止:在循环终止时, $i = d + 1$ ,根据循环不变式,数组 $A$ 已经根据低 $i - 1 = d$ 位排好序了。
24 |
25 | 而 $A$ 本身就是由 $n$ 个 $d$ 位的元素组成的,所以数组 $A$ 也已经排好序了,基数排序的算法是正确的。
26 |
27 |
28 | #### Problem 2 (教材习题 8.3-4)
29 |
30 | 说明如何在 $O(n)$ 的时间内,对 $0$ 到 $n^3 - 1$ 区间内的 $n$ 个整数进行排序。
31 |
32 | ##### Solution
33 |
34 | 首先,将这 $n$ 个整数全部写成 $n$ 进制的形式,由于所有的整数都在 $0$ 到 $n^3 - 1$ 的区间内,所以它们都可以写作 $n$ 进制下的 $3$ 位数(可以有前导 $0$ )。
35 |
36 | 接下来,对这个具有 $n$ 个 $3$ 位数(其中每一位数由 $0$ 到 $n - 1$ 这 $n$ 种可能)的序列调用基数排序,基数排序的内部稳定排序选用计数排序即可。
37 |
38 | 根据引理8.3(见[第7讲PPT第18页](/slides/lec07-sorting-in-linear-time.pdf#page=18))运行时间为 $\Theta(d(n+k)) = \Theta(3(n + n)) = \Theta(n)$ 。
39 |
40 |
41 | #### Problem 3 (教材习题 8.4-2)
42 |
43 | 解释为什么桶排序在最坏情况下的运行时间为 $\Theta(n^2)$ ?我们应该如何修改算法,使其在保持平均情况为线性时间代价的同时,最坏情况下代价为 $O(n\log n)$ ?
44 |
45 | ##### Solution
46 |
47 | 如果所有的元素都落在了一个桶中,并且在桶中是逆序的,这是我们的最坏情况,我们需要对整个长度为 $n$ 的逆序的线性表调用插入排序,时间为 $\Theta(n^2)$ 。
48 |
49 | 我们可以使用归并排序或者堆排序来代替插入排序,从而改进最坏情况的运行时间。
50 |
51 | 选择插入排序是因为它可以很好地运行在链表上,只需要常数项的额外空间。
52 |
53 | 如果选择堆排序或者插入排序,我们则需要先花 $O(n)$ 的时间将链表转化成数组,然后对数组花 $O(n\log n)$ 排序,然后花 $O(n)$ 将数组转回链表。这需要非常数项的额外空间,并且虽然这样做渐近意义上更快,但实际运行的时候可能更慢,因为到达渐近意义需要的数据规模 $n_0$ 是大的。
54 |
55 |
56 | #### Problem 4 (教材习题 8.4-4)
57 |
58 | 在单位圆内给定 $n$ 个点, $p_i = (x_i, y_i)$ ,对所有 $i = 1, 2,\cdots , n$ ,有 $0 < x_i^2 + y_i^2 \le 1$ 。假设所有的点服从均匀分布,即在单位圆的任一区域内找到给定点的概率与该区域的面积成正比。请设计一个在平均情况下有 $\Theta(n)$ 时间代价的算法,它能够按照点到原点之间的距离 $d_i = \sqrt{x_i^2 + y_i^2}$ 对这 $n$ 个点进行排序。(提示:在 BUCKET-SORT 中,设计适当的桶大小,用以反应各个点在单位圆中的均匀分布情况。)
59 |
60 | ##### Solution
61 |
62 | 想要让桶排序的平均情况运行时间达到线性时间,需要一个条件:输入数据在概率上均匀地分布在所有的桶中。
63 |
64 | 我们的输入数据是 $n$ 个单位圆内均匀分布的点,每个桶对应着单位圆内部的一个圆环,因为到原点的距离在一定范围内的点组成的集合是一个圆环。我们需要让每个圆环的面积相等,这样单位圆内均匀分布的点才会均匀的落在每个桶中。
65 |
66 | 记从内到外的第 $i$ 个圆的半径为 $r_i$ ,则这个圆的面积应该是前 $i$ 个圆环的面积之和,即整个单位圆面积的 $i / n$ ,于是我们有
67 |
68 | $$
69 | \pi r_i^2 = \frac{i}{n} \pi 1^2 \Leftrightarrow r_i = \sqrt{\frac{i}{n}}
70 | $$
71 |
72 | 因此,每个桶为 $c_i = \{(x, y) | r_{i-1} \le \sqrt{x^2 + y^2}\le r_i \}$ 。
73 |
74 | 输入数据在概率上是均匀分布在每个桶中的,因此使用桶排序就可以实现平均情况线性时间的排序了。
75 |
76 |
77 | #### Problem 5 (教材习题 8.4-5)
78 |
79 | 定义随机变量 $X$ 的概率分布函数 $P(x)$ 为 $P(x) = Pr\{X \le x\}$ 。假设有 $n$ 个随机 $X_1, X_2, \cdots ,X_n$ 服从一个连续概率分布函数 $P$ ,且它可以在 $O(1)$ 时间内被计算得到。设计一个算法,使其能够在平均情况下在线性时间内完成这些数的排序。
80 |
81 | ##### Solution
82 |
83 | 使用桶排序。对于 $n$ 个输入数据,我们只需要构造 $n$ 个桶,使得输入数据在概率上是均匀分布在每个桶中的即可。
84 |
85 | 定义 $p_i$ 满足
86 |
87 | $$
88 | P(p_i) = \frac{i}{n}
89 | $$
90 |
91 | 构造 $n$ 个桶为
92 |
93 | $$
94 | [p_0, p_1), [p_1, p_2), \cdots , [p_{n-1}, p_n)
95 | $$
96 |
97 | 这样,输入数据落在任意桶 $[p_i, p_{i+1})$ 中的概率都是
98 |
99 | $$
100 | P(p_{i+1}) - P(p_{i}) = \frac{i+1}{n} - \frac{i}{n} = \frac{1}{n}
101 | $$
102 |
103 | 从而桶排序算法可以在平均情况下线性时间内完成排序。
104 |
105 | 这里我们会发现,这 $n$ 个桶的大小是不一定一样的,因为分布函数不一定是线性函数(也就是输入不一定是均匀分布的)。
106 |
107 | 桶排序并不需要每个桶大小相同,只需要输入数据能够均匀地分布在每个桶中就可以了。
108 |
109 |
110 |
--------------------------------------------------------------------------------
/source/_posts/solution8/exe8-1.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 练习 8-1 解答
3 | date: 2023-2-3 01:40:00
4 | description: 顺序统计量相关的习题解答
5 | ---
6 |
7 | #### Problem 1 (教材习题 9.1-1)
8 |
9 | 证明:可以用最坏情况下 $n + \lceil \log n \rceil - 2$ 次比较,找到 $n$ 个元素中的第二小的元素。(提示:可以同时找最小元素。)
10 |
11 | ##### Solution
12 |
13 | 证明:可以使用锦标赛的方式来进行比较,胜者(更小的那一个)晋级,败者淘汰,记录下每个选手的手下败将有哪些。这个两两比较晋级再两两比较最终到达决赛的过程形成了一棵自底向上的二叉树。
14 |
15 | 我们可以在 $n - 1$ 次比较中选出最小元素,第二小的元素一定在这个最小元素的手下败将中,因为能够打败第二小元素的只可能是最小的元素。最小元素最多有 $\lceil \log n \rceil$ 个手下败将,因为有 $n$ 个叶结点的二叉树的高度不超过 $\lceil \log n \rceil$ 。
16 |
17 | 接下来,我们可以在这 $\lceil \log n \rceil$ 个最小元素的手下败将中再进行一轮锦标赛,使用 $\lceil \log n \rceil - 1$ 次比较找到其中的最小元素,这就是全部 $n$ 个元素中的第 2 小元素。
18 |
19 | 上述算法一共使用了 $n - 1 + \lceil \log n \rceil - 1 = n + \lceil \log n \rceil - 2$ 次比较。
20 |
21 |
22 | #### Problem 2 (教材习题 9.1-2)
23 |
24 | 证明:在最坏情况下,同时找到 $n$ 个元素中最大值和最小值的比较次数的下界是 $\lceil 3n/2\rceil - 2$ 。(提示:考虑有多少个数有成为最大值或者最小值的潜在可能,然后分析一下每一次比较会如何影响这些计数)
25 |
26 | ##### Solution
27 |
28 | 证明:用 $MAX$ 表示所有可能成为最大值的元素的集合, $MIN$ 表示可能所有可能成为最小值的元素的集合。起初,没有进行任何比较的时候,这 $n$ 个元素都有可能是最大值或者最小值,从而 $MAX$ 和 $MIN$ 里面都是全部的 $n$ 个元素。于是,寻找最大最小元素就相当于将 $MAX$ 和 $MIN$ 变成单元集。
29 |
30 | 比较操作可能会有三种情况:
31 |
32 | 1. $a \in MAX$ 和 $b \in MIN$ 比较得到 $a < b$ ,则 $MAX = MAX - \{a\}$ , $MIN = MIN - \{b\}$ ;
33 |
34 | 2. $a, b \in MAX$ 比较得到 $a < b$ ,则 $MAX = MAX - \{a\}$ ;
35 |
36 | 3. $a, b \in MIN$ 比较得到 $a < b$ ,则 $MIN = MIN - \{b\}$ 。
37 |
38 | 不难看出,第 1 种比较是最优的,因为它能够通过一次比较同时减小 $MIN$ 和 $MAX$ 两个集合的大小。至少进行 $\lceil n / 2\rceil$ 次第 1 种比较就能使得 $MIN \cap MAX = \emptyset$ ,两个集合的大小分别为 $\lfloor n / 2\rfloor$ 和 $\lceil n/2 \rceil$ 。
39 |
40 | 之后就只能进行第 2 种和第 3 种类型的比较了,它们只能让 $MAX$ 或者 $MIN$ 的大小减一。让大小为 $\lfloor n / 2\rfloor$ 的集合变成单元集至少需要 $\lfloor n / 2 \rfloor - 1$ 次比较;让大小为 $\lceil n / 2\rceil$ 的集合变成单元集至少需要 $\lceil n / 2 \rceil - 1$ 次比较 。
41 |
42 | 综上,同时寻找 $n$ 个元素的最大值和最小值所需要的比较次数的下界为
43 |
44 | $$
45 | \lceil \frac{n}{2} \rceil + \lfloor \frac{n}{2} \rfloor - 1 + \lceil \frac{n}{2} \rceil - 1 = \lceil \frac{n}{2} \rceil + n - 2 = \lceil\frac{3n}{2}\rceil - 2
46 | $$
47 |
48 |
49 | #### Problem 3 (教材习题 9.2-2)
50 |
51 | 请讨论:指示器随机变量 $X_k$ 和 $T(\max(k-1, n-1))$ 是独立的。(变量定义见[第8讲PPT第11页](/slides/lec08-medians-and-order-statistics.pdf#page=11))
52 |
53 | ##### Solution
54 |
55 | 即使我们知道 $k - 1$ 和 $\max(k-1, n-1)$ , $X_k = 1$ 的概率也是不变的,依旧是 $1 / n$ ,因为 RANDOMIZED-PARTITION 的结果不受 $k$ 的影响。也就是说,对于 $a = 0,1$ , $m = k - 1, n - k$ ,有
56 |
57 | $$
58 | Pr\{X_k = a | \max(k - 1, n - k) = m\} = Pr\{X_k = a\}
59 | $$
60 |
61 | 因此 $X_k$ 和 $\max(k - 1, n - k)$ 是独立的。又因为独立随机变量的函数依旧是独立的,所以 $X_k$ 和 $T(\max(k - 1, n - k))$ 也是独立的。
62 |
63 |
64 | #### Problem 4 (教材习题 9.2-3)
65 |
66 | 给出 RANDOMIZED-SELECT (详见[第8讲PPT第10页](/slides/lec08-medians-and-order-statistics.pdf#page=10))算法的迭代版本。
67 |
68 | ##### Solution
69 |
70 | RANDOMIZED-SELECT 算法是一个尾递归算法,它可以很容易优化成一个只需要常数项额外空间的迭代算法。
71 |
72 |
73 |
74 |
75 | #### Problem 5 (教材习题 9.3-1)
76 |
77 | 在算法 SELECT (见[第8讲PPT第14页](/slides/lec08-medians-and-order-statistics.pdf#page=14))中,输入元素被分为每组 5 个元素。如果它们被分为每组 7 个元素,该算法仍会是线性时间吗?证明:如果分成每组 3 个元素, SELECT 的运行时间不是线性的。
78 |
79 | ##### Solution
80 |
81 | 分为每组 7 个元素,该算法仍会是线性的。和每组 5 个元素类似的,除了 $x$ 所在的组和不能整除剩余的组以外,至少有一半的组中有 4 个元素大于 $x$ ,因此大于 $x$ 的元素个数至少为
82 |
83 | $$
84 | 4(\lceil\frac{1}{2}\lceil \frac{n}{7}\rceil \rceil - 2) \ge \frac{2n}{7} - 8
85 | $$
86 |
87 | 于是,子问题规模最多为 $n - (2n/7 - 8) = 5n/7 + 8$ ,从而有递归式
88 |
89 | $$
90 | T(n) \le T(\lceil \frac{n}{7} \rceil) + T(\frac{5n}{7} + 8) + O(n)
91 | $$
92 |
93 | 通过代入法不难证明 $T(n) = O(n)$ 。因此分成每组 7 个,该算法仍然是线性时间。
94 |
95 | 不过,如果分成每组 3 个的话就不是线性时间了。
96 |
97 | 证明:除了 $x$ 所在的组和不能整除剩余的组以外,至少有一半的组中有 2 个元素大于 $x$ ,因此大于 $x$ 的元素个数至少为
98 |
99 | $$
100 | 2(\lceil\frac{1}{2}\lceil \frac{n}{3}\rceil \rceil - 2) \ge \frac{n}{3} - 4
101 | $$
102 |
103 | 于是,子问题规模最多为 $n - (n/3 - 4) = 2n/3 + 4$ 。从而有递归式
104 |
105 | $$
106 | T(n) \le T(\lceil\frac{n}{3}\rceil) + T(\frac{2n}{3} + 4) + O(n)
107 | $$
108 |
109 | 考虑类似的递归式 $T(n) = T(n/3) + T(2n/3) + O(n)$ ,根据[练习3-2](/solution3/exe3-2/)问题2,其解为 $T(n) = \Theta(n\log n)$ 。因此, $T(n)$ 的最坏情况超过线性时间。
110 |
111 | 综上,如果分成每组 3 个元素, SELECT 的运行时间不是线性的。
112 |
113 |
--------------------------------------------------------------------------------
/source/_posts/solution8/exe8-2.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 练习 8-2 解答
3 | date: 2023-2-3 14:48:00
4 | description: 顺序统计量相关的习题解答
5 | ---
6 |
7 | #### Problem 1 (教材习题 9.3-3)
8 |
9 | 假设所有元素都是互异的,说明在最坏情况下,如何才能使快速排序的运行时间为 $O(n\log n)$ ?
10 |
11 | ##### Solution
12 |
13 | 由于我们已经可以通过 BFPRT 的 SELECT 算法(详见[第8讲PPT第14页](/slides/lec08-medians-and-order-statistics.pdf#page=14))在每次 PARTITION 的时候手动地在线性时间内选择中位数作为主元,从而快速排序的递归式变为
14 |
15 | $$
16 | T(n) = 2T(n / 2) + O(n)
17 | $$
18 |
19 | 不难得到,$T(n) = O(n\log n)$ ,快速排序的最坏情况运行时间也被改进到了 $O(n\log n)$ ,只不过这里面蕴含的常数系数会很大。
20 |
21 |
22 | #### Problem 2 (教材习题 9.3-4)
23 |
24 | 对一个包含 $n$ 个元素的集合,假设一个算法只使用比较来确定第 $i$ 小的元素,证明:无需额外的比较操作,它也能找到前 $i - 1$ 小的元素和前 $n - i$ 大的元素。
25 |
26 | ##### Solution
27 |
28 | 证明:构建一个有 $n$ 个结点 $\{1, 2, \cdots ,n\}$ 的有向图,如果在该算法运行过程中比较了 $A[i]$ 和 $A[j]$ ,并且得出结论 $A[i] \ge A[j]$ ,则有一条从结点 $i$ 到结点 $j$ 的有向边。从而,整张图中的有向边表示了该算法过程中发现的所有的大于等于关系。
29 |
30 | 假设第 $i$ 大的元素为 $A[x]$ ,观察到, $A[i]$ 是前 $i - 1$ 小的元素之一当且仅当图中存在一条从 $x$ 到 $i$ 的路径; $A[i]$ 是前 $n - i$ 大的元素之一当且仅当图中存在一条从 $i$ 到 $x$ 的路径。
31 |
32 | 图中的每个结点 $i$ 都必须在一条从 $x$ 出发的或者走向 $x$ 的路径上面,否则算法就无法区分 $A[i] \le A[x]$ 和 $A[i] \ge A[x]$ 。此外,如果既存在从 $x$ 到 $i$ 的路径,也存在从 $i$ 到 $x$ 的路径,说明 $A[x] \ge A[i] \wedge A[i] \ge A[x]$ ,即 $A[i] = A[x]$ ,我们可以任意地区分相等的元素是前 $i - 1$ 小还是前 $n - i$ 大。
33 |
34 | 综上,我们可以判断所有的结点到底是前 $i - 1$ 小的元素还是前 $n - i$ 大的元素,且无需额外的比较操作。
35 |
36 |
37 | #### Problem 3 (教材习题 9.3-6)
38 |
39 | 对一个包含 $n$ 个元素的集合来说, $k$ **分位数** 是指能把有序集合分成 $k$ 个等大集合的 $k - 1$ 个顺序统计量。给出一个能找出某一集合的 $k$ 分位数的 $O(n\log k)$ 时间的算法。
40 |
41 | ##### Solution
42 |
43 | 不失一般性,假设 $n$ 和 $k$ 都是 $2$ 的幂次。首先使用 SELECT 算法在 $O(n)$ 时间内寻找第 $n/2$ 个顺序统计量 ,利用该顺序统计量,我们将原问题划分为两个子问题:我们只需要分别在前 $n/2$ 和后 $n/2$ 个元素中寻找其 $k/2$ 分位数即可。该算法的递归式为
44 |
45 | $$
46 | T(n, k) = \begin{cases}
47 | O(1) & \text{if } k = 1\\
48 | 2T(n/2, k/2) + O(n) & \text{if }k > 1
49 | \end{cases}
50 | $$
51 |
52 | 代入 $O(n) \le cn$ ,解该递归式,有
53 |
54 | $$
55 | \begin{aligned}
56 | T(n, k) &\le cn + 2T(n/2, k/2)\\
57 | &\le 2cn + 4T(n/4, k/4)\\
58 | &\le 3cn + 8T(n/8, k/8)\\
59 | &\vdots\\
60 | &\le (\log k) \cdot cn + k T(n/k, 1)
61 | &=(\log k) \cdot cn + k \cdot O(1)\\
62 | &=O(n\log k)
63 | \end{aligned}
64 | $$
65 |
66 | 于是,该算法的复杂度为 $O(n\log k)$ 。其实这就是一个朴素二分的分治算法。
67 |
68 |
69 | #### Problem 4 (教材习题 9.3-7)
70 |
71 | 设计一个 $O(n)$ 时间的算法,对于一个给定的包含 $n$ 个互异元素的集合 $S$ 和一个正整数 $k \le n$ ,该算法能够确定 $S$ 中最接近中位数的 $k$ 个元素。
72 |
73 | ##### Solution
74 |
75 | 先用 $O(n)$ 的时间找到中位数。创建一个新的数组,其中每个元素是原本元素减去中位数的绝对值,需要 $O(n)$ 时间。寻找这个新数组中的前 $k$ 小元素,需要 $O(n)$ 时间。原来数组中最接近中位数的 $k$ 个元素就是和中位数作差后绝对值前 $k$ 小的元素。上述算法整体复杂度为 $O(n)$ 。
76 |
77 |
78 | #### Problem 5 (教材习题 9.3-8)
79 |
80 | 设 $X[1..n]$ 和 $Y[1..n]$ 为两个数组,每个都包含 $n$ 个有序的元素。请设计一个 $O(\log n)$ 时间的算法来找出数组 $X$ 和 $Y$ 中所有 $2n$ 个元素的中位数。
81 |
82 | ##### Solution
83 |
84 | 不失一般性,假设 $n$ 是 $2$ 的幂次。
85 |
86 |
87 |
88 | 记算法的运行时间为 $T(n)$ ,不难看出 $T(n) = T(n/2) + O(1)$ ,则 $T(n) = O(\log n)$ 。
89 |
90 |
91 | #### Problem 6 (教材习题 9.3-9)
92 |
93 | 熊教授是一家石油公司的顾问。这家公司正在计划建造一条从东向西的大型输油管道,这一管道将穿越一个有 $n$ 口油井的油田。公司希望有一条管道支线沿着最短路径从每口油井连接到主管道(方向或南或北),如下图所示。
94 |
95 |
96 |
97 |
98 |
99 | 给定每口油井的 $x$ 和 $y$ 坐标,教授应该如何选择主管道的最优位置,使得各直线的总长度最小?证明:该最优位置可以在线性时间内确定。
100 |
101 | ##### Solution
102 |
103 | 首先,东西方向的主管道长度是固定的,就是 $x$ 的最大值减去 $x$ 的最小值,这是可以在线性时间内确定的,并且和主管道的位置无关。
104 |
105 | 下面,我们只需要考虑如何选择主管道的位置,使得各个南北方向的连接管道长度之和最短即可。
106 |
107 | 假设 $y_1, y_2,\cdots,y_n$ 是所有油田的纵坐标, $y$ 是主管道的位置,则所有南北方向的连接管道的长度之和 $f(y)$ 为
108 |
109 | $$
110 | f(y) = \sum_{i=1}^{n} |y - y_i|
111 | $$
112 |
113 | 不难看出 $f(y)$ 是一个先减后增的连续函数。
114 |
115 | - 当 $n$ 为奇数时, $f(y)$ 在 $y$ 取 $y_1, y_2,\cdots,y_n$ 的中位数时取到最小值,求中位数只需线性时间。
116 |
117 | - 当 $n$ 为偶数时, $f(y)$ 在 $y$ 等于 $y_1, y_2,\cdots,y_n$ 的上下中位数之间的任何值(含上下中位数)的时候取到最小值,求上下中位数只需线性时间。
118 |
119 | 综上,如果有奇数个油田就取纵坐标的中位数位置建主管道,偶数个油田就取纵坐标上下中位数之间(含上下中位数)的任意位置建主管道。主管道的东西端点取所有油田横坐标的最小值和最大值即可。这些都可以在线性时间内确定。
120 |
121 |
--------------------------------------------------------------------------------
/source/_posts/solution9/exe9-1.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 练习 9-1 解答
3 | date: 2024-1-29 17:00:00
4 | description: 栈、队列和链表相关的习题解答
5 | ---
6 |
7 | #### Problem 1 (教材习题 10.1-4)
8 |
9 | 重写 ENQUEUE 和 DEQUEUE 的代码(见[第9讲PPT第13页](/slides/lec09-elementary-data-structures.pdf#page=13)或教材10.1节),使之能处理队列的下溢和上溢。
10 |
11 | ##### Solution
12 |
13 |
14 |
15 | #### Problem 2 (教材习题 10.1-5)
16 |
17 | 栈插入和删除元素只能在同一端进行,队列的插入和删除操作分别在两端进行,与它们不同的是,有一种 **双端队列** (Deque, Double Ended Queue),其插入和删除操作都可以在两端进行。写出 $4$ 个时间均为 $O(1)$ 的过程,分别实现在双端队列的两端插入和删除元素的操作,该队列是用一个数组实现的。
18 |
19 | ##### Solution
20 |
21 |
22 |
23 | 其中, QUEUE-EMPTY 和 QUEUE-FULL 过程见算法 1 。
24 |
25 | #### Problem 3 (教材习题 10.1-6)
26 |
27 | 说明如何用两个栈实现一个队列,并分析相关队列操作的运行时间。
28 |
29 | ##### Solution
30 |
31 |
32 |
33 | 其中, STACK-EMPTY 、 PUSH 和 POP 过程见[第9讲PPT第10页](/slides/lec09-elementary-data-structures.pdf#page=10)或教材10.1节。
34 |
35 | - ENQUEUE 的运行时间为 $O(1)$ ;
36 |
37 | - DEQUEUE 最坏情况运行时间为 $O(n)$ (其中 $n$ 是队列中的元素个数),平摊情况(Amortized)运行时间为 $O(1)$ 。
38 |
39 | - 平摊分析的技巧将会在第16讲介绍,读者若感兴趣可提前预习。
40 |
41 | #### Problem 4 (教材习题 10.1-7)
42 |
43 | 说明如何用两个队列实现一个栈,并分析相关栈操作的运行时间。
44 |
45 | ##### Solution
46 |
47 |
48 |
49 | 其中, ENQUEUE 和 DEQUEUE 过程见算法 1 。
50 |
51 | - PUSH 的运行时间为 $O(1)$ ;
52 |
53 | - POP 的运行时间为 $\Theta(n)$ (其中 $n$ 为栈中元素的个数)。
54 |
55 | #### Problem 5 (教材习题 10.2-5)
56 |
57 | 使用单向循环链表实现字典操作 INSERT 、 DELETE 和 SEARCH ,并给出所写过程的运行时间。
58 |
59 | ##### Solution
60 |
61 |
62 |
63 | - INSERT 的运行时间为 $O(1)$ ;
64 |
65 | - DELETE 的最坏情况运行时间为 $O(n)$ ;
66 |
67 | - SEARCH 的最坏情况运行水岸为 $O(n)$ 。
68 |
69 | 其中, $n$ 是链表中的元素个数。
70 |
71 | #### Problem 6 (教材习题 10.2-6)
72 |
73 | 动态集合操作 UNION 以两个不相交的集合 $S_1$ 和 $S_2$ 作为输入,并返回集合 $S = S_1 \cup S_2$ ,包含 $S_1$ 和 $S_2$ 的所有元素。该操作通常会破坏集合 $S_1$ 和 $S_2$ 。试说明如何选用一种合适的表类数据结构,来支持 $O(1)$ 时间的 UNION 操作。
74 |
75 | ##### Solution
76 |
77 | 选用有哨兵的双向循环链表示,$O(1)$ 时间的 UNION 操作如下:
78 |
79 |
80 |
81 | #### Problem 7 (教材习题 10.2-7)
82 |
83 | 给出一个 $\Theta(n)$ 时间的非递归过程,实现对一个含 $n$ 个元素的单链表的逆转。要求除存储链表本身所需的空间外,该过程只能使用固定大小的的存储空间。
84 |
85 | ##### Solution
86 |
87 |
88 |
89 | > 这是一道经典的链表面试题,这里给出[LeetCode链接](https://leetcode.cn/problems/UHnkqh/description/)。
90 |
91 |
92 | #### Problem 8 (教材习题 10.2-8)
93 |
94 | 说明如何在每个元素仅使用一个指针 $x.np$ (而不是通常的两个指针 $next$ 和 $prev$ )的情况下实现双链表。假设所有指针的值都可视为 $k$ 位的整型数,且定义
95 |
96 | $$
97 | x.np = x.next \oplus x.prev
98 | $$
99 |
100 | 即 $x.next$ 和 $x.prev$ 的 $k$ 位异或。( $NIL$ 的值用 $0$ 表示。)
101 |
102 | 注意要说明获取表头所需的信息,并说明如何在该表上实现 SEARCH 、 INSERT 和 DELETE 操作,以及如何在 $O(1)$ 时间内实现该表的逆转。
103 |
104 | ##### Solution
105 |
106 | 先简单解释一下原理,对于异或操作,恒有 $A \oplus (A \oplus B) = B$ ,于是:
107 |
108 | - 当我们知道 $x.prev$ 和 $x.np$ 时,很容易得出
109 |
110 | $$
111 | x.next = x.prev \oplus (x.prev \oplus x.next) = x.prev \oplus x.np
112 | $$
113 |
114 | - 类似地,当我们知道 $x.next$ 和 $x.np$ 时,很容易得出
115 |
116 | $$
117 | x.prev = x.next \oplus (x.next \oplus x.prev) = x.next \oplus x.np
118 | $$
119 |
120 | 也就是说,$x.prev$ 、 $x.np$ 和 $x.next$ 三者知二求一。
121 |
122 | 比如说我知道 $L.head.prev = L.nil$ 和 $L.head.np$ 之后就可以得到 $L.head.next = L.nil \oplus L.head.np$ 了,依此类推便可遍历整个链表;想要常数项时间翻转链表也就只需要让 $L.head = L.nil.prev$ 即可,这一点也可以通过异或操作完成。
123 |
124 | 具体的算法如下:
125 |
126 |
127 |
128 | 其中,
129 |
130 | - SEARCH 的最坏情况运行时间为 $O(n)$ ;
131 |
132 | - INSERT 的运行时间为 $O(1)$ ;
133 |
134 | - DELETE 的最坏情况运行时间为 $O(n)$ ;
135 |
136 | - REVERSE 的运行时间为 $O(1)$ 。
137 |
138 |
--------------------------------------------------------------------------------
/source/_posts/solution9/exe9-2.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 练习 9-2 解答
3 | date: 2024-1-30 02:11:00
4 | description: 指针、对象和有根树相关的习题解答
5 | ---
6 |
7 | #### Problem 1 (教材习题 10.3-2)
8 |
9 | 对一组同构对象用单数组表示法实现,写出过程 ALLOCATE-OBJECT 和 FREE-OBJECT 。
10 |
11 | ##### Solution
12 |
13 | 单数组表示法的示意图见[第9讲PPT第29页](/slides/lec09-elementary-data-structures.pdf#page=29)或教材10.3节,结合图示更容易理解下面的算法。
14 |
15 |
16 |
17 | #### Problem 2 (教材习题 10.3-5)
18 |
19 | 设 $L$ 是一个长度为 $n$ 的双向链表,存储于长度为 $m$ 的数组 $key$ 、 $prev$ 和 $next$ 中。假设这些数组由维护双链自由表 $F$ 的两个过程 ALLOCATE-OBJECT 和 FREE-OBJECT 进行管理。又假设 $m$ 个元素中,恰有 $n$ 个元素在链表 $L$ 上, $m - n$ 个在自由表上。
20 |
21 | 给定链表 $L$ 和自由表 $F$ ,试写出一个过程 COMPACTIFY-LIST$(L, F)$ ,用来移动 $L$ 中的元素使其占用数组中 $1, 2, \cdots , n$ 的位置,调整自由表 $F$ 以保持其正确性,并且占用数组中 $n + 1, n + 2, \cdots, m$ 的位置。要求所写的过程运行时间应为 $\Theta(n)$ ,且只使用固定量的额外存储空间。请证明所写的过程是正确的。
22 |
23 | ##### Solution
24 |
25 | 基本思路是通过交换元素位置的方式将 $L$ 的元素挪到前 $n$ 个位置。
26 |
27 |
28 |
29 | 增量算法正确性分析找循环不变式即可,上述算法的循环不变式是:第 11 到 17 行 **while** 每次迭代开始前,$prev[1..i-1]$ 、 $key[1..i-1]$ 和 $next[1..i-1]$ 按顺序依次存储着链表 $L$ 的前 $i - 1$ 个元素, $F$ 指向自由表的表头。正确性不难理解。
30 |
31 | #### Problem 3 (教材习题 10.4-2)
32 |
33 | 给定一个 $n$ 个结点的二叉树,写出一个 $O(n)$ 时间的递归过程,将该树每个结点的关键字输出。
34 |
35 | ##### Solution
36 |
37 |
38 |
39 | 以上是二叉树递归版本的前序遍历,你可以调整算法第 5 到 7 行代码的顺序来实现中序遍历和后序遍历。
40 |
41 | #### Problem 4 (教材习题 10.4-3)
42 |
43 | 给定一个 $n$ 个结点的二叉树,写出一个 $O(n)$ 时间的非递归过程,将该树每个结点的关键字输出。可以使用一个栈作为辅助数据结构。
44 |
45 | ##### Solution
46 |
47 |
48 |
49 | 以上是二叉树非递归版本的前序遍历,中序遍历和后序遍历的非递归版本就不是简单调整第 8 到 10 行代码就能实现的了,感兴趣的读者可以自行搜索相关的算法。
50 |
51 | 其中栈相关实现详见[第9讲PPT第10页](/slides/lec09-elementary-data-structures.pdf#page=10)或教材10.1节。
52 |
53 | #### Problem 5 (教材习题 10.4-4)
54 |
55 | 对于一个含 $n$ 个结点的任意有根树,写出一个 $O(n)$ 时间的过程,输出其所有关键字。该树以左孩子右兄弟表示法存储。
56 |
57 | ##### Solution
58 |
59 |
60 |
61 | 以上是递归版本的任意有根树的前序遍历算法,读者可自行思考非递归的版本应当怎样实现。
62 |
63 |
64 | #### Problem 6 (教材习题 10.4-5)
65 |
66 | 给定一个 $n$ 结点的二叉树,写出一个 $O(n)$ 时间的非递归过程,将该树每个结点的关键字输出。要求除该树本身的存储空间外只能使用固定量的额外存储空间,且在过程中不得修改该树,即使是暂时的修改也不允许。
67 |
68 | ##### Solution
69 |
70 |
71 |
72 | #### Problem 7 (教材习题 10.4-6)
73 |
74 | 任意有根树的左孩子右兄弟表示法中每个结点用到三个指针: $left\text{-}child$ 、 $right\text{-}sibling$ 和 $parent$ 。对于任何结点,都可以在常数时间到达其父结点,并在与其孩子数呈线性关系的时间内到达所有孩子结点。说明如何在每个结点中只使用两个指针和一个布尔值的情况下,使结点的父结点或者其所有孩子结点可以在与其孩子数呈线性关系的时间内到达。
75 |
76 | ##### Solution
77 |
78 | 两个指针是 $left\text{-}child$ 、 $right\text{-}sibling$ ,一个布尔值为 $right\text{-}sibling\text{-}to\text{-}parent$ ,规则如下:
79 |
80 | - $x.left\text{-}child$ 指向 $x$ 的左子女;
81 |
82 | - 当 $x$ 有右兄弟时, $x.right\text{-}sibling$ 指向 $x$ 的右兄弟,
83 |
84 | $$
85 | x.right\text{-}sibling\text{-}to\text{-}parent = \mathbf{false}
86 | $$
87 |
88 | - 当 $x$ 没有右兄弟时, $x.right\text{-}sibling$ 指向 $x$ 的父结点,
89 |
90 | $$
91 | x.right\text{-}sibling\text{-}to\text{-}parent = \mathbf{true}
92 | $$
93 |
94 | 于是,我们可以在与 $x$ 的孩子数呈线性关系的时间内到达 $x$ 的所有孩子结点是显然的;在与 $x$ 的孩子数呈线性关系的时间内到达 $x$ 的父结点的算法如下:
95 |
96 |
97 |
98 |
--------------------------------------------------------------------------------
/source/_posts/syllabus.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 【置顶】课程大纲
3 | date: 2022-12-14 21:04:00
4 | description: 本网站提供的所有资源的汇总
5 | top: 10
6 | ---
7 |
8 | > 友情提醒:习题很重要,所有的习题都提供了打印版并提供了解答,强烈建议做一遍;有一些在视频和PPT中没有涉及的拓展内容会放在习题中;同时,做习题也是一个很重要的复习巩固手段。
9 |
10 | #### 第一部分:基础知识
11 |
12 | |讲次|主题|练习题|作业题|
13 | |:-:|:-:|:-:|:-:|
14 | |1|算法基础
[`slides`](/slides/lec01-getting-started.pdf) [`video`](https://www.bilibili.com/video/BV1xD4y1V77a/)|[`exe1-1`](/exercise/exe1-1.pdf) [`sol`](/solution1/exe1-1/)
[`exe1-2`](/exercise/exe1-2.pdf) [`sol`](/solution1/exe1-2/)|[`hw1`](/homework/hw1.pdf) [`sol`](/solution1/hw1/)|
15 | |2|函数的增长
[`slides`](/slides/lec02-growth-of-functions.pdf) [`video`](https://www.bilibili.com/video/BV1vx4y1G7oQ/)|[`exe2-1`](/exercise/exe2-1.pdf) [`sol`](/solution2/exe2-1/)
[`exe2-2`](/exercise/exe2-2.pdf) [`sol`](/solution2/exe2-2/)|[`hw2`](/homework/hw2.pdf) [`sol`](/solution2/hw2/)|
16 | |3|分治策略
[`slides`](/slides/lec03-divide-and-conquer.pdf) [`video`](https://www.bilibili.com/video/BV1MM411F7Er/)|[`exe3-1`](/exercise/exe3-1.pdf) [`sol`](/solution3/exe3-1/)
[`exe3-2`](/exercise/exe3-2.pdf) [`sol`](/solution3/exe3-2/)|[`hw3`](/homework/hw3.pdf) [`sol`](/solution3/hw3/)|
17 | |4|概率分析和随机算法
[`slides`](/slides/lec04-probabilistic-analysis-and-randomized-algorithms.pdf) [`video`](https://www.bilibili.com/video/BV1Dd4y1j7n3/)|[`exe4-1`](/exercise/exe4-1.pdf) [`sol`](/solution4/exe4-1/)
[`exe4-2`](/exercise/exe4-2.pdf) [`sol`](/solution4/exe4-2/)|[`hw4`](/homework/hw4.pdf) [`sol`](/solution4/hw4/)|
18 |
19 |
20 | #### 第二部分:排序和顺序统计量
21 |
22 | |讲次|主题|练习题|作业题|
23 | |:-:|:-:|:-:|:-:|
24 | |5|堆排序
[`slides`](/slides/lec05-heapsort.pdf) [`video`](https://www.bilibili.com/video/BV1mT411y7Hx/)|[`exe5-1`](/exercise/exe5-1.pdf) [`sol`](/solution5/exe5-1/)
[`exe5-2`](/exercise/exe5-2.pdf) [`sol`](/solution5/exe5-2/)|[`hw5`](/homework/hw5.pdf) [`sol`](/solution5/hw5/)|
25 | |6|快速排序
[`slides`](/slides/lec06-quicksort.pdf) [`video`](https://www.bilibili.com/video/BV1Qv4y167Jb/)|[`exe6`](/exercise/exe6.pdf) [`sol`](/solution6/exe6/)|[`hw6-1`](/homework/hw6-1.pdf) [`sol`](/solution6/hw6-1/)
[`hw6-2`](/homework/hw6-2.pdf) [`sol`](/solution6/hw6-2/)|
26 | |7|线性时间排序
[`slides`](/slides/lec07-sorting-in-linear-time.pdf) [`video`](https://www.bilibili.com/video/BV1yj411T7pJ/)|[`exe7-1`](/exercise/exe7-1.pdf) [`sol`](/solution7/exe7-1/)
[`exe7-2`](/exercise/exe7-2.pdf) [`sol`](/solution7/exe7-2/)|[`hw7-1`](/homework/hw7-1.pdf) [`sol`](/solution7/hw7-1/)
[`hw7-2`](/homework/hw7-2.pdf) [`sol`](/solution7/hw7-2/)|
27 | |8|中位数和顺序统计量
[`slides`](/slides/lec08-medians-and-order-statistics.pdf) [`video`](https://www.bilibili.com/video/BV1yA411z7f1/)|[`exe8-1`](/exercise/exe8-1.pdf) [`sol`](/solution8/exe8-1/)
[`exe8-2`](/exercise/exe8-2.pdf) [`sol`](/solution8/exe8-2/)|[`hw8`](/homework/hw8.pdf) [`sol`](/solution8/hw8/)|
28 |
29 |
30 | #### 第三部分:数据结构
31 |
32 | |讲次|主题|练习题|作业题|
33 | |:-:|:-:|:-:|:-:|
34 | |9|基本数据结构
[`slides`](/slides/lec09-elementary-data-structures.pdf) [`video`](https://www.bilibili.com/video/BV15U421Z7tm/)|[`exe9-1`](/exercise/exe9-1.pdf) [`sol`](/solution9/exe9-1/)
[`exe9-2`](/exercise/exe9-2.pdf) [`sol`](/solution9/exe9-2/)|[`hw9`](/homework/hw9.pdf) [`sol`](/solution9/hw9/)|
35 | |10|散列表|||
36 | |11|二叉搜索树|||
37 | |12|红黑树|||
38 | |13|数据结构的扩张|||
39 |
40 |
41 | #### 第四部分:高级设计和分析技术
42 |
43 | |讲次|主题|练习题|作业题|
44 | |:-:|:-:|:-:|:-:|
45 | |14|动态规划|||
46 | |15|贪心算法|||
47 | |16|平摊分析|||
48 |
49 |
50 | #### 第五部分:高级数据结构
51 |
52 | |讲次|主题|练习题|作业题|
53 | |:-:|:-:|:-:|:-:|
54 | |17|B 树|||
55 | |18|佩波那契堆|||
56 | |19|van Emde Boas 树|||
57 | |20|并查集|||
58 |
59 |
60 | #### 第六部分:图算法
61 |
62 | |讲次|主题|练习题|作业题|
63 | |:-:|:-:|:-:|:-:|
64 | |21|基本的图算法|||
65 | |22|最小生成树|||
66 | |23|单源点最短路径|||
67 | |24|所有结点对的最短路径|||
68 | |25|最大流|||
69 |
70 |
71 | #### 第七部分:算法问题选讲
72 |
73 | |讲次|主题|练习题|作业题|
74 | |:-:|:-:|:-:|:-:|
75 | |26|多线程算法|||
76 | |27|矩阵运算|||
77 | |28|线性规划|||
78 | |29|多项式与快速傅里叶变换|||
79 | |30|数论算法|||
80 | |31|字符串匹配|||
81 | |32|计算几何学|||
82 | |33|NP完全性|||
83 | |34|近似算法|||
84 |
85 |
--------------------------------------------------------------------------------
/source/about/cover.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JacyCui/introduction-to-algorithms/7681e65d95c0b6b48d0af6f4c574760c80def865/source/about/cover.png
--------------------------------------------------------------------------------
/source/about/index.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 关于
3 | date: 2022-12-15 16:33:54
4 | ---
5 |
6 | ### 关于本站
7 |
8 | > 在计算机出现之前,就有了算法。现在有了计算机,就需要更多的算法,算法是计算的核心。 ——《算法导论》
9 |
10 | 本站是一个《算法导论》的学习网站,笔者不定期更新视频、PPT、习题以及习题解答等学习资料。如果你想要系统学习“算法”本身,并且不满足于知其然,想要触摸“其所以然”的世界,那么这是一个适合你的网站。
11 |
12 | 本站的源代码开源于[这个github仓库](https://github.com/JacyCui/introduction-to-algorithms.git),提问和勘误或者更优质简单的题解可以在github上面提交issue,我会及时回应和增补。
13 |
14 | 有其他需求可以联系学生邮箱(201220014@smail.nju.edu.cn)或者私人邮箱(jiacaicui@163.com)。
15 |
16 | 
17 |
18 | 本站使用的教材版本是《算法导论》第3版(中文翻译参照机械工业出版社大黑书)。其实现在《算法导论》已经出了第4版(只有英文版),不过笔者内容仍以第3版为准,最后会补充第4版中新增的内容。
19 |
20 | 教材的电子版可以在这里下载:
21 |
22 | - [`英文版《算法导论》第3版PDF下载`](/book/introduction-to-algorithm-third-edition.pdf)
23 |
24 | - [`英文版《算法导论》第4版PDF下载`](/book/introduction-to-algorithm-fourth-edition.pdf)
25 |
--------------------------------------------------------------------------------
/source/assets/build-max-heap-prime.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JacyCui/introduction-to-algorithms/7681e65d95c0b6b48d0af6f4c574760c80def865/source/assets/build-max-heap-prime.png
--------------------------------------------------------------------------------
/source/assets/column-sort.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JacyCui/introduction-to-algorithms/7681e65d95c0b6b48d0af6f4c574760c80def865/source/assets/column-sort.png
--------------------------------------------------------------------------------
/source/assets/compact-list-search-prime.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JacyCui/introduction-to-algorithms/7681e65d95c0b6b48d0af6f4c574760c80def865/source/assets/compact-list-search-prime.png
--------------------------------------------------------------------------------
/source/assets/compact-list-search.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JacyCui/introduction-to-algorithms/7681e65d95c0b6b48d0af6f4c574760c80def865/source/assets/compact-list-search.png
--------------------------------------------------------------------------------
/source/assets/compare-exchange.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JacyCui/introduction-to-algorithms/7681e65d95c0b6b48d0af6f4c574760c80def865/source/assets/compare-exchange.png
--------------------------------------------------------------------------------
/source/assets/count-sort-modify.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JacyCui/introduction-to-algorithms/7681e65d95c0b6b48d0af6f4c574760c80def865/source/assets/count-sort-modify.png
--------------------------------------------------------------------------------
/source/assets/hoare-partition.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JacyCui/introduction-to-algorithms/7681e65d95c0b6b48d0af6f4c574760c80def865/source/assets/hoare-partition.png
--------------------------------------------------------------------------------
/source/assets/insertion-sort.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JacyCui/introduction-to-algorithms/7681e65d95c0b6b48d0af6f4c574760c80def865/source/assets/insertion-sort.png
--------------------------------------------------------------------------------
/source/assets/oil-pipeline.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JacyCui/introduction-to-algorithms/7681e65d95c0b6b48d0af6f4c574760c80def865/source/assets/oil-pipeline.png
--------------------------------------------------------------------------------
/source/assets/tail-recursive-quicksort.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JacyCui/introduction-to-algorithms/7681e65d95c0b6b48d0af6f4c574760c80def865/source/assets/tail-recursive-quicksort.png
--------------------------------------------------------------------------------
/source/book/introduction-to-algorithm-fourth-edition.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JacyCui/introduction-to-algorithms/7681e65d95c0b6b48d0af6f4c574760c80def865/source/book/introduction-to-algorithm-fourth-edition.pdf
--------------------------------------------------------------------------------
/source/book/introduction-to-algorithm-third-edition.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JacyCui/introduction-to-algorithms/7681e65d95c0b6b48d0af6f4c574760c80def865/source/book/introduction-to-algorithm-third-edition.pdf
--------------------------------------------------------------------------------
/source/errata/index.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 中文版教材勘误
3 | date: 2023-2-6 12:54:54
4 | ---
5 |
6 | ### 中文版教材勘误
7 |
8 | > 本页是笔者在阅读中文版原书第3版教材的过程中发现的一些错误的合集(目前全部是翻译错误,英文原版没有错),罗列如下,不定期更新。
9 |
10 |
11 | - 第 85 页第 17 行倒数第3个字:将“队”改为“堆”
12 |
13 | - 第 88 页第 2 行:将“高度为 $h$ 的堆最多包含 $\lceil n/2^{h+1} \rceil$ 个结点”改为“堆中至多有 $\lceil n/2^{h+1} \rceil$ 个高度为 $h$ 的结点” 。
14 |
15 | - 第 93 页习题 6.5-8 第 1 行最倒数第 2 个字:将“堆”改为“最大堆”。
16 |
17 | - 第 93 页习题6.5-9:原书是将 $k$ 个有序的 list 合并为一个有序的 list ,list准确翻译应该是线性表,包括顺序表和链表两种,这里翻译成链表虽然题目本身依旧可做,但特化了原本的题目。
18 |
19 | - 第 104 页上半页算法第 10 行:将 $A[j] \ge x$ 改成 $A[i] \ge x$ ,否则算法错误。
20 |
21 | - 第 105 页习题 7-5 第 b 问:将“这一概率”改为“两种实现概率的比值”,这道题问的是平凡实现和非平凡实现下,两种概率比值的极限值;而不是题目中这种非平凡的实现的概率的极限值,这个概率值的极限是不存在的。
22 |
23 | - 英文版题目:Assume that $n \to \infty$ , and give the limiting ratio of these probabilities.
24 |
25 | - 第 114 页习题 8.4-4 第 2 行第 16 个字:将“元”改为“圆”。
26 |
27 | - 第 114 页习题 8-1 第 d 问第 1 行第 4 个字:将“d”删去。
28 |
29 | - 第 115 页习题 8-2 第 d 问第 1 行:将“你设计的”四个字删去,英文版中没有这几个字,前面三问的算法不一定需要你自己设计,直接给书上已经有的算法就可以了。
30 |
31 | - 英文原文:Can you use any of your sorting algorithms from parts (a)–(c) as the sorting method used in line 2 of RADIX-SORT, so that RADIX-SORT sorts n records with b-bit keys in $O(bn)$ time? Explain how or why not.
32 |
33 | - 第 115 页习题 8-2 第 e 问第 2 行最后:将“ $O(k)$ 使用”改成“使用 $O(k)$ ” ,翻译语序有问题。
34 |
35 | - 第 116 页倒数第 5 行第 5 个字:将“反例”改成“逆否命题”,英文版中 contrapositive 意思是逆否命题,不是反例。
36 |
37 | - 第 117 页习题 8-7 第 a 问:将“当 $A[q] > A[p]$ 时”中的“当”和“时”去掉,$A[q] > A[p]$ 是需要证明的结论,不是可以使用的前提条件,有了 $A[q] > A[p]$ 之后, $B[p] = 0$ 和 $B[q] = 1$ 其实就显然了。
38 |
39 | - 英文原文: Argue that $A[q] > A[p]$ , so that $B[p] = 0$ and $B[q] = 1$ .
40 |
41 | - 第 120 页习题 9.1-1 :翻译语序有问题,整道题除了“证明:”和“(提示:...)”以外的其他部分改为“可以用最坏情况下 $n + \lceil \log n \rceil - 2$ 次比较,找到 $n$ 个元素中的第二小的元素。”,这题只用证明“可以”,给出算法即可。如果像中文版中翻译的,要证明“需要”,光给出算法是不够的,还要证明没有办法更快了。
42 |
43 | - 英文原文:Show that the second smallest of $n$ elements can be found with $n + \lceil \log n \rceil - 2$ comparisons in the worst case.
44 |
45 | - 第 124 页习题 9.3-4 第 2 行:要寻找的是“前” $i-1$ 小,不是“第” $i - 1$ 小;“前” $n-i$ 大,不是“第” $n - i$ 大,也就是要证明找到顺序统计量的同时可以不需要额外的比较操作就能够以这个顺序统计量为主元划分数组。
46 |
47 | - 英文原文:Show that it can also find the $i - 1$ smaller elements and the $n - i$ larger elements without performing any additional comparisons.
48 |
49 | - 第 128 页第 15 行最后的“具有相同关键字的集合”改成“关键字不明确的集合”,第 16 行最后的 $MAXIMUM$ 改为 $MINIMUM$。
50 |
51 | - 英文原文:In some situations, we can extend the queries $SUCCESSOR$ and $PREDECESSOR$ so that they apply to sets with nondistinct keys. For a set on $n$ keys, the normal presumption is that a call to $MINIMUM$ followed by $n - 1$ calls to $SUCCESSOR$ enumerates the elements in the set in sorted order.
52 |
53 | - 第 130 页图 10-1 注解中“修改后的 $PUSH$ 和 $POP$ 操作”改成“修改操作 $PUSH$ 和 $POP$ ”。
54 |
55 | - 英文原文:the effects of the modifying operations $PUSH$ and $POP$ 。
56 |
57 |
58 |
--------------------------------------------------------------------------------
/source/exercise/exe1-1.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JacyCui/introduction-to-algorithms/7681e65d95c0b6b48d0af6f4c574760c80def865/source/exercise/exe1-1.pdf
--------------------------------------------------------------------------------
/source/exercise/exe1-2.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JacyCui/introduction-to-algorithms/7681e65d95c0b6b48d0af6f4c574760c80def865/source/exercise/exe1-2.pdf
--------------------------------------------------------------------------------
/source/exercise/exe2-1.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JacyCui/introduction-to-algorithms/7681e65d95c0b6b48d0af6f4c574760c80def865/source/exercise/exe2-1.pdf
--------------------------------------------------------------------------------
/source/exercise/exe2-2.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JacyCui/introduction-to-algorithms/7681e65d95c0b6b48d0af6f4c574760c80def865/source/exercise/exe2-2.pdf
--------------------------------------------------------------------------------
/source/exercise/exe3-1.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JacyCui/introduction-to-algorithms/7681e65d95c0b6b48d0af6f4c574760c80def865/source/exercise/exe3-1.pdf
--------------------------------------------------------------------------------
/source/exercise/exe3-2.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JacyCui/introduction-to-algorithms/7681e65d95c0b6b48d0af6f4c574760c80def865/source/exercise/exe3-2.pdf
--------------------------------------------------------------------------------
/source/exercise/exe4-1.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JacyCui/introduction-to-algorithms/7681e65d95c0b6b48d0af6f4c574760c80def865/source/exercise/exe4-1.pdf
--------------------------------------------------------------------------------
/source/exercise/exe4-2.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JacyCui/introduction-to-algorithms/7681e65d95c0b6b48d0af6f4c574760c80def865/source/exercise/exe4-2.pdf
--------------------------------------------------------------------------------
/source/exercise/exe5-1.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JacyCui/introduction-to-algorithms/7681e65d95c0b6b48d0af6f4c574760c80def865/source/exercise/exe5-1.pdf
--------------------------------------------------------------------------------
/source/exercise/exe5-2.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JacyCui/introduction-to-algorithms/7681e65d95c0b6b48d0af6f4c574760c80def865/source/exercise/exe5-2.pdf
--------------------------------------------------------------------------------
/source/exercise/exe6.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JacyCui/introduction-to-algorithms/7681e65d95c0b6b48d0af6f4c574760c80def865/source/exercise/exe6.pdf
--------------------------------------------------------------------------------
/source/exercise/exe7-1.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JacyCui/introduction-to-algorithms/7681e65d95c0b6b48d0af6f4c574760c80def865/source/exercise/exe7-1.pdf
--------------------------------------------------------------------------------
/source/exercise/exe7-2.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JacyCui/introduction-to-algorithms/7681e65d95c0b6b48d0af6f4c574760c80def865/source/exercise/exe7-2.pdf
--------------------------------------------------------------------------------
/source/exercise/exe8-1.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JacyCui/introduction-to-algorithms/7681e65d95c0b6b48d0af6f4c574760c80def865/source/exercise/exe8-1.pdf
--------------------------------------------------------------------------------
/source/exercise/exe8-2.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JacyCui/introduction-to-algorithms/7681e65d95c0b6b48d0af6f4c574760c80def865/source/exercise/exe8-2.pdf
--------------------------------------------------------------------------------
/source/exercise/exe9-1.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JacyCui/introduction-to-algorithms/7681e65d95c0b6b48d0af6f4c574760c80def865/source/exercise/exe9-1.pdf
--------------------------------------------------------------------------------
/source/exercise/exe9-2.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JacyCui/introduction-to-algorithms/7681e65d95c0b6b48d0af6f4c574760c80def865/source/exercise/exe9-2.pdf
--------------------------------------------------------------------------------
/source/homework/hw1.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JacyCui/introduction-to-algorithms/7681e65d95c0b6b48d0af6f4c574760c80def865/source/homework/hw1.pdf
--------------------------------------------------------------------------------
/source/homework/hw2.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JacyCui/introduction-to-algorithms/7681e65d95c0b6b48d0af6f4c574760c80def865/source/homework/hw2.pdf
--------------------------------------------------------------------------------
/source/homework/hw3.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JacyCui/introduction-to-algorithms/7681e65d95c0b6b48d0af6f4c574760c80def865/source/homework/hw3.pdf
--------------------------------------------------------------------------------
/source/homework/hw4.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JacyCui/introduction-to-algorithms/7681e65d95c0b6b48d0af6f4c574760c80def865/source/homework/hw4.pdf
--------------------------------------------------------------------------------
/source/homework/hw5.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JacyCui/introduction-to-algorithms/7681e65d95c0b6b48d0af6f4c574760c80def865/source/homework/hw5.pdf
--------------------------------------------------------------------------------
/source/homework/hw6-1.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JacyCui/introduction-to-algorithms/7681e65d95c0b6b48d0af6f4c574760c80def865/source/homework/hw6-1.pdf
--------------------------------------------------------------------------------
/source/homework/hw6-2.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JacyCui/introduction-to-algorithms/7681e65d95c0b6b48d0af6f4c574760c80def865/source/homework/hw6-2.pdf
--------------------------------------------------------------------------------
/source/homework/hw7-1.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JacyCui/introduction-to-algorithms/7681e65d95c0b6b48d0af6f4c574760c80def865/source/homework/hw7-1.pdf
--------------------------------------------------------------------------------
/source/homework/hw7-2.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JacyCui/introduction-to-algorithms/7681e65d95c0b6b48d0af6f4c574760c80def865/source/homework/hw7-2.pdf
--------------------------------------------------------------------------------
/source/homework/hw8.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JacyCui/introduction-to-algorithms/7681e65d95c0b6b48d0af6f4c574760c80def865/source/homework/hw8.pdf
--------------------------------------------------------------------------------
/source/homework/hw9.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JacyCui/introduction-to-algorithms/7681e65d95c0b6b48d0af6f4c574760c80def865/source/homework/hw9.pdf
--------------------------------------------------------------------------------
/source/pseudocode/lec1/binary-addition.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |