Repository: gzc/CLRS Branch: master Commit: b7d3df5ba834 Files: 231 Total size: 656.5 KB Directory structure: gitextract_c2bux98h/ ├── .gitignore ├── C01-The-Role-of-Algorithms-in-Computing/ │ ├── 1.1.md │ ├── 1.2.md │ └── problem.md ├── C02-Getting-Started/ │ ├── 2.1.md │ ├── 2.2.md │ ├── 2.3.md │ ├── exercise_code/ │ │ ├── Insertion_sort_with_binary_search.py │ │ ├── binary-search.py │ │ ├── inversions.cpp │ │ ├── inversions.py │ │ └── merge-sort.py │ └── problem.md ├── C03-Growth-of-Functions/ │ ├── 3.1.md │ ├── 3.2.md │ └── problem.md ├── C04-Recurrences/ │ ├── 4.1.md │ ├── 4.2.md │ ├── 4.3.md │ ├── 4.4.md │ ├── exercise_code/ │ │ ├── findIndex.py │ │ └── findMissing.py │ └── problem.md ├── C05-Probabilistic-Analysis-and-Randomized-Algorithms/ │ ├── 5.1.md │ ├── 5.2.md │ ├── 5.3.md │ ├── 5.4.md │ ├── myrandom.py │ └── problem.md ├── C06-Heapsort/ │ ├── 6.1.md │ ├── 6.2.md │ ├── 6.3.md │ ├── 6.4.md │ ├── 6.5.md │ ├── d-ary-heaps.cpp │ ├── heap.cpp │ ├── main.cpp │ ├── makefile │ ├── p_queue.cpp │ ├── p_queue.h │ ├── problem.md │ └── young.cpp ├── C07-Quicksort/ │ ├── 7.1.md │ ├── 7.2.md │ ├── 7.3.md │ ├── 7.4.md │ ├── exercise_code/ │ │ ├── fuzzy_sort.py │ │ ├── hoare.py │ │ ├── quickSortWithEqualElements.cpp │ │ ├── quicksort.py │ │ └── tailrecursive.py │ ├── problem.md │ ├── quicksort.py │ └── randomized-quicksort.py ├── C08-Sorting-in-Linear-Time/ │ ├── 8.1.md │ ├── 8.2.md │ ├── 8.3.md │ ├── 8.4.md │ ├── exercise_code/ │ │ ├── in_place_counting_sort.py │ │ ├── intergerQuery.cpp │ │ ├── radixSort.cpp │ │ └── water-jugs.py │ └── problem.md ├── C09-Medians-and-Order-Statistics/ │ ├── 9.1.md │ ├── 9.2.md │ ├── 9.3.md │ ├── exercise_code/ │ │ ├── k-close2median.py │ │ ├── k-quantile.py │ │ ├── randomized-select-iterative.cpp │ │ └── second-smallest.cpp │ ├── minmax.c │ ├── problem.md │ ├── problems/ │ │ ├── i-largest.cpp │ │ ├── i-largest.py │ │ └── weighted_median.py │ ├── randomized-select.cpp │ └── worst-case-linear-time.cpp ├── C10-Elementary-Data-Structures/ │ ├── 10.1.md │ ├── 10.2.md │ ├── 10.3.md │ ├── 10.4.md │ ├── README.md │ ├── exercise_code/ │ │ ├── af-obj.c │ │ ├── deque.cpp │ │ ├── deque.py │ │ ├── dict.cpp │ │ └── traversal.cpp │ └── problem.md ├── C11-Hash-Tables/ │ ├── 11.1.md │ ├── 11.2.md │ ├── 11.3.md │ ├── 11.4.md │ ├── 11.5.md │ ├── README.md │ └── problem.md ├── C12-Binary-Search-Trees/ │ ├── 12.1.md │ ├── 12.2.md │ ├── 12.3.md │ ├── 12.4.md │ ├── BSTree.h │ ├── main.cpp │ └── makefile ├── C13-Red-Black-Trees/ │ ├── 13.1.md │ ├── 13.2.md │ ├── 13.3.md │ ├── 13.4.md │ ├── problem.md │ ├── rbtree.c │ └── rbtree.cpp ├── C14-Augmenting-Data-Structures/ │ ├── 14.1.md │ ├── 14.2.md │ ├── 14.3.md │ ├── exercise_code/ │ │ ├── m-Josephus.cpp │ │ └── test │ └── problem.md ├── C15-Dynamic-Programming/ │ ├── 15.1.md │ ├── 15.2.md │ ├── 15.3.md │ ├── 15.4.md │ ├── 15.5.md │ ├── Assembly-line-sche.c │ ├── Matrix-chain-multiplication.c │ ├── lincrs.cpp │ ├── optimalBST.cpp │ └── rodcutting.cpp ├── C16-Greedy-Algorithms/ │ ├── 16.1.md │ ├── 16.2.md │ ├── 16.3.md │ └── huffman/ │ ├── BinaryStdIn.cpp │ ├── BinaryStdIn.h │ ├── BinaryStdOut.cpp │ ├── BinaryStdOut.h │ ├── HUFFMAN.cpp │ ├── HUFFMAN.h │ ├── binary_test/ │ │ ├── main.cpp │ │ └── makefile │ ├── binarystdin_test/ │ │ ├── main.cpp │ │ └── makefile │ ├── binarystdout_test/ │ │ ├── data │ │ ├── main.cpp │ │ └── makefile │ └── huffman_test/ │ ├── compressed │ ├── huffman_test_client.cpp │ └── makefile ├── C17-Amortized-Analysis/ │ ├── 17.1.md │ ├── 17.2.md │ ├── 17.3.md │ └── 17.4.md ├── C18-B-Trees/ │ ├── 18.1.md │ ├── 18.2.md │ ├── 18.3.md │ ├── btree.cpp │ └── btree.py ├── C19-Binomial-Heaps/ │ ├── 19.1.md │ ├── 19.2.md │ ├── BinomialHeap.h │ └── Main.cpp ├── C21-Data-Structures-for-Disjoint-Sets/ │ ├── 21.1.md │ ├── 21.2.md │ ├── 21.3.md │ ├── problem.md │ └── uf.cpp ├── C22-Elementary-Graph-Algorithms/ │ ├── 22.1.md │ ├── 22.2.md │ ├── 22.3.md │ ├── 22.4.md │ ├── 22.5.md │ ├── README.md │ ├── elementary_graph_algo.py │ ├── exercise_code/ │ │ └── EulerTour.cpp │ └── problem.md ├── C23-Minimum-Spanning-Trees/ │ ├── 23.1.md │ └── 23.2.md ├── C24-Single-Source-Shortest-Paths/ │ ├── 24.1.md │ ├── 24.2.md │ ├── 24.3.md │ ├── 24.4.md │ └── README.md ├── C25-All-Pairs-Shortest-Paths/ │ ├── 25.1.md │ ├── 25.2.md │ ├── 25.3.md │ ├── Floyd_Warshall.cpp │ └── README.md ├── C26-Flow-networks/ │ ├── 26.1.md │ ├── 26.2.md │ ├── 26.3.md │ └── maxflow/ │ ├── FlowEdge.cpp │ ├── FlowNetwork.cpp │ ├── FordFulkerson.cpp │ ├── input.txt │ ├── makefile │ ├── readme.md │ ├── testFlowEdge.cpp │ └── testFlowNetwork.cpp ├── C31-Number-Theoretic-Algorithms/ │ ├── 31.1.md │ ├── 31.2.md │ ├── 31.7.md │ ├── euclid.py │ ├── exercise_code/ │ │ ├── binary2decimal.py │ │ └── lcm.py │ └── extended_euclid.py ├── C32-String-Matching/ │ ├── 32.1.md │ ├── 32.2.md │ ├── 32.3.md │ ├── 32.4.md │ ├── BF.c │ ├── BM.c │ ├── FA.c │ ├── KMP.c │ ├── README.md │ ├── RK.c │ └── exercise_code/ │ └── str_spin.c ├── C33-Computational-Geometry/ │ ├── 33.1.md │ ├── Graham_Scan.py │ ├── exercise_code/ │ │ ├── area.cpp │ │ ├── colinear.cpp │ │ ├── convex_polygon.cpp │ │ ├── pointpolygon.cpp │ │ ├── polarCMP.cpp │ │ └── ray_intersection.cpp │ └── twoline.cpp ├── C35-Approximation-Algorithms/ │ ├── 35.1.md │ └── 35.2-5.md ├── LICENSE ├── README.md └── other/ ├── Karatsuba/ │ ├── Karatsuba.cpp │ ├── main.cpp │ └── makefile ├── segmentTree.cpp ├── stringSpilit.cpp └── trie.cpp ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ .DS_Store *.out *.log *.gz *.aux *.tex images/ Tools/ ================================================ FILE: C01-The-Role-of-Algorithms-in-Computing/1.1.md ================================================ ### Exercises 1.1-1 *** Give a real-world example in which one of the following computational problems appears: sorting, determining the best order for multiplying matrices, or finding the convex hull. ### `Answer` * 用到排序的应用很多,比如电商网站对商品按价格进行排序 * The multiplication process in computers are time consuming than addition process. If we have three or more matrices to multiply, then we should check the order in which to multiply to reduce the number of multiplications. * [From Wiki]The problem of finding convex hulls finds its practical applications in pattern recognition, image processing, statistics, geographic information system, game theory, construction of phase diagrams, and static code analysis by abstract interpretation. It also serves as a tool, a building block for a number of other computational-geometric algorithms such as the rotating calipers method for computing the width and diameter of a point set. also application in [CV](http://docs.opencv.org/doc/tutorials/imgproc/shapedescriptors/hull/hull.html). ### Exercises 1.1-2 *** Other than speed, what other measures of efficiency might one use in a real-world setting? ### `Answer` 内存使用,资源占用(网络,磁盘等) Memory requirement, Degree of parallelism, Resource use( cpu or gpu cycles, Disk IO etc), Accessibility(Cloud/local) ### Exercises 1.1-3 *** Select a data structure that you have seen previously, and discuss its strengths and limitations. ### `Answer` 数组,访问速度快,不能动态调整大小. ### Exercises 1.1-4 *** How are the shortest-path and traveling-salesman problems given above similar? How are they different? ### `Answer` * 相同点:都是在图中找一条路径 * 不同点:最短路径只是求2个点之间的路径,但是旅行商问题要遍历全部点并且回到起点,是一个全排列问题. * In travelling salesman problem we want to know an order of delivery of stops that yields "lowest overall distance" travelled . * This "lowest overall distance" is similar to shortest path finding situation . * Shortest path is polynomially solvable but travelling -salesman is NP-Complete ### Exercises 1.1-5 *** Come up with a real-world problem in which only the best solution will do. Then come up with one in which a solution that is "approximately" the best is good enough. ### `Answer` 这题好抽象 0 0 求根如果是无理数近似就行了找不到最优的. *** Follow [@louis1992](https://github.com/gzc) on github to help finish this task. ================================================ FILE: C01-The-Role-of-Algorithms-in-Computing/1.2.md ================================================ ### Exercises 1.2-1 *** Give an example of an application that requires algorithmic content at the application level, and discuss the function of the algorithms involved. ### `Answer` 导航!!! Fingerprint matching algorithm used by forensics. Storing the fingerprints, and comparing them with the suspects prints it requires algorithms at application level. Function of algorithm is to have accurate matching and quick response. Akinator is a web application that tells what was on our mind simply by asking questions. It uses Decision trees to come to conclusion. http://en.akinator.com/ ### Exercises 1.2-2 *** Suppose we are comparing implementations of insertion sort and merge sort on the same machine. For inputs of size n, insertion sort runs in ![](http://latex.codecogs.com/gif.latex?8n^2) steps, while merge sort runs in ![](http://latex.codecogs.com/gif.latex?64nlg{n}) steps. For which values of n does insertion sort beat merge sort? ### `Answer` 解方程 ![](http://latex.codecogs.com/gif.latex?8n^2=64nlg{n}) for 2 < n < 43 ### Exercises 1.2-3 *** What is the smallest value of n such that an algorithm whose running time is ![](http://latex.codecogs.com/gif.latex?100n^2) runs faster than an algorithm whose running time is ![](http://latex.codecogs.com/gif.latex?2^n) on the same machine? ### `Answer` 还是解方程 ![](http://latex.codecogs.com/gif.latex?100n^2=2^n) for n = 15 *** Follow [@louis1992](https://github.com/gzc) on github to help finish this task. ================================================ FILE: C01-The-Role-of-Algorithms-in-Computing/problem.md ================================================ ### Problems 1 : Comparison of running times *** For each function **f**(n) and time t in the following table, determine the largest size n of a problem that can be solved in time t, assuming that the algorithm to solve the problem takes **f**(n) microseconds. ### `Answer` Item | 1 second | 1 miniute | 1 hour | 1 day | 1 month | 1 year | 1 century :----:|----:|----:|----:|----:|----:|----:|----: ![](http://latex.codecogs.com/gif.latex?\\lg{n}) | ![](http://latex.codecogs.com/gif.latex?\\2^{10^6}}) | ![](http://latex.codecogs.com/gif.latex?\\2^{6*10^7}}) | ![](http://latex.codecogs.com/gif.latex?\\2^{36*10^8}}) | ![](http://latex.codecogs.com/gif.latex?\\2^{864*10^8}}) | ![](http://latex.codecogs.com/gif.latex?\\2^{25920*10^8}}) | ![](http://latex.codecogs.com/gif.latex?\\2^{315360*10^8}}) | ![](http://latex.codecogs.com/gif.latex?\\2^{31556736*10^8}}) ![](http://latex.codecogs.com/gif.latex?\\/{n}^{1/2}) | ![](http://latex.codecogs.com/gif.latex?\\10^{12}) | ![](http://latex.codecogs.com/gif.latex?\\36*10^{14}) | ![](http://latex.codecogs.com/gif.latex?\\1296*10^{16}) | ![](http://latex.codecogs.com/gif.latex?\\746496*10^{16}) | ![](http://latex.codecogs.com/gif.latex?\\6718464*10^{18}) | ![](http://latex.codecogs.com/gif.latex?\\994519296*10^{18}) | ![](http://latex.codecogs.com/gif.latex?\\995827586973696*10^{16}) ![](http://latex.codecogs.com/gif.latex?\\/{n}) | ![](http://latex.codecogs.com/gif.latex?\\10^6) | ![](http://latex.codecogs.com/gif.latex?6*10^7) | ![](http://latex.codecogs.com/gif.latex?36*10^8) | ![](http://latex.codecogs.com/gif.latex?864*10^8) | ![](http://latex.codecogs.com/gif.latex?2592*10^9) | ![](http://latex.codecogs.com/gif.latex?31536*10^9) | ![](http://latex.codecogs.com/gif.latex?31556736*10^8) ![](http://latex.codecogs.com/gif.latex?\\/{n}\\lg{n}) | 62746 | 2801417 | 133378058 | 2755147513| 71870856404 | 797633893349 | 68654697441062 ![](http://latex.codecogs.com/gif.latex?\\/{n}^2) | 1000 | 7745 | 60000 | 293938 | 1609968 | 5615692 | 56175382 ![](http://latex.codecogs.com/gif.latex?\\/{n}^3) | 100 | 391 | 1532 | 4420 | 13736 | 31593 | 146677 ![](http://latex.codecogs.com/gif.latex?\\2^n) | 19 | 25 | 31 | 36 | 41 | 44 | 51 ![](http://latex.codecogs.com/gif.latex?\\/{n}!) | 9 | 11 | 12 | 13 | 15 | 16 | 17 *** Follow [@louis1992](https://github.com/gzc) on github to help finish this task. ================================================ FILE: C02-Getting-Started/2.1.md ================================================ ### Exercises 2.1-1 *** Using Figure 2.2 as a model, illustrate the operation of INSERTION-SORT on the array A = [31, 41, 59, 26, 41, 58]. ### `Answer` ![pic](./repo/s1/1.png) ### Exercises 2.1-2 *** Rewrite the INSERTION-SORT procedure to sort into nonincreasing instead of nondecreasing order. ### `Answer` ![pic](./repo/s1/2.png) ### Exercises 2.1-3 *** Consider the searching problem: * **Input**: A sequence of n numbers A = [a1, a2, . . . , an] and a value v. * **Output**: An index i such that v = A[i] or the special value NIL if v does not appear in A. Write pseudocode for **linear search**, which scans through the sequence, looking for v. Using a loop invariant, prove that your algorithm is correct. Make sure that your loop invariant fulfills the three necessary properties. ### `Answer` ![pic](./repo/s1/3.png) ### Exercises 2.1-4 *** Consider the problem of adding two n-bit binary integers, stored in two n-element arrays A and B. The sum of the two integers should be stored in binary form in an (n + 1)-element array C. State the problem formally and write pseudocode for adding the two integers. ### `Answer` ![pic](./repo/s1/algorithm.png) ``` C = Array[A.length+1] carry <- 0, for i <- A.length to 1 C[i+1] <- (A[i] + B[i] + carry) % 2; carry = (A[i] + B[i] + carry)/2; C[1] <- carry; return C ``` *** Follow [@louis1992](https://github.com/gzc) on github to help finish this task. ================================================ FILE: C02-Getting-Started/2.2.md ================================================ ### Exercises 2.2-1 *** Express the function ![](http://latex.codecogs.com/gif.latex?n^3/1000-100n^2-100n+3) in terms of Θ-notation. ### `Answer` Θ(n^3) ### Exercises 2.2-2 *** Consider sorting n numbers stored in array A by first finding the smallest element of A and exchanging it with the element in A[1]. Then find the second smallest element of A, and exchange it with A[2]. Continue in this manner for the first n - 1 elements of A. Write pseudocode for this algorithm, which is known as **selection sort**. What loop invariant does this algorithm maintain? Why does it need to run for only the first n - 1 elements, rather than for all n elements? Give the best-case and worst-case running times of selection sort in Θ- notation. ### `Answer` ![pic](./repo/s2/1.png) Loop invariant: at the start of each iteration of the outer for loop, the subarray A[1..i-1] consists of the i-1 smallest elements in the array A[1..n] and this subarray is in sorted order. After the first n-1 elements, the subarray A[1..n-1] contains the smallest n-1 elements, sorted, and therefore element A[n] must be the largest element 时间都是Θ(![](http://latex.codecogs.com/gif.latex?n^2)) ### Exercises 2.2-3 *** Consider linear search again (see Exercise 2.1-3). How many elements of the input sequence need to be checked on the average, assuming that the element being searched for is equally likely to be any element in the array? How about in the worst case? What are the average-case and worst-case running times of linear search in Θ-notation? Justify your answers. ### `Answer` * 平均情况应该要查找(n+1)/2个元素 * 最坏情况是n个 * Assuming equal probability of occurrence 1/n, average number of elements which need to be checked is 1/n * (1 + 2 + ... +n) = (n+1)/2. Running time is Θ(n) * Worst case, the element to search is dead last in the array. In that case n elements need to be searched. Running time is Θ(n) 所以都是Θ(n) ### Exercises 2.2-4 *** How can we modify almost any algorithm to have a good best-case running time? ### `Answer` 算法开始检测输入数据,若符合特殊条件则输出事先计算好的结果 Modify the algorithm so it checks whether the input satisfies some special case condition. If it does, output a pre-computed answer. *** Follow [@louis1992](https://github.com/gzc) on github to help finish this task. ================================================ FILE: C02-Getting-Started/2.3.md ================================================ ### Exercises 2.3-1 *** Using Figure 2.4 as a model, illustrate the operation of merge sort on the array A = [3, 41, 52, 26, 38, 57, 9, 49]. ### `Answer` ![pic](./repo/s3/1.png) ### Exercises 2.3-2 *** Rewrite the MERGE procedure so that it does not use sentinels, instead stopping once either array L or R has had all its elements copied back to A and then copying the remainder of the other array back into A. ### `Answer` [code](./exercise_code/merge-sort.py) ### Exercises 2.3-3 *** Use mathematical induction to show that when n is an exact power of 2, the solution of the recurrence ![](./repo/s3/2.png) is ![](http://latex.codecogs.com/gif.latex?T\(n\)=n\\lg{n}) 1.定义 ![](http://latex.codecogs.com/gif.latex?F\(k\)=T\(2^k\)) 2.当k = 1时,![](http://latex.codecogs.com/gif.latex?F\(1\)=T\(2\)=2=2\lg{2}=2^1\lg{2^1}) 3.假设![](http://latex.codecogs.com/gif.latex?F\(k\)=2^k\\lg{2^k}) 4.![](http://latex.codecogs.com/gif.latex?F\(k+1\)=T\(2^{k+1}\)=2T\(2^k\)+2^{k+1}) ![](http://latex.codecogs.com/gif.latex?=2*2^k\lg{2^k}+2^{k+1}=2^{k+1}\(\lg{2^k}+1\)) ![](http://latex.codecogs.com/gif.latex?=2^{k+1}\(\lg{2^k}+\lg{2}\)=2^{k+1}\lg{2^{k+1}}) ### `Answer` [code](./exercise_code/merge-sort.py) ### Exercises 2.3-4 *** Insertion sort can be expressed as a recursive procedure as follows. In order to sort A[1..n], we recursively sort A[1..n -1] and then insert A[n] into the sorted array A[1..n - 1]. Write a recurrence for the running time of this recursive version of insertion sort. ### `Answer` ![pic](./repo/s3/3.png) ### Exercises 2.3-5 *** Referring back to the searching problem (see Exercise 2.1-3), observe that if the sequence A is sorted, we can check the midpoint of the sequence against v and eliminate half of the sequence from further consideration. **Binary search** is an algorithm that repeats this procedure, halving the size of the remaining portion of the sequence each time. Write pseudocode, either iterative or recursive, for binary search. Argue that the worst-case running time of binary search is Θ(lg n). ### `Answer` ![](https://i.imgur.com/spolzzP.png) [python code](./exercise_code/binary-search.py) ### Exercises 2.3-6 *** Observe that the while loop of lines 5 - 7 of the **INSERTION-SORT** procedure in Section 2.1 uses a linear search to scan (backward) through the sorted subarray A[1..j - 1]. Can we use a binary search (see Exercise 2.3-5) instead to improve the overall worst-case running time of insertion sort to Θ(n lg n)? ### `Answer` 不可以,查找可以达到对数级的,但是依然要移动元素,依然是线性的. Although we can reduce the number of comparisons by using binary search, we still need to shift all the elements greater than key towards the end of the array to insert key. And this shifting of elements runs at Θ(n) time, even in average case (as we need to shift half of the elements). So, the overall worst-case running time of insertion sort will still be Θ(n^2). Pseudo-code: ``` A = [1 .. n]; selectionSort(A){ for (i = 2) to (i = n) // find the correct position of A[i] in array A[1 .. i-1] pos = binarySearch(1,i-1,A[i]); // shifting of elements to place A[i] in its correct position pos for (j = i-1) to (j = pos) temp = A[j+1]; A[j+1] = A[j]; A[j] = temp; endfor endfor } binarySearch(low,high,v) mid = (low + high) / 2; // we are not looking for the value v explicitly, but for its correct position if (v >= A[mid] && v < A[mid+1]) return mid else if (v < A[mid]) return binarySearch(low,mid,v) else return binarySearch(mid,high,v) endif endif ``` [python code](./exercise_code/Insertion_sort_with_binary_search.py) ### Exercises 2.3-7 *** Describe a Θ(n lg n)-time algorithm that, given a set S of n integers and another integer x, determines whether or not there exist two elements in S whose sum is exactly x. 先用mergesort进行排序,然后两根指针分别在集合的头和尾,往中间扫描~ 这个题目可以利用哈希表(散列表)达到O(n),[my code](https://github.com/gzc/leetcode/blob/master/cpp/001-010/Two%20Sum.cpp) *** Follow [@louis1992](https://github.com/gzc) on github to help finish this task. ================================================ FILE: C02-Getting-Started/exercise_code/Insertion_sort_with_binary_search.py ================================================ # Exercise 2.3-6 in book # Standalone Python version 2.7 code import os import re import math import time from random import randint def insertion_sort(array): for j, v in enumerate(array): key = v i = j - 1 while i > -1 and array[i] > key: array[i+1] = array[i] i = i - 1 array[i+1] = key def insertion_sort_v2(array): for j, v in enumerate(array): if j > 0: key = array[j] a = binary_search(array, key, j) for i in range(j, a, -1): array[i] = array[i-1] array[a] = key def binary_search(array, searchingelement, arraypart): array = list(array[:arraypart]) last = array.__len__() mid = int(last/2) min = 0 for i in range(int(math.log(last)/math.log(2)) + 1): if array[mid] == searchingelement: return mid elif array[mid] < searchingelement: min = mid mid = int((last + mid) / 2) else: last = mid mid = int((mid + min) / 2) if array[mid] < searchingelement: return mid+1 elif array[mid] > searchingelement: if mid-1 > -1: return mid-1 else: return mid else: return mid if __name__ == '__main__': array1 = [] for i in range(10000): array1.append(randint(0, 1000)) array = list(array1) t0 = time.clock() insertion_sort(array) t1 = time.clock() print "insertion_sort: " + str(t1-t0) array = list(array1) t0 = time.clock() insertion_sort_v2(array) t1 = time.clock() print "insertion_sort_v2: " + str(t1-t0) # Test results shows that worst case of improved insertion sort is O(n * (n\2) * lg(n)) # Better than insertion sort but still very bad # Tested for 1000 random elements # insertion_sort:----0.0390096090178 # insertion_sort_v2:-0.0287921815039 # Tested for 10000 random elements # insertion_sort:----3.76619711492 # insertion_sort_v2:-2.25984142782 # End of 2.3-6 in book ================================================ FILE: C02-Getting-Started/exercise_code/binary-search.py ================================================ #!/usr/bin/env python # coding=utf-8 # Standalone python code, version 2.7 # I can provide older version to compare them with each other import os import re import math import time def binary_search(array, searchingelement): last = array.__len__() mid = int(last/2) min = 0 for i in range(int(math.log(last)/math.log(2)) + 1): if array[mid] == searchingelement: return str(mid) + " th index" elif array[mid] < searchingelement: min = mid mid = int((last + mid) / 2) else: last = mid mid = int((mid + min) / 2) return null if __name__ == '__main__': array = [] for i in range(1000000): array.append(i) t0 = time.clock() print binary_search(array, 345676) t1 = time.clock() print "binary_search: " + str(t1-t0) ================================================ FILE: C02-Getting-Started/exercise_code/inversions.cpp ================================================ // Created by wander on 16/5/14. // Copyright © 2016年 W4anD0eR96. All rights reserved. // 采用左闭右开的区间描述方式 #include "bits/stdc++.h" using namespace std; void MergeWithCountInversions(vector& v, int p, int q, int r, int& cnt) { int n1 = q - p, n2 = r - q; vector L, R; for (int i = 0; i < n1; ++i) L.push_back(v[p + i]); for (int i = 0; i < n2; ++i) R.push_back(v[q + i]); L.push_back(INT_MAX); R.push_back(INT_MAX); int i = 0, j = 0; for (int k = p; k < r; ++k) { // i n1 j n2 // | | | | // |--------)|---------) // 若此时发生L[i] > R[j],则由于L[0..i) < R[j]且R[0..j) < R[j] // 故与R[j]构成逆序对的元素是L[i..n1) if (L[i] <= R[j]) { v[k] = L[i]; i += 1; } else { v[k] = R[j]; j += 1; cnt += n1 - i; { for (int u = i; u < n1; ++u) cout << "(" << L[u] << "," << R[j - 1] << "), "; } // Print Inversions } } } void MergeSort(vector& v, int p, int r, int& cnt) { if (p >= r - 1) return; int q = (p + r) >> 1; MergeSort(v, p, q, cnt); MergeSort(v, q, r, cnt); MergeWithCountInversions(v, p, q, r, cnt); } int main(int argc, const char * argv[]) { vector v = { 31, 41, 59, 26, 41, 58 }; int cntInversions = 0; cout << "v: "; for (auto i : v) cout << i << " "; cout << endl; cout << "Total inversions are: "; MergeSort(v, 0, (int)v.size(), cntInversions); cout << endl; cout << "Whose amount is: " << cntInversions << "." << endl; return 0; } ================================================ FILE: C02-Getting-Started/exercise_code/inversions.py ================================================ #!/usr/bin/env python # coding=utf-8 def merge(items, p, q, r): L = items[p:q+1] R = items[q+1:r+1] i = j = 0 k = p inversions = 0 while i < len(L) and j < len(R): if(L[i] < R[j]): items[k] = L[i] i += 1 else: items[k] = R[j] j += 1 inversions += (len(L) - i) k += 1 if(j == len(R)): items[k:r+1] = L[i:] return inversions def mergesort(items, p, r): inversions = 0 if(p < r): q = (p+r)/2 inversions += mergesort(items, p, q) inversions += mergesort(items, q+1, r) inversions += merge(items, p, q, r) return inversions items = [4,3,2,1,17] inversions = mergesort(items, 0, len(items)-1) print items,inversions ================================================ FILE: C02-Getting-Started/exercise_code/merge-sort.py ================================================ #!/usr/bin/env python # coding=utf-8 import math def merge(items, p, q, r): L = items[p:q+1] R = items[q+1:r+1] i = j = 0 k = p while i < len(L) and j < len(R): if(L[i] < R[j]): items[k] = L[i] i += 1 else: items[k] = R[j] j += 1 k += 1 if(j == len(R)): items[k:r+1] = L[i:] def mergesort(items, p, r): if(p < r): q = math.floor((p+r)/2) mergesort(items, p, q) mergesort(items, q+1, r) merge(items, p, q, r) items = [4,3,2,1,17] mergesort(items, 0, len(items)-1) print items ================================================ FILE: C02-Getting-Started/problem.md ================================================ ### Problems 1 : Insertion sort on small arrays in merge sort *** Although merge sort runs in Θ(n lg n) worst-case time and insertion sort runs in Θ(n^2) worst- case time, the constant factors in insertion sort make it faster for small n. Thus, it makes sense to use insertion sort within merge sort when subproblems become sufficiently small. Consider a modification to merge sort in which n/k sublists of length k are sorted using insertion sort and then merged using the standard merging mechanism, where k is a value to be determined. a. Show that the n/k sublists, each of length k, can be sorted by insertion sort in Θ(nk) worst-case time. b. Show that the sublists can be merged in Θ(n lg (n/k) worst-case time. c. Given that the modified algorithm runs in Θ(nk + n lg (n/k)) worst-case time, what is the largest asymptotic (Θnotation) value of k as a function of n for which the modified algorithm has the same asymptotic running time as standard merge sort? d. How should k be chosen in practice? ### `Answer` **(a)** T(n) = (n/k)\*Θ(k2) = Θ(nk) **(b)** If there are n/k sublists, then the height of the tree formed will be lg(n/k). And at each level of the tree, the complexity of merging is Θ(n). So the worst case to merge the sublists is Θ(n lg(n/k)). **(c)** Θ(nk + nlg(n/k)) = Θ(nk + nlgn - nlgk)) should be equal to Θ(nlgn) To satisfy this, `k` cannot grow faster than `lgn`, otherwise `nk` term will run worse than `Θ(nlgn)`. So `k <= Θ(lgn)` to satisfy the above condition. So largest value of `k = lgn`. **(d)** Time complexity of insertion sort = c1n2 and the time complexity of merge sort is c2nlgn. To find the value of k c1k2 <= c2klgk k <= (c2/c1)lgk Now we can check manually by putting different values of k. ### Problems 2 : Correctness of bubblesort *** Bubblesort is a popular sorting algorithm. It works by repeatedly swapping adjacent elements that are out of order. ### `Answer` a. 我们还需要证明数组里面的元素就是原来的那些元素 b. **每次循环前,子数组A[j..]的最小元素是A[j]**
Initialization:刚开始的时候,子数组只有一个元素,是A的最后一个元素,循环不变式自然成立
Maintenance:每个迭代,会比较A[j]和A[j-1]的大小,并把小的往前放
Termination:迭代结束时,j = i,A[i]是子数组A[i..]最小的元素
c. **每次循环前,A[1,i-1]是一个排序好的数组,且小于等于A[i..]中的元素** Initialization:刚开始的时候,子数组为空,循环不变式自然成立
Maintenance:每个迭代,A[i]会变成A[i..]中的最小元素
Termination:迭代结束时,i = n, 我们获得了一个排序好的数组
d. **bubblesort**的最坏运行时间与**insertion sort**一样都是Θ(n^2),但是一般来说**bubblesort**会慢点,因为它有许多的**swap**操作. ### Problems 3 : Correctness of Horner's rule *** ### `Answer` **a.** Θ(n) **b.** Naive-Poly-Eval: y = 0 for i = 0 to n m = 1 for k = 1 to i m = m·x y = y + aᵢ·m 运行时间是Θ(n2),非常慢 **c.** **Initialization:** 一开始没有项,y = 0 **Maintenance:**根据循环不变式,第i次迭代结束有 ![](http://latex.codecogs.com/gif.latex?y=a_i+x\\sum_{k=0}^{n-\(i+1\)}a_{k+i+1}x^k=a_ix^0+\\sum_{k=0}^{n-i-1}a_{k+i+1}x^{k+1}=\\sum_{k=-1}^{n-i-1}a_{k+i+1}x^{k+1}=\\sum_{k=0}^{n-i}a_{k+i}x^k) **Termination:**循环结束时 i = -1, 将i = 0代入 ![](http://latex.codecogs.com/gif.latex?y=sum_{k=0}^{n}a_{k}x^k) **d.** 前面已经证明了循环不变式,结论自然是成立的. ### Problems 4 : Inversions *** Let A[1..n] be an array of n distinct numbers. If i < j and A[i] > A[j], then the pair (i, j) is called an inversion of A. a. List the five inversions of the array 2, 3, 8, 6, 1. b. What array with elements from the set {1, 2, . . . , n} has the most inversions? How many does it have? c. What is the relationship between the running time of insertion sort and the number of inversions in the input array? Justify your answer. d. Give an algorithm that determines the number of inversions in any permutation on n elements in Θ(n lg n) worst-case time. (Hint: Modify merge sort.) ### `Answer` **a.** (1, 5), (2, 5), (3, 5), (4, 5), (3, 4) **b.** The array with decending order arrangement i.e. {n, n-1, n-2, ..., 3, 2, 1} has the most inversions. Number of inversions = Number of ways to choose two distinct element from the above set = n(n-1)/2. **c.** we know that the inner while loop of insertion sort shift the elements to left to their right position. So if there is more inversion in an array, then we need to shift more elements. Hence as the number of inversions increases, running time of insertion sort increases. **d.** [PythonCode](./exercise_code/inversions.py) [CppCode](./exercise_code/inversions.cpp) *** Follow [@louis1992](https://github.com/gzc) on github to help finish this task. ================================================ FILE: C03-Growth-of-Functions/3.1.md ================================================ ### Exercises 3.1-1 *** Let f(n) and g(n) be asymptotically nonnegative functions. Using the basic definition of Θ- notation, prove that max(f(n), g(n)) = Θ(f(n) + g(n)). ### `Answer` 我们需要证明 c1(f(n) + g(n)) <= max(f(n), g(n)) <= c2(f(n) + g(n)) 因为f和g都是非负函数,只需要令c1 = 0.5, c2 = 1即可. English: Use the definition of Θ-notation and set it up with the given values: 0 <= c1(f(n) + g(n)) <= max(f(n), g(n)) <= c2(f(n) + g(n)) ---If we can Solve All inequalities, we can then use that as proof for the definition of Θ-notation. c2 = 1: This holds because the Max must be lower than the sum of the two functions. c1 = 1/2: This holds because the Max can at its lowest be equal to 1/2 of the Sum of the two functions. 0 <= c1(f(n) + g(n)) is trivial. Thus, since all inequalities hold, we've proven that: max(f(n), g(n)) = Θ(f(n) + g(n)). ### Exercises 3.1-2 *** Show that for any real constants a and b, where b > 0, ![](http://latex.codecogs.com/gif.latex?\(n+a\)^b=\\Theta\(n^b\)) ### `Answer` Here (n + a) <= 2n, when |a| <= n and (n + a) >= n/2, when |a| <= n/2. So n >= 2a So we can write, 0 <= n/2 <= (n + a) <= 2n Now raising to the power b, we get 0 <= (n/2)b <= (n + a)b <= (2n)b 0 <= (1/2)bnb <= (n + a)b <= 2bnb Comparing this with 0 <= c1nb <= (n + a)b <= c2nb, we get c1 = (1/2)b , c2 = 2b and n0 = 2a Therefore (n + a)b = Θ(nb). ### Exercises 3.1-3 *** Explain why the statement, "The running time of algorithm A is at least O(n^2)," is meaningless. ### `Answer` O-notation确定的是一个上界,而at least确定的是一个下界 ### Exercises 3.1-4 *** Is ![](http://latex.codecogs.com/gif.latex?2^{n+1}=O\(2^n\)?)Is![](http://latex.codecogs.com/gif.latex?2^{2n}=O\(2^n\)?) ### `Answer` (1)成立,因为当c0=2时,对所有的n>=1有0<=2^(n+1)<=2*2^n。 (2)不成立,假设存在常数c使得2^(2*n)<=c*2^n,则有2*n<=lg c+n,即n<=lg c,并不存在一个常数c使得这个不等式对n成立。 English: for f(n) = O(g(n)), the definition of O-notation is: 0 <= f(n) <= c(g(n)) for all n > n0. (1) 2^(n+1) = 2(2^n) <= c*2^n. If c = 2, the inequality holds and we prove 2^(n+1) = O(2^n) to be True! Answer: Yes. (2) 2^2n = (2^n)^2. There is no possible constant c that can make 2^n into (2^n)^2. Thus, 2^2n =/= O(2^n). Answer: No. ### Exercises 3.1-5 *** Prove Theorem 3.1. *For any two functions f(n) and g(n), we have f(n) = Θ(g(n)) if and only if f(n) = O(g(n)) and f(n) = Ω(g(n)).* ### `Answer` 充分性:f(n)=Θ(g(n))意味着存在c1、c2和n0(其中n>=n0)使得0<=c1g(n)<=f(n)<=c2g(n)。我们由此可推出以下两个结论: * 存在c1和n0(其中n>=n0)使得0<=c1g(n)<=f(n),即f(n)=Ω(g(n)) * 存在c2和n0(其中n>=n0)使得0<=f(n)<=c2g(n),即f(n)=O(g(n)) 必要性:f(n)=Ω(g(n))意味着“存在c1'和n1(其中n>=n1)使得0<=c1'g(n)<=f(n)”。类似的,f(n)=O(g(n))意味着“存在c2'和n2(其中n>=n2)使得0<=f(n)<=c2'g(n)”。 令c1=c1',c2=c2',n0=max{n1, n2},由Θ的定义我们有:f(n) = Θ(g(n))。 ### Exercises 3.1-6 *** Prove that the running time of an algorithm is Θ(g(n)) if and only if its worst-case running time is O(g(n)) and its best-case running time is Ω(g(n)). ### `Answer` Theorem 3.1. ### Exercises 3.1-7 *** Prove that o(g(n)) ∩ ω(g(n)) is the empty set. ### `Answer` 假设o(g(n)) ∩ ω(g(n))不是一个空集,则等价于说:对于所有的c1,c2>0有0<=c1g(n)\=max(n1, n2)。 我们令c1=c2,可以得出悖论,因此假设不成立,所以o(g(n)) ∩ ω(g(n))是一个空集。 ### Exercises 3.1-8 *** We can extend our notation to the case of two parameters n and m that can go to infinity independently at different rates. For a given function g(n, m), we denote by O(g(n, m)) the set of functions O(g(n, m)) = {f(n, m): there exist positive constants c, n0, and m0 such that 0 ≤ f(n, m) ≤ cg(n, m)for all n≥n0 and m≥m0}. Give corresponding definitions for Ω(g(n, m)) and Θ(g(n, m)). ### `Answer` Ω(g(n,m)={f(n,m):there exist positive constants c,n0, and m0 such that 0 ≤ cg(n,m) ≤ f(n,m) for all n≥n0 or m≥m0. Θ(g(n,m)={f(n,m):there exist positive constants c1,c2,n0, and m0 such that 0 ≤ c1g(n,m) ≤ f(n,m) ≤ c2g(n,m) for all n≥n0 or m≥m0. *** Follow [@louis1992](https://github.com/gzc) on github to help finish this task. ================================================ FILE: C03-Growth-of-Functions/3.2.md ================================================ ### Exercises 3.2-1 *** Show that if f(n) and g(n) are monotonically increasing functions, then so are the functions f(n) + g(n) and f (g(n)), and if f(n) and g(n) are in addition nonnegative, then f(n) · g(n) is monotonically increasing. ### `Answer` Given that f(n) and g(n) are monotonically increasing functions, So f(n) <= f(n+1)         (1) g(n) <= g(n+1)         (2) Adding eqn. (1) and (2), we get f(n) + g(n) <= f(n+1) + g(n+1) Therefore f(n) + g(n) is monotonically increasing function. Since, g(n) <= g(n+1) and f(n) is a monotonically increasing function. So, f(g(n)) <= f(g(n+1)) Therefore f(g(n)) is a monotonically increasing function. Given f(n) >=0 and g(n) >= 0 Then multiplying eqn. (1) and (2) results into f(n) · g(n) <= f(n+1) · g(n+1) Therefore f(n) · g(n) is monotonically increasing. ### Exercises 3.2-2 *** Prove equation (3.15). ### `Answer` ![](http://latex.codecogs.com/gif.latex?a^{\\log_{b}{c}}=a^{\\frac{\\log_{a}{c}}{\\log_{a}{b}}}=\(a^{\\log_{a}{c}}\)^{\\frac{1}{\\log_{a}{b}}}=c^{\\log_{b}{a}}) ### Exercises 3.2-3 *** Prove equation (3.18). Also prove that n! = ω(2^n) and n! = o(n^n). ### `Answer` 采用暴力方法,直接证明极限![](http://latex.codecogs.com/gif.latex?\\lim\\limits_{n\\rightarrow\\infty}\\frac{\\lg{n!}}{n\\lg{n}})在一个常数区间内 ![](http://latex.codecogs.com/gif.latex?\\lim\\limits_{n\\rightarrow\\infty}\\frac{\\lg{n!}}{n\\lg{n}}=\\lim\\limits_{n\\rightarrow\\infty}\\frac{1}{n}\\sum_{k=1}^{n}\\frac{\\lg{k}}{\\lg{n}}\\le\\lim\\limits_{n\\rightarrow\\infty}\\frac{1}{n}\\sum_{k=1}^{n}\\frac{k}{n}=1) ![](http://latex.codecogs.com/gif.latex?\\lim\\limits_{n\\rightarrow\\infty}\\frac{\\lg{n!}}{n\\lg{n}}=\\lim\\limits_{n\\rightarrow\\infty}\\frac{\(\\lg{1}+\\lg{n}\)+\(\\lg{2}+\\lg{\(n-1\)}\)+...}{n\\lg{n}}\\ge\\lim\\limits_{n\\rightarrow\\infty}\\frac{\\frac{n}{2}*\\lg{n}}{n\\lg{n}}=\\frac{1}{2}) ### Exercises 3.2-4 *** Is the function ![](http://latex.codecogs.com/gif.latex?\\lceil\\lg{n}\\rceil!) polynomially bounded? Is the function ![](http://latex.codecogs.com/gif.latex?\\lceil\\lg{{\\lg{n}}}\\rceil!) polynomially bounded? ### `Answer` 多项式边界也就是函数![](http://latex.codecogs.com/gif.latex?f\(n\)\\lecn^k) 两边取对数得到![](http://latex.codecogs.com/gif.latex?\\lg{f\(n\)}\\le\\lg{c}+k\\lg{n}) 所以,如果一个函数f(n)是有多项式边界,就满足![](http://latex.codecogs.com/gif.latex?\\lg{f\(n\)}=o\(\\lg{n}\)) 令![](http://latex.codecogs.com/gif.latex?m=\\lceil\\lg{n}\\rceil) ![](http://latex.codecogs.com/gif.latex?\\lg{m!}=\\Theta\(m\\lg{m}\)=\\Theta\(\\lceil\\lg{n}\\rceil\\lg{\\lceil\\lg{n}\\rceil}\)>\\Theta\(\\lg{n}\)) 令![](http://latex.codecogs.com/gif.latex?p=\\lceil\\lg{{\\lg{n}}}\\rceil) ![](http://latex.codecogs.com/gif.latex?\\lg{p!}=\\Theta\(p\\lg{p}\)=\\Theta\(\\lceil\\lg{{\\lg{n}}}\\rceil\\lg{\\lceil\\lg{{\\lg{n}}}\\rceil}\)=\\Theta\(\\lg{{\\lg{n}}}\\lg{\\lg{{\\lg{n}}}}\)=o\(\\lg{{\\lg{n}}}\\lg{{\\lg{n}}}\)=o\(\\lg{n}\)) ### Exercises 3.2-5 *** Which is asymptotically larger: **lg(lg* n)** or **lg*(lg n)**? ### `Answer` 第2个大. Suppose log star of n is k, then the first one is log(k) while the second one is k - 1. ...表示多重对数函数的逆操作 ### Exercises 3.2-6 *** Prove by induction that the ith Fibonacci number satisfies the equality ![](http://latex.codecogs.com/gif.latex?F_i=\\frac{\\phi^i-\\widehat\\phi^i}{\\sqrt5}) ### `Answer` ![](http://latex.codecogs.com/gif.latex?F_{i+1}=F_{i-1}+F{i}\\\\~\\hspace{14mm}=\\frac{\\phi^{i-1}-\\widehat\\phi^{i-1}}{\\sqrt5}+\\frac{\\phi^i-\\widehat\\phi^i}{\\sqrt5}\\\\~\\hspace{14mm}=\\frac{\(\\phi-\\widehat\\phi\)\(\\phi^{i-2}\\widehat\\phi^0+\\phi^{i-3}\\widehat\\phi^1+...+\\phi^{0}\\widehat\\phi^{i-2}\)+\(\\phi-\\widehat\\phi\)\(\\phi^{i-1}\\widehat\\phi^0+\\phi^{i-2}\\widehat\\phi^1+...+\\phi^{0}\\widehat\\phi^{i-1}\)}{\\sqrt5}\\\\~\\hspace{14mm}=\\frac{\(\\phi-\\widehat\\phi\)\(\\phi^{i}\\widehat\\phi^0+\\phi^{i-1}\\widehat\\phi^1+...+\\phi^{0}\\widehat\\phi^{i}\)}{\\sqrt5}\\\\~\\hspace{14mm}=\\frac{\\phi^{i+1}-\\widehat\\phi^{i+1}}{\\sqrt5}) ### Exercises 3.2-7 *** Prove that for i ≥ 0, the (i + 2)nd Fibonacci number satisfies Fi+2 ≥ φi. ### `Answer` ![](http://latex.codecogs.com/gif.latex?\\hspace{20mm}F_{i+2}\\ge\\phi^i\\\\~\\hspace{14mm}\\iff\\frac{\\phi^{i+2}-\\widehat\\phi^{i+2}}{\\sqrt5}\\ge\\phi^i\\\\~\\hspace{14mm}\\iff\(\\phi^2-\\sqrt5\)\\phi^i\\ge\\widehat\\phi^2\\widehat\\phi^i\\\\~\\hspace{14mm}\\iff\\phi^i\\ge\\widehat\\phi^i) *** Follow [@louis1992](https://github.com/gzc) on github to help finish this task. ================================================ FILE: C03-Growth-of-Functions/problem.md ================================================ ### Problems 1 : Asymptotic behavior of polynomials *** ### `Answer` 显而易见. ### Problems 2 : Relative asymptotic growths *** Indicate for each pair of expressions (A,B) in the table below, whether A is O, o, Ω, ω, or Θ of B. Assume that k≥1, ϵ>0, and c>1 are constants. Your answer should be in the form of the table with "yes" or "no" written in each box. ### `Answer` A | B | O | o | Ω | ω | Θ :----:|:----:|:----:|:----:|:----:|:----:|:----: ![](http://latex.codecogs.com/gif.latex?\\lg^kn) | ![](http://latex.codecogs.com/gif.latex?n^\\epsilon) | yes | yes | no | no | no ![](http://latex.codecogs.com/gif.latex?\\n^k) | ![](http://latex.codecogs.com/gif.latex?c^n) | yes | yes | no | no | no ![](http://latex.codecogs.com/gif.latex?\\sqrt{n}) | ![](http://latex.codecogs.com/gif.latex?n^\\sin{n}) | no | no | no | no | no ![](http://latex.codecogs.com/gif.latex?\\2^n) | ![](http://latex.codecogs.com/gif.latex?2^{n/2}) | no | no | yes | yes | no ![](http://latex.codecogs.com/gif.latex?n^\\lg{c}) | ![](http://latex.codecogs.com/gif.latex?c^\\lg{n}) | yes | no | yes | no | yes ![](http://latex.codecogs.com/gif.latex?\\lg{\(n!\)}) | ![](http://latex.codecogs.com/gif.latex?\\lg{\(n^n\)}) | yes | no | yes | no | yes For this problem, some people point out that for 0 < epsilon < 1, lg^k n > n^epsilon and for epsilon >= 1, lg^k n) < n^epsilon Therefore, the correct answer is YES YES YES YES YES [issue 103](https://github.com/gzc/CLRS/issues/103) ### Problems 3 : Ordering by asymptotic growth rates *** a. Rank the following functions by order of growth; that is, find an arrangement ![](https://latex.codecogs.com/gif.latex?g_1,&space;g_2,...,g_{30}) of the functions satisfying ![](https://latex.codecogs.com/png.latex?g_1=\Omega\(g_2\),g_2=\Omega\(g_3\),...,g_{29}=\Omega\(g_{30}\)). Partition your list into equivalence classes such that functions ![](https://latex.codecogs.com/png.latex?f\(n\)) and ![](https://latex.codecogs.com/png.latex?g_i\(n\)) are in the same class if and only if ![](https://latex.codecogs.com/png.latex?f\(n\)=\Theta\(g\(n\)\)). ![](https://latex.codecogs.com/png.latex?lg\(lg^*n\)\quad&space;2^{lg^*n}\quad&space;\(\sqrt{2}\)^{lgn}\quad&space;n^2&space;\quad&space;n!\quad&space;\(lgn\)!\newline&space;\(\frac{2}{3}\)^n\quad&space;n^3\quad&space;lg^2n\quad&space;lg\(n!\)\quad&space;2^2^n\quad&space;n^{1/lgn}\newline&space;ln&space;lnn\quad&space;lg^*n\quad&space;n\cdot&space;2^n\quad&space;n^{lg&space;lgn}\quad&space;lnn\quad&space;1\newline&space;2^{lgn}\quad&space;{\(lgn\)}^{lgn}\quad&space;e^n\quad&space;4^{lgn}\quad&space;\(n+1\)!\quad&space;\sqrt{lgn}\newline&space;lg^*\(lgn\)\quad&space;2^{\sqrt{2lgn}}\quad&space;n\quad&space;2^n\quad&space;nlgn\quad&space;2^{2^{n+1}}) b. Give an example of a single nonnegative function ![](https://latex.codecogs.com/png.latex?f\(n\)) such that for all functions ![](https://latex.codecogs.com/png.latex?g_i\(n\)) in part (a), ![](https://latex.codecogs.com/png.latex?f\(n\)) is neither ![](https://latex.codecogs.com/gif.latex?\O\(g_i\(n\)\)) nor ![](https://latex.codecogs.com/gif.latex?\Omega\(g_i\(n\)\)). ### `Answer` a. ![](https://latex.codecogs.com/gif.latex?2^{2^{n+1}}\quad&space;2^{2^{n}}\newline&space;\(n+1\)!\quad&space;n!\newline&space;e^n\quad&space;n\cdot&space;2^n\quad&space;2^n\quad&space;\(\frac{3}{2}\)^n\newline&space;\(lgn\)^{lgn}=n^{lglgn}\quad&space;\(lgn\)!\newline&space;n^3\quad&space;n^2=4^{lgn}\quad&space;nlgn=lg\(n!\)\quad&space;2^{lgn}=n\quad&space;\(\sqrt{2}\)^{lgn}=\sqrt{n}\quad&space;2^{\sqrt{2\cdot&space;lgn}}\newline&space;lg^{2}n\quad&space;lnn\quad&space;\sqrt{lgn}\newline&space;lnlnn\quad&space;2^{lg^{*}n}\newline&space;lg^{*}n\quad&space;lg^{*}\(lgn\)\quad&space;lg\(lg^{*}\)n\newline&space;n^{\frac{1}{lgn}}=2\quad&space;1) ![](https://latex.codecogs.com/gif.latex?\(lgn\)^{lgn}=n^{lglgn}\quad&space;because&space;of\quad&space;a^{log_bc}=c^{log_ba}\newline&space;n^{\frac{1}{lgn}}=2\quad&space;because\quad&space;n^{\frac{1}{lgn}}=2^{lgn\cdot&space;\frac{1}{lgn}}=2) b. ![](https://latex.codecogs.com/png.latex?2^{2^\sin{n}}) *** Follow [@louis1992](https://github.com/gzc) on github to help finish this task. ================================================ FILE: C04-Recurrences/4.1.md ================================================ ### Exercises 4.1-1 *** Show that the solution of ![](http://latex.codecogs.com/gif.latex?%20T\(n\)%20=%20T\(\\lceil%20n/2%20\\rceil\)%20+%201) is O(lg n). ### `Answer` 我们猜想 ![](http://latex.codecogs.com/gif.latex?%20T\(n\)%20\\le%20c\\lg\(n-2\)%20) ![](http://latex.codecogs.com/gif.latex?%20T\(n\)%20=%20T\(\\lceil%20n/2%20\\rceil\)%20+%201%20\\le%20T\(n/2+1\)%20+1%20\\\\%20%20~%0d%0a\\hspace{15%20mm}%20\\le%20c\\lg\(n/2-1\)+1%20%20\\\\%20%20~%0d%0a\\hspace{15%20mm}%20=clg\(n-2\)%20-c\\lg2%20+%201%20\\\\%20%20~%0d%0a\\hspace{15%20mm}%20\\le%20clg\(n-2\)%0d%0a) ### Exercises 4.1-2 *** We saw that the solution of ![](http://latex.codecogs.com/gif.latex?%20T\(n\)%20=%202T\(\\lfloor%20n/2%20\\rfloor\)%20+%20n) is O(n lg n). Show that the solution of this recurrence is also Ω(n lg n). Conclude that the solution is Θ(n lg n). ### `Answer` 我们假设![](http://latex.codecogs.com/gif.latex?%20T\(n\)%20\\ge%20cn\\lg{n}%20) ![](http://latex.codecogs.com/gif.latex?%20T\(n\)%20=%202T\(\\lfloor%20n/2%20\\rfloor\)%20+%20n%20\\\\%20%20~%0d%0a\\hspace{15%20mm}%20\\ge%20cn\(\\lg{n}%20-%20\\lg{2}\)+n%20\\\\%20%20~%0d%0a\\hspace{15%20mm}%20=cn\\lg{n}%20+\(1-c\\lg{2}\)n%20\\\\%20%20~%0d%0a\\hspace{15%20mm}%20\\ge%20cnlg\(n\)%0d%0a\\\\%20%20~%0d%0a\\hspace{15%20mm}%20for%20~%201-c\\lg{2}%20<%200%0d%0a) ### Exercises 4.1-3 *** Show that by making a different inductive hypothesis, we can overcome the difficulty with the boundary condition T (1) = 1 for the recurrence (4.4) without adjusting the boundary conditions for the inductive proof. ### `Answer` 我们假设 ![](http://latex.codecogs.com/gif.latex?%20T\(n\)%20\\le%20n\\lg{n}+n) ![](http://latex.codecogs.com/gif.latex?%20T\(n\)%20\\le%202\(c\\lfloor%20n/2%20\\rfloor%20\\lg\(\\lfloor%20n/2%20\\rfloor\)%20+%20\\lfloor%20n/2%20\\rfloor\)%20+%20n%20\\\\%20%20~%0d%0a\\hspace{15%20mm}%20\\le%202c\(n/2\)\\lg\(n/2\)%20+%202\(n/2\)%20+%20n%20\\\\%20%20~%0d%0a\\hspace{15%20mm}%20\\le%20cn\\lg\(n/2\)%20+%202n%20\\\\%20%20~%0d%0a\\hspace{15%20mm}%20\\le%20cn\\lg{n}%20-%20\\lg{2}cn%20+%202n%20\\\\%20%20~%0d%0a\\hspace{15%20mm}%20\\le%20cn\\lg{n}%20+%20\(2-c\)n%20\\\\%20%20~%0d%0a\\hspace{15%20mm}%20\\le%20cn\\lg{n}%20+%20n%20~~~~~~~~~~~~%20for%20~%20c%20\\ge%201%0d%0a) ### Exercises 4.1-4 *** Show that Θ(n lg n) is the solution to the "exact" recurrence (4.2) for merge sort. ### `Answer` 我们假设![](http://latex.codecogs.com/gif.latex?%20T\(n\)%20\\ge%20cn\\lg{n}%20) ![](http://latex.codecogs.com/gif.latex?%20T\(n\)%20\\ge%202T\(n/2\)%20+%20kn%20\\\\%20%20~%0d%0a\\hspace{15%20mm}%20=cn\\lg{n}%20+\(k-c\\lg{2}\)n%20\\\\%20%20~%0d%0a\\hspace{15%20mm}%20\\ge%20cn\\lg{n}%20%20~~~~~~%20if%20~%20k%20\\le%20c\\lg{2}%0d%0a) 我们假设![](http://latex.codecogs.com/gif.latex?%20T\(n\)%20\\le%20c\(n-2\)\\lg\(n-2\)%20) ![](http://latex.codecogs.com/gif.latex?%20T\(n\)%20\\le%20T\(n/2+1\)%20+%20T\(n/2\)%20+%20kn%20\\\\%20%20~%0d%0a\\hspace{15%20mm}%20\\le%20c\(n-2\)\\lg\(\\frac{n-2}{2}\)%20+%20kn%20\\\\%20%20~%0d%0a\\hspace{15%20mm}%20=%20c\(n-2\)\\lg\(n-2\)%20+kn%20-%20c\\lg{2}\(n-2\)%20\\\\%20%20~%0d%0a\\hspace{15%20mm}%20\\le%20c\(n-2\)\\lg\(n-2\)%20~~~~~~~~if~~kn%20\\le%20c\\lg{2}\(n-2\)%0d%0a) ### Exercises 4.1-5 *** Show that the solution to ![](http://latex.codecogs.com/gif.latex?%20T\(n\)%20=%202T\(\\lfloor%20n/2%20\\rfloor%20+%2017\)%20+%20n%20) is O(n lg n). ### `Answer` 我们假设![](http://latex.codecogs.com/gif.latex?%20T\(n\)%20\\le%20c\(n-a\)\\lg\(n-a\)%20) ![](http://latex.codecogs.com/gif.latex?%20T\(n\)%20\\le%202c\(\\lfloor%20n/2%20\\rfloor%20+%2017%20-%20a\)\\lg\(\\lfloor%20n/2%20\\rfloor%20+%2017-a\)%20+%20n\\\\%20%20~%0d%0a\\hspace{15%20mm}%20\\le%20%202c\(n/2%20+1+%2017%20-%20a\)\\lg\(%20n/2%20+1+%2017-a\)%20+%20n%20\\\\%20%20~%0d%0a\\hspace{15%20mm}%20\\le%20c\(n+36-2a\)\\lg\(\\frac{n+36-2a}{2}\)+n%20\\\\%20%20~%0d%0a\\hspace{15%20mm}%20\\le%20c\(n+36-2a\)\\lg\(n+36-2a\)%20-%20c\(n+36-2a\)+n%20\\\\%20%20~%0d%0a\\hspace{15%20mm}%20\\le%20c\(n+36-2a\)\\lg\(n+36-2a\)%20~~~if%20~~%20c%20>%201%20\\\\%20%20~%20%0d%0a\\hspace{15%20mm}%20\\le%20c\(n-a\)\\lg\(n-a\)%20~~~~if%20~~%20a%20\ge%2036%0d%0a) ### Exercises 4.1-6 *** Solve the recurrence ![](http://latex.codecogs.com/gif.latex?%20T\(n\)%20=%202T\(\\sqrt{n}\)+1%20) by making a change of variables. Your solution should be asymptotically tight. Do not worry about whether values are integral. ### `Answer` 设n = lgn,得到新的递归式 ![](http://latex.codecogs.com/gif.latex?%20T\(2^n\)%20=%202T\(2^{n/2}\)%20+%201) 再令S(n) = T(2^n)可以得到 ![](http://latex.codecogs.com/gif.latex?%20S\(n\)%20=%20S2\(m/2\)%20+%201) 按照前面的方法解这个递归式即可 *** Follow [@louis1992](https://github.com/gzc) on github to help finish this task. ================================================ FILE: C04-Recurrences/4.2.md ================================================ ### Exercises 4.2-1 *** Use a recursion tree to determine a good asymptotic upper bound on the recurrence ![](http://latex.codecogs.com/gif.latex?%20T\(n\)%20=%203T\(\\lceil%20n/2%20\\rceil\)%20+%20n). Use the substitution method to verify your answer. ### `Answer` ![recursion tree](./repo/s2/1.png) 树的高度是lgn,有3^lgn个叶子节点. ![](http://latex.codecogs.com/gif.latex?%20T\(n\)%20=%20n\\sum_{i%20=%200}^{lg\(n\)-1}\(\\frac{3}{2}\)^i%20+%20\\Theta\(3^{\\lg{n}}\)%20\\\\%20%20~%0d%0a\\hspace{15%20mm}%20=%20\\Theta\(n^{\\lg{3}}\)%20+%20\\Theta\(3^{\\lg{n}}\)%20\\\\%20%20~%0d%0a\\hspace{15%20mm}%20=%20\\Theta\(n^{\\lg{3}}\)%20+%20\\Theta\(n^{\\lg{3}}\)%20\\\\%20%20~%0d%0a\\hspace{15%20mm}%20=%20\\Theta\(n^{\\lg{3}}\)%0d%0a) 我们猜想 ![](http://latex.codecogs.com/gif.latex?%20T\(n\)%20\\le%20cn^{\\lg{3}}+2n%20) ![](http://latex.codecogs.com/gif.latex?%20T\(n\)%20\\le%203c\(n/2\)^{\\lg{3}}%20+%202n%20%20\\\\%20%20~%0d%0a\\hspace{15%20mm}%20\\le%20cn^{\\lg{3}}+2n%20%20\\\\%20%20~%0d%0a\\hspace{15%20mm}%20=%20\\Theta\(n^{\\lg{3}}\)%0d%0a) ### Exercises 4.2-2 *** Argue that the solution to the recurrence ![](http://latex.codecogs.com/gif.latex?%20T\(n\)%20=%20T\(n/3\)%20+%20T\(2n/3\)%20+%20cn%20) , where c is a constant, is Ω(nlgn) by appealing to the recurrsion tree. ### `Answer` 最短的叶子高度是lg3n,每一层都要cn.也就是说,只考虑最短叶子的那一层(忽略其他层)已经有cnlg3n. ### Exercises 4.2-3 *** Draw the recursion tree for ![](http://latex.codecogs.com/gif.latex?%20T\(n\)%20=%204T\(\\lfloor%20n/2%20\\rfloor\)%20+%20cn) ,where c is a constant, and provide a tight asymptotic bound on its solution. Verify your bound by the substitution method. ### `Answer` ![recursion tree](./repo/s2/2.png) 很明显是n^2的级别 我们假设 ![](http://latex.codecogs.com/gif.latex?%20T\(n\)%20\\le%20n^2+2cn) ![](http://latex.codecogs.com/gif.latex?%20T\(n\)%20\\le%20%204c\(n/2\)^2%20+%202cn/2+cn%20\\le%20cn^2+2cn) 我们假设 ![](http://latex.codecogs.com/gif.latex?%20T\(n\)%20\\ge%20n^2+2cn) ![](http://latex.codecogs.com/gif.latex?%20T\(n\)%20\\ge%20%204c\(n/2\)^2%20+%202cn/2+cn%20\\ge%20cn^2+2cn) ### Exercises 4.2-4 *** Use a recursion tree to give an asymptotically tight solution to the recurrence T(n) = T(n - a) + T(a) + cn, where a ≥ 1 and c > 0 are constants. ### `Answer` ![recursion tree](./repo/s2/3.png) ![](http://latex.codecogs.com/gif.latex?%20T\(n\)%20=%20\\sum_{i=0}^{n/a}c\(n-ia\)%20+%20\(n/a\)ca%0d%0a=%20\\Theta\(n^2\)) 我们假设 ![](http://latex.codecogs.com/gif.latex?%20T\(n\)%20\\le%20cn^2) ![](http://latex.codecogs.com/gif.latex?%20T\(n\)%20\\le%20c\(n-a\)^2%20+%20ca%20+%20cn%20%20\\\\%20%20~%0d%0a\\hspace{15%20mm}%20\\le%20cn^2-2acn+ca+cn%20%20\\\\%20%20~%0d%0a\\hspace{15%20mm}%20\\le%20cn^2-c\(2an-a-n\)%20\\\\%20%20~%0d%0a\\hspace{15%20mm}\\le%20cn^2%20-%20cn%20~~~~%20if%20~~%20a%20>%201/2,n%20>%202a%20\\\\%20%20~%0d%0a\\hspace{15%20mm}\\le%20cn^2%20\\\\%20%20~%0d%0a\\hspace{15%20mm}%20=%20O\(n^2\)%0d%0a) 另外一个方向的证明和这个基本一样. ### Exercises 4.2-5 *** Use a recursion tree to give an asymptotically tight solution to the recurrence T(n) = T(αn) + T((1 - α)n) + cn, where α is a constant in the range 0 <α < 1 and c > 0 is also a constant. ### `Answer` ![recursion tree](./repo/s2/4.png) 可以假设α < 1/2,因此树的高度有![](http://latex.codecogs.com/gif.latex?%20\\log_{1/%20\\alpha}{n}%20) ![](http://latex.codecogs.com/gif.latex?%20T\(n\)%20=%20\\sum_{i%20=%200}^{\\log_{1/%20\\alpha}{n}}cn%20+%20\\Theta\(n\)%20=%20cn\\log_{1/%20\\alpha}{n}%20+%20\\Theta\(n\)%20=%20\\Theta\(n\\lg{n}\)%20) *** Follow [@louis1992](https://github.com/gzc) on github to help finish this task. ================================================ FILE: C04-Recurrences/4.3.md ================================================ ### Exercises 4.3-1 *** Use the master method to give tight asymptotic bounds for the following recurrences. a. ![](http://latex.codecogs.com/gif.latex?%20T\(n\)%20=%204T\(n/2\)+n%20) b. ![](http://latex.codecogs.com/gif.latex?%20T\(n\)%20=%204T\(n/2\)+n^2%20) c. ![](http://latex.codecogs.com/gif.latex?%20T\(n\)%20=%204T\(n/2\)+n^3%20) ### `Answer` ![](http://latex.codecogs.com/gif.latex?%20n^{\\log_{b}{a}}%20=%20n^{\\log_{2}{4}}%20=%20n^2) a. ![](http://latex.codecogs.com/gif.latex?%20\\Theta\(n^2\)%20) b. ![](http://latex.codecogs.com/gif.latex?%20\\Theta\(n^2%20\\lg{n}\)%20) c. ![](http://latex.codecogs.com/gif.latex?%20\\Theta\(n^3\)%20) ### Exercises 4.3-2 *** The recurrence T(n) = 7T (n/2)+n2 describes the running time of an algorithm A. A competing algorithm A′ has a running time of T′(n) = aT′(n/4) + n2. What is the largest integer value for a such that A′ is asymptotically faster than A? ### `Answer` 根据主定理,算法A的运行时间为![](http://latex.codecogs.com/gif.latex?%20T\(n\)%20=%20\\Theta\(\\lg{7}\)\ \\approx%20n^{2.8}%20) A'的运行时间在a > 16时超过n^2,此时 ![](http://latex.codecogs.com/gif.latex?%20T\(n\)%20=%20\\Theta\(n^{\\log_{4}{a}}\)%20<%20%20\\lg{7}%20=%20\\log_{4}{49}) 所以最大值为48 ### Exercises 4.3-3 *** Use the master method to show that the solution to the binary-search recurrence T(n) = T (n/2) + Θ(1) is T(n) = Θ(lg n). (See Exercise 2.3-5 for a description of binary search.) ### `Answer` ![](http://latex.codecogs.com/gif.latex?%20n^{\\log_{b}{a}}%20=%20n^{\\log_{2}{1}%20=%201}%20) so the solution is Θ(lgn). ### Exercises 4.3-4 *** Can the master method be applied to the recurrence ![](http://latex.codecogs.com/gif.latex?%20T\(n\)%20=%204T\(n/2\)%20+%20n^2%20\\lg{n}%20) Why or why not? Give an asymptotic upper bound for this recurrence. ### `Answer` ![](http://latex.codecogs.com/gif.latex?%20n^{\\log_{b}{a}}%20=%20n^{\\log_{2}{4}}%20=%20n^2%20) The problem is that it is not polynomially larger. The ratio  ![](http://latex.codecogs.com/gif.latex?%20f\(n\)%20/%20n^{\\log_{b}{a}}%20=%20\\lg{n}) is asymptotically less than ![](http://latex.codecogs.com/gif.latex?%20n^\\epsilon%20) for any positive constant ![](http://latex.codecogs.com/gif.latex?%20\\epsilon%20) ### Exercises 4.3-5 *** Consider the regularity condition af (n/b) ≤ cf(n) for some constant c < 1, which is part of case 3 of the master theorem. Give an example of constants a ≥ 1 and b > 1 and a function f (n) that satisfies all the conditions in case 3 of the master theorem except the regularity condition. ### `Answer` let a = 1 b = 2 f(n) = 2n - n * cos(n) We are in Case 3, but the regularity condition is violated. (Consider n = 2πk, where k is odd and arbitrarily large. For any such choice of n, you can show that c ≥ 3/2, thereby violating the regularity condition.) *** Follow [@louis1992](https://github.com/gzc) on github to help finish this task. ================================================ FILE: C04-Recurrences/4.4.md ================================================ ### Exercises 4.4-1 *** Give a simple and exact expression for nj in equation (4.12) for the case in which b is a positive integer instead of an arbitrary real number. ### `Answer` ![](http://latex.codecogs.com/gif.latex?%20n^j%20=%20\\lceil%20n%20/%20b^j%20\\rceil%20) ### Exercises 4.4-2 *** Show that if ![](http://latex.codecogs.com/gif.latex?%20f\(n\)%20=%20\\Theta\(n^{\\log_{b}{a}}\)%20\\lg^k{n}%20) , where k ≥ 0, then the master recurrence has solution Show that if ![](http://latex.codecogs.com/gif.latex?%20T\(n\)%20=%20\\Theta\(n^{\\log_{b}{a}}\)%20\\lg^{k+1}{n}%20). ### `Answer` ![](http://latex.codecogs.com/gif.latex?%20%0d%0ag\(n\)%20=%20\\sum_{j%20=%200}^{\\log_{b}{n-1}}%20a^jf\(n/b^j\)%20\\\\%20%20~%20f\(n/b^j\)%20=%20\\Theta\\Big\(\(n/b^j\)^{\\log_b{a}}\\lg^k\(n/b^j\)\\Big\)%20\\\\%0d%0ag\(n\)%20=%20\\Theta\\Big\(\\sum_{j=0}^{\\log_b{n}-1}a^j\\big\(\\frac{n}{b^j}\\big\)^{\\log_b{a}}\\lg^k\\big\(\\frac{n}{b^j}\\big\)\\Big\)%20=%20\\Theta\(A\)%20\\\\%0d%0aA%20=%20\\sum_{j=0}^{\\log_b{n}-1}a^j\\big\(\\frac{n}{b^j}\\big\)^{\\log_b{a}}\\lg^k\\frac{n}{b^j}%0d%0a%20%20%20=%20n^{\\log_b{a}}\\sum_{j=0}^{\\log_b{n}-1}\\Big\(\\frac{a}{b^{\\log_b{a}}}\\Big\)^j\\lg^k\\frac{n}{b^j}%0d%0a%20%20%20=%20n^{\\log_b{a}}\\sum_{j=0}^{\\log_b{n}-1}\\lg^k\\frac{n}{b^j}%20%0d%0a%20%20%20=%20n^{\\log_b{a}}B%20\\\\%0d%0a\\lg^k\\frac{n}{d}%20=%20\(\\lg{n}%20-%20\\lg{d}\)^k%20=%20\\lg^k{n}%20+%20o\(\\lg^k{n}\)%20\\\\%0d%0aB%20=%20\\sum_{j=0}^{\\log_b{n}-1}\\lg^k\\frac{n}{b^j}%0d%0a%20%20%20%20%20=%20\\sum_{j=0}^{\\log_b{n}-1}\\Big\(\\lg^k{n}%20-%20o\(\\lg^k{n}\)\\Big\)%0d%0a%20%20%20%20%20=%20\\log_b{n}\\lg^k{n}%20+%20\\log_b{n}%20\\cdot%20o\(\\lg^k{n}\)%0d%0a%20%20%20%20%20=%20\\Theta\(\\log_b{n}\\lg^k{n}\)%0d%0a%20%20%20%20%20=%20\\Theta\(\\lg^{k+1}{n}\)%20\\\\%0d%0ag\(n\)%20=%20\\Theta\(A\)%20=%20\\Theta\(n^{\\log_b{a}}B\)%20=%20\\Theta\(n^{\\log_b{a}}\\lg^{k+1}{n}\)%0d%0a) ### Exercises 4.3-3 *** Show that case 3 of the master theorem is overstated, in the sense that the regularity condition af(n/b) ≤ cf(n) for some constant c < 1 implies that there exists a constant ![](http://latex.codecogs.com/gif.latex?%20\\epsilon%20>%200%20~%20such%20~%20that%20~%20f\(n\)%20=%20\\Omega\(n^{\\log_b{a+\\epsilon}}\)). ### `Answer` 根据case3,我们有![](http://latex.codecogs.com/gif.latex?%20af\(n/b\)%20\\le%20cf\(n\)%20~~~~%20a%20\\ge%201,b%20\\ge%201%20c%20<%201%20) 变形一下 ![](http://latex.codecogs.com/gif.latex?%20f\(bn\)%20\\ge%20kf\(n\)%20~~~where%20~%20k%20=%20a/c%20>%20a%20\\\\%20\\Rightarrow%20f\(b^i\)%20\\ge%20k^i%20f\(1\)%20\\\\%0d%0aif~~%20n%20=%20b^i,%20i%20=%20\\log_b{n},%20then%20f\(n\)%20\\ge%20k^{\\log_b{n}}f\(1\)%20\\\\%0d%0ak^{\\log_b{n}}%20=%20n^{\\log_b{k}}%20~%20and%20~%20\\log_b{k}%20\\ge%20\\log_b{a}%20~so~%20\\log_b{k}%20=%20\\log_b{a}%20+%20\\epsilon%0d%0a) *** Follow [@louis1992](https://github.com/gzc) on github to help finish this task. ================================================ FILE: C04-Recurrences/exercise_code/findIndex.py ================================================ #!/usr/bin/env python # coding=utf-8 def mergeindex(items, evens): res = [] l = len(items[0]) for i in range(len(items)): left = evens[i] right = -1 if i == len(items)-1 and len(evens) == len(items): right = l - 1 else: right = evens[i+1] minimum = items[i][left] pos = left for j in range(left, right+1): if items[i][j] < minimum: minimum = items[i][j] pos = j res.append(evens[i]) res.append(pos) if len(evens) > len(items): res.append(evens[-1]) return res def findindex(items): if len(items) == 1: res = 0 minimum = 2**31-1 for i in range(len(items[0])): if items[0][i] < minimum: minimum = items[0][i] res = i return [res] evens = items[::2] evenres = findindex(evens) res = mergeindex(items[1::2], evenres) return res items=[[37,23,24,32],[21,6,7,10],[53,34,30,31],[32,13,9,6],[43,21,15,8]] print findindex(items) ================================================ FILE: C04-Recurrences/exercise_code/findMissing.py ================================================ #!/usr/bin/env python # coding=utf-8 def findmissing(items, i) : if len(items) == 0: return 0 numone = numzero = 0 itemone = [] itemzero = [] for e in items: if (e >> i) & 1 == 0: numzero += 1 itemzero.append(e) else: numone += 1 itemone.append(e) if numone >= numzero: #remove 0 return 2 * findmissing(itemzero, i+1) else: #remove 1 return 2 * findmissing(itemone, i+1) + 1 items = [0,1,2,3,5,6,7,8] print findmissing(items, 0) ================================================ FILE: C04-Recurrences/problem.md ================================================ ### Problems 1 : Recurrence examples *** Throughout this book, we assume that parameter passing during procedure calls takes constant time, even if an N-element array is being passed. This assumption is valid in most systems because a pointer to the array is passed, not the array itself. This problem examines the implications of three parameter-passing strategies: 1. An array is passed by pointer. Time = Θ(1). 2. An array is passed by copying. Time = Θ(N), where N is the size of the array. 3. An array is passed by copying only the subrange that might be accessed by the called procedure. Time = Θ(q - p + 1) if the subarray A[p...q] is passed. **a.** Consider the recursive binary search algorithm for finding a number in a sorted array (see Exercise 2.3-5). Give recurrences for the worst-case running times of binary search when arrays are passed using each of the three methods above, and give good upper bounds on the solutions of the recurrences. Let N be the size of the original problem and n be the size of a subproblem. **b.** Redo part (a) for the MERGE-SORT algorithm from Section 2.3.1. ### `Answer` 1. Θ(n^4) 2. Θ(n) 3. Θ(n^2lgn) 4. Θ(n^2) 5. ![](http://latex.codecogs.com/gif.latex?%20\\Theta\(n^{\\log_{2}{7}}\)%20) 6. ![](http://latex.codecogs.com/gif.latex?%20\\sqrt{n}%20\\lg{n}%20) 7. Θ(n^3) ### Problems 2 : Finding the missing integer *** An array A[1...n] contains all the integers from 0 to n except one. It would be easy to determine the missing integer in O(n) time by using an auxiliary array B[0...n] to record which numbers appear in A. In this problem, however, we cannot access an entire integer in A with a single operation. The elements of A are represented in binary, and the only operation we can use to access them is "fetch the jth bit of A[i]," which takes constant time. Show that if we use only this operation, we can still determine the missing integer in O(n) time. ### `Answer` * 00000 * 00001 * 00010 * 00011 * 00101 * 00110 * 00111 * 01000 [code](./exercise_code/findMissing.py) 我们用上面的8个数字当作例子,[0,8]缺4.
1.第一次迭代发现最末位1出现4次0出现3次,所以missnum的最后一位是4,排除掉末位为1的数字 2.然后一次次迭代 ### Problems 3 : Parameter-passing costs Throughout this book, we assume that parameter passing during procedure calls takes constant time, even if an N-element array is being passed. This assumption is valid in most systems because a pointer to the array is passed, not the array itself. This problem examines the implications of three parameter-passing strategies: 1. An array is passed by pointer. Time = Θ(1). 2. An array is passed by copying. Time = Θ(N), where N is the size of the array. 3. An array is passed by copying only the subrange that might be accessed by the calledprocedure. Time = Θ(q - p + 1) if the subarray A[p...q] is passed. **a.** Consider the recursive binary search algorithm for finding a number in a sorted array (see Exercise 2.3-5). Give recurrences for the worst-case running times of binary search when arrays are passed using each of the three methods above, and give good upper bounds on the solutions of the recurrences. Let N be the size of the original problem and n be the size of a subproblem. **b.** Redo part (a) for the MERGE-SORT algorithm from Section 2.3.1. ### `Answer` **a.**
1. T(n) = T(n/2) + 2, O(lgN) 2. T(n) = T(n/2) + N, O(NlgN) 3. T(n) = T(n/2) + n, O(N) **b.**
1. ![](http://latex.codecogs.com/gif.latex?T%28n%29%20%3D%202T%28%5Cfrac%7Bn%7D%7B2%7D%29%20+%20n%20+%202%2C%20O%28NlgN%29) 2. ![](http://latex.codecogs.com/gif.latex?T%28n%29%20%3D%202T%28%5Cfrac%7Bn%7D%7B2%7D%29%20+%20n%20+%202N%2C%20%5CTheta%282%5E%7Blg%7BN%7D%7DN%29%20%3D%20%5CTheta%28N%5E2%29) 3. ![](http://latex.codecogs.com/gif.latex?T%28N%29%20%3D%202T%28%5Cfrac%7Bn%7D%7B2%7D%29%20+%20n%20+%202n%2C%20O%28NlgN%29) ### Problems 4 : More recurrence examples **a.**
![](http://latex.codecogs.com/gif.latex?%20T\(n\)%20=%203T\(n/2\)%20+%20\\lg{n}%20\\\\%20%0d%0aby~applying~master~method~\\Theta\(n^{\\log_3{4}}\)) *** **b.**
![](http://latex.codecogs.com/gif.latex?%20T\(n\)%20=%205T\(n/5\)%20+%20n/\\lg{n}%20\\\\%0d%0aT\(n\)%20=%205T\(n/5\)%20+%20\\frac{n}{\\lg{n}}%20=%2025T\(n/25\)%20+%205\\frac{n/5}{\\lg\(n/5\)}%20+%20\\frac{n}{\\lg{n}}%20=%2025T\(n/25\)%20+%20\\frac{n}{\\lg{n}-\\lg{5}}%20+%20\\frac{n}{\\lg{n}}%20=%20nT\(1\)+\\sum_{i%20=%200}^{\\lg{n}-1}\\frac{n}{\\lg{n}-i\\lg{5}}%20%20=%20nT\(1\)+n\\sum_{i%20=%201}^{\\lg{n}}\\frac{1}{\\lg{n}}%20=%20\\Theta\(n\\lg{\\lg{n}}\)) *** **c.**
![](http://latex.codecogs.com/gif.latex?%20T\(n\)%20=%204T\(n/2\)+n^2\\sqrt{n}%20\\\\%0d%0aby%20~applying~master~method~%20\\Theta\(n^2\\sqrt{n}\)%20) *** **d.**
![](http://latex.codecogs.com/gif.latex?%20T\(n\)%20=%203T\(n/3+5\)%20+%20n/2%20\\\\%0d%0aby%20~applying~master~method~%20\\Theta\(n\\lg{n}\)%20%0d%0a) *** **e.**
The same as b *** **f.**
![](http://latex.codecogs.com/gif.latex?%20T\(n\)%20=%20T\(n/2\)%20+%20T\(n/4\)%20+%20T\(n/8\)%20+%20n\\\\%0d%0aLet's%20~%20guess%20~%20\\Theta\(n\)%20\\\\%0d%0aT\(n\)%20=%20cn/2%20+%20cn/4%20+%20cn/8%20\\le%20cn%20=%20O\(n\)%20\\\\%0d%0aT\(n\)%20=%20cn/2%20+%20cn/4%20+%20cn/8%20\\ge%20cn%20=%20\\Omega\(n\)%0d%0a) *** **g.**
![](http://latex.codecogs.com/gif.latex?%20T\(n\)%20=%20T\(n-1\)%20+%201/n%20\\\\%0d%0aT\(n\)%20=%20\\sum_{i%20=%201}^{n}\\frac{1}{i}%20=%20\\Theta\(lg{n}\)%0d%0a) *** **h.**
![](http://latex.codecogs.com/gif.latex?%20T\(n\)%20=%20T\(n-1\)%20+%20\\lg{n}%20\\\\%0d%0aT\(n\)%20=%20\\sum_{i=1}^{n}\\lg{i}%20=%20\\lg{n!}%20=%20\\Theta\(n\\lg{n}\)%20~%20remember%20~we%20~prove%20~it%20~in%20~section~1%0d%0a) *** **i.**
![](http://latex.codecogs.com/gif.latex?%20T\(n\)%20=%20T\(n-2\)%20+%202\\lg{n}%20~%20The%20~same%0d%0a) *** **j.**
![](http://latex.codecogs.com/gif.latex?%20T\(n\)%20=%20\\sqrt{n}T\(\\sqrt{n}\)+n%20\\\\%0d%0aLet's%20~%20guess%20~%20\\Theta\(cn\\lg{\\lg{n}}\)%20\\\\%0d%0aT\(n\)%20\\le%20\\sqrt{n}c\\sqrt{n}\\lg{\\lg{\\sqrt{n}}}+n%20\\\\%0d%0a=%20cn\\lg{\\lg{\\sqrt{n}}}+n%20\\\\%0d%0a=%20cn\\lg{\\frac{\\lg{n}}{2}}+n%20\\\\%0d%0a=%20cn\\lg{\\lg{n}}%20-%20cn\\lg{2}+n%20\\\\%0d%0a=%20cn\\lg{\\lg{n}}%20+%20\(1-c\)n%20%20%20%20\\\\%0d%0a\\le%20cn\\lg{\\lg{n}}%20~~~~~~~~~%20if%20~%20c%20>%201%20\\\\%0d%0a=%20\\Theta\(cn\\lg{\\lg{n}}\)%0d%0a) ### Problems 5 : Fibonacci numbers *** This problem develops properties of the Fibonacci numbers, which are defined by recurrence (3.21). We shall use the technique of generating functions to solve the Fibonacci recurrence. Define the generating function (or formal power series) F as ![](http://latex.codecogs.com/gif.latex?%20\\mathcal{F}\(z\)%20=%20\\sum_{i=0}^{\\infty}F\_iz^i%20\\\\%0d%0a=%200%20+%20z%20+%20z^2%20+%202z^3%20+%203z^4%20+%205z^5%20+%208z^6%20+%2013z^7%20+%2021z^8%20+%20\\ldots%20\\\\%0d%0awhere~%20F_i%20~is%20~the~%20ith%20~Fibonacci~%20number.) a. Show that ![](http://latex.codecogs.com/gif.latex?%20\\mathcal{F}\(z\)%20=%20z%20+%20z\mathcal{F}\(z\)%20+%20z^2\mathcal{F}\(z\)) b. Show that ![](http://latex.codecogs.com/gif.latex?%20\\mathcal{F}\(z\)%20=%20\\frac{z}{1-z-z^2}%20=%20\\frac{z}{\(1-\\phi{z}\)\(1-\\widehat\\phi{z}\)}%20=%20\\frac{1}{\\sqrt{5}}\(\\frac{1}{1-\\phi{z}}-\\frac{1}{1-\\widehat\\phi{z}}\)%0d%0a) c. Show that Show that ![](http://latex.codecogs.com/gif.latex?%20\\mathcal{F}\(z\)%20=%20\\sum_{i=0}^{\\infty}\\frac{1}{\\sqrt{5}}\(\\phi^i%20-%20\\widehat\\phi^i\)z^i%20) d. Prove that ![](http://latex.codecogs.com/gif.latex?%20\\mathcal{F}\(z\)%20=%20\\phi^i/\\sqrt{5}) for i > 0 , rounded to the nearest integer. e. Prove that ![](http://latex.codecogs.com/gif.latex?%20F_{i+2}%20\\ge%20\\phi%20i) for i ≥ 0. ### `Answer` **a.** ![](http://latex.codecogs.com/gif.latex?%20z%20+%20z\mathcal{F}\(z\)%20+%20z^2\mathcal{F}\(z\)%20\\\\%0d%0a=%20z%20+%20z\\sum_{i=0}^{\\infty}F\_iz^i%20+%20z^2\\sum_{i=0}^{\\infty}F\_iz^i%20\\\\%0d%0a=%20z%20+%20z\\sum_{i=1}^{\\infty}F\_{i-1}z^i%20+%20z^2\\sum_{i=2}^{\\infty}F\_{i-2}z^i%20\\\\%0d%0a=%20z%20+%20F_1z%20+%20\\sum_{i=2}^{\\infty}\(F_{i-1}+F_{i-2}\)z^i%20\\\\%0d%0a=%20z%20+%20F_1z%20+%20\\sum_{i=2}^{\\infty}F_iz^i%20\\\\%0d%0a=%20\\mathcal{F}\(z\)) **b.** 这个结论的证明还是很straight-forward的,就不写公式啦. **c.** ![](http://latex.codecogs.com/gif.latex?%20%0d%0a\\quad\\text{First,%20we%20have%20}%20\\frac{1}{1%20-%20x}%20=%20\\sum_{k=0}^{\\infty}x^k%20\\quad\\text{when%20}%20|x|%20<%201%20\\\\%0d%0a\\mathcal{F}\(n\)%20=%20\\frac{1}{\\sqrt5}\\Big\(\\frac{1}{1%20-%20\\phi%20z}%20-%20\\frac{1}{1%20-%20\\hat\\phi%20z}\\Big\)%20=%20\\frac{1}{\\sqrt5}\\Big\(\\sum_{i=0}^{\\infty}\\phi^i%20z^i%20-%20\\sum_{i=0}^{\\infty}\\hat{\\phi}^i%20z^i\\Big\)=%20\\sum_{i=0}^{\\infty}\\frac{1}{\\sqrt5}\(\\phi^i%20-%20\\hat{\\phi}^i\)%20z^i%0d%0a) **d.** ![](http://latex.codecogs.com/gif.latex?%0d%0a\\mathcal{F}\(z\)%20=%20\\sum_{i=0}^{\\infty}\\alpha_iz^i%20\\quad\\text{%20where%20}%20\\alpha_i%20=%20\\frac{\\phi^i%20-%20\\hat{\\phi}^i}{\\sqrt5}%20\\\\%0d%0a\\quad\\text{%20so%20we%20have%20}%20\\alpha_i%20=%20F_i%20\\\\%0d%0aF_i%20=%20\\frac{\\phi^i%20-%20\\hat{\\phi}^i}{\\sqrt5}%20%20=%20\\frac{\\phi^i}{\\sqrt5}%20-%20\\frac{\\hat{\\phi}^i}{\\sqrt5}%20\\\\%0d%0a\\quad\\text{because%20}%20|\\hat\\phi|%20<%201%20\\quad\\text{,%20so%20}%20\\frac{|\\hat\\phi^i|}{\\sqrt{5}}%20<%200.5%0d%0a) **e.** [we had prove it previously](https://github.com/gzc/CLRS/blob/master/C03-Growth-of-Functions/3.2.md#exercises-32-7) ### Problems 6 : VLSI chip testing *** Professor Diogenes has n supposedly identical VLSI[1] chips that in principle are capable of testing each other. The professor's test jig accommodates two chips at a time. When the jig is loaded, each chip tests the other and reports whether it is good or bad. A good chip always reports accurately whether the other chip is good or bad, but the answer of a bad chip cannot be trusted. Thus, the four possible outcomes of a test are as follows: Chip A says | Chip B says | Conclusion :----:|:----:|:----: B is good | A is good | both are good, or both are bad B is good | A is bad | at least one is bad B is bad | A is good | at least one is bad B is bad | A is bad | at least one is bad a. Show that if more than n/2 chips are bad, the professor cannot necessarily determine which chips are good using any strategy based on this kind of pairwise test. Assume that the bad chips can conspire to fool the professor. b. Consider the problem of finding a single good chip from among n chips, assuming that more than n/2 of the chips are good. Show that ⌊n/2⌋ pairwise tests are sufficient to reduce the problem to one of nearly half the size. c. Show that the good chips can be identified with Θ(n) pairwise tests, assuming that more than n/2 of the chips are good. Give and solve the recurrence that describes the number of tests. ### `Answer` 中文版 a. 如果超过一半是坏的,那么我们可以从这些坏的中取出一组数量和好的一样多的,他们的表现能和好的一样. b. 将所有的芯片两两配对,如果报告是both are good or bad,那么就从中随机选一个留下来,否则全部扔掉. 一直这样递归下去,最后剩下的是好的. c. T(n) = T(n/2)+n/2,是Θ(n)的. English Version a. consider the situation that at least half chips are bad. Denote good chip number is N. **We can choice more than N bad chips, and make them act as same as GOOD one.** In this situation, we cannot distinguish whether is GOOD or BAD.(The BAD chips are always major and perfectly confuse information GOOD chips make) b. Let make floor(N / 2) pairs arbitrarily. We **focus the pairs that both of it report opponent is GOOD.** Because at least half chips are GOOD, **the pairs both report opponent is GOOD contains at least half GOOD-GOOD pairs.** Next, **we discard one chip of the GOOD-GOOD pair, per every GOOD-GOOD pairs.** After all, **we operate the remained chips as same as this action.**(make pairs -> focus GOOD-GOOD reported pairs -> discard one chip per pairs) We can decrease the target chips by half by discarding, and **after discarding we can still assure that target chips contains at least half GOOD chips.** As a conclusion, if we make this actions till the last chip remained, the last must be GOOD. c. The recurrence is T(n) = T(n / 2) + n / 2 This is Θ(n) admittedly. 日本語版 a. 過半数のチップがbadの時、**goodのチップより多くのbadチップに、goodチップと同じ挙動をしてもらえばよい。** そのとき、判別者はどれがgoodでどれがbadチップなのかを判別できない。 b. 任意にfloor(N / 2)ペア組んで、その中で「どちらもGOOD」のペアに着目する。この時、**必ず過半数のペアはGOOD-GOODである(つまり、BAD-BADよりもGOOD-GOODの方が必ず多い)** 次に、その「どちらもGOOD」の各ペアのうち片方のチップを除外する。この操作後でも、着目するチップのうち、GOODチップはBADチップより必ず多くなる。 よって、その半分にする作業を最後の1つになるまで繰り返せば、残ったチップはGOODだと確定する。(常にGOODチップは BADチップより多いため) c. T(n)は、n / 2回のペア判別と捨てるを行った後、T(n / 2)の計算に移るので、漸化式は T(n) = T(n / 2) + n / 2 この漸化式は明らかに、Θ(n)の式となる。 ### Problems 7 : Monge arrays *** An m × n array A of real numbers is a **Monge array** if for all i, j, k, and l such that 1 ≤ i < k ≤ m and 1 ≤ j < l ≤ n, we have A[i, j] + A[k, l] ≤ A[i, l] + A[k, j]. In other words, whenever we pick two rows and two columns of a Monge array and consider the four elements at the intersections of the rows and the columns, the sum of the upper-left and lower-right elements is less or equal to the sum of the lower-left and upper-right elements. For example, the following array is Monge: ![](http://latex.codecogs.com/gif.latex?%20%20\\begin{matrix}%0d%0a%20%20%2010%20&%2017%20&%2013%20&%2028%20&%2023%20\\\\%0d%0a%20%20%2017%20&%2022%20&%2016%20&%2029%20&%2023%20\\\\%0d%0a%20%20%2024%20&%2028%20&%2022%20&%2034%20&%2024%20\\\\%0d%0a%20%20%2011%20&%2013%20&%20%206%20&%2017%20&%20%207%20\\\\%0d%0a%20%20%2045%20&%2044%20&%2032%20&%2037%20&%2023%20\\\\%0d%0a%20%20%2036%20&%2033%20&%2019%20&%2021%20&%20%206%20\\\\%0d%0a%20%20%2075%20&%2066%20&%2051%20&%2053%20&%2034%0d%0a%20%20%20\\end{matrix}) a. Prove that an array is Monge if and only if for all i = 1,2,...,m-1 and j = 1,2,...,n- 1, we have
A[i, j] + A[i + 1, j + 1] ≤ A[i, j + 1] + A[i + 1, j].
Note (For the "only if" part, use induction separately on rows and columns.) b. The following array is not Monge. Change one element in order to make it Monge. ![](http://latex.codecogs.com/gif.latex?%20%20\\begin{matrix}%0d%0a%20%20%2037%20&%2023%20&%2022%20&%2072%20%20\\\\%0d%0a%20%20%2021%20&%206%20&%207%20&%2010%20%20\\\\%0d%0a%20%20%2053%20&%2034%20&%2030%20&%2031%20\\\\%0d%0a%20%20%2032%20&%2013%20&%20%209%20&%206%20%20\\\\%0d%0a%20%20%2043%20&%2021%20&%2015%20&%208%20%20\\\\%0d%0a%20%20%20\\end{matrix}) c. Let f(i) be the index of the column containing the leftmost minimum element of row i. Prove that f(1) ≤ f(2) ≤ ··· ≤ f(m) for any m × n Monge array. d. Here is a description of a divide-and-conquer algorithm that computes the left-most minimum element in each row of an m × n Monge array A:
Construct a submatrix A′ of A consisting of the even-numbered rows of A. Recursively determine the leftmost minimum for each row of A′. Then compute the leftmost minimum in the odd-numbered rows of A.
Explain how to compute the leftmost minimum in the odd-numbered rows of A (given that the leftmost minimum of the even-numbered rows is known) in O(m + n) time. e. Write the recurrence describing the running time of the algorithm described in part (d). Show that its solution is O(m + n log m). ### `Answer` **a.** ![](http://latex.codecogs.com/gif.latex?%20%0d%0aA[i,%20j]%20+%20A[k,%20l]%20\\le%20A[i,%20l]%20+%20A[k,%20j]%0d%0a%20%20%20\\%20\xrightarrow[k%20=%20i+1]{l%20=%20j+1}%0d%0a%20%20%20A[i,j]%20+%20A[i+1,%20j+1]%20\\le%20A[i,%20j+1]%20+%20A[i+1,j]) *** ![](http://latex.codecogs.com/gif.latex?%20%20%20\\quad\\text{assume%20}%20A[i,j]+A[k,j+1]%20\\le%20A[i,j+1]+A[k,j]%20\\\\%0d%0a\\quad\\text{to%20prove%20}%20%20%20A[i,j]+A[k+1,j+1]%20\\le%20A[i,j+1]+A[k+1,j]%20\\\\%20\\\\%0d%0aA[k,j]+A[k+1,j+1]%20\\le%20A[k,j+1]+A[k+1,j]%20\\\\%0d%0a\\rightarrow%20A[i,%20j]%20+%20A[k,%20j+1]%20+%20A[k,%20j]%20+%20A[k+1,%20j+1]%20\\le%0d%0a%20%20%20A[i,%20j+1]%20+%20A[k,%20j]%20+%20A[k,%20j+1]%20+%20A[k+1,%20j]%20\\\\%0d%0a%20%20%20\\rightarrow%20\\%0d%0a%20%20%20A[i,%20j]%20+%20A[k+1,%20j+1]%20\\le%20A[i,%20j+1]%20+%20A[k+1,%20j]) **b.** ![](http://latex.codecogs.com/gif.latex?%20%20\\begin{matrix}%0d%0a%20%20%2037%20&%2023%20&%20\\textbf{24}%20&%2072%20%20\\\\%0d%0a%20%20%2021%20&%206%20&%207%20&%2010%20%20\\\\%0d%0a%20%20%2053%20&%2034%20&%2030%20&%2031%20\\\\%0d%0a%20%20%2032%20&%2013%20&%20%209%20&%206%20%20\\\\%0d%0a%20%20%2043%20&%2021%20&%2015%20&%208%20%20\\\\%0d%0a%20%20%20\\end{matrix}) **c.** **反证法** 如果i < j,f(i) >= f(j) A[i,f(j)]+A[j,f(i))] <= A[i,f(i)]+A[j,f(j)] 但是A[i,f(i)]和A[j,f(j)]是两行最小的元素,等式不成立. **d.**根据c可以知道第i行的左端最小值落在f(i-1)和f(i+1)之间. 总共有n/2个奇数行,总共需要比较m次,所以是O(m+n). **e.** T(m) = T(m/2) + cn + dm = O(nlgm + m) [code](./exercise_code/findIndex.py) *** Follow [@louis1992](https://github.com/gzc) on github to help finish this task. ================================================ FILE: C05-Probabilistic-Analysis-and-Randomized-Algorithms/5.1.md ================================================ ### Exercises 5.1-1 *** Show that the assumption that we are always able to determine which candidate is best in line 4 of procedure HIRE-ASSISTANT implies that we know a total order on the ranks of the candidates. ### `Answer` **always**这个词表示对所有n!种组合,都能够确定,而这n!种组合已经囊括了所有的两两比较. ### Exercises 5.1-2 *** Describe an implementation of the procedure RANDOM(a, b) that only makes calls to RANDOM(0, 1). What is the expected running time of your procedure, as a function of a and b? ### `Answer` Without loss of generality we may assume that a = 0. Otherwise we can generate a random number between 0 and b − a, then add a to it. [solution](./myrandom.py) Each iteration of the while loop takes O(n) time to run. The probability that the while loop stops on a given iteration is (b+1)/(2^n). Thus the expected running time is the expected number of iterations of the while-loop times n. This is given by: ![](5.1.2.png) Since we assume a = 0 in the first, the final running time is: O(lg(b-a)) But this algorithm is non-deterministic. [Reference1: mathcamp.org](https://www.mathcamp.org/2015/academics/michelle/AlgorithmsHomework3Solutions.pdf) [Reference2: stackoverflow](https://stackoverflow.com/questions/8692818/how-to-implement-randoma-b-with-only-random0-1) ### Exercises 5.1-3 *** Suppose that you want to output 0 with probability 1/2 and 1 with probability 1/2. At your disposal is a procedure BIASED-RANDOM, that outputs either 0 or 1. It outputs 1 with some probability p and 0 with probability 1 - p, where 0 < p < 1, but you do not know what p is. Give an algorithm that uses BIASED-RANDOM as a subroutine, and returns an unbiased answer, returning 0 with probability 1/2 and 1 with probability 1/2. What is the expected running time of your algorithm as a function of p? ### `Answer` while true: x = BIASED-RANDOM() y = BIASED-RANDOM() if x != y: return x expected running time = 1/(2p(1-p)) *** Follow [@louis1992](https://github.com/gzc) on github to help finish this task. ================================================ FILE: C05-Probabilistic-Analysis-and-Randomized-Algorithms/5.2.md ================================================ ### Exercises 5.2-1 *** In HIRE-ASSISTANT, assuming that the candidates are presented in a random order, what is the probability that you will hire exactly one time? What is the probability that you will hire exactly n times? ### `Answer` 分别是1/n和1/n! ### Exercises 5.2-2 *** In HIRE-ASSISTANT, assuming that the candidates are presented in a random order, what is the probability that you will hire exactly twice? ### `Answer` 如果第一个雇员的质量是k,那么质量高于k的雇员都必须在质量最高的雇员后面. 假设有n个雇员,质量分别是1,2,...,n.当第一个质量为k时,只雇佣2次的概率p = 1/(n-k).因为有n-k个质量比k高的,而且必须要最高的那个在前.而第一个质量为k的概率是1/n.所以 ![](http://latex.codecogs.com/gif.latex?%20p%20=%20\\sum_{k%20=%201}^{n-1}\\frac{1}{n}\\frac{1}{n-k}%20=%20\\frac{1}{n}\\sum_{k%20=%201}^{n-1}\\frac{1}{k}) ### Exercises 5.2-3 *** Use indicator random variables to compute the expected value of the sum of n dice. ### `Answer` Expectation of a single die ![](http://latex.codecogs.com/gif.latex?%20E\(X_i\)%20=%20\\frac{1+2+3+4+5+6}{6}%20=%203.5%20%20) Expectation of N dies ![](http://latex.codecogs.com/gif.latex?%20E\(X\)%20=%20\\sum_{i%20=%201}^{n}%20E\(X_i\)%20=%203.5n%20) ### Exercises 5.2-4 *** Use indicator random variables to solve the following problem, which is known as the **hat- check problem**. Each of n customers gives a hat to a hat-check person at a restaurant. The hat- check person gives the hats back to the customers in a random order. What is the expected number of customers that get back their own hat? ### `Answer` ![](5.2-4.png) ### Exercises 5.2-5 *** Let A[1...n] be an array of n distinct numbers. If i < j and A[i] > A[j], then the pair (i, j) is called an inversion of A. (See Problem 2-4 for more on inversions.) Suppose that each element of A is chosen randomly, independently, and uniformly from the range 1 through n. Use indicator random variables to compute the expected number of inversions. ### `Answer` 最简单的解法如下: 因为概率是一样的,所以出现正序和逆序是等量的,总共有n(n-1)/2对,所以期望是n(n-1)/4对. Explaination: Let I{A} be an indicator random variable defined as: 1, if a given pair in array is inversion; 0 if it pair is not inversion. Then we know that E[I{A}] = P{A} i.e. Execpecation of above randon variable is same as probability of event A. Now, since the numbers in array are **distinct** (_this is important_) and are **uniform random permutations** then the probability of event A is 1/2. let X be a random varaible giving us number of inversions in array. Y is random variable that a given pair is inversion. And let there be k such pairs, then sum over all k. And you might have guessed already By linearity of expectation: Hence *** Follow [@louis1992](https://github.com/gzc) on github to help finish this task. ================================================ FILE: C05-Probabilistic-Analysis-and-Randomized-Algorithms/5.3.md ================================================ ### Exercises 5.3-1 *** Professor Marceau objects to the loop invariant used in the proof of Lemma 5.5. He questions whether it is true prior to the first iteration. His reasoning is that one could just as easily declare that an empty subarray contains no 0-permutations. Therefore, the probability that an empty subarray contains a 0-permutation should be 0, thus invalidating the loop invariant prior to the first iteration. Rewrite the procedure RANDOMIZE-IN-PLACE so that its associated loop invariant applies to a nonempty subarray prior to the first iteration, and modify the proof of Lemma 5.5 for your procedure. ### `Answer` We can redefine the algo in a very simple way that we do not have to deal with 0-permutation of n array at all and then leave no iota of doubt for professor Marceau. RANDOMIZE-IN-PLACE(A) n = A.legth if n == 1 return else swap A[1] with RANDOM(1, n) for i = 2 to n swap A[i] with RANDOM(i, n) And now we can use the same loop invarient that was used earlier. ### Exercises 5.3-2 *** Professor Kelp decides to write a procedure that will produce at random any permutation besides the identity permutation. He proposes the following procedure: PERMUTE-WITHOUT-IDENTITY(A) 1 n ← length[A] 2 for i ← 1 to n 3 do swap A[i] ↔ A[RANDOM(i + 1, n)] Does this code do what Professor Kelp intends? ### `Answer` 没有,因为[1,3,2...]这样的序列虽然不是identity permutation但是却不会产生. ### Exercises 5.3-3 *** Suppose that instead of swapping element A[i] with a random element from the subarray A[i .. n], we swapped it with a random element from anywhere in the array:  PERMUTE-WITH-ALL(A) 1 n ← length[A] 2 for i ← 1 to n 3 do swap A[i] ↔ A[RANDOM(1, n)] Does this code produce a uniform random permutation? Why or why not? ### `Answer` 不可以,因为本来有n!种排列,但是这种做法每个迭代有n种选择,所以有n^n种选择.而n^n不能被n!整除,因此不是uniform的排列. ### Exercises 5.3-4 *** Professor Armstrong suggests the following procedure for generating a uniform random permutation: PERMUTE-BY-CYCLIC(A) 1 n ← length[A] 2 offset ← RANDOM(1, n) 3 for i <- i to n 4 do dest <- i + offset 5 if dest > n 6 then dest <- dest-n 7 B[dest] -< A[i] 8 return B Show that each element A[i] has a 1/n probability of winding up in any particular position in B. Then show that Professor Armstrong is mistaken by showing that the resulting permutation is not uniformly random. ### `Answer` 这个算法对于给定的offset,只会产生一种排列...也就是最后只有n种排列而不是n!种. 只会将原数组**平移**,谈不上**随机**. ### Exercises 5.3-5 *** Prove that in the array P in procedure PERMUTE-BY-SORTING, the probability that all elements are unique is at least 1 - 1/n. ### `Answer` ![](http://latex.codecogs.com/gif.latex?%20P%20=%201\(1-\\frac{1}{n^3}\)\(1-\\frac{2}{n^3}\)%20\(1-\\frac{3}{n^3}\)\\ldots%20\\\\%20~%20\\hspace{10%20mm}%0d%0a\\ge%201\(1-\\frac{n}{n^3}\)\(1-\\frac{n}{n^3}\)%20\(1-\\frac{n}{n^3}\)\\ldots%20\\\\%20~%20\\hspace{10%20mm}%0d%0a\\ge%20\(1-\\frac{1}{n^2}\)^n%20\\\\%20~%20\\hspace{10%20mm}%0d%0a\\ge%201-\\frac{1}{n}%20) ### Exercises 5.3-6 *** Explain how to implement the algorithm PERMUTE-BY-SORTING to handle the case in which two or more priorities are identical. That is, your algorithm should produce a uniform random permutation, even if two or more priorities are identical. ### `Answer` 既然冲突了,那就重新产生嘛... *** Follow [@louis1992](https://github.com/gzc) on github to help finish this task. ================================================ FILE: C05-Probabilistic-Analysis-and-Randomized-Algorithms/5.4.md ================================================ ### Exercises 5.4-1 *** How many people must there be in a room before the probability that someone has the same birthday as you do is at least 1/2? How many people must there be before the probability that at least two people have a birthday on July 4 is greater than 1/2? ### `Answer` ![](http://latex.codecogs.com/gif.latex?%201%20-%20\(\\frac{364}{365}\)^n%20\\ge%20\\frac{1}{2}%20\\rightarrow%20n%20\\ge%20253%20) ![](http://latex.codecogs.com/gif.latex?%201%20-%20C^1_n\(\\frac{1}{365}\)\(\\frac{364}{365}\)^{n-1}%20-%20C^0_n\(\\frac{364}{365}\)^n%20\\ge%20\\frac{1}{2}%20\\rightarrow%20n%20\\ge%20613%20) ### Exercises 5.4-2 *** Suppose that balls are tossed into b bins. Each toss is independent, and each ball is equally likely to end up in any bin. What is the expected number of ball tosses before at least one of the bins contains two balls? ### `Answer` This is a rewording of the Birthday Problem. The answer is the following: ![](http://latex.codecogs.com/gif.latex?%20E\(B\)%20=%201%20+%20\\sum_{k%20=%201}^{B}%20\\frac{B!}{\(B-k\)!B^k}%20) check [Quora](http://www.quora.com/What-is-the-expected-number-of-ball-tosses-until-some-bin-contains-two-balls) and [wiki](https://en.wikipedia.org/wiki/Birthday_problem#Average_number_of_people) ### Exercises 5.4-3 *** For the analysis of the birthday paradox, is it important that the birthdays be mutually independent, or is pairwise independence sufficient? Justify your answer. ### `Answer` Pairwise independence is sufficient. ### Exercises 5.4-4 *** How many people should be invited to a party in order to make it likely that there are three people with the same birthday? ### `Answer` If n=365 and k is the number of people at the party: ![](http://latex.codecogs.com/gif.latex?%20E%20=%20C^3_k%20\\frac{1}{n^2}%20=%201%20\\rightarrow%20k%20\\approx%2094) ### Exercises 5.4-5 *** What is the probability that a k-string over a set of size n is actually a k-permutation? How does this question relate to the birthday paradox? ### `Answer` ![](http://latex.codecogs.com/gif.latex?%20P%20=%201\\cdot%20\\frac{n-1}{n}%20\\cdot%20\\frac{n-2}{n}%20\\ldots%20\\%20\\cdot%20\\frac{n-k+1}{n}) To be a k-permutation, there can be no repeated letters, so this is the birthday problem where k is the number of people and n is the number of days. ### Exercises 5.4-6 *** Suppose that n balls are tossed into n bins, where each toss is independent and the ball is equally likely to end up in any bin. What is the expected number of empty bins? What is the expected number of bins with exactly one ball? ### `Answer` ![](http://latex.codecogs.com/gif.latex?Pr\\{X_i\\}%20\\quad\\text{is%20the%20probability%20that%20ith%20bin%20is%20empty}%20\\\\%20P_r\\{X_i\\}%20=%20\(\\frac{n-1}{n}\)^n%20%20=%20\(1-\\frac{1}{n}\)^n%20\\approx%20\\frac{1}{e}%20\\\\%0d%0aE[X]%20=%20\\sum_{1}^{n}E[X_i]%20=%20\\frac{n}{e}%20\\\\%0d%0aPr\\{Y_i\\}%20\\quad\\text{is%20the%20probability%20that%20ith%20has%20one%20ball}%20\\\\%0d%0a%20P_r\\{Y_i\\}%20=%20n%20\\cdot%20\\frac{1}{n}%20\(\\frac{n-1}{n}\)^{n-1}%20\\approx%20\\frac{1}{e}%20\\\\E[Y]%20=%20\\sum_{1}^{n}E[Y_i]%20=%20\\frac{n}{e}%20\\\\) ### Exercises 5.4-7 *** Sharpen the lower bound on streak length by showing that in n flips of a fair coin, the probability is less than 1/n that no streak longer than lg n-2 lg lg n consecutive heads occurs. ### `Answer` ![Alt text](./5_7.JPG?raw=true "Figure 2") *** Follow [@louis1992](https://github.com/gzc) on github to help finish this task. ================================================ FILE: C05-Probabilistic-Analysis-and-Randomized-Algorithms/myrandom.py ================================================ #! /usr/bin/env python # -*- coding: utf-8 -*- import numpy as np from math import log from random import randint from collections import Counter import matplotlib.pyplot as plt def _m_random(b): n = int(log(b, 2)) + 1 while True: r = 0 for i in range(n): r = 2*r + randint(0, 1) if r <= b: return r def m_random(a, b): return _m_random(b - a) + a def main(): result = Counter() for i in xrange(10000): temp = m_random(1,7) result.update('{}'.format(temp)) key, value = zip(*result.items()) indexes = np.arange(len(key)) width = 1 plt.bar(indexes,value,width) plt.xticks(indexes + 0.5 * width, key) plt.show() if __name__ == '__main__': main() ================================================ FILE: C05-Probabilistic-Analysis-and-Randomized-Algorithms/problem.md ================================================ ### Problems 1 : Probabilistic counting *** With a b-bit counter, we can ordinarily only count up to 2b - 1. With R. Morris's probabilistic counting, we can count up to a much larger value at the expense of some loss of precision.  We let a counter value of i represent a count of ni for i = 0, 1,..., 2b -1, where the ni form an increasing sequence of nonnegative values. We assume that the initial value of the counter is 0, representing a count of n0 = 0. The INCREMENT operation works on a counter containing the value i in a probabilistic manner. If i = 2b - 1, then an overflow error is reported. Otherwise, the counter is increased by 1 with probability 1/(ni+1 - ni), and it remains unchanged with probability 1 - 1/(ni+1 - ni). If we select ni = i for all i ≥ 0, then the counter is an ordinary one. More interesting situations arise if we select, say, ni = 2i-1 for i > 0 or ni = Fi (the ith Fibonacci number-see Section 3.2). For this problem, assume that n_(2^b-1) is large enough that the probability of an overflow error is negligible. a. Show that the expected value represented by the counter after n INCREMENT operations have been performed is exactly n. b. The analysis of the variance of the count represented by the counter depends on the sequence of the ni. Let us consider a simple case: ni = 100i for all i ≥ 0. Estimate the variance in the value represented by the register after n INCREMENT operations have been performed. ### `Answer` **a.** 每一次递增操作增加的期望为 ![](http://latex.codecogs.com/gif.latex?%20E%20=%200%20\\cdot%20\(1-\\frac{1}{n_{i+1}-n_i}\)%20+%201\\cdot%20\(n_{i+1}-n_i\)%20\\cdot%20\\frac{1}{n_{i+1}-n_i}%20=%201%20) **b.** ![](http://latex.codecogs.com/gif.latex?%20X_j%20\\quad\\text%20{stands%20for%20jth%20increment}%20\\\\%20Var[X_j]%20=%20E[X_j^2]%20-%20E^2[X_j]%20=%20\(0^2%20\\cdot%20\\frac{99}{100}%20+%20100^2\\cdot\\frac{1}{100}\)%20-%201^2%20=%2099%20\\\\%20%0d%0aVar[X]%20=%20\\sum_{i=%201}^{n}Var[X_i]%20=%2099n) ### Problems 2 : Searching an unsorted array *** Thus problem examines three algorithms for searching for a value x in an unsorted array A consisting of n elements. Consider the following randomized strategy: pick a random index i into A. If A[i] = x, then we terminate; otherwise, we continue the search by picking a new random index into A. We continue picking random indices into A until we find an index j such that A[j] = x or until we have checked every element of A. Note that we pick from the whole set of indices each time, so that we may examine a given element more than once. **a.** Write pseudocode for a procedure RANDOM-SEARCH to implement the strategy above. Be sure that your algorithm terminates when all indices into A have been picked. **b.** Suppose that there is exactly one index i such that A[i] = x. What is the expected number of indices into A that must be picked before x is found and RANDOM- SEARCH terminates? **c.** Generalizing your solution to part (b), suppose that there are k ≥ 1 indices i such that A[i] = x. What is the expected number of indices into A that must be picked before x is found and RANDOM-SEARCH terminates? Your answer should be a function of n and k. **d.** Suppose that there are no indices i such that A[i] = x. What is the expected number of indices into A that must be picked before all elements of A have been checked and RANDOM-SEARCH terminates? Now consider a deterministic linear search algorithm, which we refer to as DETERMINISTIC-SEARCH. Specifically, the algorithm searches A for x in order, considering A[1], A[2], A[3],..., A[n] until either A[i] = x is found or the end of the array is reached. Assume that all possible permutations of the input array are equally likely. **e.** Suppose that there is exactly one index i such that A[i] = x. What is the expected running time of DETERMINISTIC-SEARCH? What is the worst-case running time of DETERMINISTIC-SEARCH? **f.** Generalizing your solution to part (e), suppose that there are k ≥ 1 indices i such that A[i] = x. What is the expected running time of DETERMINISTIC-SEARCH? What is the worst-case running time of DETERMINISTIC-SEARCH? Your answer should be a function of n and k. **g.** Suppose that there are no indices i such that A[i] = x. What is the expected running time of DETERMINISTIC-SEARCH? What is the worst-case running time of DETERMINISTIC-SEARCH? Finally, consider a randomized algorithm SCRAMBLE-SEARCH that works by first randomly permuting the input array and then running the deterministic linear search given above on the resulting permuted array. **h.** Letting k be the number of indices i such that A[i] = x, give the worst-case and expected running times of SCRAMBLE-SEARCH for the cases in which k = 0 and k = 1. Generalize your solution to handle the case in which k ≥ 1. **i.** Which of the three searching algorithms would you use? Explain your answer. ### `Answer` **a.** RANDOM-SEARCH(A, v): B = new array[n] count = 0 while(count < n): r = RANDOM(1, n) if A[r] == v: return r if B[r] == false: count += 1 B[r] = true return false **b.** 就是几何分布~n **c.** 还是几何分布~n/k **d.** Section5.4.2,在每个盒子里至少有一个球前,要投多少个球. n(lnn+O(1)) **e.** 平均查找时间是n/2,最坏查找时间是n **f.** 最坏运行时间是n-k+1.平均运行时间是n/(k+1) **g.** 都是n **h.** 跟DETERMINISTIC-SEARCH是一样的,这时候的期望就是平均. **i.** 自然是**DETERMINISTIC-SEARCH** *** Follow [@louis1992](https://github.com/gzc) on github to help finish this task. ================================================ FILE: C06-Heapsort/6.1.md ================================================ ### Exercises 6.1-1 *** What are the minimum and maximum numbers of elements in a heap of height h? ### `Answer` 最多就是一颗很完美的二叉树,是2^(h+1) − 1 ; 最少的话最后一层只有一个,是2^h What it is a perfect complete tree, it is 2^(h+1) - 1; when the last level has only one element, we have 2^h. ### Exercises 6.1-2 *** Show that an n-element heap has height ⌞lg n⌟ ### `Answer` ![](http://latex.codecogs.com/gif.latex?%202^{h+1}-1\\geq%20x%20\\geq%202^{h}%20\\rightrightarrows%20%20\\lg{x}%20\\geq%20h%20\\geq%20\\lg\(x+1\)-1%20) 所以h = ⌞lg n⌟ ### Exercises 6.1-3 *** Show that in any subtree of a max-heap, the root of the subtree contains the largest value occurring anywhere in that subtree. ### `Answer` 这就是最大堆的性质. This is the property of maximum heap. ### Exercises 6.1-4 *** Where in a max-heap might the smallest element reside, assuming that all elements are distinct? ### `Answer` It must be in the leaf node. ### Exercises 6.1-5 *** Is an array that is in sorted order a min-heap? ### `Answer` 没有说明是递增数组还是递减数组,所以不一定. We don't know whether it's an increasing order or descending order. ### Exercises 6.1-6 *** Is the sequence [23, 17, 14, 6, 13, 10, 1, 5, 7, 12] a max-heap? ### `Answer` NO, because 7 > 6 ### Exercises 6.1-7 *** Show that, with the array representation for storing an n-element heap, the leaves are the nodes indexed by ⌞n/2⌟ + 1, ⌞n/2⌟ + 2, ... , n. ### `Answer` 挺容易推的,因为每增加两个节点,树的叶子节点就会往后推移一位.再注意一下取整的位置就行. It is easy to conclude, every time we add two nodes, the index of leaf nodes will increase 1. *** Follow [@louis1992](https://github.com/gzc) on github to help finish this task. ================================================ FILE: C06-Heapsort/6.2.md ================================================ ### Exercises 6.2-1 *** Using Figure 6.2 as a model, illustrate the operation of MAX-HEAPIFY(A, 3) on the array A = 27,17, 3, 16, 13, 10, 1, 5, 7, 12, 4, 8, 9, 0. ### `Answer` ![](./repo/s2/1.png) ### Exercises 6.2-2 *** Starting with the procedure MAX-HEAPIFY, write pseudocode for the procedure MIN- HEAPIFY(A, i), which performs the corresponding manipulation on a min-heap. How does the running time of MIN-HEAPIFY compare to that of MAX-HEAPIFY? ### `Answer` MIN-HEAPIFY(A, i): l <- LEFT(i) r <- RIGHT(i) smallest <- i if l ≤ heap-size[A] and A[l] < A[i]: then smallest <- l if r ≤ heap-size[A] and A[r] < A[smallest]: then smallest <- r if smallest ≠ i: then swap(A[i], A[smallest]) MIN-HEAPIFY(A, smallest) ### Exercises 6.2-3 *** What is the effect of calling MAX-HEAPIFY(A, i) when the element A[i] is larger than its children? ### `Answer` 函数直接返回. Just return. ### Exercises 6.2-4 *** What is the effect of calling MAX-HEAPIFY(A, i) for i > heap-size[A]/2? ### `Answer` 这种情况下,这个节点是叶子节点. Under this condition, this node is a leaf node. ### Exercises 6.2-5 *** The code for MAX-HEAPIFY is quite efficient in terms of constant factors, except possibly for the recursive call in line 10, which might cause some compilers to produce inefficient code. Write an efficient MAX-HEAPIFY that uses an iterative control construct (a loop) instead of recursion. ### `Answer` MIN-HEAPIFY(A, i): while i ≤ heap-size[A]: l <- LEFT(i) r <- RIGHT(i) largest <- i if l ≤ heap-size[A] and A[l] > A[i]: then largest <- l if r ≤ heap-size[A] and A[r] > A[largest]: then largest <- r if largest ≠ i: then swap(A[i], A[largest]) i = largest else break ### Exercises 6.2-6 *** Show that the worst-case running time of MAX-HEAPIFY on a heap of size n is Ω(lg n). (Hint: For a heap with n nodes, give node values that cause MAX-HEAPIFY to be called recursively at every node on a path from the root down to a leaf.) ### `Answer` 最坏情况是从root一直递归到leaf,因为heap的高度为⌞lg n⌟,所以最坏运行时间是Ω(lgn). In the worst case, this function will call until the leaf node. *** Follow [@louis1992](https://github.com/gzc) on github to help finish this task. ================================================ FILE: C06-Heapsort/6.3.md ================================================ ### Exercises 6.3-1 *** Using Figure 6.3 as a model, illustrate the operation of BUILD-MAX-HEAP on the array A = [5, 3, 17, 10, 84, 19, 6, 22, 9]. ### `Answer` ![](./repo/s3/1.png) ### Exercises 6.3-2 *** Why do we want the loop index i in line 2 of BUILD-MAX-HEAP to decrease from ⌞length[A]/2⌟ to 1 rather than increase from 1 to ⌞length[A]/2⌟? ### `Answer` 如果先从1开始,它的子树并不是最大堆,肯定不能这样迭代. If we start from 1, because its subtree is not a maximum heap, we can't follow this order. ### Exercises 6.3-3 *** Show that there are at most ![](http://latex.codecogs.com/gif.latex?%20\\lceil%20n/\(2^{h+1}\)%20\\rceil) nodes of height h in any n-element heap. ### `Answer` According to [6.1.1](./6.1.md)we have ![](http://latex.codecogs.com/gif.latex?%202^{h_0}%20\\le%20n%20\\le%202^{h_0+1}-1%20) Mark h0 as the height of tree. ![](http://latex.codecogs.com/gif.latex?%20\\lceil%20n/\(2^{h+1}\)%20\\rceil%20\\le%20\\lceil%20\(2^{h_0+1}-1\)/\(2^{h+1}\)%20\\rceil%20=%202^{h_0-h}) If the current layer is full, then we have equal. If in the leaf node and not full, we have less. *** Follow [@louis1992](https://github.com/gzc) on github to help finish this task. ================================================ FILE: C06-Heapsort/6.4.md ================================================ ### Exercises 6.4-1 *** Using Figure 6.4 as a model, illustrate the operation of HEAPSORT on the array A = [5, 13, 2, 25, 7, 17, 20, 8, 4]. ### `Answer` ![](./repo/s4/1.png) ### Exercises 6.4-2 *** Argue the correctness of HEAPSORT using the following loop invariant: • At the start of each iteration of the for loop of lines 2-5, the subarray A[1...i] is a max-heap containing the i smallest elements of A[1...n], and the subarray A[i + 1...n] contains the n - i largest elements of A[1...n], sorted. ### `Answer` It is very obvious. ### Exercises 6.4-3 *** What is the running time of heapsort on an array A of length n that is already sorted in increasing order? What about decreasing order? ### `Answer` If the array is in descending order, then we have the worst case, we need ![](http://latex.codecogs.com/gif.latex?%20\\sum_{i%20=%201}^{n}\\lg{i}%20=%20\\lg{n!}%20=%20\\Theta\(n\\lg{n}\)%20) If it is in increasing order, we still need![](http://latex.codecogs.com/gif.latex?\\Theta\(n\\lg{n}\)%20),Because the cost of Max_heapify doesn't change. ### Exercises 6.4-4 *** Show that the worst-case running time of heapsort is Ω(n lg n). ### `Answer` Same as 6.3.3. ### Exercises 6.4-5 *** Show that when all elements are distinct, the best-case running time of heapsort is Ω(n lg n). ### `Answer` It is actually a hard problem, see [solution](http://stackoverflow.com/questions/4589988/lower-bound-on-heapsort) *** Follow [@louis1992](https://github.com/gzc) on github to help finish this task. ================================================ FILE: C06-Heapsort/6.5.md ================================================ ### Exercises 6.5-1 *** Illustrate the operation of HEAP-EXTRACT-MAX on the heap A = [15, 13, 9, 5, 12, 8, 7, 4, 0, 6, 2, 1]. ### `Answer` ![](./repo/s5/1.png) ### Exercises 6.5-2 *** Illustrate the operation of MAX-HEAP-INSERT(A, 10) on the heap A = [15, 13, 9, 5, 12, 8, 7, 4, 0, 6, 2, 1]. Use the heap of Figure 6.5 as a model for the HEAP-INCREASE-KEY call. ### `Answer` ![](./repo/s5/2.png) ### Exercises 6.5-3 *** Write pseudocode for the procedures HEAP-MINIMUM, HEAP-EXTRACT-MIN, HEAP- DECREASE-KEY, and MIN-HEAP-INSERT that implement a min-priority queue with a min-heap. ### `Answer` My implementation on priority queue. [p_queue.h](./p_queue.h) [p_queue.cpp](./p_queue.cpp) ### Exercises 6.5-4 *** Why do we bother setting the key of the inserted node to -∞ in line 2 of MAX-HEAP- INSERT when the next thing we do is increase its key to the desired value? ### `Answer` keey the HEAP-INCREASE-KEY condition still holds. ### Exercises 6.5-5 *** Argue the correctness of HEAP-INCREASE-KEY using the following loop invariant: • At the start of each iteration of the while loop of lines 4-6, the array A[1...heap- size[A]] satisfies the max-heap property, except that there may be one violation: A[i] may be larger than A[PARENT(i)]. ### `Answer` obvious loop-invariant. ### Exercises 6.5-6 *** Each exchange operation on line 5 of HEAP-INCREASE-KEY typically requires three asignments. Show how to use the idea of the inner loop of INSERTION-SORT to reduce the three assignments down to just one assignment. ### `Answer` HEAP-INCREASE-KEY(A, i, key): if key < A[i] error "New key is smaller than current key" A[i] = key while i > 1 and A[PARENT(i)] < key A[i] = A[PARENT(i)] i = PARENT(i) A[i] = key ### Exercises 6.5-7 *** Show how to implement a first-in, first-out queue with a priority queue. Show how to implement a stack with a priority queue. (Queues and stacks are defined in Section 10.1.) ### `Answer` * 先进先出队列: 每次都给新插入的元素赋予更低的优先级即可. * 栈:每次都给新插入的元素赋予更高的优先级. * First-in, first-out queue: Assign a lower priority to the newly inserted element. * stack:Assign a higher priority to the newly inserted element. ### Exercises 6.5-8 *** The operation HEAP-DELETE(A, i) deletes the item in node i from heap A. Give an implementation of HEAP-DELETE that runs in O(lg n) time for an n-element max-heap. ### `Answer` ```c HEAP-DELETE(A, i): if A[i] < A[A.heap-size] HEAP-INCREASE-KEY(A, i, A[A.heap-size]) A.heap-size -= 1 else A[i] = A[A.heap-size] A.heap-size -= 1 MAX-HEAPIFY(A,i) ``` **Notice: What's wrong with the implementation bellow?** ```c HEAP-DELETE(A, i): A[i] = A[A.heap-size] A.heap-size -= 1 MAX-HEAPIFY(A, i) ``` You can't assume there always be A[i] > A[A.heap-size]. For example: ``` 10 / \ 5 9 / \ / \ 2 3 7 8 ``` If you want to delete key 2, the A[A.heap-size] is 8. But 8 should climb up to the position of 5. ### Exercises 6.5-9 *** Give an O(n lg k)-time algorithm to merge k sorted lists into one sorted list, where n is the total number of elements in all the input lists. (Hint: Use a min-heap for k-way merging.) ### `Answer` The problem occurs in [leetcode](https://leetcode.com/problems/merge-k-sorted-lists/) This is my [solution](https://github.com/gzc/leetcode/blob/master/cpp/021-030/Merge%20k%20Sorted%20Lists%20.cpp) *** Follow [@louis1992](https://github.com/gzc) on github to help finish this task. ================================================ FILE: C06-Heapsort/d-ary-heaps.cpp ================================================ /************************************************************************* > File Name: d-ary-heaps.cpp > Author: Louis1992 > Mail: zhenchaogan@gmail.com > Blog: http://gzc.github.io > Created Time: Tue Jun 23 19:22:21 2015 ************************************************************************/ #include #include #include using namespace std; #define PARENT(i,d) ((i - 1) / d) #define CHILD(i,c,d) (d * i + c + 1) typedef struct { int *elements; int d; int heap_size; } heap_t; void max_heapify(heap_t *heap, int i) { int largest = i; int basechild = CHILD(i, 0, heap->d); for (int k = 0; k < heap->d; k++) { int child = basechild+k; if (child < heap->heap_size && heap->elements[child] > heap->elements[largest]) largest = child; } if (largest != i) { swap(heap->elements[i],heap->elements[largest]); max_heapify(heap, largest); } } int extract_max(heap_t *heap) { int max = heap->elements[0]; heap->elements[0] = heap->elements[heap->heap_size - 1]; heap->heap_size--; max_heapify(heap, 0); return max; }; void increase_key(heap_t *heap, int i, int key) { if (key < heap->elements[i]) { cerr << "new key is smaller than current key" << endl; exit(-1); } while (i > 0 && heap->elements[PARENT(i,heap->d)] < key) { heap->elements[i] = heap->elements[PARENT(i,heap->d)]; i = PARENT(i,heap->d); } heap->elements[i] = key; } void insert(heap_t *heap, int key) { heap->heap_size++; heap->elements[heap->heap_size - 1] = INT_MIN; increase_key(heap, heap->heap_size - 1, key); } ================================================ FILE: C06-Heapsort/heap.cpp ================================================ /************************************************************************* > File Name: 2.cpp > Author: Louis1992 > Mail: zhenchaogan@gmail.com > Blog: http://gzc.github.io > Created Time: 一 12/22 21:47:37 2014 ************************************************************************/ #include using namespace std; /* * get the parent of this node */ int parent(int i) { return (i-1)/2; } /* * get the left child */ int left(int i) { return 2*i+1; } /* * get the right child */ int right(int i) { return 2*i+2; } /* * construct a sub-tree whose root is node i */ void maxHeapify(int A[], int n, int i) { int l = left(i); int r = right(i); int largest(0); if (l <= (n-1) && A[l] > A[i]) largest = l; else largest = i; if (r <= (n-1) && A[r] > A[largest]) largest = r; if(largest != i) { swap(A[i], A[largest]); maxHeapify(A, n, largest); } } /* * build the heap */ void buildMaxHeap(int A[], int n) { for(int i = n/2-1;i >= 0;i--) maxHeapify(A, n, i); } /* * heapsort */ void heapsort(int A[], int n) { buildMaxHeap(A, n); for(int i = n-1;i > 0;i--) { swap(A[0],A[i]); maxHeapify(A, --n, 0); } } void print(int A[], int n) { for(int i = 0;i < n;i++) cout << A[i] << " "; cout << endl; } int main() { int A[10] = {16,14,10,8,7,9,3,2,4,1}; heapsort(A, 10); print(A, 10); return 0; } ================================================ FILE: C06-Heapsort/main.cpp ================================================ /************************************************************************* > File Name: main.cpp > Author: Louis1992 > Mail: zhenchaogan@gmail.com > Blog: http://gzc.github.io > Created Time: Sun Jan 4 19:51:21 2015 ************************************************************************/ #include #include #include #include #include #include #include #include #include #include #include "p_queue.h" int main() { int A[10] = {10,9,8,7,6,5,4,3,2,1}; p_queue q1(A, 10); std::cout << q1.maximum() << std::endl; q1.insert(20); q1.insert(10); std::cout << q1.maximum() << std::endl; q1.extract_max(); std::cout << q1.maximum() << std::endl; q1.increase_key(0, 5); std::cout << q1.maximum() << std::endl; q1.extract_max(); std::cout << q1.maximum() << std::endl; q1.increase_key(1, 30); std::cout << q1.maximum() << std::endl; return 0; } ================================================ FILE: C06-Heapsort/makefile ================================================ sample : main.o p_queue.o g++ -o sample main.o p_queue.o main.o : main.cpp p_queue.h g++ -c main.cpp p_queue.o : p_queue.h p_queue.cpp g++ -c p_queue.cpp clean : rm sample main.o p_queue.o ================================================ FILE: C06-Heapsort/p_queue.cpp ================================================ /************************************************************************* > File Name: p_queue.cpp > Author: Louis1992 > Mail: zhenchaogan@gmail.com > Blog: http://gzc.github.io > Created Time: Sun Jan 4 19:28:46 2015 ************************************************************************/ #include "p_queue.h" #include p_queue::p_queue(int *A, int n) { for(int i = 0;i < n;i++) { a[i] = A[i]; } size = n; buildMaxHeap(a, size); } void p_queue::insert(int x) { size++; a[size-1] = x; int i = size-1; while(i > 0 && a[parent(i)] < a[i]) { std::swap(a[i], a[parent(i)]); i = parent(i); } } int p_queue::maximum() { if(size <= 0) { std::cerr << "heap overflow" << std::endl; return -1; } return a[0]; } int p_queue::extract_max() { if(size <= 0) { std::cerr << "heap overflow" << std::endl; return -1; } int max = a[0]; a[0] = a[size-1]; size--; maxHeapify(a, size, 0); return max; } void p_queue::increase_key(int i, int x) { a[i] = std::max(a[i], x); while (i > 0 && a[parent(i)] < a[i]) { std::swap(a[parent(i)], a[i]); i = parent(i); } } int p_queue::parent(int i) { return (i-1)/2; } int p_queue::left(int i) { return 2*i+1; } int p_queue::right(int i) { return 2*i+2; } void p_queue::maxHeapify(int A[], int n, int i) { int l = left(i); int r = right(i); int largest(0); if (l <= (n-1) && A[l] > A[i]) largest = l; else largest = i; if (r <= (n-1) && A[r] > A[largest]) largest = r; if(largest != i) { std::swap(A[i], A[largest]); maxHeapify(A, n, largest); } } void p_queue::buildMaxHeap(int A[], int n) { for(int i = n/2-1;i >= 0;i--) maxHeapify(A, n, i); } void p_queue::heapsort(int A[], int n) { buildMaxHeap(A, n); for(int i = n-1;i > 0;i--) { std::swap(A[0],A[i]); maxHeapify(A, --n, 0); } } ================================================ FILE: C06-Heapsort/p_queue.h ================================================ /************************************************************************* > File Name: p_queue.h > Author: Louis1992 > Mail: zhenchaogan@gmail.com > Blog: http://gzc.github.io > Created Time: Sun Jan 4 19:16:19 2015 ************************************************************************/ #ifndef _P_QUEUE_H #define _P_QUEUE_H #endif #include class p_queue { public: p_queue(int *A, int n); void insert(int x); int maximum(void); int extract_max(void); void increase_key(int i, int x); private: int a[10000]; int size; int parent(int i); int left(int i); int right(int i); void maxHeapify(int A[], int n, int i); void buildMaxHeap(int A[], int n); void heapsort(int A[], int n); }; ================================================ FILE: C06-Heapsort/problem.md ================================================ ### Problems 1 : Building a heap using insertion *** The procedure BUILD-MAX-HEAP in Section 6.3 can be implemented by repeatedly using MAX-HEAP-INSERT to insert the elements into the heap. Consider the following implementation: BUILD-MAX-HEAP'(A) heap-size[A] ← 1 for i←2 to length[A] do MAX-HEAP-INSERT(A, A[i]) a. Do the procedures BUILD-MAX-HEAP and BUILD-MAX-HEAP' always create the same heap when run on the same input array? Prove that they do, or provide a counterexample. b. Show that in the worst case, BUILD-MAX-HEAP' requires Θ(n lg n) time to build an n-element heap. ### `Answer` **a.** 不一定.对于数组[1,2,3,4,5,6].有 ![](./repo/p/1.png) **b.** 当原数组是递增序列时,需要的时间 ![](http://latex.codecogs.com/gif.latex?%20T%20=%20\\sum_{i%20=%202}^{n}\\lg{i}%20=%20\\lg{n!}%20=%20\\Theta\(n\\lg{n}\)%20) ### Problems 2 : Analysis of d-ary heaps *** A d-ary heap is like a binary heap, but (with one possible exception) non-leaf nodes have d children instead of 2 children. a. How would you represent a d-ary heap in an array? b. What is the height of a d-ary heap of n elements in terms of n and d? c. Give an efficient implementation of EXTRACT-MAX in a d-ary max-heap. Analyze its running time in terms of d and n. d. Give an efficient implementation of INSERT in a d-ary max-heap. Analyze its running time in terms of d and n. e. Give an efficient implementation of INCREASE-KEY(A, i, k), which first sets A[i] ← max(A[i], k) and then updates the d-ary max-heap structure appropriately. Analyze its running time in terms of d and n. ### `Answer` [implementation](./d-ary-heaps.cpp) ![](http://latex.codecogs.com/gif.latex?%20\\quad\\text{Height%20:}%20\\log_{d}{n}%20) ![](http://latex.codecogs.com/gif.latex?%20\\quad\\text{\\textbf{Complexity}}%20\\\\%0d%0a\\quad\\text{%20EXTRACT-MAX%20:%20}%20d\\log_{d}{n}%20\\\\%0d%0a\\quad\\text{%20INSERT%20:%20}%20\\log_{d}{n}%20\\\\%20%0d%0a\\quad\\text{%20INCREASE-KEY%20:%20}%20\\log_{d}{n}%20) ### Problems 3 : Young tableaus *** An m × n Young tableau is an m × n matrix such that the entries of each row are in sorted order from left to right and the entries of each column are in sorted order from top to bottom. Some of the entries of a Young tableau may be ∞, which we treat as nonexistent elements. Thus, a Young tableau can be used to hold r ≤ mn finite numbers. a. Draw a 4×4 Young tableau containing the elements {9, 16, 3, 2, 4, 8, 5, 14, 12}. b. Arguethatanm×nYoungtableauYisemptyifY[1,1]=∞.ArguethatYisfull (contains mn elements) if Y[m, n] < ∞. c. Give an algorithm to implement EXTRACT-MIN on a nonempty m × n Young tableau that runs in O(m + n) time. Your algorithm should use a recursive subroutine that solves an m × n problem by recursively solving either an (m - 1) × n or an m × (n - 1) subproblem. (Hint: Think about MAX-HEAPIFY.) Define T(p), where p = m + n, to be the maximum running time of EXTRACT-MIN on any m × n Young tableau. Give and solve a recurrence for T(p) that yields the O(m + n) time bound. d. Show how to insert a new element into a nonfull m × n Young tableau in O(m + n) time. e. Using no other sorting method as a subroutine, show how to use an n × n Young tableau to sort n^2 numbers in O(n^3) time. f. Give an O(m+n)-time algorithm to determine whether a given number is stored in a given m × n Young tableau. ### `Answer` [implementation](./young.cpp) **a.** ![](http://latex.codecogs.com/gif.latex?%0d%0a\\begin{matrix}%202%20&%203%20&%2012%20&%20\\infty%20\\\\%204%20&%208%20&%2016%20&%20\\infty%20\\\\%205%20&%209%20&%20\\infty%20&%20\\infty%20\\\\%2014%20&%20\\infty%20&%20\\infty%20&%20\\infty%20\\\\%20\\end{matrix}%20) **b.** 这是显然的~ **c.** T(p) = T(p-1) + O(1) = O(p) **d.** 跟c是一个相反的操作,具体见代码. **e.** ![](http://latex.codecogs.com/gif.latex?%20%0d%0aT%20=%20n^2O\(p\)%20%20=%20O\(n^3\)) **f.** 实现见代码. *** Follow [@louis1992](https://github.com/gzc) on github to help finish this task. ================================================ FILE: C06-Heapsort/young.cpp ================================================ /************************************************************************* > File Name: young.cpp > Author: Louis1992 > Mail: zhenchaogan@gmail.com > Blog: http://gzc.github.io > Created Time: Tue Jun 23 20:05:44 2015 ************************************************************************/ #include #include #include using namespace std; int EXTRACT_MIN(vector > &young) { int minimum = young[0][0]; int i(0),j(0); while(i < young.size() && j < young[0].size()) { int cur = young[i][j]; int ori(i),orj(j); int right(INT_MAX),down(INT_MAX); if(i < young.size()-1) down = young[i+1][j]; if(j < young[0].size()-1) right = young[i][j+1]; if(right == INT_MAX && down == INT_MAX) { young[i][j] = INT_MAX; break; } else if(down <= right) i++; else j++; swap(young[i][j], young[ori][orj]); } return minimum; } void INSERT(vector > &young, int v) { int i = young.size()-1; int j = young[0].size()-1; young[i][j] = v; while(i >= 0 && j >= 0) { int left(INT_MIN),up(INT_MIN); if(i > 0) up = young[i-1][j]; if(j > 0) left = young[i][j-1]; if(v >= up && v >= left) break; else if(v < up) { swap(young[i][j], young[i-1][j]); i--; } else { swap(young[i][j], young[i][j-1]); j--; } } } bool EXIST(vector > &young, int v) { int i(young.size()-1),j(0); while(i >= 0 && j < young[0].size()) { cout << i << " " << j << endl; if(young[i][j] == v) return true; if(young[i][j] < v) j++; else i--; } return false; } void print_young(vector > &young) { for(int i = 0;i < young.size();i++) { for(int j = 0;j < young[0].size();j++) { cout << young[i][j] << " "; } cout << endl; } } int main() { int arr1[4] = {2,3,12,INT_MAX}; int arr2[4] = {4,8,16,INT_MAX}; int arr3[4] = {5,9,INT_MAX,INT_MAX}; int arr4[4] = {14,INT_MAX,INT_MAX,INT_MAX}; vector v1(arr1,arr1+4); vector v2(arr2,arr2+4); vector v3(arr3,arr3+4); vector v4(arr4,arr4+4); vector > v; v.push_back(v1); v.push_back(v2); v.push_back(v3); v.push_back(v4); for(int i = 0;i < 9;i++) cout << EXTRACT_MIN(v) << endl; for(int i = 0;i < 9;i++) INSERT(v, i); print_young(v); cout << EXIST(v, 8) << endl; return 0; } ================================================ FILE: C07-Quicksort/7.1.md ================================================ ### Exercises 7.1-1 *** Using Figure 7.1 as a model, illustrate the operation of PARTITION on the array A = [13, 19, 3, 5, 12, 8, 7, 4, 21, 2, 6, 11]. ### `Answer` ![](./repo/s1/1.png) ### Exercises 7.1-2 *** What value of q does PARTITION return when all elements in the array A[p...r] have the same value? Modify PARTITION so that q = (p+r)/2 when all elements in the array A[p...r] have the same value. ### `Answer` It will return r. So we have to modify the code in case the worst situation. [code](./exercise_code/quicksort.py) ### Exercises 7.1-3 *** Give a brief argument that the running time of PARTITION on a subarray of size n is Θ(n). ### `Answer` Because we just iterate the array once. ### Exercises 7.1-4 *** How would you modify QUICKSORT to sort into nonincreasing order? ### `Answer` Change the condition **A[j] <= x** to **A[j] >= x** *** Follow [@louis1992](https://github.com/gzc) on github to help finish this task. ================================================ FILE: C07-Quicksort/7.2.md ================================================ ### Exercises 7.2-1 *** Use the substitution method to prove that the recurrence T (n) = T (n - 1) + Θ(n) has the solution T (n) = Θ(n^2), as claimed at the beginning of Section 7.2. ### `Answer` T(n) = T(n-1) + Θ(n) = Θ(n) + Θ(n-1) + ... + Θ(1) = Θ(n^2) Here is the substitution method: ``` Let's assume that T(n - 1) <= c(n-1)^2 Then T(n) <= c*(n-1)^2 + Θ(n) <= c*n^2 -(2cn - Θ(n)) So we can find a c which is large enough to make 2cn > Θ(n),eg. assume Θ(n) = kn, then we make c > k/2 So T(n) <= c*n^2 T(n) = Θ(n^2) ``` ### Exercises 7.2-2 *** What is the running time of QUICKSORT when all elements of array A have the same value? ### `Answer` Θ(n^2) ### Exercises 7.2-3 *** Show that the running time of QUICKSORT is Θ(n^2) when the array A contains distinct elements and is sorted in decreasing order. ### `Answer` Similar to the above problem,because T(n) = T(n-1) + Θ(n) The pivot value in quicksort is smaller than all of the other values, and hence can never achieve a balanced array in each recursive step. ### Exercises 7.2-4 *** Banks often record transactions on an account in order of the times of the transactions, but many people like to receive their bank statements with checks listed in order by check number. People usually write checks in order by check number, and merchants usually cash them with reasonable dispatch. The problem of converting time-of-transaction ordering to check-number ordering is therefore the problem of sorting almost-sorted input. Argue that the procedure INSERTION-SORT would tend to beat the procedure QUICKSORT on this problem. ### `Answer` 原数组越有序,逆序对越少,插入排序越快. The more ordered the original array, the less the inversions and Insertion Sort will run much more quickly. INSERTION-SORT's while loop which checks for whether or not a new insertion needs to be done would stop after only one iteration, and in addition quicksort would not be able to achieve a balanced split at every recursive step due to the pivot being the largest number in the new array to sort. ### Exercises 7.2-5 *** Suppose that the splits at every level of quicksort are in the proportion 1 - α to α, where 0 < α ≤ 1/2 is a constant. Show that the minimum depth of a leaf in the recursion tree is approximately - lg n/ lg α and the maximum depth is approximately -lg n/ lg(1 - α). (Don't worry about integer round-off.) ### `Answer` First calculate the minimum height: ![](http://latex.codecogs.com/gif.latex?%20n\\alpha^x%20\\le%201%20\\Rightarrow%20%20x%20\\ge%20\\log_{\\alpha}{\\frac{1}{n}}%20\\\\%20\\log_{\\alpha}{\\frac{1}{n}}%20%20=%20-\\log_{\\alpha}{n}%20=%20-\\frac{\\lg{n}}{\\lg{\\alpha}}) As for the maximum height, replace α with (1 - α) ### Exercises 7.2-6 *** Argue that for any constant 0 < α ≤ 1/2, the probability is approximately 1 - 2α that on a random input array, PARTITION produces a split more balanced than 1-α to α. ### `Answer` 因为比1-α to α更好的分布的数的在他们之间,有1 - 2α的比例.比他们差的有α+α = 2α The number better than [1-α,α] among them, and has a propotion 1 - 2α. So number worse than then is 2α *** Follow [@louis1992](https://github.com/gzc) on github to help finish this task. ================================================ FILE: C07-Quicksort/7.3.md ================================================ ### Exercises 7.3-1 *** Why do we analyze the average-case performance of a randomized algorithm and not its worst-case performance? ### `Answer` 因为最坏情况很极端才会发生,我们想要的是期望时间. Because the worst case happen rarely, we want the average case to be fine. ### Exercises 7.3-2 *** During the running of the procedure RANDOMIZED-QUICKSORT, how many calls are made to the random-number generator RANDOM in the worst case? How about in the best case? Give your answer in terms of Θ-notation. ### `Answer` The best : T(n) = 2T(n/2) + 1 = Θ(n) The worst : T(n) = T(n-1) + 1 = Θ(n) *** Follow [@louis1992](https://github.com/gzc) on github to help finish this task. ================================================ FILE: C07-Quicksort/7.4.md ================================================ ### Exercises 7.4-1 *** Show that in the recurrence ![](http://latex.codecogs.com/gif.latex?%20T\(n\)%20=%20\\max%20\\limits_{0%20\\le%20q%20\\le%20n-1}%20\(T\(q\)%20+%20T\(n-q-1\)\)+\\Theta\(n\)%20\\\\%0d%0aT\(n\)%20=%20\\Omega%20\(n^2\)%0d%0a) ### `Answer` ![](http://latex.codecogs.com/gif.latex?%20T\(n\)%20=%20\\max%20\\limits_{0%20\\le%20q%20\\le%20n-1}%20\(T\(q\)%20+%20T\(n-q-1\)\)+\\Theta\(n\)%20\\\\%20~\\hspace{15%20mm}%0d%0a=%20T\(n-1\)%20+%20\\Theta\(n\)%20\\\\%20~\\hspace{15%20mm}%0d%0a=%20\\Theta\(n^2\)%20\\\\%0d%0a\\quad\\text{because%20it%20is%20}%20\\Theta\(n^2\)%0d%0a\quad\\text{so%20it%20is%20also%20}%20\\Omega\(n^2\)%0d%0a) ### Exercises 7.4-2 *** Show that quicksort's best-case running time is Ω(nlgn). ### `Answer` ![](http://latex.codecogs.com/gif.latex?%20T\(n\)%20=%202T\(n/2\)%20+%20\\Theta\(n\)%0d%0a) According to the master theorem,it is Ω(nlgn) ### Exercises 7.4-3 *** Show that q^2 +(n-q-1)^2 achieves a maximum over q = 0,1,...,n-1 when q=0 or q=n-1. ### `Answer` 抛物线的简单性质~~~~ 或者可以用不等式,转化成已知A+B = n - 1,求A^2 + B^2在[0,n-1]的最大值. Just get by the property parabolic curve. ### Exercises 7.4-4 *** Show that RANDOMIZED-QUICKSORT's expected running time is Ω(n lg n). ### `Answer` ![](http://latex.codecogs.com/gif.latex?%20%0d%0aE[X]%20=%20%20%20\\sum_{i=1}^{n-1}%20\\sum_{j=i+1}^n%20\\frac{2}{j-i+1}%20\\\\%20~%20\\hspace{16%20mm}%0d%0a=%20\\sum_{i=1}^{n-1}%20\\sum_{k=1}^{n-i}%20\\frac{2}{k%20+%201}%20%20\\\\%20~%20\\hspace{16%20mm}%0d%0a\\ge%20\\sum_{i=1}^{n-1}%20\\sum_{k=1}^{n-i}%20\\frac{2}{2k}%20\\\\%20~%20\\hspace{16%20mm}%0d%0a\\ge%20\\sum_{i=1}^{n-1}%20\\Omega\(\\lg{n}\)%20\\\\%20~%20\\hspace{16%20mm}%0d%0a=%20%20%20\\Omega\(n\\lg{n}\)%0d%0a) For a monotonically increasing function ![](http://latex.codecogs.com/gif.latex?f%28k%29), we can approximate it by integrals: ![(A.11)\int_{m-1}^{n}f(x)dx\leqslant \sum_{k=m}^{n}f(k)\leqslant \int_{m}^{n+1}f(x)dx](http://latex.codecogs.com/gif.latex?%28A.11%29%5Cint_%7Bm-1%7D%5E%7Bn%7Df%28x%29dx%5Cleqslant%20%5Csum_%7Bk%3Dm%7D%5E%7Bn%7Df%28k%29%5Cleqslant%20%5Cint_%7Bm%7D%5E%7Bn+1%7Df%28x%29dx) Similarly, for a monotonically decreasing function ![](http://latex.codecogs.com/gif.latex?g%28x%29), would have: ![\int_{m-1}^{n}f(x)dx\geqslant \sum_{k=m}^{n}f(k)\geqslant \int_{m}^{n+1}f(x)dx](http://latex.codecogs.com/gif.latex?%5Cint_%7Bm-1%7D%5E%7Bn%7Df%28x%29dx%5Cgeqslant%20%5Csum_%7Bk%3Dm%7D%5E%7Bn%7Df%28k%29%5Cgeqslant%20%5Cint_%7Bm%7D%5E%7Bn+1%7Df%28x%29dx) Similar to the proof in (7.4): ![E[X]=\sum_{i=1}^{n-1}\sum_{j=i+1}^{n}\frac{2}{j-i+1}\\~\hspace{15.5mm}=\sum_{i=1}^{n-1}\sum_{j=i+1}^{n}\frac{2}{j-i+1}\\~\hspace{15.5mm}=\sum_{i=1}^{n-1}\sum_{k=1}^{n-i}\frac{2}{k+1}\\~\hspace{15.5mm}\geqslant \sum_{i=1}^{n-1}2\int_{1}^{n-i}\frac{1}{k+1}\\~\hspace{15.5mm}=\sum_{i=1}^{n-1}2(ln(n-i+2)-O(1))\\~\hspace{15.5mm}\geqslant2\int_{1}^{n}ln(n-i+2)di-O(n)\\~\hspace{15.5mm}=\int_{2}^{n+1}ln(x)dx-O(n)\\~\hspace{15.5mm}=2nln(n+1)-O(n)=\Omega (nlgn)](http://latex.codecogs.com/gif.latex?E%5BX%5D%3D%5Csum_%7Bi%3D1%7D%5E%7Bn-1%7D%5Csum_%7Bj%3Di+1%7D%5E%7Bn%7D%5Cfrac%7B2%7D%7Bj-i+1%7D%20%5C%5C%7E%5Chspace%7B15.5mm%7D%3D%5Csum_%7Bi%3D1%7D%5E%7Bn-1%7D%5Csum_%7Bj%3Di+1%7D%5E%7Bn%7D%5Cfrac%7B2%7D%7Bj-i+1%7D%20%5C%5C%7E%5Chspace%7B15.5mm%7D%3D%5Csum_%7Bi%3D1%7D%5E%7Bn-1%7D%5Csum_%7Bk%3D1%7D%5E%7Bn-i%7D%5Cfrac%7B2%7D%7Bk+1%7D%20%5C%5C%7E%5Chspace%7B15.5mm%7D%5Cgeqslant%20%5Csum_%7Bi%3D1%7D%5E%7Bn-1%7D2%5Cint_%7B1%7D%5E%7Bn-i%7D%5Cfrac%7B1%7D%7Bk+1%7D%20%5C%5C%7E%5Chspace%7B15.5mm%7D%3D%5Csum_%7Bi%3D1%7D%5E%7Bn-1%7D2%28ln%28n-i+2%29-O%281%29%29%20%5C%5C%7E%5Chspace%7B15.5mm%7D%5Cgeqslant2%5Cint_%7B1%7D%5E%7Bn%7Dln%28n-i+2%29di-O%28n%29%20%5C%5C%7E%5Chspace%7B15.5mm%7D%3D%5Cint_%7B2%7D%5E%7Bn+1%7Dln%28x%29dx-O%28n%29%20%5C%5C%7E%5Chspace%7B15.5mm%7D%3D2nln%28n+1%29-O%28n%29%3D%5COmega%20%28nlgn%29) ### Exercises 7.4-5 *** The running time of quicksort can be improved in practice by taking advantage of the fast running time of insertion sort when its input is "nearly" sorted. When quicksort is called on a subarray with fewer than k elements, let it simply return without sorting the subarray. After the top-level call to quicksort returns, run insertion sort on the entire array to finish the sorting process. Argue that this sorting algorithm runs in O(nk + nlg(n/k)) expected time. How should k be picked, both in theory and in practice? ### `Answer` 先看快速排序的那部分,当深度为lg(n/k)便停止了快速排序,所以快速排序的时间为O(nlg(n/k));再看插入排序那部分,现在有n/k个小数组,每个数组的大小为k,需要O(k^2)的时间排序,所以插入排序的时间是n/k * O(k^2) = O(nk). 理论上很难确定k,因为快速排序的渐近函数在数量级上优于插入排序;用实验确定是比较靠谱的方法. The main idea is to note that the recursion stops when n^2*i = k, that is i = log2(n)(k). The recursion takes in total O(n * lg(n)(k)). The resulting array is composed of k subarrays of size n=k, where the elements in each subarray are all less than all the subarrays following it. Running Insertion-Sort on the entire array is thus equivalent to sorting each of the n/k subarrays of size k, which takes on the average n/k * O(k2) = O(nk) (the expected running time of Insertion-Sort is O(n^2)). If k is chosen too big, then the O(nk) cost of insertion becomes bigger than (n lg n). Therefore k must be O(lg n). Furthermore it must be that O(nk + n lg (n)(k) ) = O(n lg n). If the constant factors in the big-oh notation are ignored, than it follows that k should be such that k < lg k which is impossible (unless k = 1) - the error comes from ignoring the constant factors. Let c1 be the constant factor in quicksort, and c2 be the constant factor in insertion sort. Than k must be chosen such that c2k + c1 lg n k < c1 lg n which requires c1k intersection[0]: intersection[0] = items[i][0] if items[i][1] < intersection[1]: intersection[1] = items[i][1] s = p for i in range(p, r): if before(items[i], intersection): items[i], items[s] = items[s], items[i] s += 1 items[r], items[s] = items[s], items[r] t = s + 1 while t <= i: if intersects(items[i], intersection): items[t], items[i] = items[i], items[t] t += 1 else: i -= 1 return (s, t) def fuzzy_sort(items, p, r): if (p < r): pivot = partition(items, p, r); fuzzy_sort(items, p, pivot[0]); fuzzy_sort(items, pivot[1], r); items = [[2,4],[0,1],[3,5],[-1,-2],[0,99]] print items fuzzy_sort(items, 0, len(items)-1) print items ================================================ FILE: C07-Quicksort/exercise_code/hoare.py ================================================ #!/usr/bin/env python # coding=utf-8 def quicksort(items, p, r): if p < r: q = partition(items, p, r) quicksort(items, p, q) quicksort(items, q+1, r) def partition(items, p, r): x = items[p] i = p - 1 j = r + 1 while True: while True: j = j - 1 if items[j] <= x: break while True: i = i + 1 if items[i] >= x: break if i < j: items[i],items[j] = items[j],items[i] else: return j items = [13,19,9,5,12,8,7,4,11,2,6,21] quicksort(items, 0, len(items)-1) print items ================================================ FILE: C07-Quicksort/exercise_code/quickSortWithEqualElements.cpp ================================================ #include #include #include #include #include #include using namespace std; class QuickSort{ public: QuickSort(){} ~QuickSort(){} void qsort(vector &data); bool isSorted(int start, int end, vector & data); private: void sortHelper(int start, int end, vector & data); int hoarePartition(int start, int end, vector & data); void threeSortHelper(int start, int end, vector & data); pair threeWayPartition(int start, int end, vector & data); pair fastThreePartition(int start, int end, vector & data); int getRandom(int start, int end); }; void QuickSort::qsort(vector & data){ threeSortHelper(0, data.size() - 1, data); } void QuickSort::sortHelper(int start, int end, vector & data){ if(start < end){ int pivot = hoarePartition(start, end, data); sortHelper(start, pivot - 1, data); sortHelper(pivot + 1, end, data); } } void QuickSort::threeSortHelper(int start, int end, vector & data){ if(start < end){ int randomIdx = getRandom(start, end); swap(data[start], data[randomIdx]); auto bound = fastThreePartition(start, end, data); threeSortHelper(start,bound.first - 1, data); threeSortHelper(bound.second + 1, end, data); } } /*///////////////////////////////////////// * * Partition Algorithm Block * *///////////////////////////////////////// /* * Modified Hoare Partition, which stops scanning on equal elements to avoid * the occurence of worst case O(n^2) * * Ref: Programming Perls 2nd Edition, Jon Bentley */ int QuickSort::hoarePartition(int start, int end, vector & data){ int pivot = data[start]; int i = start; int j = end + 1; while(true){ do{ ++i; } while( i <= end && data[i] < pivot); do{ --j; } while (data[j] > pivot); if(i > j){ break; } swap(data[i], data[j]); } swap(data[start], data[j]); return j; } /** * Ref:Algorithms, 4th Edition by Robert S. and Kevin W. */ pair QuickSort::threeWayPartition(int start, int end, vector & data){ int pivot = data[start]; int low = start; int high = end; int i = low + 1; while(i <= high){ if(data[i] < pivot){ swap(data[i++], data[low++]); } else if (data[i] > pivot){ swap(data[i], data[high--]); } else { ++i; } } return make_pair(low, high); } /** * The three way partition above does extra swaps for elements not equal to pivot. * The fast three way partition below does extra swaps for elements equal to pivot to i. * This algorithm does fewer swaps when the number of elements equal to pivot is smaller. * * Ref:Algorithms, 4th Edition by Robert S. and Kevin W. * */ pair QuickSort::fastThreePartition(int start, int end, vector & data){ int i = start, j = end + 1; int p = start, q = end + 1; int v = data[start]; while(true){ while(data[++i] < v){ if(i == end){ break; } } while(data[--j] > v){ if(j == start){ break; } } if(i == j && data[i] == v){ swap(data[++p], data[i]); } if(i >= j) break; swap(data[i], data[j]); if(data[i] == v){ swap(data[++p], data[i]); } if(data[j] == v){ swap(data[--q], data[j]); } } i = j + 1; for(int k = start; k <= p; ++k){ swap(data[k], data[j--]); } for(int k = end; k>= q; --k){ swap(data[k], data[i++]); } return make_pair(j + 1, i - 1); } /*///////////////////////////////////////// * * Utility Function Block * *///////////////////////////////////////// int QuickSort::getRandom(int start, int end){ srand(time(NULL)); return rand() % (end - start + 1) + start; } bool QuickSort::isSorted(int start, int end, vector & data){ for(int i = start + 1; i <= end; ++i){ if(data[i] < data[i-1]){ return false; } } return true; } int main(){ vector test1 {0}; vector test2 {-1, 3, 2, 4,-10, 100}; QuickSort solver; solver.qsort(test1); solver.qsort(test2); assert(solver.isSorted(0, test1.size()-1, test1)); assert(solver.isSorted(0, test2.size()-1, test2)); } ================================================ FILE: C07-Quicksort/exercise_code/quicksort.py ================================================ #!/usr/bin/env python # coding=utf-8 def quicksort(items, p, r): if p < r: q = partition(items, p, r) quicksort(items, p, q-1) quicksort(items, q+1, r) def partition(items, p, r): x = items[r] i = p-1 count = 0 for j in range(p, r): if items[j] == x: count += 1 if items[j] <= x: i = i + 1 items[i],items[j] = items[j],items[i] items[i+1],items[r] = items[r],items[i+1] return i+1-count/2 items = [2,5,9,3,7,0,-1] quicksort(items, 0, len(items)-1) print items ================================================ FILE: C07-Quicksort/exercise_code/tailrecursive.py ================================================ #!/usr/bin/env python # coding=utf-8 def tail_quicksort(items, p, r): while p < r: q = partition(items, p, r) if r-q >= q-p: tail_quicksort(items, p, q-1) p = q+1 else: tail_quicksort(items, q+1, r) r = q-1; def partition(items, p, r): x = items[r] i = p-1 for j in range(p, r): if items[j] <= x: i = i + 1 items[i],items[j] = items[j],items[i] items[i+1],items[r] = items[r],items[i+1] return i+1 items = [2,5,9,3,7,0,-1] tail_quicksort(items, 0, len(items)-1) print items ================================================ FILE: C07-Quicksort/problem.md ================================================ ### Problems 1 : Hoare partition correctness *** The version of PARTITION given in this chapter is not the original partitioning algorithm. Here is the original partition algorithm, which is due to T. Hoare: HOARE-PARTITION(A, p, r): x <- A[p] i <- p - 1 j <- r + 1 while TRUE: do repeat j <- j - 1 until A[j] <= x repeat i <- i + 1 until A[i] >= x if i < j: then exchange A[i] <-> A[j] else return j **a.** Demonstrate the operation of HOARE-PARTITION on the array A = [13, 19, 9, 5, 12, 8, 7, 4, 11, 2, 6, 21], showing the values of the array and auxiliary values after each iteration of the for loop in lines 4-11. The next three questions ask you to give a careful argument that the procedure HOARE- PARTITION is correct. Prove the following: **b.** The indices i and j are such that we never access an element of A outside the subarray A[p...r]. **c.** When HOARE-PARTITION terminates, it returns a value j such that p ≤ j < r. **d.** Every element of A[p...j] is less than or equal to every element of A[j+1...r] when HOARE-PARTITION terminates. The PARTITION procedure in Section 7.1 separates the pivot value (originally in A[r]) from the two partitions it forms. The HOARE-PARTITION procedure, on the other hand, always places the pivot value (originally in A[p]) into one of the two partitions A[p...j] and A[j + 1...r]. Since p ≤ j < r, this split is always nontrivial. **e.** Rewrite the QUICKSORT procedure to use HOARE-PARTITION. ### `Answer` **a.** ![](./repo/p/1.png) **b.** 这是肯定的,因为i,j是往中间靠拢的. **c.** j至少会减2次. 若第一次进入while,j只减了1次,那么会做一次swap.然后继续进入while. **d.** 很显然,小于x的放前面了,大于等于x的在后面. **e.** [implementation](./exercise_code/hoare.py) ### Problems 2 : Alternative quicksort analysis *** An alternative analysis of the running time of randomized quicksort focuses on the expected running time of each individual recursive call to QUICKSORT, rather than on the number of comparisons performed. **a.** Argue that, given an array of size n, the probability that any particular element is chosen as the pivot is 1/n. Use this to define indicator random variables Xi = I{ith smallest element is chosen as the pivot}. What is E [Xi]? **b.** Let T (n) be a random variable denoting the running time of quicksort on an array of size n. Argue that ![](http://latex.codecogs.com/gif.latex?%0d%0aE[T\(n\)]%20=%20E\\bigg[\\sum_{q=1}^nX_q\(T\(q-1\)%20+%20T\(n-q\)%20+%20\\Theta\(n\)\)\\bigg]%20) **c.** Show that equation (7.5) simplifies to ![](http://latex.codecogs.com/gif.latex?%0d%0aE[T\(n\)]%20=%20\\frac{2}{n}\\sum_{q=2}^{n-1}E[T\(q\)]%20+%20\\Theta\(n\)%20) **d.** Show that ![](http://latex.codecogs.com/gif.latex?%0d%0a\\sum_{k=2}^{n-1}k\\lg{k}%20\\le%20\\frac{1}{2}n^2\\lg{n}%20-%20\\frac{1}{8}n^2%20) **e.** Using the bound from equation (7.7), show that the recurrence in equation (7.6) has the solution E [T (n)] = Θ(n lg n). (Hint: Show, by substitution, that E[T (n)] ≤ an log n - bn for some positive constants a and b.) ### `Answer` **a.** E[Xi]=1/n. **b.** 这个算式的本质和之前分析快速排序的算式是一样的. **c.** 就是简单的化简 **d.** ![](http://latex.codecogs.com/gif.latex?%0d%0a%20%20%20\\sum_{k=2}^{n-1}k\\lg{k}%0d%0a%20%20%20=%20%20%20\\sum_{k=2}^{\\lceil%20n/2%20\\rceil%20-%201}k\\lg{k}%20+%20\\sum_{k=\l\ceil%20n/2%20\\rceil}^{n%20-%201}k\\lg{k}%20\\\\%20~%20\\hspace{22%20mm}%0d%0a%20%20%20\\le%20\\sum_{k=2}^{n/2}k\\lg{k}%20+%20\\sum_{k=n/2%20+%201}^{n}k\\lg{k}%20\\\\%20~%20\\hspace{22%20mm}%0d%0a%20%20%20\\le%20\\sum_{k=2}^{n/2}k\\lg\(n/2\)%20+%20\\sum_{k=n/2%20+%201}^{n}k\\lg{n}%20\\\\%20~%20\\hspace{22%20mm}%0d%0a%20%20%20=%20%20%20\\lg\(n/2\)\\sum_{k=2}^{n/2}k%20+%20\\lg{n}\\sum_{k=n/2%20+%201}^{n}k%20\\\\%20~%20\\hspace{22%20mm}%0d%0a%20%20%20=%20%20%20\(\\lg{n}%20-%20\\lg{2}\)\\bigg\(\\frac{\(n/2\)\(n/2%20+%201\)}{2}\\bigg\)%20+%20\\lg{n}\\bigg\(\\frac{n\(n+1\)}{2}%20-%20\\frac{\(n/2\)\(n/2%20+%201\)}{2}\\bigg\)%20\\\\%20%20~%20\\hspace{22%20mm}%0d%0a%20%20%20=%20%20%20\\lg{n}\\frac{n\(n+1\)}{2}%20-%20\\frac{\(n/2\)\(n/2%20+%201\)}{2}%20\\\\%20%20~%20\\hspace{22%20mm}%0d%0a%20%20%20=%20%20%20\\frac{1}{2}\\lg{n}\(n^2%20+%202n%20+%201\)%20-%20\\frac{1}{8}\(n^2%20+%202n%20+%201/8\)%20\\\\%20~%20\\hspace{22%20mm}%0d%0a%20%20%20=%20%20%20\\frac{1}{2}n^2\\lg{n}%20-%20\\frac{1}{8}n^2%20-%20\\frac{8n\\lg{n}%20+%204\\lg{n}%20-%202n%20-%201/8}{8}%20\\\\%20~%20\\hspace{22%20mm}%0d%0a%20%20%20\\le%20\\frac{1}{2}n^2\\lg{n}%20-%20\\frac{1}{8}n^2) **e.** 我们猜想 E[T(n)] ≤ anlgn ![](http://latex.codecogs.com/gif.latex?%0d%0aE[T\(n\)]%20=%20%20%20\\frac{2}{n}\\sum_{q=2}^{n-1}E[T\(q\)]%20+%20\\Theta\(n\)%20\\\\%20~%20\\hspace{21%20mm}%0d%0a%20%20%20%20%20%20%20%20%20%20%20%20\\le%20\\frac{2}{n}\\sum_{q=2}^{n-1}an\\lg{n}%20+%20\\Theta\(n\)%20%20%20\\\\%20~%20\\hspace{21%20mm}%0d%0a%20%20%20%20%20%20%20%20%20%20%20%20\\le%20\\frac{2a}{n}\\bigg\(\\frac{1}{2}n^2\\lg{n}%20-%20\\frac{1}{8}n^2\\bigg\)%0d%0a%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20+%20\\Theta\(n\)%20\\\\\ ~%20\\hspace{21%20mm}%0d%0a%20%20%20%20%20%20%20%20%20%20%20%20=%20%20%20an\\lg{n}%20-%20\\frac{a}{4}n%20+%20\\Theta\(n\)%20\\\\%20~%20\\hspace{21%20mm}%0d%0a%20%20%20%20%20%20%20%20%20%20%20%20\\le%20an\\lg{n}%20%20) ### Problems 3 : Stooge sort *** Professors Howard, Fine, and Howard have proposed the following "elegant" sorting algorithm: STOOGE-SORT(A, i, j): if A[i] > A[j] then exchange A[i] <-> A[j] if i + 1 >= j then return k <- ⌊(j-i+1)/3⌋ STOOGE-SORT(A, i, j-k) STOOGE-SORT(A, i+k, j) STOOGE-SORT(A, i, j-k) a. Argue that, if n = length[A], then STOOGE-SORT(A, 1, length[A]) correctly sorts the input array A[1...n]. b. Give a recurrence for the worst-case running time of STOOGE-SORT and a tight asymptotic (Θ-notation) bound on the worst-case running time. c. Compare the worst-case running time of STOOGE-SORT with that of insertion sort, merge sort, heapsort, and quicksort. Do the professors deserve tenure? ### `Answer` **a.** 分三次进行排序,先将前2/3排好,再排后2/3,那么这时候最大的1/3已经在后面了。最后再对前面的2/3进行排序。是正确的。 **b.** T(n) = 3T(2n/3) + O(1) 根据主定理最坏运行时间是![](http://latex.codecogs.com/gif.latex?%20\\Theta\(n^{\\log{\\frac{3}{2}}{3}}\)) **c.** 当然不会! ### Problems 4 : Stack depth for quicksort *** The QUICKSORT algorithm of Section 7.1 contains two recursive calls to itself. After the call to PARTITION, the left subarray is recursively sorted and then the right subarray is recursively sorted. The second recursive call in QUICKSORT is not really necessary; it can be avoided by using an iterative control structure. This technique, called tail recursion, is provided automatically by good compilers. Consider the following version of quicksort, which simulates tail recursion. QUICKSORT'(A, p, r): while p < r: do Partition and sort left subarray. q <- PARTITION(A, p, r) QUICKSORT'(A, p, q-1) p <- q + 1 **a.** Argue that QUICKSORT'(A, 1, length[A]) correctly sorts the array A. Compilers usually execute recursive procedures by using a stack that contains pertinent information, including the parameter values, for each recursive call. The information for the most recent call is at the top of the stack, and the information for the initial call is at the bottom. When a procedure is invoked, its information is pushed onto the stack; when it terminates, its information is popped. Since we assume that array parameters are represented by pointers, the information for each procedure call on the stack requires O(1) stack space. The stack depth is the maximum amount of stack space used at any time during a computation. **b.** Describe a scenario in which the stack depth of QUICKSORT' is Θ(n) on an n-element input array. **c.** Modify the code for QUICKSORT' so that the worst-case stack depth is Θ(lg n). Maintain the O(n lg n) expected running time of the algorithm. ### `Answer` **a.** 这个递推式总是先把左边的排好序,再排右边的. 顺序上和以前的版本是一样的. **b.** 当运气比较差的时候,每次PARTITION都return r,会产生O(n)的堆栈深度. **c.** key idea : 先选range小的那一边迭代 [implementation](./exercise_code/tailrecursive.py) ### Problems 5 : Median-of-3 partition *** One way to improve the RANDOMIZED-QUICKSORT procedure is to partition around a pivot that is chosen more carefully than by picking a random element from the subarray. One common approach is the **median-of-3** method: choose the pivot as the median (middle element) of a set of 3 elements randomly selected from the subarray. (See Exercise 7.4-6.) For this problem, let us assume that the elements in the input array A[1...n] are distinct and that n ≥ 3. We denote the sorted output array by A'[1...n]. Using the median-of-3 method to choose the pivot element x, define pi = Pr{x = A'[i]}. **a.** Give an exact formula for pi as a function of n and i for i=2,3,...,n-1.(Note that p1 = pn = 0.) **b.** By what amount have we increased the likelihood of choosing the pivot as x = A'[⌊(n + 1/2⌋], the median of A[1...n], compared to the ordinary implementation? Assume that n → ∞, and give the limiting ratio of these probabilities. **c.** If we define a "good" split to mean choosing the pivot as x = A'[i], where n/ ≤ i ≤ 2n/3, by what amount have we increased the likelihood of getting a good split compared to the ordinary implementation? (Hint: Approximate the sum by an integral.) **d.** Argue that in the Ω(n lg n) running time of quicksort, the median-of-3 method affects only the constant factor. ### `Answer` **a.** ![](http://latex.codecogs.com/gif.latex?%20p_i%20=%20\\frac{6\(i-1\)\(n-i\)}{n\(n-1\)\(n-2\)}%20) **b.** ![](http://latex.codecogs.com/gif.latex?%0d%0a\\lim_{n%20\\to%20\\infty}\\frac{6\(i-1\)\(n-i\)}{n\(n-1\)\(n-2\)}/\\frac{1}{n}%0d%0a%20=%20\\lim_{n%20\\to%20\\infty}\\frac{6n\(n/2%20-%201\)\(n/2\)}{\(n-1\)\(n-2\)}%20=%201.5%20) **c.** ![](http://latex.codecogs.com/gif.latex?%0d%0a\\lim_{n%20\\to%20\\infty}\\sum_{i=n/3}^{2n/3}\\frac{6\(i-1\)\(n-i\)}{n\(n-1\)\(n-2\)}%20=%20%0d%0a\\lim_{n%20\\to%20\\infty}\\frac{6}{n\(n-1\)\(n-2\)}\\sum_{i=n/3}^{2n/3}\(i-1\)\(n-i\)%20=%20%0d%0a\\frac{13}{27}) **d.** 这种方法无法保证能取最优划分点,依然是概率问题,而且比普通的提升并没有很大. ### Problems 6 : Fuzzy sorting of intervals Consider a sorting problem in which the numbers are not known exactly. Instead, for each number, we know an interval on the real line to which it belongs. That is, we are given n closed intervals of the form [ai, bi], where ai ≤ bi. The goal is to **fuzzy-sort** these intervals, i.e., produce a permutation [i1, i2,..., in] of the intervals such that there exist ![](http://latex.codecogs.com/gif.latex?c_j\\in[a_{i_j},b_{i_j}]%20), satisfying c1 ≤c2 ≤···≤cn. **a.** Design an algorithm for fuzzy-sorting n intervals. Your algorithm should have the general structure of an algorithm that quicksorts the left endpoints (the ai 's), but it should take advantage of overlapping intervals to improve the running time. (As the intervals overlap more and more, the problem of fuzzy-sorting the intervals gets easier and easier. Your algorithm should take advantage of such overlapping, to the extent that it exists.) ### `Answer` [implementation](./exercise_code/fuzzy_sort.py) 类似于quicksort,只是当重叠区域越多,pivot内的区别就越多~~ ### Problems 2 (3rd Edition): Quicksort with equal element values The analysis of the expected running time of randomized quicksort in Section 7.4.2 assumes that all element values are distinct. In this problem, we examine what happens when they are not. **a.** Suppose that all element values are equal. What would be randomized quick-sort's running time in this case? If the algorithm uses Lumuto Partition, then running time is O(n^2) If the algorithm uses modified Hoare partition that stops scanning at equal case, then the worst case can be avoided. **b.** Write a 3-way partition algorithm **c.** Implement randomized-3-way-partition [implementation](./exercise_code/quickSortWithEqualElements.cpp) **d.** Using 3-way-partition-qsort, adjust the analysis in Section 7.4.2 to avoid the assumpton that all elements are equal. *** Follow [@louis1992](https://github.com/gzc) on github to help finish this task. ================================================ FILE: C07-Quicksort/quicksort.py ================================================ #!/usr/bin/env python # coding=utf-8 def quicksort(items, p, r): if p < r: q = partition(items, p, r) quicksort(items, p, q-1) quicksort(items, q+1, r) def partition(items, p, r): x = items[r] i = p-1 for j in range(p, r): if items[j] <= x: i = i + 1 items[i],items[j] = items[j],items[i] items[i+1],items[r] = items[r],items[i+1] return i+1 items = [2,5,9,3,7,0,-1] quicksort(items, 0, len(items)-1) print items ================================================ FILE: C07-Quicksort/randomized-quicksort.py ================================================ #!/usr/bin/env python # coding=utf-8 import random def randomized_quicksort(items, p, r): if p < r: q = randomized_partition(items, p, r) randomized_quicksort(items, p, q-1) randomized_quicksort(items, q+1, r) def randomized_partition(items, p, r): i = random.randint(p, r) items[i],items[r] = items[r],items[i] return partition(items, p, r) def partition(items, p, r): x = items[r] i = p-1 for j in range(p, r): if items[j] <= x: i = i + 1 items[i],items[j] = items[j],items[i] items[i+1],items[r] = items[r],items[i+1] return i+1 items = [2,5,9,3,7,0,-1] randomized_quicksort(items, 0, len(items)-1) print items ================================================ FILE: C08-Sorting-in-Linear-Time/8.1.md ================================================ ### Exercises 8.1-1 *** What is the smallest possible depth of a leaf in a decision tree for a comparison sort? ### `Answer` 当数组已经排序好,是n-1. ### Exercises 8.1-2 *** Obtain asymptotically tight bounds on lg(n!) without using Stirling's approximation. Instead, evaluate the summation  ![](http://latex.codecogs.com/gif.latex?%20\\sum_{k=1}^{n}\\lg{k}) using techniques from Section A.2. ### `Answer` ![](http://latex.codecogs.com/gif.latex?%0d%0a\\sum_{k=1}^n\\lg{k}%20\\le%20\\sum_{k=1}^n\\lg{n}%20=%20n\\lg{n}%20=%20O\(n\\lg{n}\)%20) ![](http://latex.codecogs.com/gif.latex?%0d%0a\\sum_{k=1}^n\\lg{k}%20\\ge%20\\lg{\\sqrt{n}^n}%20=%20\\frac{n}{2}\\lg{n}%20=%20O\(n\\lg{n}\)%20) ### Exercises 8.1-3 *** Show that there is no comparison sort whose running time is linear for at least half of the n! inputs of length n. What about a fraction of 1/n of the inputs of length n? What about a fraction 1/2^n? ### `Answer` n!/2, n!/n, n!/(2^n) are smaller than 2^n only when n is small. ### Exercises 8.1-4 *** You are given a sequence of n elements to sort. The input sequence consists of n/k subsequences, each containing k elements. The elements in a given subsequence are all smaller than the elements in the succeeding subsequence and larger than the elements in the preceding subsequence. Thus, all that is needed to sort the whole sequence of length n is to sort the k elements in each of the n/k subsequences. Show an Ω(n lg k) lower bound on the number of comparisons needed to solve this variant of the sorting problem. (Hint: It is not rigorous to simply combine the lower bounds for the individual subsequences.) ### `Answer` ![](http://latex.codecogs.com/gif.latex?%0d%0a\(k!\)^{n/k}%20\\le%202^h%20\\\\%0d%0a\\Rightarrow%20h%20\\ge%20\\lg\(k!\)^{n/k}%20\\\\%0d%0a%20%20%20%20%20=%20%20%20\(n/k\)\lg\(k!\)%20\\\\%0d%0a%20%20%20%20%20\\ge%20\(n/k\)k\\lg{k}%20\\\\%0d%0a%20%20%20%20%20=%20%20%20\\Omega\(n\\lg{k}\)) *** Follow [@louis1992](https://github.com/gzc) on github to help finish this task. ================================================ FILE: C08-Sorting-in-Linear-Time/8.2.md ================================================ ### Exercises 8.2-1 *** Using Figure 8.2 as a model, illustrate the operation of COUNTING-SORT on the array A = [6, 0, 2, 0, 1, 3, 4, 6, 1, 3, 2]. ### `Answer` ![](./repo/s1/1.png) ### Exercises 8.2-2 *** Prove that COUNTING-SORT is stable. ### `Answer` COUNTING-SORT最后是从后面往前面扫,并且把遇到的每一个item放在同大小(key)的最后面的位子.所以不改变相对顺序. ### Exercises 8.2-3 *** Suppose that the for loop header in line 9 of the COUNTING-SORT procedure is rewritten as 9 for j ← 1 to length[A] Show that the algorithm still works properly. Is the modified algorithm stable? ### `Answer` 可以正常工作,只是不stable. ### Exercises 8.2-4 *** Describe an algorithm that, given n integers in the range 0 to k, preprocesses its input and then answers any query about how many of the n integers fall into a range [a...b] in O(1) time. Your algorithm should use Θ(n + k) preprocessing time. ### `Answer` 利用数组C,[a,b]间的个数是C[b]-C[a-1].(C[-1] = 0) [implementation](./exercise_code/integerQuery.cpp) *** Follow [@louis1992](https://github.com/gzc) on github to help finish this task. ================================================ FILE: C08-Sorting-in-Linear-Time/8.3.md ================================================ ### Exercises 8.3-1 *** Using Figure 8.3 as a model, illustrate the operation of RADIX-SORT on the following list of English words: COW, DOG, SEA, RUG, ROW, MOB, BOX, TAB, BAR, EAR, TAR, DIG, BIG, TEA, NOW, FOX. ### `Answer` 1| 2 |3 | 4 :----:|:----:|:----:|:----: COW | SE**A** | T**A**B | **B**AR DOG | TE**A** | B**A**R | **B**IG SEA | MO**B** | E**A**R | **B**OX RUG | TA**B** | T**A**R | **C**OW ROW | DO**G** | S**E**A | **D**IG MOB | RU**G** | T**E**A | **D**OG BOX | DI**G** | D**I**G | **E**AR TAB | BI**G** | B**I**G | **F**OX BAR | BA**R** | M**O**B | **M**OB EAR | EA**R** | D**O**G | **N**OW TAR | TA**R** | C**O**W | **R**OW DIG | CO**W** | R**O**W | **R**OG BIG | RO**W** | N**O**W | **S**EA TEA | NO**W** | B**O**X | **T**AB NOW | BO**X** | F**O**X | **T**AR FOX | FO**X** | R**O**G | **T**EA ### Exercises 8.3-2 *** Which of the following sorting algorithms are stable: insertion sort, merge sort, heapsort, and quicksort? Give a simple scheme that makes any sorting algorithm stable. How much additional time and space does your scheme entail? ### `Answer` * stable: insertion sort, merge sort * not stable: heapsort, quicksort 给每个item都增加一个原始index属性,最后相同的元素根据index再排一次.需要O(n)的额外空间. ### Exercises 8.3-3 *** Use induction to prove that radix sort works. Where does your proof need the assumption that the intermediate sort is stable? ### `Answer` 循环不变式:每次**for**循环前,最后的**i-1**个数字是排好序的. Initialization:循环还没开始,0个数字,是排好的. Maintenance:排第i个数字时,如果数字不相同,那肯定是排号序的,如果相同,因为采用stable的COUNTTING-SORT算法,后面的i-1位是排好序的,所以能保持性质. Termination:最后是排好序的. ### Exercises 8.3-4 *** Show how to sort n integers in the range 0 to n^2 - 1 in O(n) time. Note: In 3rd Edition, the number range is 0 to n^3 - 1, the basic idea is same ### `Answer` 将这些数字看成n进制,使用radix-sort. The running time is O(4n) = O(n) The radix sort running time is O(d * (n + k)) , where d is the number of digit, n is the number of elements, and k is the number of possible values. In this case, k equals n. So the total running time is O(2 * (n + n)) = O(4n) = O(n) Naive Implementation with a lot of copy: [implementation](./exercise_code/radixSort.cpp) ### Exercises 8.3-5 *** In the first card-sorting algorithm in this section, exactly how many sorting passes are needed to sort d-digit decimal numbers in the worst case? How many piles of cards would an operator need to keep track of in the worst case? ### `Answer` 从最高位往后面排序不是一种好办法,需要递归去做. 需要k^d次. 要同时care nk堆数据. *** Follow [@louis1992](https://github.com/gzc) on github to help finish this task. ================================================ FILE: C08-Sorting-in-Linear-Time/8.4.md ================================================ ### Exercises 8.4-1 *** Using Figure 8.4 as a model, illustrate the operation of BUCKET-SORT on the array A =[.79, .13, .16, .64, .39, .20, .89, .53, .71, .42] ### `Answer` ![](./repo/s4/1.png) ### Exercises 8.4-2 *** What is the worst-case running time for the bucket-sort algorithm? What simple change to the algorithm preserves its linear expected running time and makes its worst-case running time O(n lg n)? ### `Answer` 当所有的item都落在一个bucket内,是最坏情况,相当于对所有数据做一次插入排序,是O(n^2) 可以用merge-sort,只是操作链表比较麻烦. [insertion-sort-list](https://leetcode.com/problems/insertion-sort-list/) [merge-sort-list](https://leetcode.com/problems/sort-list/) 答案可以在我的[github](https://github.com/gzc/leetcode)上找到 ### Exercises 8.4-3 *** Let X be a random variable that is equal to the number of heads in two flips of a fair coin. What is E [X^2]? What is E^2[X]? ### `Answer` E[X^2] = 1^2 * P(head in one flip) + 0^2 * P(tail in one flip) = 1 * 1/2 + 0 * 1/2 = 1/2 E^2[X] = E[X] * E[X] = 1/2 * 1/2 = 1/4 ### Exercises 8.4-4 *** We are given n point s in the unit circl e,pi =(xi,yi),such that ![](http://latex.codecogs.com/gif.latex?%0d%0a0%20<%20x_i^2%20+%20y_i^2%20\\le%201) for i = 1, 2,...,n. Suppose that the points are uniformly distributed; that is, the probability of finding a point in any region of the circle is proportional to the area of that region. Design a Θ(n) expected-time algorithm to sort the n points by their distances ![](http://latex.codecogs.com/gif.latex?%20d_i%20=%20\\sqrt{x_i^2+y_i^2}%20) from the origin. (Hint: Design the bucket sizes in BUCKET-SORT to reflect the uniform distribution of the points in the unit circle.) ### `Answer` 初中数学题~ 就是划分n个面积相同的圆环. [More details](http://clrs.skanev.com/08/04/04.html) ### Exercises 8.4-5 *** A probability distribution function P(x) for a random variable X is defined by P(x) = Pr {X ≤ x}. Suppose that a list of n random variables X1, X2, . . .,Xn is drawn from a continuous probability distribution function P that is computable in O(1) time. Show how to sort these numbers in linear expected time. ### `Answer` 共有n个数字,bucket的划分点i1,i2...in为P(i1) = 1/n,p(i2) = 2/n, ..., P(i_n) = (n-1)/n. 因为找出这些划分点需要n*O(1) = O(n)的时间,桶排序也是O(n)的,所以最终是O(n)的. *** Follow [@louis1992](https://github.com/gzc) on github to help finish this task. ================================================ FILE: C08-Sorting-in-Linear-Time/exercise_code/in_place_counting_sort.py ================================================ #!/usr/bin/env python # coding=utf-8 def in_place_counting_sort(items, k): C = [0] * k for e in items: C[e] += 1 for i in range(1,k): C[i] += C[i-1] i = len(items) - 1 while i >= 0: print items,C v = items[i] pos = C[v] - 1 if i > pos: i -= 1 elif v != items[pos]: items[i],items[pos] = items[pos],items[i] C[v] -= 1 else: C[v] -= 1 items = [2, 5, 3, 0, 2, 3, 0, 3]; print items print "sorting --------------" in_place_counting_sort(items, 6) print "sorting finishing -------------" print items ================================================ FILE: C08-Sorting-in-Linear-Time/exercise_code/intergerQuery.cpp ================================================ #include #include #include #include using namespace std; class IntegerQuery{ public: IntegerQuery(vector & data); int countInRange(int a, int b); private: vector countArray; int k; bool noData; }; IntegerQuery::IntegerQuery(vector & data){ noData = data.empty(); if(!noData){ k = *max_element(data.begin(), data.end()); countArray = vector (k + 1); for(auto & num: data){ ++countArray[num]; } for(int i = 1; i <= k; ++i){ countArray[i] += countArray[i - 1]; } } } int IntegerQuery::countInRange(int a, int b){ if(a > b || b < 0 || a > k || noData){ return 0; } int sum = a <= 0 ? countArray[0] : 0; sum += countArray[min(k, b)] - countArray[max(0, a - 1)]; return sum; } int main(){ vector test{0, 0, 0, 1, 1, 1, 2, 2, 2, 3, 3, 3}; IntegerQuery query(test); assert(query.countInRange(-1, 4) == 12); assert(query.countInRange(-2, -1) == 0); assert(query.countInRange(-3, 0) == 3); assert(query.countInRange(0,1) == 6); } ================================================ FILE: C08-Sorting-in-Linear-Time/exercise_code/radixSort.cpp ================================================ #include #include #include #include #include using namespace std; /* * The RandInt class is adapted from the code segments in * The C++ Programming Language, 4th Edition, Bjarne S., P130 * */ class RandInt{ public: // range [low, high] RandInt(int low, int high): ge(chrono::system_clock::now().time_since_epoch().count()),dist(low, high) {} int operator()() {return dist(ge);} private: minstd_rand ge; uniform_int_distribution dist; }; class RadixSort{ public: RadixSort(int n, int powValue); ~RadixSort(){} void printData(); void sort(); private: int size; int digitNum; vector sortData; vector> dataCopy; void countingSortDigit(); }; RadixSort::RadixSort(int n, int powValue): size(n), digitNum(powValue), sortData(vector(n)), dataCopy(vector>(n)){ RandInt rnd{0, (int)pow(n, powValue) - 1}; for(auto & num: sortData){ num = rnd(); } } void RadixSort::printData(){ for(auto & num: sortData){ cout << num << " "; } cout << endl; } void RadixSort::sort(){ for(int i = 0; i < size; ++i){ dataCopy[i] = make_pair(i, sortData[i]); } for(int i = 1; i <= digitNum; ++i){ countingSortDigit(); } vector auxData(sortData.begin(), sortData.end()); for(int i = 0; i < size; ++i){ sortData[i] = auxData[dataCopy[i].first]; } } void RadixSort::countingSortDigit(){ vector> digitData(size); for(int i = 0; i < size; ++i){ digitData[i] = make_pair(i, dataCopy[i].second % size); dataCopy[i].second /= size; } vector countArray(size); for(auto & digit: digitData){ ++countArray[digit.second]; } for(int i = 1; i < size; ++i){ countArray[i] += countArray[i-1]; } vector> auxData(dataCopy.begin(), dataCopy.end()); for(int i = size - 1; i >= 0; --i){ int curIdx = --countArray[digitData[i].second]; dataCopy[curIdx] = auxData[digitData[i].first]; } } int main(){ RadixSort rst{25, 3}; rst.printData(); rst.sort(); rst.printData(); } ================================================ FILE: C08-Sorting-in-Linear-Time/exercise_code/water-jugs.py ================================================ #!/usr/bin/env python # coding=utf-8 import random def match(reds, blues): if not reds: return r = random.randint(0,len(reds)-1) v = reds[r] smaller_red = [item for item in reds if item < v] larger_red = [item for item in reds if item > v] smaller_blue = [item for item in blues if item < v] larger_blue = [item for item in blues if item > v] print v,v match(smaller_red,smaller_blue) match(larger_red,larger_blue) reds = [3,2,1,5,4] blues = [2,3,5,1,4] match(reds, blues) ================================================ FILE: C08-Sorting-in-Linear-Time/problem.md ================================================ ### Problems 1 : Average-case lower bounds on comparison sorting *** In this problem, we prove an Ω(n lg n) lower bound on the expected running time of any deterministic or randomized comparison sort on n distinct input elements. We begin by examining a deterministic comparison sort A with decision tree TA. We assume that every permutation of A's inputs is equally likely. **a.** Suppose that each leaf of TA is labeled with the probability that it is reached given a random input. Prove that exactly n! leaves are labeled 1/n! and that the rest are labeled 0. **b.** Let D(T) denote the external path length of a decision tree T ; that is, D(T) is the sum of the depths of all the leaves of T. Let T be a decision tree with k > 1 leaves, and let LT and RT be the left and right subtrees of T. Show that D(T) = D(LT) + D(RT) + k. **c.** Let d(k) be the minimum value of D(T) over all decision trees T with k > 1 leaves. Show that ![](http://latex.codecogs.com/gif.latex?%0d%0ad\(k\)%20=%20\\min_{1%20\\le%20i%20\\le%20k-1}\\{d\(i\)%20+%20d\(k-i\)%20+%20k\\}%20) (Hint: Consider a decision tree T with k leaves that achieves the minimum. Let i0 be the number of leaves in LT and k - i0 the number of leaves in RT.) **d.** Prove that for a given value of k > 1 and i in the range 1 ≤ i ≤ k-1,the function ilgi+ (k - i)lg(k - i) is minimized at i = k/2. Conclude that d(k) = Θ(klgk). The PARTITION procedure in Section 7.1 separates the pivot value (originally in A[r]) from the two partitions it forms. The HOARE-PARTITION procedure, on the other hand, always places the pivot value (originally in A[p]) into one of the two partitions A[p...j] and A[j + 1...r]. Since p ≤ j < r, this split is always nontrivial. **e.** Prove that D(TA) = Θ(n! lg(n!)), and conclude that the expected time to sort n elements is Θ(n lg n). Now, consider a randomized comparison sort B. We can extend the decision-tree model to handle randomization by incorporating two kinds of nodes: ordinary comparison nodes and "randomization" nodes. A randomization node models a random choice of the form RANDOM(1, r) made by algorithm B; the node has r children, each of which is equally likely to be chosen during an execution of the algorithm. **f.** Show that for any randomized comparison sort B, there exists a deterministic comparison sort A that makes no more comparisons on the average than B does. ### `Answer` **a.** 总共有n!个排列,也就是n!种可能,所以概率当然是1/n!. **b.** 因为T比RT和LT高度少1,所以有多少叶子节点,就需要多加这么多个1.很好思考~ **c.** 由前一小题可知,有k个节点的树有关系式D(T)=D(LT)+D(RT)+k. 那么很自然可以知道,就是左子树的叶子节点从1到k-1这么多可能. ![](http://latex.codecogs.com/gif.latex?%0d%0ad\(k\)%20=%20D\(T\)%20=%20D\(LT\)%20+%20D\(RT\)%20+%20k%20=%20\\min_{1%20\\le%20i%20\\le%20k-1}\\{d\(i\)%20+%20d\(k-i\)%20+%20k\\}%20) **d.** 比较简单的求导. ![](http://latex.codecogs.com/gif.latex?%0d%0af\(i\)%20=%20i\\lg{i}%20+%20\(k-i\)\\lg\(k-i\)%20\\\\%20~%20\\hspace{6%20mm}%0d%0a%20%20%20%20%20f'\(i\)%20=%20\\lg{i}%20+%201%20-%20\\lg\(k-i\)%20-%201%20=%20\\lg\\frac{i}{k-i}%20\\\\%20~%20\\hspace{6%20mm}%0d%0a%20%20%20%20%20f'\(i\)%20=%200%20%20\\Leftrightarrow%20\\lg\\frac{i}{k-i}%20=%200%20\\Rightarrow%20i/\(k-i\)%20=%201%20\\Rightarrow%20i%20=%20\\frac{k}{2}%20) **e.** TA一共有n!个叶子节点,所以有D(T) > d(n!) = Ω(n!lg(n!)) 我们已经求出了外路径长度,总共有n!个概率相同的节点,所以最终有 ![](http://latex.codecogs.com/gif.latex?%0d%0a\\frac{\\Omega\(n!\\lg\(n!\)\)}{n!}%20=%20\\Omega\(\\lg\(n!\)\)%20=%20\\Omega\(n\\lg{n}\)%20) **f.** 非随机化版本有n!种路径,囊括了所有可能. 而任何一种随机化都肯定在这n!里面,而且少了random的操作. ### Problems 2 : Sorting in place in linear time *** Suppose that we have an array of n data records to sort and that the key of each record has the value 0 or 1. An algorithm for sorting such a set of records might possess some subset of the following three desirable characteristics: 1. The algorithm runs in O(n) time. 2. The algorithm is stable. 3. The algorithm sorts in place, using no more than a constant amount of storage space in addition to the original array. **a.** Give an algorithm that satisfies criteria 1 and 2 above. **b.** Give an algorithm that satisfies criteria 1 and 3 above. **c.** Give an algorithm that satisfies criteria 2 and 3 above. **d.** Can any of your sorting algorithms from parts (a)-(c) be used to sort n records with b- bit keys using radix sort in O(bn) time? Explain how or why not. **e.** Suppose that the n records have keys in the range from 1 to k. Show how to modify counting sort so that the records can be sorted in place in O(n + k) time. You may use O(k) storage outside the input array. Is your algorithm stable? (Hint: How would you do it for k = 3?) ### `Answer` **a.** 计数排序就可以. **b.** Using HOARE-PARTITION can do it.More details in [Exercise7.1](https://github.com/gzc/CLRS/blob/master/C07-Quicksort/7.1.md) **c.** merge-sort就可以 [stable in-place sort](http://www.codeproject.com/Articles/26048/Fastest-In-Place-Stable-Sort) **d.** 1. The first one can be used. 2. The second is not stable. 3. The third takes `Theta(nlgn)`. **e.** [implementation](./exercise_code/in_place_counting_sort.py) 我的实现不是stable的 ### Problems 3 : Sorting variable-length items *** a. You are given an array of integers, where different integers may have different numbers of digits, but the total number of digits over all the integers in the array is n. Show how to sort the array in O(n) time. b. You are given an array of strings, where different strings may have different numbers of characters, but the total number of characters over all the strings is n. Show how to sort the strings in O(n) time. (Note that the desired order here is the standard alphabetical order; for example, a < ab < b.) ### `Answer` **a.** For the nubers, we can do this: - Group the nunbers by number of digits and order groups. - RADIX sort each group. We let the `Gi` be the group of numbers with i digits and ci = |Gi|, thus: we can multiply the `n*Ci` with `i`, then sum from `i = 1` to `i = highest digit`. We can get the ![](http://latex.codecogs.com/gif.latex?T\(n\)=%20\\sum_{i%20=%201}nc_i%20\\cdot%20i%20=%20n%20) **b.** reverse所有的字符串,用RADIX-SORT.当当遍历到某一个字符串超过其大小时,用空格代替(空格 > 其他字母),并且将该数字放在该组正常有数字的前面,以后不要迭代.排序后reverse回来. 保证了O(n)的时间. 假设我们要排序字符串d,c,b,bcd,bc,bd,bdc.过程如下: 0 | 1 | 2 | 3 | 4 | result | reverse :----: | :----: | :----: | :----: | :----: | :----: | :----: d | **b** | b | | | b | b c | dc**b** | d**c**b | cb | | cb | bc b | c**b** | **c**b | **d**cb | dcb | dcb | bcd dcb | d**b** | **d**b | db | | db | bd cb | cd**b** | c**d**b | **c**db | cdb |cdb | bdc db | **c** | c | | | c | c cdb | **d** | d | | | d | d ### Problems 4 : Water jugs *** Suppose that you are given n red and n blue water jugs, all of different shapes and sizes. All red jugs hold different amounts of water, as do the blue ones. Moreover, for every red jug, there is a blue jug that holds the same amount of water, and vice versa. It is your task to find a grouping of the jugs into pairs of red and blue jugs that hold the same amount of water. To do so, you may perform the following operation: pick a pair of jugs in which one is red and one is blue, fill the red jug with water, and then pour the water into the blue jug. This operation will tell you whether the red or the blue jug can hold more water, or if they are of the same volume. Assume that such a comparison takes one time unit. Your goal is to find an algorithm that makes a minimum number of comparisons to determine the grouping. Remember that you may not directly compare two red jugs or two blue jugs. a. Describe a deterministic algorithm that uses Θ(n2) comparisons to group the jugs into pairs. b. Prove a lower bound of Θ(n lg n) for the number of comparisons an algorithm solving this problem must make. c. Give a randomized algorithm whose expected number of comparisons is O(n lg n), and prove that this bound is correct. What is the worst-case number of comparisons for your algorithm? ### `Answer` **a.** 二重循环,比较所有的. **b.** 决策树的每个node都有3个sub-node(A < B | A = B | A > B).总共有n!个输出.所以有 ![](http://latex.codecogs.com/gif.latex?3^h%20\\ge%20n!%20\\Rightarrow%20h%20\\ge%20\\lg{n!}%20\\Rightarrow%20h%20=%20\\Omega\(n\\lg{n}\)) **c.** key idea : 跟快速排序是一样的 [implementation](./exercise_code/water-jugs.py) ### Problems 5 : Average sorting *** Suppose that, instead of sorting an array, we just require that the elements increase on average. More precisely, we call an n-element array A k-sorted if, for all i = 1, 2, . . ., n - k, the following holds: ![](http://latex.codecogs.com/gif.latex?%20\\frac{\\sum_{j%20=%20i}^{i+k-1}}{k}A[j]%20\\le%20\\frac{\\sum_{j%20=%20i+1}^{i+k}}{k}A[j]) **a.** What does it mean for an array to be 1-sorted? **b.** Give a permutation of the numbers 1, 2, . . ., 10 that is 2-sorted, but not sorted. **c.** Provethatann-elementarrayisk-sortedifandonlyifA[i]≤A[i+k]foralli=1,2,.. ., n - k. **d.** Give an algorithm that k-sorts an n-element array in O(n lg(n/k)) time. **e.** Show that a k-sorted array of length n can be sorted in O(n lg k) time. (Hint: Use the solution to Exercise 6.5-8.) **f.** Show that when k is a constant, it requires Θ(n lg n) time to k-sort an n-element array. (Hint: Use the solution to the previous part along with the lower bound on comparison sorts.) ### `Answer` **a.** 该数组完全排好序. **b.** [2,1,3,4,5,6,7,8,9,10] **c.** 这个证明很简单,移项就能得到. **d.** 把数组分为k组,用heapsort/mergesort. T = k * O((n/k)log(n/k)) = O(nlg(n/k)) **e.** 确实跟[练习6.5.8](https://github.com/gzc/CLRS/blob/master/C06-Heapsort/6.5.md#exercises-65-8)是一样的 **f.** 我们有d的结果,因为k是常量,所以可以忽略. ### Problems 6 : Lower bound on merging sorted lists *** The problem of merging two sorted lists arises frequently. It is used as a subroutine of MERGE-SORT, and the procedure to merge two sorted lists is given as MERGE in Section 2.3.1. In this problem, we will show that there is a lower bound of 2n - 1 on the worst-case number of comparisons required to merge two sorted lists, each containing n items. First we will show a lower bound of 2n - o(n) comparisons by using a decision tree. **a.** Show that, given 2n numbers, there are ![](http://latex.codecogs.com/gif.latex?C_{2n}^n) possible ways to divide them into two sorted lists, each with n numbers. **b.** Using a decision tree, show that any algorithm that correctly merges two sorted lists uses at least 2n - o(n) comparisons. Now we will show a slightly tighter 2n - 1 bound. **c.** Show that if two elements are consecutive in the sorted order and from opposite lists, then they must be compared. **d.** Use your answer to the previous part to show a lower bound of 2n - 1 comparisons for merging two sorted lists. ### `Answer` 这个题目在[leetcode](https://leetcode.com/problems/merge-two-sorted-lists/)出现过 **a.** 很自然,从2n数字中可以选n种. **b.** ![](http://latex.codecogs.com/gif.latex?%202^h%20\\ge%20%20C_{2n}^n%20\\\\%20~%20\\hspace{6%20mm}%0d%0a2^h%20\\ge%20\\frac{2n!}{\(n!\)^2}%20%20\\\\%20~%20\\hspace{8%20mm}%0d%0ah%20\\ge%20\\log{2n!}%20-2\\log{n!}%20\\\\%20~%20\\hspace{10%20mm}%0d%0a=%20\\Theta\(2n\\log{2n}\)%20-%202\\Theta\(n\\log{n}\)%20\\\\~%20\\hspace{10%20mm}%0d%0a=%20\\log{2}\\Theta\(2n\)%20\\\\~%20\\hspace{10%20mm}%0d%0a=%20\\Theta\(2n\)) 树的高度是2n级别的,因此最少需要2n-o(n)次 **c.** 这不是废话嘛...只有来自同一列表的前后元素才不用比较. **d.** 根据c,在已排号序的链表中,只有前后元素才有**可能**比较过,因此最多是2n-1次. *** Follow [@louis1992](https://github.com/gzc) on github to help finish this task. ================================================ FILE: C09-Medians-and-Order-Statistics/9.1.md ================================================ ### Exercises 9.1-1 *** Show that the second smallest of n elements can be found with ![](http://latex.codecogs.com/gif.latex?n+\\lceil\\lg{n}\\rceil-2) comparisons in the worst case. (Hint: Also find the smallest element.) ### `Answer` [code](./exercise_code/second-smallest.cpp) tells everything! 想法大致是这样的:首先由下往上建一颗二叉树,每个节点保存一对的最小值,这样总共需要n-1次比较.这棵树的顶点是最小值.第二小元素肯定在生成顶点的路径上,因为第二小元素只会被最小元素击败,所以两节点肯定交手过一次.因为树的高度不过超过 ![image](./repo/s1/gif-2.gif) 所以需要![image](./repo/s1/gif-2.gif) - 1 次比较.因此总共需要![image](./repo/s1/gif.gif) First we build a BST from bottom to top,each node contains the smaller one of a pair, we need n-1 comparisions to build BST. The top of this BST is minimum, then the second smallest must be in the path to generate the top. Because the second smallest can only be defeated by the smallest one. Because the height of BST does not exceed ![image](./repo/s1/gif-2.gif) so we need ![image](./repo/s1/gif-2.gif) - 1 comparisions.The total times is![image](./repo/s1/gif.gif) ### Exercises 9.1-2 *** Show that ![image](./repo/s1/gif-3.gif) comparisons are necessary in the worst case to find both the maximum and minimum of n numbers. (Hint: Consider how many numbers are potentially either the maximum or minimum, and investigate how a comparison affects these counts.) ### `Answer` As mentioned before, if n is odd,then we need ![image](./repo/s1/gif-4.gif) If n is even,then we need 3n/2-2 ![image](./repo/s1/gif-3.gif) is the same format. *** Follow [@louis1992](https://github.com/gzc) on github to help finish this task. ================================================ FILE: C09-Medians-and-Order-Statistics/9.2.md ================================================ ### Exercises 9.2-1 *** Show that in RANDOMIZED-SELECT, no recursive call is ever made to a 0-length array. ### `Answer` 答案很明显,假如划分的一边数组是空的,那么这个if语句条件是不会成立的,这个调用是不会被call的. It is obvious, if the array is empty, then the condition of if will not hold, it will definitely not be called. ### Exercises 9.2-2 *** Argue that the indicator random variable ![image](./repo/s2/1.gif) and the value T(max(k - 1, n - k)) are independent. ### `Answer` 不论![image](./repo/s2/1.gif)取0还是1, T(max(k-1,n-k))是不会变的. No matter ![image](./repo/s2/1.gif) is 0 or 1, T(max(k-1,n-k))will not change. ### Exercises 9.2-3 *** Write an iterative version of RANDOMIZED-SELECT. ### `Answer` [code](./exercise_code/randomized-select-iterative.cpp) tells everything! ### Exercises 9.2-4 *** Suppose we use RANDOMIZED-SELECT to select the minimum element of the array A = {3, 2, 9, 0, 7, 5, 4, 8, 6, 1}. Describe a sequence of partitions that results in a worst-case performance of RANDOMIZED-SELECT. ### `Answer` - pivot **9** {3, 2, 0, 7, 5, 4, 8, 6, 1} - pivot **8** {3, 2, 0, 7, 5, 4, 6, 1} - pivot **7** {3, 2, 0, 5, 4, 6, 1} - pivot **6** {3, 2, 0, 5, 4, 1} - pivot **5** {3, 2, 0, 4, 1} - pivot **4** {3, 2, 0, 1} - pivot **3** {2, 0, 1} - pivot **2** {0, 1} - pivot **1** {0} - return 0 *** Follow [@louis1992](https://github.com/gzc) on github to help finish this task. ================================================ FILE: C09-Medians-and-Order-Statistics/9.3.md ================================================ ### Exercises 9.3-1 *** In the algorithm SELECT, the input elements are divided into groups of 5. Will the algorithm work in linear time if they are divided into groups of 7? Argue that SELECT does not run in linear time if groups of 3 are used. ### `Answer` Assuming each group has k elements. The number that less(or greater) then median of median is at least n/4-k,In the worst case, next call to SELECT will recursive call 3n/4+k elements. so ![image](./repo/s3/1.png) Assuming for all n,T(n) <= cn ![image](./repo/s3/2.png) So according to 1/k+3/4 <= 1 wo get k >= 4 ### Exercises 9.3-2 *** Analyze SELECT to show that if n ≥ 140, then at least ⌈n/4⌉ elements are greater than the median-of-medians x and at least ⌈n/4⌉ elements are less than x. ### `Answer` ![image](./repo/s3/3.gif) ### Exercises 9.3-3 *** Show how quicksort can be made to run in O(![image](./repo/s3/4.gif)) time in the worst case. ### `Answer` 比较直观,我们先利用线性时间去找到中位数,再用这个中位数去做partition. It is obvious, we first use O(n) time to find median, then use median to do partition. ### Exercises 9.3-4 *** Suppose that an algorithm uses only comparisons to find the ith smallest element in a set of n elements. Show that it can also find the i - 1 smaller elements and the n - i larger elements without performing any additional comparisons. ### `Answer` 只用比较来确定的话,就跟这讲的方法是一样的. 我们既然找到了第i小元素,那么比i大和比i小的都已经被我们分类分好了. We can find ith element, then the number greater than i and the number less than i have been already partitioned. ### Exercises 9.3-5 *** Suppose that you have a "black-box" worst-case linear-time median subroutine. Give a simple, linear-time algorithm that solves the selection problem for an arbitrary order statistic. ### `Answer` [code](./exercise_code/black-box.py) tells everything. Thanks original code [here](http://clrs.skanev.com/09/03/05.html). Find the median, and recursve half of the elements. ### Exercises 9.3-6 *** The *k*th **quantiles** of an n-element set are the *k* - 1 order statistics that divide the sorted set into *k* equal-sized sets (to within 1). Give an O(n lg k)-time algorithm to list the *k*th quantiles of a set. ### `Answer` [code](./exercise_code/k-quantile.py) tells everything. Thanks original code [here](http://clrs.skanev.com/09/03/06.html). - 如果k是偶数,那么取中间一个,再对左右两边进行递归. - 如果k是奇数,取最接近中间的两个数,再对左右两边进行递归. - If k is even, then choose the middle one, recursive call left part and right part. - If k is odd,choose two index numbers most close to median,then recursive call the left part and right part. ### Exercises 9.3-7 *** Describe an O(n)-time algorithm that, given a set S of n distinct numbers and a positive integer k ≤ n, determines the k numbers in S that are closest to the median of S. ### `Answer` [code](./exercise_code/k-close2median.py) 1. 计算出中位数median 2. 将所有数减去median,再取绝对值 3. 用SELECT计算出第k小数字y 4. 遍历数组,取出所有绝对值小于等于y的 代码的局限性在于只能接收绝对值不含有相同元素的. 1. Find the median 2. For all the numbers, minus median then get the absolute value 3. use SELECT to find the kth smallest number y 4. Iterate the array choose all the absolute value that less than y ### Exercises 9.3-8 *** Let [1 .. n] and Y [1 .. n] be two arrays, each containing n numbers already in sorted order. Give an O(lg n)-time algorithm to find the median of all 2n elements in arrays X and Y. ### `Answer` Divide and conquer Here is a cpp code for this question. First, we compare the mid value of each array then we can cut down some part of this two arrays. ```cpp // X[lo1, lo1 + n) Y[lo2, lo2 + n) int median(int[] X, int[] Y, int lo1, int lo2, int n) { if (n < 3) return quickMedian(X, Y, lo1, lo2, n); int mid1 = lo1 + n / 2, mi2 = lo2 + (n - 1) / 2; if (X[mi1] < Y[mi2]) return median(X, Y, mi1, lo2, n + lo1 - mi1); // use X's right part and Y's left part else if (X[mi1] > Y[mi2]) return median(X, Y, lo1, mi2, n + lo2 - mi2); // use X's left part and Y's right part else return X[mi1]; ``` ### Exercises 9.3-9 *** Professor Olay is consulting for an oil company, which is planning a large pipeline running east to west through an oil field of n wells. From each well, a spur pipeline is to be connected directly to the main pipeline along a shortest path (either north or south), as shown in [Figure 9.2](#oil). Given x- and y-coordinates of the wells, how should the professor pick the optimal location of the main pipeline (the one that minimizes the total length of the spurs)? Show that the optimal location can be determined in linear time. ![oil](./repo/oil.png) Figure 9.2: Professor Olay needs to determine the position of the east-west oil pipeline that minimizes the total length of the north-south spurs. ### `Answer` Find the median of y. *** Follow [@louis1992](https://github.com/gzc) on github to help finish this task. ================================================ FILE: C09-Medians-and-Order-Statistics/exercise_code/k-close2median.py ================================================ #!/usr/bin/env python # coding=utf-8 import math def k_close2median(items, k): med = median(items) abs_items = [] result = [] for element in items: abs_items.append(abs(element-med)) threshold = select(abs_items, k-1) for i in range(len(items)): if abs_items[i] <= threshold: result.append(items[i]) return result def select(items, n): med = median(items) smaller = [item for item in items if item < med] larger = [item for item in items if item > med] if len(smaller) == n: return med elif len(smaller) > n: return select(smaller, n) else: return select(list(larger), n - len(smaller) - 1) def median(items): def median_index(n): if n % 2: return n // 2 else: return n // 2 - 1 def partition(items, element): i = 0 for j in range(len(items) - 1): if items[j] == element: items[j], items[-1] = items[-1], items[j] if items[j] < element: items[i], items[j] = items[j], items[i] i += 1 items[i], items[-1] = items[-1], items[i] return i def select(items, n): if len(items) <= 1: return items[0] medians = [] for i in range(0, len(items), 5): group = sorted(items[i:i + 5]) items[i:i + 5] = group median = group[median_index(len(group))] medians.append(median) pivot = select(medians, median_index(len(medians))) index = partition(items, pivot) if n == index: return items[index] elif n < index: return select(items[:index], n) else: return select(items[index + 1:], n - index - 1) return select(items[:], median_index(len(items))) array = [1,2,3,4,10,20,30] print k_close2median(array, 2) ================================================ FILE: C09-Medians-and-Order-Statistics/exercise_code/k-quantile.py ================================================ #!/usr/bin/env python # coding=utf-8 import math def k_quantiles(items, k): index = median_index(len(items)) if k == 1: return [] elif k % 2: n = len(items) left_index = math.ceil((k // 2) * (n / k)) - 1 right_index = n - left_index - 1 left = select(items, left_index) right = select(items, right_index) partition(items, left) lower = k_quantiles(items[:left], k // 2) partition(items, right) upper = k_quantiles(items[right + 1:], k // 2) return lower + [left, right] + upper else: index = median_index(len(items)) median = select(items, index) partition(items, median) return k_quantiles(items[:index], k // 2) + [median] + k_quantiles(items[index + 1:], k // 2) def median_index(n): if n % 2: return n // 2 else: return n // 2 - 1 def partition(items, element): i = 0 for j in range(len(items) - 1): if items[j] == element: items[j], items[-1] = items[-1], items[j] if items[j] < element: items[i], items[j] = items[j], items[i] i += 1 items[i], items[-1] = items[-1], items[i] return i def select(items, n): if len(items) <= 1: return items[0] medians = [] for i in range(0, len(items), 5): group = sorted(items[i:i + 5]) items[i:i + 5] = group median = group[median_index(len(group))] medians.append(median) pivot = select(medians, median_index(len(medians))) index = partition(items, pivot) if n == index: return items[index] elif n < index: return select(items[:index], n) else: return select(items[index + 1:], n - index - 1) arr = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18] print k_quantiles(arr, 4) ================================================ FILE: C09-Medians-and-Order-Statistics/exercise_code/randomized-select-iterative.cpp ================================================ /************************************************************************* > File Name: randomized-select-iterative.cpp > Author: Louis1992 > Mail: zhenchaogan@gmail.com > Blog: http://gzc.github.io > Created Time: Sun May 24 11:46:39 2015 ************************************************************************/ #include #include using namespace std; class Solution { int partition(int arr[], int l, int r) { int x = arr[r], i = l; for(int j = l; j <= r - 1; j++) { if (arr[j] <= x) { swap(arr[i], arr[j]); i++; } } swap(arr[i], arr[r]); return i; } int randomPartition(int arr[], int l, int r) { int n = r-l+1; int pivot = rand() % n; swap(arr[l + pivot], arr[r]); return partition(arr, l, r); } public: int kthSmallest(int arr[], int l, int r, int k) { int originalK = k; while (k > 0 && k <= r - l + 1) { int pos = randomPartition(arr, l, r); if (pos-l == k-1) { return arr[pos]; } else if(pos-l > k-1) { r = pos-1; } else { l = pos+1; k = originalK-l; } } return INT_MAX; } }; int main() { int arr[7] = {10, 100, 2, 4, 1, -2, 8}; Solution s; assert(s.kthSmallest(arr, 0, 6, 1) == -2); assert(s.kthSmallest(arr, 0, 6, 2) == 1); assert(s.kthSmallest(arr, 0, 6, 3) == 2); assert(s.kthSmallest(arr, 0, 6, 4) == 4); assert(s.kthSmallest(arr, 0, 6, 5) == 8); assert(s.kthSmallest(arr, 0, 6, 6) == 10); assert(s.kthSmallest(arr, 0, 6, 7) == 100); } ================================================ FILE: C09-Medians-and-Order-Statistics/exercise_code/second-smallest.cpp ================================================ /************************************************************************* > File Name: second-smallest.cpp > Author: Louis1992 > Mail: zhenchaogan@gmail.com > Blog: http://gzc.github.io > Created Time: Sun May 24 09:32:14 2015 ************************************************************************/ #include #include using namespace std; struct Node { int v; Node *left; Node *right; bool dir; // 0 mean left, 1 mean right Node() : v(-1),left(nullptr),right(nullptr){} Node(int _v, bool d) : v(_v),left(nullptr),right(nullptr),dir(d){} }; int second_smallest_element(int arr[], int n) { queue q; for(int i = 0;i < n;i++) { Node *new_node = new Node(arr[i], 0); q.push(new_node); } Node *root(nullptr); while(!q.empty()) { int size = q.size(); if(size == 1) { root = q.front(); break; } for(int i = 0;i < size;i += 2) { if(i == size - 1) { Node *n1 = q.front(); q.pop(); q.push(n1); break; } else { Node *n1 = q.front(); q.pop(); Node *n2 = q.front(); q.pop(); int smaller = 0; bool dir; if(n1 -> v <= n2 -> v) { smaller = n1->v; dir = false; } else { smaller = n2->v; dir = true; } Node *new_node = new Node(smaller,dir); new_node->left = n1; new_node->right = n2; q.push(new_node); } } } int minium = root->v; int result(INT_MAX); while(root) { if(root->left && root->right) { result = min( (root->left->v ^ root->right->v ^ minium), result); root = root->dir == true ? root->right : root->left; } else break; } return result; } int main() { int arr[8] = {3,4,1,0,7,-2,-10,100}; int result = second_smallest_element(arr, 8); cout << result << endl; return 0; } ================================================ FILE: C09-Medians-and-Order-Statistics/minmax.c ================================================ /************************************************************************* > File Name: minmax.c > Author: Louis1992 > Mail: zhenchaogan@gmail.com > Blog: http://gzc.github.io > Created Time: Sat May 23 23:04:35 2015 ************************************************************************/ #include #define maxf(a,b) ((a) >= (b) ? (a) : (b)) #define minf(a,b) ((a) <= (b) ? (a) : (b)) void minmax(int *arr, int n, int *maximum, int *minium) { int i = 0; if(n % 2 == 1) { *maximum = arr[0]; *minium = arr[0]; i = 1; } else { if(arr[1] >= arr[0]) { *maximum = arr[1]; *minium = arr[0]; } else { *maximum = arr[0]; *minium = arr[1]; } i = 2; } for(;i < n;i += 2) { if(arr[i] >= arr[i+1]) { *maximum = maxf(*maximum, arr[i]); *minium = minf(*minium, arr[i+1]); } else { *maximum = maxf(*maximum, arr[i+1]); *minium = minf(*minium, arr[i]); } } } int main() { int arr[5] = {3,5,-1,0,2}; int maximum = 0,minium = 0; minmax(arr, 5, &maximum, &minium); printf("max=%d min=%d/n", maximum, minium); } ================================================ FILE: C09-Medians-and-Order-Statistics/problem.md ================================================ ### Problems 1 : Largest i numbers in sorted order *** Given a set of *n* numbers, we wish to find the *i* largest in sorted order using a comparison- based algorithm. Find the algorithm that implements each of the following methods with the best asymptotic worst-case running time, and analyze the running times of the algorithms in terms of *n* and *i*. a. Sort the numbers, and list the *i* largest. b. Build a max-priority queue from the numbers, and call EXTRACT-MAX *i* times. c. Use an order-statistic algorithm to find the *i*th largest number, partition around that number, and sort the *i* largest numbers. ### `Answer` a和b的代码在这里.[code](./problems/i-largest.py). 如果先排序的话,就需要O(nlgn)+O(i)的时间. 用堆的话,需要O(n)+O(ilogn)的时间. 对于c,[code](./problems/i-largest.cpp) 利用顺序统计量的话,找到这个值需要O(n)的时间,然后需要对i个数排序需要O(ilogi)时间. ### Problems 2 : Weighted median *** For n distinct elements ![](http://latex.codecogs.com/gif.latex?x_1,%20x_2,%20...,%20x_n) with positive weights ![](http://latex.codecogs.com/gif.latex?w_1,%20w_2,%20...,%20w_n) such that ![](http://latex.codecogs.com/gif.latex?\\sum_{i=1}^{n}w_i%20=%201) ,**the weighted (lower)** median is the element ![](http://latex.codecogs.com/gif.latex?x_k) satisfying ![](http://latex.codecogs.com/gif.latex?\\sum_{x_i%20<%20x_k}w_i%20<%20\\frac{1}{2}%20%20%20%20%20) and ![](http://latex.codecogs.com/gif.latex?\\sum_{x_i%20>%20x_k}w_i%20\\le%20\\frac{1}{2}) a. Argue that the median of x1, x2, ..., xn is the weighted median of the xi with weights wi = 1/n for i = 1,2, ..., n. b. Show how to compute the weighted median of n elements in O(n lg n) worst-case time using sorting. c. Show how to compute the weighted median in Θ(n) worst-case time using a linear- time median algorithm such as SELECT from Section 9.3. The **post-office location problem** is defined as follows. We are given *n* points p1, p2, ..., pn with associated weights w1, w2, ..., wn. We wish to find a point p (not necessarily one of the input points) that minimizes the sum ![](http://latex.codecogs.com/gif.latex?\\sum_{i%20=%201}^{n}w_id\(p,p_i\)%0d%0a) where *d*(a, b) is the distance between points a and b. d. Argue that the weighted median is a best solution for the 1-dimensional post-office location problem, in which points are simply real numbers and the distance between points a and b is *d*(a, b) = |a - b|. e. Find the best solution for the 2-dimensional post-office location problem, in which the points are (x, y) coordinate pairs and the distance between points a = (x1, y1) and b = (x2, y2) is the **Manhattan distance** given by d(a, b) = |x1 - x2| + |y1 - y2|. ### `Answer` a. 好显然TAT. b. 先排序,然后开始遍历,直到加上某一个数字的权重 ≥ 1/2. c. [code](./problems/weighted_median.py) d. 假设p是带权中位数,只要偏离p,距离就会越来越大 e. 分别找x和y方向的带权中位数,因为用的是曼哈顿距离,x和y是独立的,所以可以分开对待. ### Problems 3 : Small order statistics *** The worst-case number T(n) of comparisons used by SELECT to select the ith order statistic from n numbers was shown to satisfy T(n) = Θ(n), but the constant hidden by the Θ-notation is rather large. When i is small relative to n, we can implement a different procedure that uses SELECT as a subroutine but makes fewer comparisons in the worst case. a. Describe an algorithm that uses Ui(n) comparisons to find the ith smallest of n elements, where ![image](./repo/p/o.png)(Hint: Begin with ![](http://latex.codecogs.com/gif.latex?%20\\lfloor%20n/2%20\\rfloor%20) disjoint pairwise comparisons, and recurse on the set containing the smaller element from each pair.) b. Show that, if i < n/2, then Ui(n) = n + O(T (2i) lg(n/i)). c. Show that if i is a constant less than n/2, then Ui(n) = n + O(lg n). d. Show that if *i* = n/k for k≥2,then Ui(n) = n+O(T(2n/k)lgk). ### `Answer` a. 1. 如果i ≥ n/2,直接调用SELECT(n, i) 2. 如果i < n/2,那么就两个两个比较,生成了小的一组,同时也记录大的一组(比如可以用map映射起来) 3. 对小的一组递归调用这个函数 4. 当调用回来时,能找到这小的一组的第i个数,这个数左边都是比它小的数字,再从map中找到这i个数字对应的数字,这样总共有2i个数字,再对这2i个数字调用SELECT. 很tricky的一点是为什么最后要调用T(2i),看下面这个数字[1,2,3,4],order-2是2,可是分组完后较小元素是[1,3],所以还要对另外i个数字进行比较. b. ![](http://latex.codecogs.com/gif.latex?%0d%0a%09U_i\(n\)%20=%20\\lfloor%20n/2%20\\rfloor%20%20+%20U_i\(\\lceil%20n/2%20\\rceil\)%20+%20T\(2i\)%20\\nonumber%20\\\\%20%20~%0d%0a\\hspace{16%20mm}%20=%20\\lfloor%20n/2%20\\rfloor%20%20+%20\\lceil%20n/2%20\\rceil%20+%20O\(T\(2i\)\\lg\(\\lfloor%20n/2%20\\rfloor%20/%20i\)\)+T\(2i\)%09%20\\\\%20%20~%0d%0a\\hspace{16%20mm}%20=%20n%20+%20O\(T\(2i\)\\lg\(n%20/%20i\)\)%20+%20T\(2i\)%20\\\\%20%20~%0d%0a\\hspace{16%20mm}=%20n%20+%20O\(T\(2i\)\\lg\(n%20/%20i\)\)%0d%0a) c. ![](http://latex.codecogs.com/gif.latex?U_i\(n\)%20=%20n%20+%20O\(T\(2i\)\\lg\(n%20/%20i\)\)%20\\\\%20%20~\\hspace{16%20mm}%20=%20n%20+%20O\(O\(1\)\\lg\(n%20/%20i\)\)%20\\\\%20%20~%0d%0a\\hspace{16%20mm}%20=%20n%20+%20O\(\\lg\(n\)%20-%20\\lg\(i\)\)%20\\\\%20%20~%0d%0a\\hspace{16%20mm}%20=%20n%20+%20O\(\\lg\(n\)%20-%20O\(1\)\)%20\\\\%20%20~%0d%0a\\hspace{16%20mm}%20=%20n%20+%20O\(\\lg\(n\)\)%20\\\\%20%20~%0d%0a) d. ![](http://latex.codecogs.com/gif.latex?U_i\(n\)%20=%20n%20+%20O\(T\(2i\)\\lg\(n%20/%20i\)\)%20\\\\%20%20%20%20%20%20%20~\\hspace{16%20mm}%20=%20n%20+%20O\(T\(2n/k\)\\lg\(n%20/%20\(n/k\)\)\)%20\\\\%20%20%20%20%20%20%20~%0d%0a\\hspace{16%20mm}%20=%20n%20+%20O\(T\(2n/k\)\\lg\(k\)\)%0d%0a) *** Follow [@louis1992](https://github.com/gzc) on github to help finish this task ================================================ FILE: C09-Medians-and-Order-Statistics/problems/i-largest.cpp ================================================ /************************************************************************* > File Name: worse-case-linear-time.cpp > Author: Louis1992 > Mail: zhenchaogan@gmail.com > Blog: http://gzc.github.io > Created Time: Sun May 24 14:09:41 2015 ************************************************************************/ // C++ implementation of worst case linear time algorithm // to find k'th smallest element #include #include #include using namespace std; int partition(int arr[], int l, int r, int k); // A simple function to find median of arr[]. This is called // only for an array of size 5 in this program. int findMedian(int arr[], int n) { sort(arr, arr+n); // Sort the array return arr[n/2]; // Return middle element } // Returns k'th smallest element in arr[l..r] in worst case // linear time. ASSUMPTION: ALL ELEMENTS IN ARR[] ARE DISTINCT int kthSmallest(int arr[], int l, int r, int k) { if (k > 0 && k <= r - l + 1) { int n = r-l+1; // Number of elements in arr[l..r] // Divide arr[] in groups of size 5, calculate median // of every group and store it in median[] array. int i, median[(n+4)/5]; // There will be floor((n+4)/5) groups; for (i=0; i k-1) return kthSmallest(arr, l, pos-1, k); return kthSmallest(arr, pos+1, r, k-pos+l-1); } return INT_MAX; } void swap(int *a, int *b) { int temp = *a; *a = *b; *b = temp; } // It searches for x in arr[l..r], and partitions the array // around x. int partition(int arr[], int l, int r, int x) { int i; for (i=l; iv; for(int i = 0;i < n;i++) if(arr[i] >= r) v.push_back(arr[i]); sort(v.begin(), v.end()); for(auto e : v) cout << e << " "; return 0; } ================================================ FILE: C09-Medians-and-Order-Statistics/problems/i-largest.py ================================================ #!/usr/bin/env python # coding=utf-8 from Queue import PriorityQueue def sort1(items, i): items = sorted(items,reverse=True) return items[:i] def sort2(items, i): pq = PriorityQueue() for element in items: pq.put((-element, element)) array = [] for k in range(i): array.append(pq.get()[1]) return array items = [3,0,7,8,-2,12] print sort1(items, 3) print sort2(items, 3) ================================================ FILE: C09-Medians-and-Order-Statistics/problems/weighted_median.py ================================================ #!/usr/bin/env python # coding=utf-8 import math def select(items, n): med = median(items) smaller = [item for item in items if item < med] larger = [item for item in items if item > med] if len(smaller) == n: return med elif len(smaller) > n: return select(smaller, n) else: return select(list(larger), n - len(smaller) - 1) def median(items): def median_index(n): if n % 2: return n // 2 else: return n // 2 - 1 def partition(items, element): i = 0 for j in range(len(items) - 1): if items[j] == element: items[j], items[-1] = items[-1], items[j] if items[j] < element: items[i], items[j] = items[j], items[i] i += 1 items[i], items[-1] = items[-1], items[i] return i def select(items, n): if len(items) <= 1: return items[0] medians = [] for i in range(0, len(items), 5): group = sorted(items[i:i + 5]) items[i:i + 5] = group median = group[median_index(len(group))] medians.append(median) pivot = select(medians, median_index(len(medians))) index = partition(items, pivot) if n == index: return items[index] elif n < index: return select(items[:index], n) else: return select(items[index + 1:], n - index - 1) return select(items[:], median_index(len(items))) def weighted_median(items, w_items, start, end): def linear_weighted_median(items, start, end): med = median(items) smaller = [item for item in items if item < med] larger = [item for item in items if item > med] leftsum = 0 rightsum = 0 for i in range(len(smaller)): leftsum += m[smaller[i]] for i in range(len(smaller)): rightsum += m[larger[i]] print leftsum,rightsum if leftsum < 0.5 and rightsum <= 0.5: return med if leftsum >= 0.5: m[med] += rightsum smaller.append(med) return linear_weighted_median(smaller, start, start+len(smaller)-1) else: m[med] += leftsum larger.insert(0, med) return linear_weighted_median(larger, start+len(smaller)+1, end) m = dict() for i in range(len(items)): m[items[i]] = w_items[i] return linear_weighted_median(items, start, end) weighted_array = [0.3,0.3,0.1,0.05,0.25] array = [5,4,0,3,2] print weighted_median(array, weighted_array, 0, 4) ================================================ FILE: C09-Medians-and-Order-Statistics/randomized-select.cpp ================================================ #include #include using namespace std; int partition(vector& arr, int l, int r) { int x = arr[r], i = l; for (int j = l; j <= r - 1; j++) { if (arr[j] <= x) { swap(arr[i], arr[j]); i++; } } swap(arr[i], arr[r]); return i; } int randomPartition(vector& arr, int l, int r) { int n = r - l + 1; int pivot = rand() % n; swap(arr[l + pivot], arr[r]); return partition(arr, l, r); } int kthSmallest(vector& arr, int l, int r, int k) { int pos = randomPartition(arr, l, r); if (pos - l == k - 1) { return arr[pos]; } else if (pos - l > k - 1) { return kthSmallest(arr, l, pos - 1, k); } else { return kthSmallest(arr, pos+1, r, k-pos+l-1); } } int kthSmallest(vector& arr, int k) { // TODO: Validate k is in valid range. return kthSmallest(arr, 0, arr.size() - 1, k); } int main() { vectorv{3,4,2,5,1}; cout << kthSmallest(v, 1) << endl; cout << kthSmallest(v, 2) << endl; cout << kthSmallest(v, 3) << endl; cout << kthSmallest(v, 4) << endl; cout << kthSmallest(v, 5) << endl; } ================================================ FILE: C09-Medians-and-Order-Statistics/worst-case-linear-time.cpp ================================================ /************************************************************************* > File Name: worse-case-linear-time.cpp > Author: Louis1992 > Mail: zhenchaogan@gmail.com > Blog: http://gzc.github.io > Created Time: Sun May 24 14:09:41 2015 ************************************************************************/ // C++ implementation of worst case linear time algorithm // to find k'th smallest element #include #include #include using namespace std; int partition(int arr[], int l, int r, int k); // A simple function to find median of arr[]. This is called // only for an array of size 5 in this program. int findMedian(int arr[], int n) { sort(arr, arr+n); // Sort the array return arr[n/2]; // Return middle element } // Returns k'th smallest element in arr[l..r] in worst case // linear time. ASSUMPTION: ALL ELEMENTS IN ARR[] ARE DISTINCT int kthSmallest(int arr[], int l, int r, int k) { if (k > 0 && k <= r - l + 1) { int n = r-l+1; // Number of elements in arr[l..r] // Divide arr[] in groups of size 5, calculate median // of every group and store it in median[] array. int i, median[(n+4)/5]; // There will be floor((n+4)/5) groups; for (i=0; i k-1) return kthSmallest(arr, l, pos-1, k); return kthSmallest(arr, pos+1, r, k-pos+l-1); } return INT_MAX; } void swap(int *a, int *b) { int temp = *a; *a = *b; *b = temp; } // It searches for x in arr[l..r], and partitions the array // around x. int partition(int arr[], int l, int r, int x) { int i; for (i=l; inext L1.head->next = L2.head->next L2.head->next = temp; return L2.head; ``` ### Exercises 10.2-7 *** Give a Θ(n)-time nonrecursive procedure that reverses a singly linked list of n elements. The procedure should use no more than constant storage beyond that needed for the list itself. ### `Answer` [solution](https://github.com/gzc/leetcode/blob/master/cpp/201-210/Reverse%20Linked%20List.cpp) ### Exercises 10.2-8 *** Explain how to implement doubly linked lists using only one pointer value np[x] per item instead of the usual two (next and prev). Assume that all pointer values can be interpreted as k-bit integers, and define np[x] to be np[x] = next[x] XOR prev[x], the k-bit "exclusive-or" of next[x] and prev[x]. (The value NIL is represented by 0.) Be sure to describe what information is needed to access the head of the list. Show how to implement the SEARCH, INSERT, and DELETE operations on such a list. Also show how to reverse such a list in O(1) time. ### `Answer` 为了访问下一个元素,只需要XOR(np,prev).为了访问前一个元素,只需要XOR(np,next). *** Follow [@louis1992](https://github.com/gzc) on github to help finish this task. ================================================ FILE: C10-Elementary-Data-Structures/10.3.md ================================================ ### Exercises 10.3-1 *** Draw a picture of the sequence[13, 4, 8, 19, 5, 11]stored as a doubly linked list using the multiple-array representation. Do the same for the single-array representation. ### `Answer`
+---+
| L |-------------------------------------------+
+---+                                           V
        1    2    3    4    5    6    7    8    9   10   11   12
     +----+----+----+----+----+----+----+----+----+----+----+----+
next |    |    |  5 |  7 | 10 |    |  / |    |  3 |  4 |    |    |
     +----+----+----+----+----+----+----+----+----+----+----+----+
 key |    |    |  4 |  5 |  8 |    | 11 |    | 13 | 19 |    |    |
     +----+----+----+----+----+----+----+----+----+----+----+----+
prev |    |    |  8 | 10 |  2 |    |  4 |    |  / |  5 |    |    |
     +----+----+----+----+----+----+----+----+----+----+----+----+
   
+----+----+----+
| key|next|prev|
+----+----+----+

13 is head.

   1    2    3     4    5    6     7    8    9    10   11   12
+----+----+----++----+----+----++----+----+----++----+----+----++--
|  4 |  7 | 13 ||  5 | 10 | 16 ||  8 | 16 |  1 || 11 |  / |  4 ||
+----+----+----++----+----+----++----+----+----++----+----+----++--

        13   14   15    16   17   18
   --++----+----+----++----+----+----+
     || 13 |  1 |  / || 19 |  4 |  7 |
   --++----+----+----++----+----+----+
### Exercises 10.3-2 *** Write the procedures ALLOCATE-OBJECT and FREE-OBJECT for a homogeneous collection of objects implemented by the single-array representation. ### `Answer` [implementation](./exercise_code/af-obj.c) ### Exercises 10.3-3 *** Why don't we need to set or reset the prev fields of objects in the implementation of the ALLOCATE-OBJECT and FREE-OBJECT procedures? ### `Answer` 因为这类似于一个栈,没有prev. ### Exercises 10.3-4 *** It is often desirable to keep all elements of a doubly linked list compact in storage, using, for example, the first m index locations in the multiple-array representation. (This is the case in a paged, virtual-memory computing environment.) Explain how the procedures ALLOCATE>- OBJECT and FREE-OBJECT can be implemented so that the representation is compact. Assume that there are no pointers to elements of the linked list outside the list itself. (Hint: Use the array implementation of a stack.) ### `Answer` * ALLOCATE:选free_list的head. * FREE-OBJECT:不是直接变成head,而是按sorted的方式插入. 这样可以保证每次分配的时候取排在最前面的空位置 ### Exercises 10.3-5 *** Let L be a doubly linked list of length m stored in arrays key, prev, and next of length n. Suppose that these arrays are managed by ALLOCATE-OBJECT and FREE-OBJECT procedures that keep a doubly linked free list F. Suppose further that of the n items, exactly m are on list L and n-m are on the free list. Write a procedure COMPACTIFY-LIST(L, F) that, given the list L and the free list F, moves the items in L so that they occupy array positions 1, 2,..., m and adjusts the free list F so that it remains correct, occupying array positions m + 1, m + 2,..., n. The running time of your procedure should be Θ(n), and it should use only a constant amount of extra space. Give a careful argument for the correctness of your procedure. ### `Answer` * 先遍历free_list,给每个item的prev标记一下,用来区别L中的item. * 两根指针,一根p1在array的头,另一个p2在尾巴.p1向右移动知道遇到free item,p2向左移动直到遇到used item,然后交换p1和p2的内容.遍历一直到p1,p2遇到为止. * 重新整理free_list中的元素. *** Follow [@louis1992](https://github.com/gzc) on github to help finish this task. ================================================ FILE: C10-Elementary-Data-Structures/10.4.md ================================================ ### Exercises 10.4-1 *** Draw the binary tree rooted at index 6 that is represented by the following fields.
index key left right
1 12 7 3
2 15 8 NIL
3 4 10 NIL
4 10 5 9
5 2 NIL NIL
6 18 1 4
7 7 NIL NIL
8 14 6 2
9 21 NIL NIL
10 5 NIL NIL
### `Answer` ![](./repo/s4/1.png) ### Exercises 10.4-2 *** Write an O(n)-time recursive procedure that, given an n-node binary tree, prints out the key of each node in the tree. ### `Answer` 前序,中序和后序遍历. ### Exercises 10.4-3 *** Write an O(n)-time nonrecursive procedure that, given an n-node binary tree, prints out the key of each node in the tree. Use a stack as an auxiliary data structure. ### `Answer` 这些题目都出现在leetcode上. [pre-order](https://leetcode.com/problems/binary-tree-preorder-traversal/) [solution](https://github.com/gzc/leetcode/blob/master/cpp/141-150/Tree%20Preorder%20Traversal.cpp) [in-order](https://leetcode.com/problems/binary-tree-inorder-traversal/) [solution](https://github.com/gzc/leetcode/blob/master/cpp/091-100/Binary%20Tree%20Inorder%20Traversal.cpp) [post-order](https://leetcode.com/problems/binary-tree-postorder-traversal/) [solution](https://github.com/gzc/leetcode/blob/master/cpp/141-150/Binary%20Tree%20Postorder%20Traversal.cpp) ### Exercises 10.4-4 *** Write an O(n)-time procedure that prints all the keys of an arbitrary rooted tree with n nodes, where the tree is stored using the left-child, right-sibling representation. ### `Answer` PROCEDURE(root): if root != NULL PRINT root->key if(root->child) PROCEDURE(root->child) if(root->sibling) PROCEDURE(root->sibling) ### Exercises 10.4-5 *** Write an O(n)-time nonrecursive procedure that, given an n-node binary tree, prints out the key of each node. Use no more than constant extra space outside of the tree itself and do not modify the tree, even temporarily, during the procedure. ### `Answer` if we don't use stack, then we must keep new attribute `parent` in `node`. The function of parent attribute is similar as the stack which help us to find the prev node. 如果不用stack,那么每个node必须要有parent属性.stack的作用其实是依路径保存parent节点. PROCEDURE(root): prev = NULL; while root != NULL: if prev == root->parent: print root->key root = root->left ? root->left : root->right ? root->right : root->parent; else if prev == root->left && root->right != NULL: prev = root; root = root -> right; else: prev = root; root = root -> parent; see my [implementation](./exercise_code/traversal.cpp) ### Exercises 10.4-6 *** The left-child, right-sibling representation of an arbitrary rooted tree uses three pointers in each node: left-child, right-sibling, and parent. From any node, its parent can be reached and identified in constant time and all its children can be reached and identified in time linear in the number of children. Show how to use only two pointers and one boolean value in each node so that the parent of a node or all of its children can be reached and identified in time linear in the number of children. ### `Answer` 很简单,去掉parent指针,新增一个表示是否是最后一个儿子的bool值,如果是最后一个,那么next指向parent,否则next指向下一个兄弟节点. *** Follow [@louis1992](https://github.com/gzc) on github to help finish this task. ================================================ FILE: C10-Elementary-Data-Structures/README.md ================================================ Problem 10.2 & 10.3 remain ================================================ FILE: C10-Elementary-Data-Structures/exercise_code/af-obj.c ================================================ #include #include #define MAX_SIZE 10 typedef int list_t; typedef int obj_t; int empty_list = -1; int cells[MAX_SIZE * 3]; int free_list; #define NEXT(i) cells[(i) + 1] #define PREV(i) cells[(i) + 2] #define KEY(i) cells[i] void init_storage() { int i; for (i = 0; i < (MAX_SIZE - 1) * 3; i += 3) NEXT(i) = i + 3; NEXT(i) = -1; free_list = 0; } list_t allocate_object() { if (free_list == -1) { fprintf(stderr, "Storage depleted\n"); exit(1); } list_t new = free_list; free_list = NEXT(free_list); return new; } void free_object(list_t list) { NEXT(list) = free_list; free_list = list; } list_t cons(obj_t key, list_t list) { list_t new = allocate_object(); NEXT(new) = list; PREV(new) = empty_list; KEY(new) = key; if (list != empty_list) { PREV(list) = new; } return new; } void delete(list_t list) { if (PREV(list) != empty_list) { NEXT(PREV(list)) = NEXT(list); } if (NEXT(list) != empty_list) { PREV(NEXT(list)) = PREV(list); } free_object(list); } obj_t get(list) { if (list == empty_list) return -1; return KEY(list); } list_t next(list) { if (list == empty_list) return -1; return NEXT(list); } int main() { init_storage(); list_t new_list = cons(5, empty_list); new_list = cons(4, new_list); printf("%d\n", get(new_list)); printf("%d\n", get(next(new_list))); delete(new_list); return 0; } ================================================ FILE: C10-Elementary-Data-Structures/exercise_code/deque.cpp ================================================ #include #include #include using namespace std; template class Deque { private: vector data_; int size_; int head_; int tail_; int capacity_; public: Deque(int size) : size_(0), head_(0), tail_(size-1), capacity_(size), data_(size) {} void push_front(T v) { assert(head_ <= tail_); data_[head_++] = v; size_++; } void push_back(T v) { assert(head_ <= tail_); data_[tail_--] = v; size_++; } void pop_front() { assert(head_ > 0); head_--; size_--; } void pop_back() { assert(tail_ < capacity_); tail_++; size_--; } T front() const { assert(head_ > 0); return data_[head_-1]; } T back() const { assert(tail_ < capacity_-1); return data_[tail_+1]; } int size() const { return size_; } int empty() const { return size_ == 0; } ~Deque() {} }; int main() { Deque mydeque(3); mydeque.push_front(1); mydeque.push_back(3); cout << mydeque.front() << " " << mydeque.back() << endl; cout << mydeque.size(); } ================================================ FILE: C10-Elementary-Data-Structures/exercise_code/deque.py ================================================ #!/usr/bin/env python # coding=utf-8 class Deque: def __init__(self, size): self.N = 0 self.head = 0 self.tail = size-1 self.array = [0]*size self.size = size def addFirst(self, item): self.array[self.head] = item self.N += 1 self.head = (self.head+1)%self.size def addLast(self, item): self.array[self.tail] = item self.N += 1 self.tail = (self.tail-1)%self.size def removeFirst(self): self.N -= 1 self.head = (self.head-1)%self.size v = self.array[self.head] return v def removeLast(self): self.N -= 1 self.tail = (self.tail+1)%self.size v = self.array[self.tail] return v def seeAll(self): print self.head,self.tail,self.N print self.array myqueue = Deque(10) myqueue.addFirst(2) myqueue.addFirst(3) myqueue.addFirst(4) myqueue.addLast(5) myqueue.addLast(6) myqueue.addLast(6) myqueue.seeAll() print myqueue.removeLast() print myqueue.removeFirst() ================================================ FILE: C10-Elementary-Data-Structures/exercise_code/dict.cpp ================================================ /************************************************************************* > File Name: dict.cpp > Author: Louis1992 > Mail: zhenchaogan@gmail.com > Blog: http://gzc.github.io > Created Time: Tue Jul 7 21:43:43 2015 ************************************************************************/ #include using namespace std; struct node_t { int key; struct node_t *next; }; struct list_t{ struct node_t nil; }; void init_list(list_t *list) { list->nil.key = 0; list->nil.next = &(list->nil); } void destroy_list(list_t *list) { node_t *node = list->nil.next; node_t *next; while (node != &(list->nil)) { next = node->next; free(node); node = next; } } void insert(list_t *list, int key) { node_t *newnode = (node_t *) malloc(sizeof(node_t)); newnode->key = key; newnode->next = list->nil.next; list->nil.next = newnode; } node_t *search(list_t *list, int key) { node_t *node = list->nil.next; list->nil.key = key; while (node->key != key) { node = node->next; } if (node == &(list->nil)) { return NULL; } else { return node; } } void del(list_t *list, int key) { node_t *node = &(list->nil); while (node->next != &(list->nil)) { if (node->next->key == key) { node_t *to_be_deleted = node->next; node->next = node->next->next; free(to_be_deleted); } else { node = node->next; } } } int main() { list_t *mydict = new list_t; init_list(mydict); insert(mydict, 2); insert(mydict, 3); insert(mydict, 4); node_t *tmp = search(mydict, 3); cout << tmp->key; return 0; } ================================================ FILE: C10-Elementary-Data-Structures/exercise_code/traversal.cpp ================================================ /************************************************************************* > File Name: travelsal.cpp > Author: Louis1992 > Mail: zhenchaogan@gmail.com > Blog: http://gzc.github.io > Created Time: Mon Aug 10 19:04:08 2015 ************************************************************************/ #include using namespace std; struct tree_t { struct tree_t *left; struct tree_t *right; struct tree_t *parent; int key; tree_t() {left = 0; right = 0; parent = 0;} }; void print_tree(tree_t *tree) { tree_t *prev; prev = 0; while (tree) { if (prev == tree->parent) { cout << tree->key << endl; prev = tree; tree = tree->left ? tree->left : tree->right ? tree->right : tree->parent; } else if (prev == tree->left && tree->right) { prev = tree; tree = tree->right; } else { prev = tree; tree = tree->parent; } } } int main() { tree_t root; root.key = 1; tree_t left; left.key = 2; tree_t right; right.key = 3; root.left = &left; root.right = &right; left.parent = &root; right.parent = &root; print_tree(&root); return 0; } ================================================ FILE: C10-Elementary-Data-Structures/problem.md ================================================ ### Problems 1 : Comparisons among lists *** For each of the four types of lists in the following table, what is the asymptotic worst-case running time for each dynamic-set operation listed? ### `Answer`
unsorted, singly linked sorted, singly linked unsorted, doubly linked sorted, doubly linked
SEARCH(L, k) linear linear linear linear
INSERT(L, x) constant linear constant linear
DELETE(L, x) linear linear constant constant
SUCCESSOR(L, x) linear constant linear constant
PREDECESSOR(L, x) linear linear linear constant
MINIMUM(L, k) linear constant linear constant
MAXIMUM(L, k) linear linear linear linear
### Problems 2 : Mergeable heaps using linked lists *** A **mergeable heap** supports the following operations: MAKE-HEAP (which creates an empty mergeable heap), INSERT, MINIMUM, EXTRACT-MIN, and UNION. Show how to implement mergeable heaps using linked lists in each of the following cases. Try to make each operation as efficient as possible. Analyze the running time of each operation in terms of the size of the dynamic set(s) being operated on. a. Lists are sorted. b. Lists are unsorted. c. Lists are unsorted, and dynamic sets to be merged are disjoint. ### `Answer` Actually we need not imitate the implementation of min-heap by array. And since we do not have `A->last`, it is not a big issue if the linked list is singly linked or doubly linked. For unsorted linked list, 1. `MAKE-HEAP`: O(1). ```` MAKE-HEAP() return MAKE-LINKED-LIST() ```` 2. `INSERT`: O(1). ```` INSERT(A, x) LIST-INSERT'(A->L, x) ```` 3. `MINIMUM`: O(n). We need iterate the whole linked list. ```` MINIMUM(A) return LIST-MINIMUM(A->L) ```` 4. `EXTRACT-MIN(A)`: O(n). Mainly due to `MINIMUM`. ```` EXTRACT-MIN(A) x = LIST-MINIMUM(A->L) LIST-DELETE(A->L, x) return x ```` 5. `UNION`: O(n). Since the linked lists here does not have `L->last`. We need O(n) to find the last element. ```` UNION(A, B) A.last->next = B.first ```` For sorted linked list, 1. `MAKE-HEAP`: O(1). Nothing different. ```` MAKE-HEAP() return MAKE-LINKED-LIST() ```` 2. `INSERT`: O(n). We need find the position. ```` INSERT(A, x) LIST-INSERT'(A->L, x) ```` 3. `MINIMUM`: O(1). ```` MINIMUM(A) return A->L.first ```` 4. `EXTRACT-MIN(A)`: O(1). ```` EXTRACT-MIN(A) x = A->L.first LIST-DELETE(A->L, A->L.first) return x ```` 5. `UNION`: O(n). Use algorithm like `MERGE` in merge sort. ```` UNION(A, B) Merge(A, B) ```` In conclusion, we find | Method | unsorted | sorted | | ------ | -------- | ------ | | `MAKE-HEAP()` | O(1) | O(1) | | `INSERT(A, x)` | O(1) | O(n) | | `MINIMUM(A)` | O(n) | O(1) | | `EXTRACT-MIN(A)` | O(n) | O(1) | | `UNION(A, B)` | O(n) | O(n) | *** Follow [@louis1992](https://github.com/gzc) on github to help finish this task. ================================================ FILE: C11-Hash-Tables/11.1.md ================================================ ### Exercises 11.1-1 *** Suppose that a dynamic set S is represented by a direct-address table T of length m. Describe a procedure that finds the maximum element of S. What is the worst-case performance of your procedure? ### `Answer` 遍历整个table. 最坏情况是O(m) ### Exercises 11.1-2 *** A **bit vector** is simply an array of bits (0's and 1's). A bit vector of length m takes much less space than an array of m pointers. Describe how to use a bit vector to Represent a Dynamic Set of Distinct Elements with no Satellite Data. Dictionary Operations Should Run in O(1) Time. ### `Answer` 用来表示整数. 1表示该数在集合中,0表示不在集合中. ### Exercises 11.1-3 *** Suggest how to implement a direct-address table in which the keys of stored elements do not need to be distinct and the elements can have satellite data. All three dictionary operations (INSERT, DELETE, and SEARCH) should run in O(1) time. (Don't forget that DELETE takes as an argument a pointer to an object to be deleted, not a key.) ### `Answer` 将每个key分别映射到一个 doubly linked list。 ### Exercises 11.1-4 *** We wish to implement a dictionary by using direct addressing on a huge array. At the start, the array entries may contain garbage, and initializing the entire array is impractical because of its size. Describe a scheme for implementing a direct-address dictionary on a huge array. Each stored object should use O(1) space; the operations SEARCH, INSERT, and DELETE should take O(1) time each; and the initialization of the data structure should take O(1) time. (Hint: Use an additional stack, whose size is the number of keys actually stored in the dictionary, to help determine whether a given entry in the huge array is valid or not.) ### `Answer` > 根据提示我们需要一个额外的栈,这个栈用于存储指向大数组中元素的指针,同时大数组的元素是对应栈的下标(top的值)。 - 向数组中插入时,我们将数组的地址入栈,并将大数组的元素初始化为对应的栈顶指针。 - 执行查找操作时,我们首先通过直接寻址在数组中查找值,然后判断数组元素是不是合法的栈的下标,如果是合法的,那么查找成功,反之,字典中无该元素,返回NULL。 - 执行删除操作时,删除元素后栈中会产生空洞,我们可以通过将栈顶元素和其对应的大数组中存储的栈的下标来填补到产生的这个空洞中,然后再执行出栈操作即可。 *** Follow [@louis1992](https://github.com/gzc) on github to help finish this task. ================================================ FILE: C11-Hash-Tables/11.2.md ================================================ ### Exercises 11.2-1 *** Suppose we use a hash function h to hash n distinct keys into an array T of length m. Assuming simple uniform hashing, what is the expected number of collisions? More precisely, what is the expected cardinality of {{k, l} : k ≠ l and h(k) = h(l)}? ### `Answer` ![](http://latex.codecogs.com/gif.latex?%20C_n^2%20\\cdot%20\\frac{1}{m}) ### Exercises 11.2-2 *** Demonstrate the insertion of the keys 5, 28, 19, 15, 20, 33, 12, 17, 10 into a hash table with collisions resolved by chaining. Let the table have 9 slots, and let the hash function be h(k) = k mod 9. ### `Answer` ![](./repo/s1/1.png) ### Exercises 11.2-3 *** Professor Marley hypothesizes that substantial performance gains can be obtained if we modify the chaining scheme so that each list is kept in sorted order. How does the professor's modification affect the running time for successful searches, unsuccessful searches, insertions, and deletions? ### `Answer` * successful searches:没有影响 * unsuccessful searches:当数据量大可以加速,可以提前判断元素超出范围(如递增排序,若小于首节点,则可判断为不成功查找) * insertions:降低了插入的速度,需要遍历链表插入在合适的位置 * deletions:没有影响 ### Exercises 11.2-4 *** Suggest how storage for elements can be allocated and deallocated within the hash table itself by linking all unused slots into a free list. Assume that one slot can store a flag and either one element plus a pointer or two pointers. All dictionary and free-list operations should run in O(1) expected time. Does the free list need to be doubly linked, or does a singly linked free list suffice? ### `Answer` 需要双链表.每个slot有一个标识标识是否已分配,如果没有分配指针指向free list的属于自己的那个位置. 删除的时候将标志位清一下,将自己加入链表头,指针指向头;插入时根据指针去取. ### Exercises 11.2-5 *** Show that if |U| > nm, there is a subset of U of size n consisting of keys that all hash to the same slot, so that the worst-case searching time for hashing with chaining is Θ(n). ### `Answer` 如果|U| = nm,假设U的全集要均匀分到m个位置上,每个位置期望就有n个元素,因此至少有一个位置是有至少n个元素的,我们选取这个集合,查找操作需要的时间就是Θ(n). *** Follow [@louis1992](https://github.com/gzc) on github to help finish this task. ================================================ FILE: C11-Hash-Tables/11.3.md ================================================ ### Exercises 11.3-1 *** Suppose we wish to search a linked list of length n, where each element contains a key k along with a hash value h(k). Each key is a long character string. How might we take advantage of the hash values when searching the list for an element with a given key? ### `Answer` 首先计算出给定关键字的hash值. 对列表中的每个元素,先验证hash值对不对,再进行字符串的比较. First, calculate the hash value for the specified keyword. For each item in the list, first check the hash value. If one string share the same value as the target string, compare the string content. ### Exercises 11.3-2 *** Suppose that a string of r characters is hashed into m slots by treating it as a radix-128 number and then using the division method. The number m is easily represented as a 32-bit computer word, but the string of r characters, treated as a radix-128 number, takes many words. How can we apply the division method to compute the hash value of the character string without using more than a constant number of words of storage outside the string itself? ### `Answer` One easy approach would adding the values of each character to get a sum and then take a modulo 128. Another way would be using n-degree equation with each character value acting as a co-efficient for the nth term. Example: S[0] * x^n + S[1] * x^(n-1)+ …. + S[n-1], for better result substitute x = 33 or 37 or 39. This is the famous Horner’s method for finding a hash value. ### Exercises 11.3-3 *** Consider a version of the division method in which h(k) = k mod m, where m = 2p - 1 and k is a character string interpreted in radix 2p. Show that if string x can be derived from string y by permuting its characters, then x and y hash to the same value. Give an example of an application in which this property would be undesirable in a hash function. ### `Answer` 有一个很简单的数论知识.先举个例子 3 * 128^3 mod 127 = 3 * 128^2 * (128 mod 127) mod 127 = 3 * 128^2 mod 127 = 3 mod 127 = 3 无论怎么交换字符串的order,radix的影响都会消失. 因为2p^n mod 2p-1 === 1. ### Exercises 11.3-4 *** Consider a hash table of size m = 1000 and a corresponding hash function h(k) = ⌊m(k A mod 1)⌋ for ![](http://latex.codecogs.com/gif.latex?%20A%20=%20\\frac{\\sqrt{5}-1}{2}) . Compute the locations to which the keys 61, 62, 63, 64, and 65 are mapped. ### `Answer` key | value :----:|:----: 61 | 700 62 | 318 63 | 936 64 | 554 65 | 172 ### Exercises 11.3-5 *** Define a family ![](http://latex.codecogs.com/gif.latex?\\mathscr{H}%20) of hash functions from a finite set U to a finite set B to be **ϵ-universal** if for all pairs of distinct elements k and l in U, ![](http://latex.codecogs.com/gif.latex?\\Pr\\{h\(k\)%20=%20h\(l\)\\}%20\\le%20\\epsilon) where the probability is over the choice of the hash function **h** drawn at random from the family ![](http://latex.codecogs.com/gif.latex?\\mathscr{H}%20). Show that an ϵ-universal family of hash functions must have ![](http://latex.codecogs.com/gif.latex?\\epsilon%20\\ge%20\\frac{1}{|B|}%20-%20\\frac{1}{|U|}) ### `Answer` Let ![](http://latex.codecogs.com/gif.latex?s_i%20%3D%20%7C%5C%7Bw%3A%20h%28w%29%20%3D%20i%29%5C%7D%7C). In other words, ![](http://latex.codecogs.com/gif.latex?s_i) is the length of linked list of slot i in hash table. Then we have ![](http://latex.codecogs.com/gif.latex?\\Pr\\{h\(k\)%20=%20h\(l\)\\}%20=%20\\frac{\\sum_{i=1}^{|B|}%20s_i\(s_i-1\)}{|U|^2}) also ![](http://latex.codecogs.com/gif.latex?\\sum_{i=1}^{|B|}%20s_i\(s_i-1\)%20=%20\\sum_{i=1}^{|B|}%20s_i^2%20-%20\\sum_{i=1}^{|B|}%20s_i) ![](http://latex.codecogs.com/gif.latex?\(\\sum_{i=1}^{|B|}%20s_i\)^2%20=%20\\sum_{i=1}^{|B|}%20s_i^2%20+%202\\sum_{i=1}^{|B|-1}%20\\sum_{j=i+1}^{|B|}%20s_i%20s_j%20\\le%20|B|\\sum_{i=1}^{|B|}%20s_i^2) From above equations: ![](http://latex.codecogs.com/gif.latex?\\sum_{i=1}^{|B|}%20s_i\(s_i-1\)%20=%20\\sum_{i=1}^{|B|}%20s_i^2%20-%20\\sum_{i=1}^{|B|}%20s_i%20\\ge%20\\frac{\(\\sum_{i=1}^{|B|}%20s_i\)^2}{|B|}%20-%20\\sum_{i=1}^{|B|}%20s_i%20=%20\\frac{|U|^2}{|B|}%20-%20|U|) And finally: ![](http://latex.codecogs.com/gif.latex?\\epsilon%20\\ge%20\\Pr\\{h\(k\)%20=%20h\(l\)\\}%20=%20\\frac{\\sum_{i=1}^{|B|}%20s_i\(s_i-1\)}{|U|^2}%20\\ge%20\\frac{\\frac{|U|^2}{|B|}%20-%20|U|}{|U|^2}%20=%20\\frac{1}{|B|}%20-%20\\frac{1}{|U|}) ### Exercises 11.3-6 *** Let U be the set of n-tuples of values drawn from Zp, and let B = Zp, where p is prime. Define the hash function hb : U → B for b in Zp on an input n-tuple [a0, a1, ..., an-1] from U as ![](http://latex.codecogs.com/gif.latex?h_b\(\\langle%20a_0,%20a_1,%20\\ldots,%20a_{n-1}%20\\rangle\)%20=%0d%0a%20%20%20\(\\sum_{j=0}^{n-1}%20a_j%20b^j}\)modp) and let H={hb:b∈Zp}. Argue that H is ((n−1)/p)-universal according to the definition of ϵ-universal in Exercise 11.3-5. (Hint: See Exercise 31.4-4.) ### `Answer` 首先用归纳法证明以下结论: 对于取自集合 ![](http://latex.codecogs.com/gif.latex?Z_p) 的 n 元组 ![](http://latex.codecogs.com/gif.latex?[a_0,%20a_1,%20...%20,%20a_n]), 满足 ![](http://latex.codecogs.com/gif.latex?\(\\sum_{j=0}^{n}a_j%20x^j\)modp%20=%200) 的 x (x ∈ Zp) 最多有 n 个。 1. 当 n = 1 时, 假设有 x = v 与 x = w (不失一般性, v > w) 同时满足 ![](http://latex.codecogs.com/gif.latex?\(a_0%20+%20a_1%20x\)modp%20=%200), 则有 ![](http://latex.codecogs.com/gif.latex?a_1%20\(v%20-%20w\)modp%20=%200), 其中 a_1 ∈ Zp, (v - w) ∈ Zp, p 为素数, 显然不成立。因此, 当 n = 1 时, 结论成立。 2. 当 n = k 时, 至多有 k 个 x (x ∈ Zp) 满足 ![](http://latex.codecogs.com/gif.latex?\(\\sum_{j=0}^{k}a_j%20x^j\)modp%20=%200) ... (0) 下面证明当 n = k + 1 时, 结论成立。 不妨设 ![](http://latex.codecogs.com/gif.latex?\\sum_{j=0}^{k%20+%201}a_j%20v^j%20\\equiv%20\\sum_{j=0}^{k%20+%201}a_j%20w^j\(modp\)) 其中 v > w, 得到 ![](http://latex.codecogs.com/gif.latex?\(\\sum_{j=1}^{k%20+%201}a_j%20\(v^j%20-%20w^j\)\)modp%20=%20\(\\sum_{j=1}^{k%20+%201}a_j%20\(v%20-%20w\)%20\\sum_{i=0}^{j-1}%20v^i%20w^{j%20-%20i-1}%20\)modp%20=%200) 由于(v - w) < p, 不影响取余结果, 上式可变形为 ![](http://latex.codecogs.com/gif.latex?\(%20\\sum_{j%20=%200}^{k}%20w^j%20\(%20\\sum_{i=j+1}^{k%20+%201}%20a_i%20v^{i%20-%20j%20-%201}%20\)%20\)modp%20=%200) 更进一步, 变为 ![](http://latex.codecogs.com/gif.latex?\(%20\\sum_{j%20=%200}^{k}%20w^j%20\(%20\(\\sum_{i=j+1}^{k%20+%201}%20a_i%20v^{i%20-%20j%20-%201}\)modp%20\)%20\)modp%20=%200) 令 ![](http://latex.codecogs.com/gif.latex?c_j%20=%20\(\\sum_{i=j+1}^{k%20+%201}%20a_i%20v^{i%20-%20j%20-%201}\)modp), 得到更直观的形式 ![](http://latex.codecogs.com/gif.latex?\(%20\\sum_{j%20=%200}^{k}%20c_j%20w^j%20\)modp%20=%200) .... (1) 上式说明, 当确定一个 v 可以确定一组取自 ![](http://latex.codecogs.com/gif.latex?Z_p) 的序列 ![](http://latex.codecogs.com/gif.latex?[c_0,%20c_1,%20...%20,%20c_k]), 根据(0)式, (1)式的解最多有k个。 即: mod p 后, 和 v 具有相同余数的 x 有 k 个 也就是说:对一组确定的 ![](http://latex.codecogs.com/gif.latex?[a_0,%20a_1,%20...%20,%20a_{k+1}]), 具有相同余数的 x 最多有 (k + 1) 个, 因此余数为0的 x 也最多只有 (k + 1) 个 命题得证。 下面证明原始的问题: 首先, 两个不同 n 元组 ![](http://latex.codecogs.com/gif.latex?[a_0,%20a_1,%20...%20,%20a_{n-1}]) 与 ![](http://latex.codecogs.com/gif.latex?[a'_0,%20a'_1,%20...%20,%20a'_{n-1}]) 散列结果相同等价于 ![](http://latex.codecogs.com/gif.latex?\(\\sum_{j=0}^{n-1}%20\(a_j%20-%20a'_j\)%20b^j\)modp%20=%200) ... (2) 由于取模运算,![](http://latex.codecogs.com/gif.latex?\(a_j%20-%20a'_j\)) 可以等价的换为 ![](http://latex.codecogs.com/gif.latex?Z_p) 中的元素, 根据上面证得的结果, (2)式最多有 (n - 1) 个 b 满足要求 即, H 中最多有 (n - 1) 个散列函数会导致两个不同输入散列结果相同, 因此 ![](http://latex.codecogs.com/gif.latex?\\Pr\\{h\(k\)%20=%20h\(l\)\\}%20\\le%20\\frac{n%20-%201}{p}) 根据定义, H 是 ![](http://latex.codecogs.com/gif.latex?\\frac{n%20-%201}{p}) 全域的 *** Follow [@louis1992](https://github.com/gzc) on github to help finish this task. ================================================ FILE: C11-Hash-Tables/11.4.md ================================================ ### Exercises 11.4-1 *** Consider inserting the keys 10, 22, 31, 4, 15, 28, 17, 88, 59 into a hash table of length m = 11 using open addressing with the primary hash function h'(k) = k mod m. Illustrate the result of inserting these keys using linear probing, using quadratic probing with c1 = 1 and c2 = 3, and using double hashing with h2(k) = 1 + (k mod (m - 1)). ### `Answer` index | linear probing | quadratic probing | double hashing :----: | :----: | :----: | :----: 0 | 22 | 22 | 22 1 | 88 | | 2 | | 88 | 59 3 | | 17 | 17 4 | 4 | 4 | 4 5 | 15 | | 15 6 | 28 | 28 | 28 7 | 17 | 59 | 88 8 | 59 | 15 | 9 | 31 | 31 | 31 10| 10 | 10 | 10 | ### Exercises 11.4-2 *** Write pseudocode for HASH-DELETE as outlined in the text, and modify HASH-INSERT to handle the special value DELETED. ### `Answer` HASH-DELETE(T, k) i = 0 repeat j = h(k, i) if T[j] == k temp = T[j] T[j] = DELETED return temp i = i + 1 until T[j] == NIL or i == m return NIL HASH-INSERT(T, k) i = 0 repeat j = h(k, i) if T[j] == NIL or T[j] == DELETED T[j] = k return j else i = i + 1 until i == m error "hash table overflow" ### Exercises 11.4-3 *** Suppose that we use double hashing to resolve collisions; that is, we use the hash function h(k, i) = (h1(k)+ih2(k)) mod m. Show that if m and h2(k) have greatest common divisor d ≥ 1 for some key k, then an unsuccessful search for key k examines (1/d)th of the hash table before returning to slot h1(k). Thus, when d = 1, so that m and h2(k) are relatively prime, the search may examine the entire hash table. (Hint: See Chapter 31.) ### `Answer` 简单了解下,这应该算是费马定理(如果没记错) 假设我们有3和7,3作为generator可以生成阶为6的子群,在这种情况下 3*1 mod 7 = 3 3*2 mod 7 = 6 3*3 mod 7 = 2 3*4 mod 7 = 5 3*5 mod 7 = 1 3*6 mod 7 = 4 3*7 mod 7 = 0 如果两个数字互质,那么一个数字的幂的模可以遍历一圈!也就是3 * n mod 7 = 3 * (n+7) mod 7. 对这个题目来说,如果d = 1,则要检查全部的散列.因为要遍历m个位置. 如果d > 1,那么h2(k)和m同时除d后又互质.可能要遍历m/d个位置. ### Exercises 11.4-4 *** Consider an open-address hash table with uniform hashing. Give upper bounds on the expected number of probes in an unsuccessful search and on the expected number of probes in a successful search when the load factor is 3/4 and when it is 7/8. ### `Answer` Theorem 11.6. Given an open address hash table with load factor ![](https://latex.codecogs.com/gif.latex?\alpha=&space;\frac{n}{m}<&space;1), the expected number of probes in an unsuccessful search is at most ![](https://latex.codecogs.com/gif.latex?\frac{1}{1-\alpha}), assuming uniform hashing. ![](https://latex.codecogs.com/gif.latex?\alpha=&space;\frac{3}{4}&space;\quad&space;\frac{1}{1-\alpha}&space;=&space;\frac{1}{1-\frac{3}{4}}&space;=&space;4probes) ![](https://latex.codecogs.com/gif.latex?\alpha=&space;\frac{7}{8}&space;\quad&space;\frac{1}{1-\alpha}&space;=&space;\frac{1}{1-\frac{7}{8}}&space;=&space;8probes) Theorem 11.8. Given an open address hash table with load factor ![](https://latex.codecogs.com/gif.latex?\alpha=&space;\frac{n}{m}<&space;1), the expected number of probes in a successful search is at most ![](https://latex.codecogs.com/gif.latex?\frac{1}{\alpha}ln\frac{1}{1-\alpha}), assuming uniform hashing and assuming that each key in the table is equally likely to be searched for. ![](https://latex.codecogs.com/gif.latex?\alpha=&space;\frac{3}{4}&space;\quad&space;\frac{1}{\alpha}ln\frac{1}{1-\alpha}&space;=&space;\frac{1}{\frac{3}{4}}ln\frac{1}{1-\frac{3}{4}}&space;\approx&space;1.85probes) ![](https://latex.codecogs.com/gif.latex?\alpha=&space;\frac{7}{8}&space;\quad&space;\frac{1}{\alpha}ln\frac{1}{1-\alpha}&space;=&space;\frac{1}{\frac{7}{8}}ln\frac{1}{1-\frac{7}{8}}&space;\approx&space;2.37probes) ### Exercises 11.4-5 *** Consider an open-address hash table with a load factor α. Find the nonzero value α for which the expected number of probes in an unsuccessful search equals twice the expected number of probes in a successful search. Use the upper bounds given by Theorems 11.6 and 11.8 for these expected numbers of probes. ### `Answer` 1/(1-α) = ln(1/(1-α)) * 2/α 解得α = 0.717 *** Follow [@louis1992](https://github.com/gzc) on github to help finish this task. ================================================ FILE: C11-Hash-Tables/11.5.md ================================================ ### Exercises 11.5-1 *** Suppose that we insert n keys into a hash table of size m using open addressing and uniform hashing. Let p(n, m) be the probability that no collisions occur. Show that p(n, m) ≤ e^(-n(n-1)/2m). (Hint: See equation (3.11).) Argue that when n exceeds sqrt(m), the probability of avoiding collisions goes rapidly to zero. ### `Answer` 下面采用数学归纳法证明p(n,m) ≤ e^((-n(n-1))/2m),1≤n≤m: 1).当n=1时,因为只有一个关键字不会有冲突,所以p(1,m)=1 ≤ e^((-1(1-1))/2m) = 1成立; 2).假设n=k时,p(k,m) ≤ e^((-k(k-1))/2m),k < m-1成立。 因为第k+1个关键字的探查序列等可能为0,1,…,m-1的所有排列中的任何一种,所以第k+1个关键字的探查序列的第一个探查位置h(k+1,0)等可能为0,1,…,m-1中的任何一个。故在前k个关键字无冲突的条件下,第k+1个关键字的探查序列的第一个探查位置,h(k+1,0)不与前面的k个关键字中的任何一个关键字冲突,那么这k+1个关键字也没有冲突。 所以: p(k+1,m) = p(k,m)*(m-k)/m ≤ e^((-k(k-1))/2m)*(1-k.m) ≤ e^((-k(k-1))/2m) * e^(-k/m) = e^((-(k+1)k)/2m) 即p(k+1,m) = e^((-(k+1)k)/2m)。 3)综上,p(n,m) ≤ e^((-n(n-1))/2m),对于1≤n≤m成立。 *** Follow [@louis1992](https://github.com/gzc) on github to help finish this task. ================================================ FILE: C11-Hash-Tables/README.md ================================================ UNSOLVED [11.3.5](./11.3.md#exercises-113-5) [11.3.6](./11.3.md#exercises-113-6) [11.5.1](./11.5.md#exercises-115-1) ================================================ FILE: C11-Hash-Tables/problem.md ================================================ ### Problems 1 : Longest-probe bound for hashing *** A hash table of size m is used to store n items, with n ≤ m/2. Open addressing is used for collision resolution. **a.**Assuming uniform hashing, show that for i=1,2,…,n, the probability is at most 2^−k that the ith insertion requires strictly more than k probes. **b.**Show that for i=1,2,…,n, the probability is O(1/n^2) that the ith insertion requires more than 2lgn probes. Let the random variable Xi denote the number of probes required by the ith insertion. You have shown in part (b) that ![](http://latex.codecogs.com/gif.latex?%0d%0a\\Pr\\{X_i%20>%202\\lg{n}\\}%20=%0d%0aO\(1/n^2\)%20) . Let the random variable ![](http://latex.codecogs.com/gif.latex?%0d%0aX%20=%20max_{1%20\\le%20i%20\\le%20n}X_i) denote the maximum number of probes required by any of the n insertions. **c.**Show that Pr{X > 2lgn}=O(1/n). **d.**Show that the expected length E[X] of the longest probe sequence is O(lgn). ### `Answer` **a.** P = (n/m)^k < (1/2)^k = 2^-k **b.** 代入a中的结论即可 **c.** ![](http://latex.codecogs.com/gif.latex?P%20=%20\\prod_{i=0}^{2\\lg{n}}\\frac{m/2-i}{m}%20<%20\\prod_{i=0}^{2\\lg{n}}%20\\frac{1}{2}%20=%20\\frac{1}{2}^{2\\lg{n}}%20=%20\\frac{1}{4}^{\\lg{n}}%20=%204^{\\lg{n^{-1}}}%20=%20O\(n^{-1}\)%20) **d.** 该题可以参考5.4.2节的关于**球与盒子**的结论和5.4.3节的关于**序列**的结论. 我们把每次的概率放大为1/2(实际上是≤ 1/2的) 所以是O(lgn) ### Problems 2 : Slot-size bound for chaining *** Suppose that we have a hash table with n slots, with collisions resolved by chaining, and suppose that n keys are inserted into the table. Each key is equally likely to be hashed to each slot. Let M be the maximum number of keys in any slot after all the keys have been inserted. Your mission is to prove an O(lg n/lg lg n) upper bound on E[M], the expected value of M. **a.** Argue that the probability Qk that exactly k keys hash to a particular slot is given by ![](http://latex.codecogs.com/gif.latex?%20Q_k%20=%20\(\\frac{1}{n}^k%20\)%20\(1-\\frac{1}{n}\)^{n-k}%20C_k^n) ### `Answer` *** Follow [@louis1992](https://github.com/gzc) on github to help finish this task. ================================================ FILE: C12-Binary-Search-Trees/12.1.md ================================================ ### Exercises 12.1-1 *** For the set of keys {1, 4, 5, 10, 16, 17, 21}, draw binary search trees of height 2, 3, 4, 5, and 6. ### `Answer` ![image](./repo/s1/1.png)1 ### Exercises 12.1-2 *** What is the difference between the binary-search-tree property and the min-heap property (see page 129)? Can the min-heap property be used to print out the keys of an n-node tree in sorted order in O(n) time? Explain how or why not. ### `Answer` 做不到,因为基于比较的排序的上限是nlgn。堆排序建堆时间是 O(n), 但是每次 拿出去后要用 lg n 的时间去调整。 It is impossible. Because the lower bound based on comparision is nlgn. Although it takes O(n) to build a heap. But each time we take the top, we have to adjust the heap whcih takes O(lgn) time. ### Exercises 12.1-3 *** Give a nonrecursive algorithm that performs an inorder tree walk. (Hint: There is an easy solution that uses a stack as an auxiliary data structure and a more complicated but elegant solution that uses no stack but assumes that two point- ers can be tested for equality.) ### `Answer` ![image](./repo/s1/2.png) ### Exercises 12.1-4 *** Give recursive algorithms that perform preorder and postorder tree walks in Θ (n) time on a tree of n nodes. ### `Answer` ![image](./repo/s1/3.png) ### Exercises 12.1-5 *** Argue that since sorting n elements takes Ω(nlgn) time in the worst case in the comparison model, any comparison-based algorithm for constructing a binary search tree from an arbitrary list of n elements takes Ω(nlgn) time in the worst case. ### `Answer` 如果下界不是nlgn, 就会产生矛盾。我们之前知道可以用 O(n) 的时间按序遍历输 出 BST 中的元素,如果基于比较的 BST 构造下界不是 n lg n, 那我们就可以利用这 个方法产生基于比较的速度优于 n lg n 的排序算法,矛盾. If it is not O(nlgn), we can find a conflict. We know it takes O(n) time to inorder print elements in a BST. If the lower bound in the worst case in the comparison model is better than O(nlgn), then we can utilize this to give a better algorithm. *** Follow [@louis1992](https://github.com/gzc) on github to help finish this task. ================================================ FILE: C12-Binary-Search-Trees/12.2.md ================================================ ### Exercises 12.2-1 *** Suppose that we have numbers between 1 and 1000 in a binary search tree and want to search for the number 363. Which of the following sequences could not be the sequence of nodes examined? ### `Answer` * 2, 252, 401, 398, 330, 344, 397, 363. * 924, 220, 911, 244, 898, 258, 362, 363. * 925, 202, 911, 240, 912, 245, 363. * 2, 399, 387, 219, 266, 382, 381, 278, 363. * 935, 278, 347, 621, 299, 392, 358, 363. ***If a sequence is a binary search tree, it must have the properties of the binary search tree, so that 's the key to this problem.*** GROUP 3 is impossible,912 > 911 GROUP 5 is impossible,347 > 299 ### Exercises 12.2-2 *** Exercises 12.2-2 Write recursive versions of the TREE-MINIMUM and TREE-MAXIMUM procedures. ### `Answer` ![image](./repo/s2/1.png) ![image](./repo/s2/2.png) ### Exercises 12.2-3 *** Write the TREE-PREDECESSOR procedure. ### `Answer` ![image](./repo/s2/3.png) ### Exercises 12.2-4 *** Professor Bunyan thinks he has discovered a remarkable property of binary search trees. Suppose that the search for key k in a binary search tree ends up in a leaf. Consider three sets: A, the keys to the left of the search path; B, the keys on the search path; and C, the keys to the right of the search path. Professor Bunyan claims that any three keys a∈A, b∈B, and c∈C must satisfy a ≤ b ≤ c. Give a smallest possible counterexample to the professor’s claim. ### `Answer` ![image](./repo/s2/4.png) ### Exercises 12.2-5 *** Show that if a node in a binary search tree has two children, then its successor has no left child and its predecessor has no right child. ### `Answer` 如果后继有左子女,那么那个左子女就是后继,所以后继不可能有左子女。同理前 继没有右子女. If a successor has left child, then the left child should be successor. The same idea for predecessor. ### Exercises 12.2-6 *** Consider a binary search tree T whose keys are distinct. Show that if the right subtree of a node x in T is empty and x has a successor y, then y is the lowest ancestor of x whose left child is also an ancestor of x. (Recall that every node is its own ancestor.) ### `Answer` If y is the lowest ancestor of x whose left child is also an ancestor of x, then x must be the node with the maximum key in the left subtree of y, since if we walk from y to x, we never walk left except at the first step. ### Exercises 12.2-7 *** inorder tree walk of an n-node binary search tree can be implemented by finding the minimum element in the tree with TREE-MINIMUM and then making n − 1 calls to TREE- SUCCESSOR. Prove that this algorithm runs in Θ(n)time. ### `Answer` 这个算法只遍历了每条边各两次,所以是 O(n) 的. This algorithm traverse each edge twice, so running time is O(n). ### Exercises 12.2-8 *** Prove that no matter what node we start at in a height-h binary search tree, k successive calls to TREE-SUCCESSOR take O(k + h) time. ### `Answer` 上一题是这题的一个实例。只需要证明访问边的次数 N ≤ (4h + 2k) ,假设 S 是开始起点,E 是结束点. * 当S和E在同一条路径时(S是E的祖先或者E是S的祖先),结论很明显。 * 当S和E不在同一条路径时,令A为S和E的最小公共祖先。考虑节点S到 A,后继函数回溯的成本至多只有 2h(思考的时候千万不要将后继到的点的 成本算进去,那部分在 2k 里),A 到 E 的多余的成本至多也只有 2h, 其他都 是正常的回溯到的点访问边 2k. ### Exercises 12.2-9 *** Let T be a binary search tree whose keys are distinct, let x be a leaf node, and let y be its parent. Show that key[y] is either the smallest key in T larger than key[x] or the largest key in T smaller than key[x]. ### `Answer` 若x是y的左叶子节点,那么x的后继是y;若x是y的右叶子节点,那么y的 后继是 x. If x is the left leaf node of y, then succeesor of x is y; else if x is the right leaf node of x, then successor of y is x. *** Follow [@louis1992](https://github.com/gzc) on github to help finish this task. ================================================ FILE: C12-Binary-Search-Trees/12.3.md ================================================ ### Exercises 12.3-1 *** Give a recursive version of the TREE-INSERT procedure. ### `Answer` ![image](./repo/s3/1.png) ### Exercises 12.3-2 *** Suppose that a binary search tree is constructed by repeatedly inserting distinct values into the tree. Argue that the number of nodes examined in searching for a value in the tree is one plus the number of nodes examined when the value was first inserted into the tree. ### `Answer` 多检查一次是否相等. We should check one more time of this node, obviously. ### Exercises 12.3-3 *** We can sort a given set of n numbers by first building a binary search tree containing these numbers (using TREE-INSERT repeatedly to insert the numbers one by one) and then printing the numbers by an inorder tree walk. What are the worst-case and best-case running times for this sorting algorithm? ### `Answer` 退化成链表就是最坏情况. If it's Degenerated into a list, then the worst situation. ### Exercises 12.3-4 Suppose that another data structure contains a pointer to a node y in a binary search tree, and suppose that y's predecessor z is deleted from the tree by the procedure TREE-DELETE. What problem can arise? How can TREE-DELETE be rewritten to solve this problem? ### `Answer` 当要删除的节点有两个子节点时,节点y会被删除. 如果指向的是节点y,那么就出问题的.在这种情况下应该修改成指向节点z. If the node being deleted has two child nodes, then y will be deleted. If the pointer point to y, there will be problem. Under such situation, the pointer should point to node z. ### Exercises 12.3-5 *** Is the operation of deletion "commutative" in the sense that deleting x and then y from a binary search tree leaves the same tree as deleting y and then x? Argue why it is or give a counterexample. ### `Answer` NO. ![image](./repo/s3/2.png) ### Exercises 12.3-6 *** When node z in TREE-DELETE has two children, we could splice out its predecessor rather than its successor. Some have argued that a fair strategy, giving equal priority to predecessor and successor, yields better empirical performance. How might TREE-DELETE be changed to implement such a fair strategy? ### `Answer` ``` Tree-Delete(T, z) if z.left = NULL Transplant(T, z, z.right) else if z.right = NULL Transplant(T, z, z.left) else y = Tree-Maximum(z.left) if y.p != z Transplant(T, y, y.left) y.left = z.left y.left.p = y Transplant(T, z, y) y.right = z.right y.right.p = y ``` * Assign each node with an attribute **height**, choose the higher one. *** Follow [@louis1992](https://github.com/gzc) on github to help finish this task. ================================================ FILE: C12-Binary-Search-Trees/12.4.md ================================================ ### Exercises 12.4-1 *** Prove equation (12.3). ### `Answer` This is basic property of permutations and combinations. You can use Pascal triangle to prove ![](http://latex.codecogs.com/gif.latex?%20%0d%0a\\mathrm{C}_n^k+\\mathrm{C}_n^{k-1}%20=%20\\mathrm{C}_{n+1}^{k}) ### Exercises 12.4-2 *** Describe a binary search tree on n nodes such that the average depth of a node in the tree is Θ(lg n) but the height of the tree is w(lg n). Give an asymptotic upper bound on the height of an n-node binary search tree in which the average depth of a node is Θ(lg n). ### `Answer` 满二叉树就满足n个节点平均高度为 Θ(lg n), 树高 lg n (w = 1) 渐进上界为 Θ(lg n) ### Exercises 12.4-3 *** Show that the notion of a randomly chosen binary search tree on n keys, where each binary search tree of n keys is equally likely to be chosen, is different from the notion of a randomly built binary search tree given in this section. ### `Answer` a randomly built binary search tree, 随机构建二叉搜索树, 指的是构建过程的随机, 也就是通过依次插入的方式构建采用的初始序列随机, 这个序列有 n! 种排列方式, 因此随机构建的过程有 n! 种不同的情况 a randomly chosen binary search tree, 随机选择二叉搜索树, 指的是对于 n 个元素构成的所有二叉树进行选择的过程随机, 由于随机构建过程的初始序列没有任何要求, 因此排列情况是 n! 种, 而二叉搜索树要求有一定的大小关系 (一个节点的左孩子关键字不大于该节点关键字, 右孩子关键字不小于该节点关键字), 这意味着 n 个元素构成的二叉搜索树的个数肯定小于等于 n! (等号只在 n = 1 时取得) ### Exercises 12.4-4 Show that the function f(x) = 2^x is convex. ### `Answer` 首先, 有 (a + b)^2 = a^2 + b^2 + 2ab >= 4ab (等号仅在 a = b时取得) 设 x1 < x2, 有 (2^x1 + 2^x2)^2 / 4 > 2^(x1 + x2) 因此, (2^x1 + 2^x2) / 2 > 2^[(x1 + x2) / 2] 因此 f(x) = 2^x 是严格凸的 ### Exercises 12.4-5 *** Consider RANDOMIZED-QUICKSORT operating on a sequence of n distinct input numbers. Prove that for any constant k > 0, all but O(1/(n^k)) of the n! input permutations yield an O(nlgn) running time. ### `Answer` 欲证明题等价于: n! 种输入排列运行时间不是 O(nlgn) 的排列个数 p(n) 一定满足 O(1/(n^k)) (对任意常数 k > 0 成立) 根据渐进上界的定义, 存在 c > 0 和 n0 > 0, 使得对所有 n >= n0, 有 0 <= p(n) <= c/(n^k) (任意常数 k > 0) 因此, p(n) 一定是常数 又因为只有当输入序列有序(最坏情况)时, 排序所需时间不为 O(nlgn), 这种排列只有常数个 *** Follow [@louis1992](https://github.com/gzc) on github to help finish this task. ================================================ FILE: C12-Binary-Search-Trees/BSTree.h ================================================ /************************************************************************* > File Name: BTREE.h > Author: Louis1992 > Mail: zhenchaogan@gmail.com > Blog: http://gzc.github.io > Created Time: Sun May 3 00:05:14 2015 ************************************************************************/ #ifndef BTREE_H_ #define BTREE_H_ #include #include using namespace std; template class BSTree { private: class BSTNode { public: BSTNode* left; BSTNode* right; type data; BSTNode():left(NULL),right(NULL) {} BSTNode(type a_data):data(a_data),left(NULL),right(NULL) {} }; typedef BSTNode* bp; bp m_root; public: BSTree():m_root(NULL) {} ~BSTree() {deleteNode(m_root);} bool isEmpty() const {return m_root == NULL;} bool find(const type& a_data) const; void insert(const type& a_data) {insertAux(m_root,a_data);} void remove(const type& a_data); void inorder(ostream& out) const {inorderAux(out, m_root);} void graph(ostream& out) const {graphAux(out, 0, m_root);} protected: void deleteNode(bp a_node); void insertAux(bp& a_subRoot, const type& a_data); void inorderAux(ostream& out, bp a_subRoot) const; void graphAux(ostream& out, int a_indent, bp a_subRoot) const; void find2(const type& a_data, bool& found, bp& a_locPtr, bp& a_parent) const; }; #endif template inline void BSTree::deleteNode(bp a_node) { if (a_node->left != NULL) deleteNode(a_node->left); else if (a_node->right != NULL) deleteNode(a_node->right); else if (a_node != NULL) { delete a_node; a_node = NULL; } } template inline void BSTree::insertAux(bp& a_subRoot, const type& a_data) { if (a_subRoot == NULL) a_subRoot = new BSTree::BSTNode(a_data); else if (a_data < a_subRoot->data) insertAux(a_subRoot->left,a_data); else if (a_subRoot->data < a_data) insertAux(a_subRoot->right,a_data); else std::cerr << "a_data already in the tree!\n"; } template inline void BSTree::inorderAux(ostream& out, BSTree::bp a_subRoot) const { if (a_subRoot != NULL) { inorderAux(out, a_subRoot->left);//L out << a_subRoot->data << " ";//V inorderAux(out, a_subRoot->right);//R } } template inline void BSTree::graphAux(ostream& out, int a_indent, bp a_subRoot) const { if (a_subRoot != NULL) { graphAux(out, a_indent+8, a_subRoot->right); //R out << setw(a_indent) << " " << a_subRoot->data << endl; //V graphAux(out, a_indent+8, a_subRoot->left); //L } } template inline bool BSTree::find(const type& a_data) const { bp locPtr = m_root; bool found = false; while (!found && locPtr != NULL) { if (a_data < locPtr->data) { locPtr = locPtr->left; } else if (locPtr->data < a_data) { locPtr = locPtr->right; } else { found = true; } } return found; } template inline void BSTree::find2(const type& a_data, bool& found, bp& a_locPtr, bp& a_parent) const { a_locPtr = m_root; a_parent = NULL; found = false; while (!found && a_locPtr != NULL) { if (a_data < a_locPtr->data) { a_parent = a_locPtr; a_locPtr = a_locPtr->left; } else if (a_locPtr->data < a_data) { a_parent = a_locPtr; a_locPtr = a_locPtr->right; } else { found = true; } } } template inline void BSTree::remove(const type& a_data) { bool found = false; bp x; //被删除的节点 bp parent; find2(a_data,found,x,parent); if (!found) { std::cerr << "a_data is not in the tree!\n"; return; } if (x->left != NULL && x->right != NULL)//节点有两个子女 { //查找x的中续后继节点及其双亲节点 bp xSucc = x->right; parent = x; while (xSucc->left != NULL) { parent = xSucc; xSucc = xSucc->left; } x->data = xSucc->data; x = xSucc; } bp subTree = x->left; if (subTree == NULL) { subTree = x->right; } if (parent == NULL) { m_root = subTree; } else if (parent->left == x) { parent->left = subTree; } else { parent->right = subTree; } delete x; } ================================================ FILE: C12-Binary-Search-Trees/main.cpp ================================================ /************************************************************************* > File Name: main.cpp > Author: Louis1992 > Mail: zhenchaogan@gmail.com > Blog: http://gzc.github.io > Created Time: Sun May 3 00:05:14 2015 ************************************************************************/ #include "BSTree.h" #include #include using namespace std; int main() { BSTree intBST; cout << "Constructing empty BST\n"; cout << "BST " << (intBST.isEmpty()?"is":"is not") << "empty\n"; int number; for (;;) { cout << "Item to insert (-999 to stop):"; cin >> number; if (number == -999) break; intBST.insert(number); } intBST.inorder(cout); cout << endl; intBST.graph(cout); //测试find for (;;) { cout << "Item to find (-999 to stop):"; cin >> number; if (number == -999) break; bool found = intBST.find(number); cout << boolalpha << found << endl; } //测试remove for (;;) { cout << "Item to remove (-999 to stop):"; cin >> number; if (number == -999) break; intBST.remove(number); cout << endl; intBST.graph(cout); cout << endl; } intBST.inorder(cout); return 0; } ================================================ FILE: C12-Binary-Search-Trees/makefile ================================================ sample : main.o g++ -o sample main.o;rm main.o main.o : main.cpp BSTree.h g++ -c main.cpp clean : rm sample main.o ================================================ FILE: C13-Red-Black-Trees/13.1.md ================================================ ### Exercises 13.1-1 *** In the style of Figure 13.1(a), draw the complete binary search tree of height 3 on the keys {1, 2, ..., 15}. Add the NIL leaves and color the nodes in three different ways such that the black-heights of the resulting red-black trees are 2, 3, and 4. ### `Answer` 因为是一颗完全二叉树,超级平衡,所以填色很容易. 感谢[psu](http://test.scripts.psu.edu/users/d/j/djh300/cmpsc465/notes-4985903869437/solutions-to-some-homework-exercises-as-shared-with-students/3-solutions-clrs-13.pdf)提供的图片 ![image](./repo/s1/1.png) ### Exercises 13.1-2 *** Draw the red-black tree that results after TREE-INSERT is called on the tree in Figure 13.1 with key 36. If the inserted node is colored red, is the resulting tree a red-black tree? What if it is colored black? ### `Answer` 插入后如果上红色,那么违反了红节点的儿子节点是黑色这个规则. ![image](./repo/s1/2.png) 插入后如果上黑色,那么违反了路径上包含相同黑节点数这个规则. ![image](./repo/s1/3.png) English: Node is Red: This is not a Rid-Black tree, because after every red node, the children must be black. Hence, a red 36 would break the tree's properties. Node is Black: This alters the Black-height of the tree. However, all the other simple paths have their old Black-height. This breaks the property of the Red-Black tree, and thus it is not a Red-Black tree. To add anything to a tree, the tree needs to be re-arranged and re-painted each time to match its properties. ### Exercises 13.1-3 *** Let us define a **relaxed red-black tree** as a binary search tree that satisfies red- black properties 1, 3, 4, and 5. In other words, the root may be either red or black. Consider a relaxed red-black tree *T* whose root is red. If we color the root of *T* black but make no other changes to *T*, is the resulting tree a red-black tree? ### `Answer` 当然还是~ ### Exercises 13.1-4 *** Suppose that we "absorb" every red node in a red-black tree into its black parent, so that the children of the red node become children of the black parent. (Ignore what happens to the keys.) What are the possible degrees of a black node after all its red children are absorbed? What can you say about the depths of the leaves of the resulting tree? ### `Answer` * 2, 如果该节点的两个子结点都是黑的. * 3, 如果仅有一个子结点是红的. * 4, 如果两个子结点都是红的. 所有的叶子节点都有相同的高度. ### Exercises 13.1-5 *** Show that the longest simple path from a node *x* in a red-black tree to a descendant leaf has length at most twice that of the shortest simple path from node x to a descendant leaf. ### `Answer` 根据性质5,最长和最短路径都有相同的黑节点数. 根据性质4可知路径上红节点数目不会超过黑节点,可得. English: For example, let's give a Red-Black Tree the black-height of 3. The shortest simple path would be: 3 black nodes in line. The longest simple path would be: 3 black nodes + 3 red nodes. Every red node must have a black child, and we give every black node a red child to maximize the red children we have on the path. In total, the lenght of the shortest possible path for black-height 2, is 3 nodes. The longest possible path is 6 nodes, which is twice, possibly less but never more, than the amount of nodes on the shortest simple path. ### Exercises 13.1-6 *** What is the largest possible number of internal nodes in a red-black tree with black-height *k*? What is the smallest possible number? ### `Answer` 假如有一颗完美二叉树,如果每个节点都是黑的. 这种情况下, 节点数最小, 是 ![](http://latex.codecogs.com/gif.latex?2^k-1) The smallest possible number of internal nodes is ![](http://latex.codecogs.com/gif.latex?2^k-1). When it's a complete binary tree with k levels with all nodes black. 假如是一黑一红交替,那么总高度就是2k-1. 这种情况下, 节点数最大, 是 ![](http://latex.codecogs.com/gif.latex?2^{2k}-1) The largest possible number of internal nodes is ![](http://latex.codecogs.com/gif.latex?2^{2k}-1). It occurs when every other node in each path is a black node. This is produced by a complete binary tree which has alternating levels of black and red nodes. Since the black height is k, the height of the tree is 2k. ### Exercises 13.1-7 *** Describe a red-black tree on *n* keys that realizes the largest possible ratio of red internal nodes to black internal nodes. What is this ratio? What tree has the smallest possible ratio, and what is the ratio? * 最大的比值是2,根节点是黑色,两个子节点是红色. * 最小的比值是0,比如只有一个黑色的根节点. *** Follow [@louis1992](https://github.com/gzc) on github to help finish this task. ================================================ FILE: C13-Red-Black-Trees/13.2.md ================================================ ### Exercises 13.2-1 *** Write pseudocode for RIGHT-ROTATE. ### `Answer` ![image](./repo/s2/1.png) 就模仿书上的left,right对调一下哈 ### Exercises 13.2-2 *** Argue that in every n-node binary search tree, there are exactly *n - 1* possible rotations. ### `Answer` 有n个节点,就有n-1对父子节点(root节点没有).对于每一个对,都有一个对应的旋转,所以是n-1. You can only rotate nodes that have children. You can do either left, right, or both. If a node has 2 children, you can do 2 possible rotations. If a node has 1 child, you can only do 1 rotation. 0 children, 0 rotations. For example, if a tree has 3 nodes, (1 root, 2 children of root), we can rotate Left and Right for the Root, and nothing for the children. You have 2 rotations, for 3 nodes. If the same tree as above, but add another child onto one of the children of the root, you'd get 2 rotations for Root, and 1 rotation for the node which got the child. 4 nodes, 3 possible rotations. We can see this follow for every addition of a new node. Thus, the amount of possible rotations is: Amount of Nodes - 1. ### Exercises 13.2-3 *** Let a, b, and c be arbitrary nodes in subtrees α, β, and γ, respectively, in the left tree of Figure 13.2. How do the depths of a, b, and c change when a left rotation is performed on node x in the figure? ### `Answer` 图上都告诉我们了. * a的深度+1 * b的深度不变 * c的深度-1 * a: +1 depth * b: +0 depth * c: -1 depth If you're curious on why this is, simply draw the two situations and you can count the path-lengths. Doesn't matter which left-or-right child a, b, c is of their respective nodes. ### Exercises 13.2-4 *** Show that any arbitrary n-node binary search tree can be transformed into any other arbitrary n-node binary search tree using O(n) rotations. (Hint: First show that at most n - 1 right rotations suffice to transform the tree into a right-going chain.) ### `Answer` 思路挺有意思的. 对于根节点左边的节点,如果有右儿子就左旋;对于根节点右边的节点,如果有左儿子就右旋;最后我们得到了一根排序好的链表. 对于另外一棵树,我们也能通过相同的方法转换为这根链表. 因此,通过逆变换就能转换回去,自然是O(n)的. ### Exercises 13.2-5 ⋆ *** We say that a binary search tree T1 can be right-converted to binary search tree T2 if it is possible to obtain T2 from T1 via a series of calls to **RIGHT-ROTATE**. Give an example of two trees T1 and T2 such that T1 cannot be right-converted to T2. Then show that if a tree T1 can be right-converted to T2, it can be right-converted using O(n^2) calls to RIGHT-ROTATE. ### `Answer` 反例很好举,T1(1,#,2),T2(2,1,#),T1无法右旋成T2. O(n^2)是这样来的. 如果T1和T2的根节点不同,那么可以通过O(n)次右旋将T1的根节点变成T2的根节点. 接下来递归调用root的右节点. 可以得到T(n) = T(n-1) + O(n). 所以是O(n^2). *** Follow [@louis1992](https://github.com/gzc) on github to help finish this task. ================================================ FILE: C13-Red-Black-Trees/13.3.md ================================================ ### Exercises 13.3-1 *** In line 16 of RB-INSERT, we set the color of the newly inserted node z to red. Notice that if we had chosen to set z's color to black, then property 4 of a red-black tree would not be violated. Why didn't we choose to set z's color to black? ### `Answer` 性质5会被破坏,黑节点多出来了. ### Exercises 13.3-2 *** Show the red-black trees that result after successively inserting the keys 41, 38, 31, 12, 19, 8 into an initially empty red-black tree. ### `Answer` 盗图感谢[psu](http://test.scripts.psu.edu/users/d/j/djh300/cmpsc465/notes-4985903869437/solutions-to-some-homework-exercises-as-shared-with-students/3-solutions-clrs-13.pdf) ![image](./repo/s3/1.png) ### Exercises 13.3-3 *** Suppose that the black-height of each of the subtrees α, β, γ, δ, ε in Figures 13.5 and 13.6 is k. Label each node in each figure with its black-height to verify that property 5 is preserved by the indicated transformation. ### `Answer` ![image](./repo/s3/2.png) ![image](./repo/s3/3.png) ### Exercises 13.3-4 *** Professor Teach is concerned that RB-INSERT-FIXUP might set color[nil[T]] to RED, in which case the test in line 1 would not cause the loop to terminate when z is the root. Show that the professor's concern is unfounded by arguing that RB-INSERT-FIXUP never sets color[nil[T]] to RED. ### `Answer` ![image](./repo/s3/4.png) 这是RB-INSERT-FIXUP的伪代码.只有第7行和第13行会修改颜色. 在第7行这个分支,z.p是红的,因为根不可能为红所以z.p.p肯定不是nil,所以这一行不会设置color[nil[T]]. 同理对于13行也是如此. ### Exercises 13.3-5 *** Consider a red-black tree formed by inserting n nodes with RB-INSERT. Argue that if n > 1, the tree has at least one red node. ### `Answer` 观察RB-INSERT-FIXUP的伪代码,一个分支会减少一个红节点,另一个分支则不变. 当n=2时,红节点 = 黑节点 = 1. 之后每次调用RB-INSERT时会插入一个红节点再调用RB-INSERT-FIXUP,如果为情况2或情况3红节点数不会减少. 而对于情况1,必有新插入的结点为红色结点。所以n>2时红色结点个数至少一个 ### Exercises 13.3-6 *** Suggest how to implement RB-INSERT efficiently if the representation for red-black trees includes no storage for parent pointers. ### `Answer` 用一个hash table去保存 =.= 或者每次插入的时候维护一个stack,这样实现起来也比较麻烦. *** Follow [@louis1992](https://github.com/gzc) on github to help finish this task. ================================================ FILE: C13-Red-Black-Trees/13.4.md ================================================ ### Exercises 13.4-1 *** Argue that after executing RB-DELETE-FIXUP, the root of the tree must be black. ### `Answer` 我们来看4种情况吧. 1. case1会被转化为case2,3,4. 2. 如果case1转变成case2退出的时候,会设置color[x] = BLACK. 否则,会继续往上递归. 3. case3会被转化成case4. 4. case4会将x置为root,循环条件不成立,color[x] = BLACK 因此,root总是黑的. ### Exercises 13.4-2 *** Argue that if in RB-DELETE both x and p[y] are red, then property 4 is restored by the call RB-DELETE-FIXUP(T, x). ### `Answer` 因为color[x] = RED,不会进入循环,第23行会直接设置color[x] = BLACK. ### Exercises 13.4-3 *** In Exercise 13.3-2, you found the red-black tree that results from successively inserting the keys 41, 38, 31, 12, 19, 8 into an initially empty tree. Now show the red-black trees that result from the successive deletion of the keys in the order 8, 12, 19, 31, 38, 41. ### `Answer` Thanks [uta](http://ranger.uta.edu/~weems/NOTES2320/HWANS/hw2ansold.pdf) for the picture. ![image](./repo/s4/1.png) ### Exercises 13.4-4 *** In which lines of the code for RB-DELETE-FIXUP might we examine or modify the sentinel nil[T]? ### `Answer` 如果y没有孩子,那么x为哨兵nil[T]. 只有第2行会检测. ### Exercises 13.4-5 *** In each of the cases of Figure 13.7, give the count of black nodes from the root of the subtree shown to each of the subtrees α, β, ..., ζ, and verify that each count remains the same after the transformation. When a node has a color attribute c or c′, use the notation count(c) or count(c′) symbolically in your count. ### `Answer` 看着4个case慢慢数 = = ### Exercises 13.4-6 *** Professors Skelton and Baron are concerned that at the start of case 1 of RB- DELETE-FIXUP, the node x:p might not be black. If the professors are correct, then lines 5–6 are wrong. Show that x:p must be black at the start of case 1, so that the professors have nothing to worry about. ### `Answer` 如果p(x)是红色的,那么w不可能是红色的,第4行就不会成立. ### Exercises 13.4-7 *** Suppose that a node x is inserted into a red-black tree with RB-INSERT and then immediately deleted with RB-DELETE. Is the resulting red-black tree the same as the initial red-black tree? Justify your answer. ### `Answer` 不一定. thanks [uta](http://ranger.uta.edu/~weems/NOTES2320/HWANS/hw2ansold.pdf) for the picture. ![image](./repo/s4/2.png) *** Follow [@louis1992](https://github.com/gzc) on github to help finish this task. ================================================ FILE: C13-Red-Black-Trees/problem.md ================================================ ### Problems 1 : Persistent dynamic sets *** a. For a general persistent binary search tree, identify the nodes that need to be changed to insert a key k or delete a node y. b. Write a procedure PERSISTENT-TREE-INSERT that, given a persistent tree T and a key k to insert, returns a new persistent tree T′ that is the result of inserting k into T. c. If the height of the persistent binary search tree T is h, what are the time and space requirements of your implementation of PERSISTENT-TREE-INSERT? (The space requirement is proportional to the number of new nodes allocated.) d. Suppose that we had included the parent field in each node. In this case, PERSISTENT-TREE-INSERT would need to perform additional copying. Prove that PERSISTENT-TREE-INSERT would then require Ω(n) time and space, where n is the number of nodes in the tree. e. Show how to use red-black trees to guarantee that the worst-case running time and space are O(lg n) per insertion or deletion. ### `Answer` a. 插入的时候,需要改变根节点到这个叶子节点路径上的所有节点. BST删除有3种情况,如果删除的是根节点,那么需要改变根节点到这个叶子节点路径上的所有节点; 如果删除的节点只有一个子节点,那么需要改变根节点到这个删除节点路径之间的所有节点; 如果删除的节点有两个子节点,那么也是需要修改被影响的路径.只有一条路径被影响,因为某个节点和其后继肯定是同一条路径的. b. 先定义两个操作. MAKE-NEW-NODE(k) 创建一个键值为k的新节点,左儿子和右儿子默认为NIL,并返回这个节点;COPY-NODE(x) 创建一个和x一模一样的节点并返回. r是根节点 ![image](./repo/p/1.png) c. O(h)的时间和空间 d. 很好理解,因为根节点变了,所以子节点要指向新的节点,必然也要创建新的子节点. e. 首先要认识到最重要的两点. 1. 根据d可知节点不能有parent属性,因此我们在RB-INSERT的时候需要用stack去保存从根到叶子节点,然后将这个stack作为参数传到RB-INSERT-FIXUP或者RB-DELETE. 2. 旋转或重新着色不会改变超过O(lgn)的节点.

INSERT : 首先和BST一样,最后插入了红节点. 有3种case,第一种节点z上升两层,修改了z的父亲节点(INSERT的时候已经创建),爷爷节点(INSERT的时候已经创建)和叔父节点(调用COPY-NODE创建并修改颜色),所以这种情况只创建了一个新节点和修改3次颜色. 而case2和case3执行后是不会再次循环的,最多旋转两次. case2和case3只对6个节点进行操作,而且这6个节点都已经创建出来,只是简单的修改指针和颜色.

DELETE : 4个case,只有case2会进入循环,最多会执行3次rotation结束. case1会创建新节点D;case2也会创建新节点D,然后节点x往上移动一层;case3会创建新节点C和D;case4会创建新节点D和E(注意,不一定会创建,如果一个case是从其他来的有些节点就已经创建好了). ### Problems 2 : Join operation on red-black trees *** a. Given a red-black tree T, we store its black-height as the field bh[T]. Argue that this field can be maintained by RB-INSERT and RB-DELETE without requiring extra storage in the nodes of the tree and without increasing the asymptotic running times. Show that while descending through T, we can determine the black-height of each node we visit in O(1) time per node visited. b. Assume that bh[T1] ≥ bh[T2]. Describe an O(lg n)-time algorithm that finds a black node y in T1 with the largest key from among those nodes whose black-height is bh[T2]. c. Let Ty be the subtree rooted at y. Describe how ![](http://latex.codecogs.com/gif.latex?%20T_y%20\\cup%20\\{x\\}%20\\cup%20T_2) can replace ![](http://latex.codecogs.com/gif.latex?%20T_y%20) in O(1) time without destroying the binary-search-tree property. d. What color should we make x so that red-black properties 1, 3, and 5 are maintained? Describe how properties 2 and 4 can be enforced in O(lg n) time. e. Argue that no generality is lost by making the assumption in part (b). Describe the symmetric situation that arises when bh[T1] = bh[T2]. f. Argue that the running time of RB-JOIN is O(lg n). ### `Answer` a. 在insert时,如果迭代回到根节点并修改了颜色,那么黑高度就+1;在delete时,如果迭代回到根节点,那么黑高度就-1;当T沿高度下降时,每遇到一个黑节点就将黑高度-1,自然是O(1)的. b. 从T1往下迭代,有右节点就走右节点;碰到黑节点黑高度就-1,一直到黑高度为bh[T2]. c. 构造子树![](http://latex.codecogs.com/gif.latex?%20T_x)以x为根,左儿子是![](http://latex.codecogs.com/gif.latex?%20T_y)右儿子是![](http://latex.codecogs.com/gif.latex?%20T_2),将x挂到y的父节点下面,并将x设为RED(保持性质5). d. RED.当y的父节点是红色时需要调整,根INSERT-FIXUP的case1类似,是O(lgn). e. 情况反一下而已~ f. 根据前面的分析,是log(n)的. ### Problems 3 : AVL trees *** An **AVL tree** is a binary search tree that is **height balanced**: for each node x, the heights of the left and right subtrees of x differ by at most 1. To implement an AVL tree, we maintain an extra field in each node: h[x] is the height of node x. As for any other binary search tree T, we assume that root[T] points to the root node. a. Prove that an AVL tree with n nodes has height O(lg n). (Hint: Prove that in an AVL tree of height h, there are at least Fh nodes, where Fh is the hth Fibonacci number.) b. To insert into an AVL tree, a node is first placed in the appropriate place in binary search tree order. After this insertion, the tree may no longer be height balanced. Specifically, the heights of the left and right children of some node may differ by 2. Describe a procedure BALANCE(x), which takes a subtree rooted at x whose left and right children are height balanced and have heights that differ by at most 2, i.e., |h[right[x]] - h[left[x]]| ≤ 2, and alters the subtree rooted at x to be height balanced. (Hint: Use rotations.) c. Using part (b), describe a recursive procedure AVL-INSERT(x, z), which takes a node x within an AVL tree and a newly created node z (whose key has already been filled in), and adds z to the subtree rooted at x, maintaining the property that x is the root of an AVL tree. As in TREE-INSERT from Section 12.3, assume that key[z] has already been filled in and that left[z] = NIL and right[z] = NIL; also assume that h[z] = 0. Thus, to insert the node z into the AVL tree T, we call AVL-INSERT(root[T], z). d. Show that AVL-INSERT,run on an n-node AVL tree,takes O(lgn) time and performs O(1) rotations. ### `Answer` a. 对于斐波那契数列有F(0) = 1, F(1) = 1, F(2) = 2,...,F(n) = F(n-1)+F(n-2).
设T(n)为高度h的AVL树的最少节点数. 我们尝试证明 ![](http://latex.codecogs.com/gif.latex?T\(n\)\\ge%20F\(n\)) .
一开始,有![](http://latex.codecogs.com/gif.latex?%20T\(1\)\\ge%20F\(1\)) 和![](http://latex.codecogs.com/gif.latex?%20T\(2\)\\ge%20F\(2\))
![](http://latex.codecogs.com/gif.latex?%20T\(n\)\\ge%20T\(n-1\)%20+%20T\(n-2\)%20+%201%20\\\\%20%20~\\hspace{15%20mm}%20\\ge%20F\(n-1\)%20+%20F\(n-2\)%20+%201%20\\\\%20~\\hspace{15%20mm}%0d%0a%20\>%20F\(n\)%20%0d%0a)
并且有![](http://latex.codecogs.com/gif.latex?%202^n%20\\le%20F\(n\)%20\\le%201.6^n), 因此![](http://latex.codecogs.com/gif.latex?%20T\(n\)%20=%20O\(%20%20\\lg\(n\)%20%20%20\)%20). b. thanks [mit](http://courses.csail.mit.edu/6.046/spring04/handouts/ps5-sol.pdf) for this picture. 只画出了右边大于左边的情况. ![image](./repo/p/2.png) ![image](./repo/p/3.png) c.
![image](./repo/p/4.png) d. 因为AVL树的高度是lg(h),所以AVL-INSERT需要P(lgn)的时间.因为BALANCE操作会将不平衡的子树的高度减掉1,所以不会影响到其他地方. ### Problems 4 : Treaps *** a. Show that given a set of nodes x1, x2, ..., xn, with associated keys and priorities (all distinct), there is a unique treap associated with these nodes. b. Show that the expected height of a treap is Θ(lg n), and hence the time to search for a value in the treap is Θ(lg n). c. Explain how TREAP-INSERT works. Explain the idea in English and give pseudocode. (Hint: Execute the usual binary-search-tree insertion procedure and then perform rotations to restore the min-heap order property.) d. Show that the expected running time of TREAP-INSERT is Θ(lg n). e. Consider the treap T immediately after x is inserted using TREAP-INSERT. Let C be the length of the right spine of the left subtree of x. Let D be the length of the left spine of the right subtree of x. Prove that the total number of rotations that were performed during the insertion of x is equal to C + D. ### `Answer` a. Treap会将结点以它们的优先级的顺序插入一颗正常的二叉树. 假如n-1个节点的Treap是确定的,因为INSERT算法是确定性算法,可以得到n个节点的Treap也是确定的. b. 采取随机分配优先级的方法和随机构造的BST是等价的,因此是O(lg n)的. c.
![image](./repo/p/5.png) d. 比较明显就不证明了. e. 每一次旋转, C+D都会增加1;画张图出来会很明显的. 这题剩下的[答案](http://www2.myoops.org/twocw/mit/NR/rdonlyres/Electrical-Engineering-and-Computer-Science/6-046JFall-2005/760259F8-457D-4895-AFDC-8CE9C73D18A5/0/ps4sol.pdf) *** Follow [@louis1992](https://github.com/gzc) on github to help finish this task ================================================ FILE: C13-Red-Black-Trees/rbtree.c ================================================ /************************************************************************* > File Name: rbtree.c > Author: Louis1992 > Mail: zhenchaogan@gmail.com > Blog: http://gzc.github.io > Created Time: Sat May 30 22:30:29 2015 ************************************************************************/ #include #include #include typedef int key_t; typedef int data_t; typedef enum color_t { RED= 0, BLACK=1 }color_t; typedef struct rb_node_t { struct rb_node_t *left, *right, *parent; key_t key; data_t data; color_t color; }rb_node_t; /* forward declaration */ rb_node_t* rb_insert(key_t key, data_t data, rb_node_t* root); rb_node_t* rb_search(key_t key, rb_node_t* root); rb_node_t* rb_erase(key_t key, rb_node_t* root); rb_node_t* rb_insert_rebalance(rb_node_t *node, rb_node_t *root); rb_node_t* rb_erase_rebalance(rb_node_t *node, rb_node_t *parent, rb_node_t *root); //八、测试用例 //主函数 int main() { int i, count = 100; key_t key; rb_node_t* root = NULL, *node = NULL; srand(time(NULL)); for (i = 1; i < count; ++i) { key = rand() % count; if ((root = rb_insert(key, i, root))) { printf("[i = %d] insert key %d success!\n", i, key); } else { printf("[i = %d] insert key %d error!\n", i, key); exit(-1); } if ((node = rb_search(key, root))) { printf("[i = %d] search key %d success!\n", i, key); } else { printf("[i = %d] search key %d error!\n", i, key); exit(-1); } if (!(i % 10)) { if ((root = rb_erase(key, root))) { printf("[i = %d] erase key %d success\n", i, key); } else { printf("[i = %d] erase key %d error\n", i, key); } } } return 0; } rb_node_t* rb_new_node(key_t key, data_t data) { rb_node_t *node = (rb_node_t*)malloc(sizeof(struct rb_node_t)); if (!node) { printf("malloc error!\n"); exit(-1); } node->key = key, node->data = data; return node; } //一、左旋代码分析 /*----------------------------------------------------------- | node right | / \ ==> / \ | a right node y | / \ / \ | b y a b //左旋 -----------------------------------------------------------*/ rb_node_t* rb_rotate_left(rb_node_t* node, rb_node_t* root) { rb_node_t* right = node->right; //指定指针指向 right<--node->right if ((node->right = right->left)) { right->left->parent = node; //好比上面的注释图,node成为b的父母 } right->left = node; //node成为right的左孩子 if ((right->parent = node->parent)) { if (node == node->parent->right) { node->parent->right = right; } else { node->parent->left = right; } } else { root = right; } node->parent = right; //right成为node的父母 return root; } //二、右旋 /*----------------------------------------------------------- | node left | / \ / \ | left y ==> a node | / \ / \ | a b b y //右旋与左旋差不多,分析略过 -----------------------------------------------------------*/ rb_node_t* rb_rotate_right(rb_node_t* node, rb_node_t* root) { rb_node_t* left = node->left; if ((node->left = left->right)) { left->right->parent = node; } left->right = node; if ((left->parent = node->parent)) { if (node == node->parent->right) { node->parent->right = left; } else { node->parent->left = left; } } else { root = left; } node->parent = left; return root; } //三、红黑树查找结点 //---------------------------------------------------- //rb_search_auxiliary:查找 //rb_node_t* rb_search:返回找到的结点 //---------------------------------------------------- rb_node_t* rb_search_auxiliary(key_t key, rb_node_t* root, rb_node_t** save) { rb_node_t *node = root, *parent = NULL; int ret; while (node) { parent = node; ret = node->key - key; if (0 < ret) { node = node->left; } else if (0 > ret) { node = node->right; } else { return node; } } if (save) { *save = parent; } return NULL; } //返回上述rb_search_auxiliary查找结果 rb_node_t* rb_search(key_t key, rb_node_t* root) { return rb_search_auxiliary(key, root, NULL); } //四、红黑树的插入 //--------------------------------------------------------- //红黑树的插入结点 rb_node_t* rb_insert(key_t key, data_t data, rb_node_t* root) { rb_node_t *parent = NULL, *node; parent = NULL; if ((node = rb_search_auxiliary(key, root, &parent))) //调用rb_search_auxiliary找到插入结点的地方 { return root; } node = rb_new_node(key, data); //分配结点 node->parent = parent; node->left = node->right = NULL; node->color = RED; if (parent) { if (parent->key > key) { parent->left = node; } else { parent->right = node; } } else { root = node; } return rb_insert_rebalance(node, root); //插入结点后,调用rb_insert_rebalance修复红黑树的性质 } //五、红黑树的3种插入情况 //接下来,咱们重点分析针对红黑树插入的3种情况,而进行的修复工作。 //-------------------------------------------------------------- //红黑树修复插入的3种情况 //为了在下面的注释中表示方便,也为了让下述代码与我的倆篇文章相对应, //用z表示当前结点,p[z]表示父母、p[p[z]]表示祖父、y表示叔叔。 //-------------------------------------------------------------- rb_node_t* rb_insert_rebalance(rb_node_t *node, rb_node_t *root) { rb_node_t *parent, *gparent, *uncle, *tmp; //父母p[z]、祖父p[p[z]]、叔叔y、临时结点*tmp while ((parent = node->parent) && parent->color == RED) { //parent 为node的父母,且当父母的颜色为红时 gparent = parent->parent; //gparent为祖父 if (parent == gparent->left) //当祖父的左孩子即为父母时。 //其实上述几行语句,无非就是理顺孩子、父母、祖父的关系。:D。 { uncle = gparent->right; //定义叔叔的概念,叔叔y就是父母的右孩子。 if (uncle && uncle->color == RED) //情况1:z的叔叔y是红色的 { uncle->color = BLACK; //将叔叔结点y着为黑色 parent->color = BLACK; //z的父母p[z]也着为黑色。解决z,p[z]都是红色的问题。 gparent->color = RED; node = gparent; //将祖父当做新增结点z,指针z上移俩层,且着为红色。 //上述情况1中,只考虑了z作为父母的右孩子的情况。 } else //情况2:z的叔叔y是黑色的, { if (parent->right == node) //且z为右孩子 { root = rb_rotate_left(parent, root); //左旋[结点z,与父母结点] tmp = parent; parent = node; node = tmp; //parent与node 互换角色 } //情况3:z的叔叔y是黑色的,此时z成为了左孩子。 //注意,1:情况3是由上述情况2变化而来的。 //......2:z的叔叔总是黑色的,否则就是情况1了。 parent->color = BLACK; //z的父母p[z]着为黑色 gparent->color = RED; //原祖父结点着为红色 root = rb_rotate_right(gparent, root); //右旋[结点z,与祖父结点] } } else { //这部分是特别为情况1中,z作为左孩子情况,而写的。 uncle = gparent->left; //祖父的左孩子作为叔叔结点。[原理还是与上部分一样的] if (uncle && uncle->color == RED) //情况1:z的叔叔y是红色的 { uncle->color = BLACK; parent->color = BLACK; gparent->color = RED; node = gparent; //同上。 } else //情况2:z的叔叔y是黑色的, { if (parent->left == node) //且z为左孩子 { root = rb_rotate_right(parent, root); //以结点parent、root右旋 tmp = parent; parent = node; node = tmp; //parent与node 互换角色 } //经过情况2的变化,成为了情况3. parent->color = BLACK; gparent->color = RED; root = rb_rotate_left(gparent, root); //以结点gparent和root左旋 } } } root->color = BLACK; //根结点,不论怎样,都得置为黑色。 return root; //返回根结点。 } //六、红黑树的删除 //------------------------------------------------------------ //红黑树的删除结点 rb_node_t* rb_erase(key_t key, rb_node_t *root) { rb_node_t *child, *parent, *old, *left, *node; color_t color; if (!(node = rb_search_auxiliary(key, root, NULL))) //调用rb_search_auxiliary查找要删除的结点 { printf("key %d is not exist!\n"); return root; } old = node; if (node->left && node->right) { node = node->right; while ((left = node->left) != NULL) { node = left; } child = node->right; parent = node->parent; color = node->color; if (child) { child->parent = parent; } if (parent) { if (parent->left == node) { parent->left = child; } else { parent->right = child; } } else { root = child; } if (node->parent == old) { parent = node; } node->parent = old->parent; node->color = old->color; node->right = old->right; node->left = old->left; if (old->parent) { if (old->parent->left == old) { old->parent->left = node; } else { old->parent->right = node; } } else { root = node; } old->left->parent = node; if (old->right) { old->right->parent = node; } } else { if (!node->left) { child = node->right; } else if (!node->right) { child = node->left; } parent = node->parent; color = node->color; if (child) { child->parent = parent; } if (parent) { if (parent->left == node) { parent->left = child; } else { parent->right = child; } } else { root = child; } } free(old); if (color == BLACK) { root = rb_erase_rebalance(child, parent, root); //调用rb_erase_rebalance来恢复红黑树性质 } return root; } //七、红黑树的4种删除情况 //---------------------------------------------------------------- //红黑树修复删除的4种情况 //为了表示下述注释的方便,也为了让下述代码与我的倆篇文章相对应, //x表示要删除的结点,*other、w表示兄弟结点, //---------------------------------------------------------------- rb_node_t* rb_erase_rebalance(rb_node_t *node, rb_node_t *parent, rb_node_t *root) { rb_node_t *other, *o_left, *o_right; //x的兄弟*other,兄弟左孩子*o_left,*o_right while ((!node || node->color == BLACK) && node != root) { if (parent->left == node) { other = parent->right; if (other->color == RED) //情况1:x的兄弟w是红色的 { other->color = BLACK; parent->color = RED; //上俩行,改变颜色,w->黑、p[x]->红。 root = rb_rotate_left(parent, root); //再对p[x]做一次左旋 other = parent->right; //x的新兄弟new w 是旋转之前w的某个孩子。其实就是左旋后的效果。 } if ((!other->left || other->left->color == BLACK) && (!other->right || other->right->color == BLACK)) //情况2:x的兄弟w是黑色,且w的俩个孩子也都是黑色的 { //由于w和w的俩个孩子都是黑色的,则在x和w上得去掉一黑色, other->color = RED; //于是,兄弟w变为红色。 node = parent; //p[x]为新结点x parent = node->parent; //x<-p[x] } else //情况3:x的兄弟w是黑色的, { //且,w的左孩子是红色,右孩子为黑色。 if (!other->right || other->right->color == BLACK) { if ((o_left = other->left)) //w和其左孩子left[w],颜色交换。 { o_left->color = BLACK; //w的左孩子变为由黑->红色 } other->color = RED; //w由黑->红 root = rb_rotate_right(other, root); //再对w进行右旋,从而红黑性质恢复。 other = parent->right; //变化后的,父结点的右孩子,作为新的兄弟结点w。 } //情况4:x的兄弟w是黑色的 other->color = parent->color; //把兄弟节点染成当前节点父节点的颜色。 parent->color = BLACK; //把当前节点父节点染成黑色 if (other->right) //且w的右孩子是红 { other->right->color = BLACK; //兄弟节点w右孩子染成黑色 } root = rb_rotate_left(parent, root); //并再做一次左旋 node = root; //并把x置为根。 break; } } //下述情况与上述情况,原理一致。分析略。 else { other = parent->left; if (other->color == RED) { other->color = BLACK; parent->color = RED; root = rb_rotate_right(parent, root); other = parent->left; } if ((!other->left || other->left->color == BLACK) && (!other->right || other->right->color == BLACK)) { other->color = RED; node = parent; parent = node->parent; } else { if (!other->left || other->left->color == BLACK) { if ((o_right = other->right)) { o_right->color = BLACK; } other->color = RED; root = rb_rotate_left(other, root); other = parent->left; } other->color = parent->color; parent->color = BLACK; if (other->left) { other->left->color = BLACK; } root = rb_rotate_right(parent, root); node = root; break; } } } if (node) { node->color = BLACK; //最后将node[上述步骤置为了根结点],改为黑色。 } return root; //返回root } ================================================ FILE: C13-Red-Black-Trees/rbtree.cpp ================================================ /************************************************************************* > File Name: rbtree.cpp > Author: Louis1992 > Mail: zhenchaogan@gmail.com > Blog: http://gzc.github.io > Created Time: Sun May 31 22:11:43 2015 ************************************************************************/ #include #include using namespace std; static const int RED = 0; static const int BLACK = 1; template class RedBlackTreeNode { public: RedBlackTreeNode():key(T()),parent(NULL),left(NULL),right(NULL),color(BLACK){} T key; RedBlackTreeNode* parent; RedBlackTreeNode* left; RedBlackTreeNode* right; int color; }; template class RedBlackTree { public: RedBlackTree(); int search_element(const T& k) const; int get_minmum(T& retmin)const; int get_maxmum(T& retmax)const; int get_successor(const T& k,T& ret) const; int get_predecessor(const T& k,T& ret) const; int insert_key(const T& k); int delete_key(const T& k); void inorder_tree_walk()const; RedBlackTreeNode* get_root() const; ~RedBlackTree(); private: RedBlackTreeNode* root; static RedBlackTreeNode *NIL; RedBlackTreeNode* get_parent(RedBlackTreeNode* pnode) const; RedBlackTreeNode* get_left(RedBlackTreeNode* pnode) const; RedBlackTreeNode* get_right(RedBlackTreeNode* pnode) const; T get_key(RedBlackTreeNode* pnode) const; int get_color(RedBlackTreeNode* pnode) const; void set_color(RedBlackTreeNode* pnode,int color); void left_rotate(RedBlackTreeNode *pnode); void right_rotate(RedBlackTreeNode *pnode); void rb_insert_fixup(RedBlackTreeNode *pnode); void rb_delete_fixup(RedBlackTreeNode *pnode); RedBlackTreeNode* get_maxmum(RedBlackTreeNode *root) const; RedBlackTreeNode* get_minmum(RedBlackTreeNode *root) const; RedBlackTreeNode* get_successor(RedBlackTreeNode *pnode) const; RedBlackTreeNode* get_predecessor(RedBlackTreeNode *pnode) const; RedBlackTreeNode* search_tree_node(const T& k)const; void make_empty(RedBlackTreeNode* root); }; template RedBlackTreeNode* RedBlackTree::NIL = new RedBlackTreeNode; template RedBlackTree::RedBlackTree() { root = NULL; } template int RedBlackTree::search_element(const T& k) const { return (NIL != search_tree_node(k)); } template int RedBlackTree::get_minmum(T& retmin)const { if(root) { retmin = get_minmum(root)->key; return 0; } return -1; } template int RedBlackTree::get_maxmum(T& retmax)const { if(root) { retmax = get_maxmum(root)->key; return 0; } return -1; } template int RedBlackTree::get_successor(const T& k,T& ret) const { RedBlackTreeNode* pnode = search_tree_node(k); if(pnode != NIL) { pnode = get_successor(pnode); if(pnode != NIL) { ret = pnode->key; return 0; } return -1; } return -1; } template int RedBlackTree::get_predecessor(const T& k,T& ret) const { RedBlackTreeNode* pnode = search_tree_node(k); if(pnode != NIL) { pnode = get_predecessor(pnode); if(pnode != NIL) { ret = pnode->key; return 0; } return -1; } return -1; } template int RedBlackTree::insert_key(const T& k) { RedBlackTreeNode *newnode = new RedBlackTreeNode; newnode->key = k; newnode->color = RED; newnode->left = NIL; newnode->right = NIL; newnode->parent = NIL; if(NULL == root) root = newnode; else { RedBlackTreeNode* pnode = root; RedBlackTreeNode* qnode; while(pnode != NIL) { qnode = pnode; if(pnode->key > newnode->key) pnode = pnode->left; else pnode = pnode->right; } newnode->parent = qnode; if(qnode->key > newnode->key) qnode->left = newnode; else qnode->right = newnode; } rb_insert_fixup(newnode); return 0; } template int RedBlackTree::delete_key(const T& k) { RedBlackTreeNode* pnode = search_tree_node(k); if(NIL != pnode) { RedBlackTreeNode* qnode,*tnode; if(get_left(pnode) == NIL || get_right(pnode) == NIL) qnode = pnode; else qnode = get_successor(pnode); if(get_left(qnode) != NIL) tnode = get_left(qnode); else tnode = get_right(qnode); tnode->parent = get_parent(qnode); if(get_parent(qnode) == NIL) root = tnode; else if(qnode == get_left(get_parent(qnode))) qnode->parent->left = tnode; else qnode->parent->right = tnode; if(qnode != pnode) pnode->key = get_key(qnode); if(get_color(qnode) == BLACK) rb_delete_fixup(tnode); delete qnode; return 0; } return -1; } template RedBlackTree::~RedBlackTree() { make_empty(root); } template RedBlackTreeNode* RedBlackTree:: get_root() const { return root; } template RedBlackTreeNode* RedBlackTree::get_parent(RedBlackTreeNode* pnode) const { return pnode->parent; } template RedBlackTreeNode* RedBlackTree::get_left(RedBlackTreeNode* pnode) const { return pnode->left; } template RedBlackTreeNode* RedBlackTree::get_right(RedBlackTreeNode* pnode) const { return pnode->right; } template T RedBlackTree::get_key(RedBlackTreeNode* pnode) const { return pnode->key; } template int RedBlackTree::get_color(RedBlackTreeNode* pnode) const { return pnode->color; } template void RedBlackTree::set_color(RedBlackTreeNode* pnode,int color) { pnode->color = color; } template void RedBlackTree::left_rotate(RedBlackTreeNode *pnode) { RedBlackTreeNode* rightnode = pnode->right; pnode->right = rightnode->left; if(rightnode->left != NIL) rightnode->left->parent = pnode; rightnode->parent = pnode->parent; if(pnode->parent == NIL) root = rightnode; else if(pnode == pnode->parent->left) pnode->parent->left = rightnode; else pnode->parent->right = rightnode; rightnode->left = pnode; pnode->parent = rightnode; } template void RedBlackTree::right_rotate(RedBlackTreeNode *pnode) { RedBlackTreeNode* leftnode = pnode->left; pnode->left = leftnode->right; if(leftnode->right != NIL) leftnode->right->parent = pnode; leftnode->parent = pnode->parent; if(pnode->parent == NIL) root = leftnode; else if(pnode == pnode->parent->left) pnode->parent->left = leftnode; else pnode->parent->right = leftnode; leftnode->right = pnode; pnode->parent = leftnode; } template void RedBlackTree::rb_insert_fixup(RedBlackTreeNode*pnode) { RedBlackTreeNode *qnode,*tnode; //当pnode的父节点为红色时,破坏性质4 while(get_color(get_parent(pnode))== RED) { qnode = get_parent(get_parent(pnode));//祖父结点 if(get_parent(pnode) == get_left(qnode)) { tnode = get_right(qnode);//pnode的叔叔结点 if(get_color(tnode) == RED) //case1 叔叔结点为红色 { set_color(get_parent(pnode),BLACK); set_color(tnode,BLACK); set_color(qnode,RED); pnode = qnode; } else //case 2 or case 3 { if(pnode == get_right(get_parent(pnode))) //case2 pnode为右孩子 { pnode = get_parent(pnode); left_rotate(pnode); } //case3 pnode为左孩子 set_color(get_parent(pnode),BLACK); qnode = get_parent(get_parent(pnode)); set_color(qnode,RED); right_rotate(qnode); } } else { tnode = get_left(qnode); if(get_color(tnode) == RED) { set_color(get_parent(pnode),BLACK); set_color(tnode,BLACK); set_color(qnode,RED); pnode = qnode; } else { if(pnode == get_left(get_parent(pnode))) { pnode = get_parent(pnode); right_rotate(pnode); } set_color(get_parent(pnode),BLACK); qnode = get_parent(get_parent(pnode)); set_color(qnode,RED); left_rotate(qnode); } } } set_color(root,BLACK); } template void RedBlackTree::rb_delete_fixup(RedBlackTreeNode *pnode) { while(pnode != root && get_color(pnode) == BLACK) { RedBlackTreeNode *qnode,*tnode; if(pnode == get_left(get_parent(pnode))) { qnode = get_right(get_parent(pnode)); if(get_color(qnode) == RED) { set_color(qnode,BLACK); set_color(get_parent(pnode),RED); left_rotate(get_parent(pnode)); qnode = get_right(get_parent(pnode)); } if(get_color(get_left(qnode)) == BLACK && get_color(get_right(qnode)) == BLACK) { set_color(qnode,RED); pnode = get_parent(pnode); } else { if(get_color(get_right(qnode)) == BLACK) { set_color(get_left(qnode),BLACK); set_color(qnode,RED); right_rotate(qnode); qnode = get_right(get_parent(pnode)); } set_color(qnode,get_color(get_parent(pnode))); set_color(get_parent(pnode),BLACK); set_color(get_right(qnode),BLACK); left_rotate(get_parent(pnode)); pnode = root; } } else { qnode = get_left(get_parent(pnode)); if(get_color(qnode) == RED) { set_color(qnode,BLACK); set_color(get_parent(pnode),RED); right_rotate(get_parent(pnode)); qnode = get_left(get_parent(pnode)); } if(get_color(get_right(qnode)) == BLACK && get_color(get_left(qnode)) == BLACK) { set_color(qnode,RED); pnode = get_parent(pnode); } else { if(get_color(get_left(qnode)) == BLACK) { set_color(get_right(qnode),BLACK); set_color(qnode,RED); left_rotate(qnode); qnode = get_left(get_parent(pnode)); } set_color(qnode,get_color(get_parent(pnode))); set_color(get_parent(pnode),BLACK); set_color(get_left(qnode),BLACK); right_rotate(get_parent(pnode)); pnode = root; } } } set_color(pnode,BLACK); } template RedBlackTreeNode* RedBlackTree::get_maxmum(RedBlackTreeNode *root) const { RedBlackTreeNode *pnode = root; while(pnode->right != NIL) pnode = pnode->right; return pnode; } template RedBlackTreeNode* RedBlackTree::get_minmum(RedBlackTreeNode *root) const { RedBlackTreeNode *pnode = root; while(pnode->left != NIL) pnode = pnode->left; return pnode; } template RedBlackTreeNode* RedBlackTree:: get_successor(RedBlackTreeNode *pnode) const { if(pnode->right != NIL) return get_minmum(pnode->right); RedBlackTreeNode* parentnode = get_parent(pnode); while(parentnode != NIL && get_right(parentnode) == pnode) { pnode = parentnode; parentnode = get_parent(pnode); } return parentnode; } template RedBlackTreeNode* RedBlackTree::get_predecessor(RedBlackTreeNode *pnode) const { if(pnode->left != NIL) return get_maxmum(pnode->left); RedBlackTreeNode* parentnode = get_parent(pnode); while(parentnode != NIL && get_left(parentnode) == pnode) { pnode = parentnode; parentnode = get_parent(pnode); } return parentnode; } template RedBlackTreeNode* RedBlackTree:: search_tree_node(const T& k)const { RedBlackTreeNode* pnode = root; while(pnode != NIL) { if(pnode->key == k) break; else if(pnode->key > k) pnode = pnode->left; else pnode = pnode->right; } return pnode; } template void RedBlackTree::make_empty(RedBlackTreeNode* root) { if(root) { RedBlackTreeNode *pleft = root->left; RedBlackTreeNode* pright = root->right; delete root; if(pleft != NIL) make_empty(pleft); if(pright != NIL) make_empty(pright); } } template void RedBlackTree::inorder_tree_walk()const { if(NULL != root) { stack* > s; RedBlackTreeNode *ptmpnode; ptmpnode = root; while(NIL != ptmpnode || !s.empty()) { if(NIL != ptmpnode) { s.push(ptmpnode); ptmpnode = ptmpnode->left; } else { ptmpnode = s.top(); s.pop(); cout<key<<":"; if(ptmpnode->color == BLACK) cout<<"Black"<right; } } } } int main() { RedBlackTree rbtree; int value; rbtree.insert_key(41); rbtree.insert_key(38); rbtree.insert_key(31); rbtree.insert_key(12); rbtree.insert_key(19); rbtree.insert_key(8); cout<<"root is: "<key<key< k return OS-KEY-RANK(left[T],k) else return r + OS-KEY-RANK(right[T],k) ### Exercises 14.1-5 *** Given an element x in an n-node order-statistic tree and a natural number i, how can the ith successor of x in the linear order of the tree be determined in O(lg n) time? ### `Answer` i-SUCCESSOR(x, i)
1.y <-- OS-RANK(T,x)
2.r <-- OS-SELECT(T,y + i)
3.return r ### Exercises 14.1-6 *** Observe that whenever the size field of a node is referenced in either OS-SELECT or OS- RANK, it is used only to compute the rank of the node in the subtree rooted at that node. Accordingly, suppose we store in each node its rank in the subtree of which it is the root. Show how this information can be maintained during insertion and deletion. (Remember that these two operations can cause rotations.) ### `Answer` **插入** 遍历所有的节点,根据节点值与插入节点值的大小,遍历到的节点的rank分别是增1和减1.(O(n),所以记录rank值效率很低) 旋转操作不影响rank价值. **删除** 删除操作跟插入类似,也需要遍历所有的节点更新rank值. ### Exercises 14.1-7 *** Show how to use an order-statistic tree to count the number of inversions (see Problem 2-4) in an array of size n in time O(n lg n). ### `Answer` 注意,红黑树建树的时间是O(nlgn),因此我们需要边建树边计算inversions. 而这个过程相当简单,因为每次INSERT的时候,我们都能利用OS-RANK计算出该节点的rank,从而计算出inversion. ### Exercises 14.1-8 *** Consider n chords on a circle, each defined by its endpoints. Describe an O(n lg n)-time algorithm for determining the number of pairs of chords that intersect inside the circle. (For example, if the n chords are all diameters that meet at the center, then the correct answer is .) Assume that no two chords share an endpoint. ### `Answer` [reference](http://bbs.csdn.net/topics/280085502) n条弦共有2n个端点,每个端点对于圆心来说,都对应一个[0,2pi)内的角度. 我们按角度大小(实际上就是逆时针方向)对这2n个角度进行排序,这一步需要花费O(n*logn) 对于每条弦来说,它的两个端点都可以看做是“事件点”:从0角度开始逆时针在圆上扫描,遇到弦的第一个点可以看成是弦的“起点”,遇到的第二个点看成是弦的“终点”. 然后,我们用一棵“顺序统计树”来辅助进行处理(初始化当然为空). 按照角度小到大的顺序遍历这2n个端点: 如果该端点是某条弦X的“起点” 将弦X插入顺序统计树中(以X的“起点”角度作为key); 如果该端点是某条弦X的“终点” 统计出目前这棵树中有多少条弦的“起点”角度比X的“起点”角度大,这就是与X相交的弦的数量; //对于顺序统计树来说,上面这步只要O(logn)就能实现 将弦X从顺序统计树中删除; //这一步同样只需要O(logn) 所以整个下来,O(n*logn)内可以完成。 *** Follow [@louis1992](https://github.com/gzc) on github to help finish this task. ================================================ FILE: C14-Augmenting-Data-Structures/14.2.md ================================================ some English answers come from [here](http://ripcrixalis.blog.com/2011/02/08/clrs-14-2-how-to-augment-a-data-structure/) ### Exercises 14.2-1 *** Show how the dynamic-set queries MINIMUM, MAXIMUM, SUCCESSOR, and PREDECESSOR can each be supported in O(1) worst-case time on an augmented order- statistic tree. The asymptotic performance of other operations on order-statistic trees should not be affected. (Hint: Add pointers to nodes.) ### `Answer` MINIMUM : 用一个指针指向最小的元素,每次插入的时候跟最小元素比较看是否需要更新,如果删除的是最小元素,那么MINIMUM更新为原来元素的SUCCESSOR MAXIMUM : 同MINIMUM类似 SUCCESSOR : 给每个节点增加一个successor指针.左旋和右旋不改变successor属性.在插入的时候,如果最终插入到x的左边,那么 temp = x->predecessor temp->successor = newnode x->predecessor = newnode newnode->predecessor = temp newnode->successor = x PREDECESSOR : 跟SUCCESSOR类似 ### Exercises 14.2-2 *** Can the black-heights of nodes in a red-black tree be maintained as fields in the nodes of the tree without affecting the asymptotic performance of any of the red-black tree operations? Show how, or argue why not. ### `Answer` 可以. 因为插入和删除影响的基本只有根节点到叶子节点路径上的点(包括每个节点的一些"邻居"),总共影响的是O(lgn)的节点,因此是可以维护的. 具体操作就要根据书中几个不同的case去处理叻. Yes, we can maintain black-heights as attributes in the nodes of a red-black tree without affecting the asymptotic performance of the red-black tree operations. Because the black-height of a node can be computed from the information at the node and its two children (actually one). According to Theorem 14.1 (page 309 of CLRS) insertion and deletion can be still performed in O(lg n) time. ### Exercises 14.2-3 *** Can the depths of nodes in a red-black tree be efficiently maintained as fields in the nodes of the tree? Show how, or argue why not. ### `Answer` No, because the depth of a node depends on the depth of its parent. When the depth of a node changes, the depths of the whole nodes in the subtree rooted at that node must be updated. It may lead to run in more than O(lg n) time in worse case. ### Exercises 14.2-4 *** Let ⊗ be an associative binary operator, and let a be a field maintained in each node of a red- black tree. Suppose that we want to include in each node x an additional field f such that f[x] = a[x1] ⊗ a[x2] ⊗ ··· ⊗ a[xm], where x1, x2,..., xm is the inorder listing of nodes in the subtree rooted at x. Show that the f fields can be properly updated in O(1) time after a rotation. Modify your argument slightly to show that the size fields in order-statistic trees can be maintained in O(1) time per rotation. ### `Answer` 1. 证明f fields 可以在O(1)时间内更新 设x的左右儿子分别为x.left和x.right,且x的左子树包含k个节点。 因为红黑树是排序二叉树,且在f的定义中,这些节点是按照中序遍历的顺序排列, 所以前k个节点属于x的左子树,后面的节点属于x的右子树, 所以有: f[x.left] = a[x1] ⊗ a[x2] ⊗ ··· ⊗ a[xk] f[x.right] = a[x(k+1)] ⊗ ··· ⊗ a[xm] 又因为f满足结合律(associative binary operator), 可得:f[x] = f[x.left] ⊗ a[x] ⊗ f[x.right] 故f可在O(1)内更新。 2. 通过f,证明size fields可以在O(1)时间内更新 令每个节点的a field都等于1,运算⊗ 为加法,则f即为每个节点的size属性,由以上证明可知,size可以在O(1)内更新 ### Exercises 14.2-5 *** We wish to augment red-black trees with an operation RB-ENUMERATE(x, a, b) that outputs all the keys k such that a ≤ k ≤ b in a red-black tree rooted at x. Describe how RB- ENUMERATE can be implemented in Θ(m +lg n) time, where m is the number of keys that are output and n is the number of internal nodes in the tree. (Hint: There is no need to add new fields to the red-black tree.) ### `Answer` RB-ENUMERATE(x, a, b) start <- TREE-SEARCH(x, a) //该方法返回不大于a的最紧下解,时间复杂度O(lgn) if start < a start <- successor[start] while start < b //O(m) 次循环 print start start <- SUCC[start] 根据[练习12.2-8](https://github.com/gzc/CLRS/blob/master/C12-Binary-Search-Trees/12.2.md#exercises-122-8)的结论,查找m个下继的时间为O(m+lgn) 因此总的时间复杂度O(m+lgn) We first call TREE-SEARCH(x, a). We must modify this procedure a bit to return the smallest element greater than or equal to a rather than NIL if a is not in the tree. This step runs in time O(lg n). Then we call a sequence of TREE-SUCCESSOR(x) until we encounter a node whose key is larger than b. So we call this m times. According to execise 12.2-8, that starting at an arbitrary node, m successive calls to TREE-SUCCESSOR take O(m + h) time. Thus RB-ENUMERATE can be implemented in Θ(m + lg n) time. We can also use SUCCESSOR and PREDECESSOR pointers as previous execise 14.2-1 shows with O(1) running time to output m keys in the second step. Total running time is O(m + lg n) too. *** Follow [@louis1992](https://github.com/gzc) on github to help finish this task. ================================================ FILE: C14-Augmenting-Data-Structures/14.3.md ================================================ ### Exercises 14.3-1 *** Write pseudocode for LEFT-ROTATE that operates on nodes in an interval tree and updates the max fields in O(1) time. ### `Answer` max[y] = max[x] max[x] = maximum(high[x], max(left[x]), max(right[x])) ### Exercises 14.3-2 *** Rewrite the code for INTERVAL-SEARCH so that it works properly when all intervals are assumed to be open. ### `Answer` INTERVAL-SEARCH(T, i) x <- root[T] while x != nil[T] and i does not overlap int[x] do if left[x] != nil[T] and max[left[x]] > low[i] then x<-left[x] else x<-right[x] return x ### Exercises 14.3-3 *** Describe an efficient algorithm that, given an interval i, returns an interval overlapping i that has the minimum low endpoint, or nil[T] if no such interval exists. ### `Answer` MIN-INTERVAL-SEARCH(T,i): x = T.root while x != T.nil: if x.left != T.nil and i.low <= x.left.max: x = x.left elif x.int overlaps i: break else x = x.right return x ### Exercises 14.3-4 *** Given an interval tree T and an interval i, describe how all intervals in T that overlap i can be listed in O(min(n, k lg n)) time, where k is the number of intervals in the output list. (Optional: Find a solution that does not modify the tree.) ### `Answer` INTERVAL-OVERLAP-LIST(T,x,i) 1.if i overlap int[x] 2. print x 3.if left[x] != nil[T] and max(left[x]) >=low[i] 4. INTERVAL-OVERLAP-LIST(T,left[x],i) 5.if right[x] != nil[T] and low[int[x]] <= high[i] and max[right[x]] >= low[i] 6. INTERVAL-OVERLAP-LIST(T,right[x] != i) 7.return x `@Brief: ` Every branch returns at least an interval, thus at the maxmum there are k branches recur down the tree giving running time O(klgn).On the other hand if all n nodes are visted we can list all the required intervals for sure. ### Exercises 14.3-5 *** Suggest modifications to the interval-tree procedures to support the new operation INTERVAL-SEARCH-EXACTLY(T, i), which returns a pointer to a node x in interval tree T such that low[int[x]] = low[i] and high[int[x]] = high[i], or nil[T] if T contains no such node. All operations, including INTERVAL-SEARCH-EXACTLY, should run in O(lg n) time on an n-node tree. ### `Answer` INTERVAL-SEARCH-EXACTLY(T, i) x = SEATCH(T, low[i]) //SEARCH为普通红黑树的查找操作 if high[x] == high[i] return x return nil[T] ### Exercises 14.3-6 *** Show how to maintain a dynamic set Q of numbers that supports the operation MIN-GAP, which gives the magnitude of the difference of the two closest numbers in Q. For example, if Q = {1, 5, 9, 15, 18, 22}, then MIN-GAP(Q) returns 18 - 15 = 3, since 15 and 18 are the two closest numbers in Q. Make the operations INSERT, DELETE, SEARCH, and MIN-GAP as efficient as possible, and analyze their running times. ### `Answer` 给节点新增3个属性 * mingap : 以当前节点为根,其子树的最小gap.叶子结点为∞ * maxval : 以当前节点为根,其子树的最大关键字 * minval : 以当前节点为根,其子树的最小关键字 根据定理14.1(红黑书的扩张):每次红黑树的插入,删除都能在O(lgn)的时间内更新这些信息
minval[x] = min(key[x], minval[x->left]) maxval[x] = max(key[x], maxval[x->right]) mingap[x] = min(mingap[left[x]], mingap[right[x], key[x]−maxval[left[x]], minval[right[x]]−key[x]) ### Exercises 14.3-7 *** VLSI databases commonly represent an integrated circuit as a list of rectangles. Assume that each rectangle is rectilinearly oriented (sides parallel to the x- and y-axis), so that a representation of a rectangle consists of its minimum and maximum x- and y-coordinates. Give an O(n lg n)-time algorithm to decide whether or not a set of rectangles so represented contains two rectangles that overlap. Your algorithm need not report all intersecting pairs, but it must report that an overlap exists if one rectangle entirely covers another, even if the boundary lines do not intersect. (Hint: Move a "sweep" line across the set of rectangles.) ### `Answer` [reference](http://blog.sina.com.cn/s/blog_4e356ecd010095hy.html) 先根据x坐标排序所有2n个点,然后将一扫描线扫过整个矩形,我们让扫描线和y轴平行,从最左开始扫描.如果碰到某矩形平行y轴的左边那条边条边,就把这条边插入到区间树中,如果碰到某矩形平行y轴的右边那条边,就把这条边从区间树中删除,如果碰到在插入边时,和前面插入的边有重叠,那么就是矩形重叠了. Time: O(n lg n) • O(n lg n) to sort the rectangles (we can use merge sort or heap sort). • O(n lg n) for interval-tree operations (insert, delete, and check for overlap). *** Follow [@louis1992](https://github.com/gzc) on github to help finish this task. ================================================ FILE: C14-Augmenting-Data-Structures/exercise_code/m-Josephus.cpp ================================================ /************************************************************************* > File Name: m-Josephus.cpp > Author: Louis1992 > Mail: zhenchaogan@gmail.com > Blog: http://gzc.github.io > Created Time: Wed Jul 15 11:49:42 2015 ************************************************************************/ #include using namespace std; struct node { int no; node *next; node(int i){no = i;} }; int main() { int m,n; node *cur,*q,*first; cout<<"请输入m的初始值 m:"; cin>>m; cout<<"请输入人数 n:"; cin>>n; for(int i = 1;i<=n;i++) { if(i == 1) first = cur = new node(i); else { q = new node(i); cur->next = q; cur = q; } } cur->next = first; cur = first; cout<<"出列顺序为: "; for (int j = 1;j<=n;j++) { for(int i = 1; i < m;i++,cur=cur->next); cout << cur->no <<" "; cur->no = cur->next->no; q = cur->next; cur->next = cur->next->next; } cout<1, I2...Ik, 其中Ik = [lk, rk]. 由于点p被这些区间覆盖,则对任意i,有li ≤ p ≤ ri. 下面证明存在一个区间端点,也同样被这些区间覆盖. 取 l\* = max{l1 .. lk}, 即这k个区间的左端点的最大值. 由l\*的定义可知, 对任意i, 有li ≤ l\* ≤ p. 另外,假设存在一个i, 使得ri < l\*, 则有li ≤ ri < l\* ≤ p, 这与点p被区间[li, ri]覆盖矛盾, 因此对任意i, 有l\* ≤ ri. 综上, li ≤ l\* ≤ ri, 即端点l\*也被这k个区间覆盖, 原命题得证. ∎ **b.** [stackoverflow](http://stackoverflow.com/questions/14780324/point-of-maximum-overlap) a bit tricky! down vote accepted quote:http://ripcrixalis.blog.com/2011/02/08/clrs-chapter-14/ Keep a RB-tree of all the endpoints. We insert endpoints one by one as a sweep line scaning from left to right. With each left endpoint e, associate a value p[e] = +1 (increasing the overlap by 1). With each right endpoint e associate a value p[e] = −1 (decreasing the overlap by 1). When multiple endpoints have the same value, insert all the left endpoints with that value before inserting any of the right endpoints with that value. Here is some intuition. Let e1, e2, . . . , en be the sorted sequence of endpoints corresponding to our intervals. Let s(i, j) denote the sum p[ei] + p[ei+1] + · · · + p[ej] for 1 ≤ i ≤ j ≤ n. We wish to find an i maximizing s(1, i ). Each node x stores three new attributes. We store v[x] = s(l[x], r [x]), the sum of the values of all nodes in x’s subtree. We also store m[x], the maximum value obtained by the expression s(l[x], i) for any i. We store o[x] as the value of i for which m[x] achieves its maximum. For the sentinel, we define v[nil[T]] = m[nil[T]] = 0. We can compute these attributes in a bottom-up fashion so as to satisfy the requirements of Theorem 14.1: v[x] = v[left[x]] + p[x] + v[right[x]] , m[x] = max{ m[left[x]] (max is in x’s left subtree), v[left[x]] + p[x] (max is at x), v[left[x]] + p[x] + m[right[x]] (max is in x’s right subtree). } Once we understand how to compute m[x], it is straightforward to compute o[x] from the information in x and its two children. FIND-POM: return the interval whose endpoint is represented by o[root[T]]. Because of how we have deÞned the new attributes, Theorem 14.1 says that each operation runs in O(lg n) time. In fact, FIND-POM takes only O(1) time. ### Problems 2 : Josephus Permutation *** The Josephus problem is defined as follows. Suppose that n people are arranged in a circle and that we are given a positive integer m ≤ n. Beginning with a designated first person, we proceed around the circle, removing every mth person. After each person is removed, counting continues around the circle that remains. This process continues until all n people have been removed. The order in which the people are removed from the circle defines the (n, m)-Josephus permutation of the integers 1, 2,..., n. For example, the (7, 3)-Josephus permutation is [3, 6, 2, 7, 5, 1, 4]. a. Suppose that m is a constant. Describe an O(n)-time algorithm that, given an integer n, outputs the (n, m)-Josephus permutation. b. Suppose that m is not a constant. Describe an O(n lg n)-time algorithm that, given integers n and m, outputs the (n, m)-Josephus permutation. ### `Answer` **a.** 使用循环列表. [implementation](./exercise_code/m-Josephus.cpp) **b.** 应用顺序统计量树(OST)。假设在某一轮中, 还剩下k个人,且被移除的其中的第j大的人. 则下一轮中, 被移除的是剩下的人中第(j + m -1)大的人,这里假设 j + m ≤ k, -1是因为第j大的人已被移除. 具体地, 1. k个排好序的人中, 第j大的人的编号是( (j-1)%k + 1 ). (假设编号从1开始, 且j可以为任意大的数). 2. 由上面分析可知, 若某一轮被移除的第j大的人, 则下一轮将要被移除的是第(j + m -1)大的, 代入上式, 可得下一轮被移除的是((j + m -2)%k + 1 ). 当然, 每一轮过后, k都要减一. 详见代码. ```` JOSEPHUS(n,m) initialize T to be empty for j ← 1 to n do create a node x with key[x] = j OS-INSERT(T, x) j ← 1 for k ← n downto 1 do j ← (( j + m − 2) mod k) + 1 x ← OS-SELECT(root[T ], j ) print key[x] OS-DELETE(T, x) ```` *** Follow [@louis1992](https://github.com/gzc) on github to help finish this task. ================================================ FILE: C15-Dynamic-Programming/15.1.md ================================================ ### Exercises 15.1-1 *** Show how to modify the PRINT-STATIONS procedure to print out the stations in increasing order of station number. (Hint: Use recursion.) ### `Answer` It's very easy. See my [implementation](./Assembly-line-sche.c) ### Exercises 15.1-2 *** Use equations (15.8) and (15.9) and the substitution method to show that ri(j), the number of references made to fi[j] in a recursive algorithm, equals 2^(n - j). ### `Answer` 1. j = n时,r(i,j) = 1 = 2^(n-n) 成立 2. 假设当j = k时, r(i,k) = 2^(n-k)
当j = k -1时, r(i,k-1) = r(1,k)+r(2,k) = 2^(n-k) + 2^(n-k) = 2^(n-(k-1)) 3. 综上所述, 等式成立 ### Exercises 15.1-3 *** Using the result of Exercise 15.1-2, show that the total number of references to all fi[j] values, or  ![](http://latex.codecogs.com/gif.latex?\\sum_{i=1}^{2}\\sum_{j%20=%201}^{n}r_i\(j\)) , is exactly 2^(n+1) - 2. ### `Answer` ![](http://latex.codecogs.com/gif.latex?\\sum_{i=1}^{2}\\sum_{j%20=%201}^{n}r_i\(j\)%0d%0a=%202\(\\sum_{j=1}^{n}2^{n-j}\)%20=%202\(2^n-1\)%20=%202^{n+1}-1) ### Exercises 15.1-4 *** Together, the tables containing fi[j] and li[j] values contain a total of 4n - 2 entries. Show how to reduce the space requirements to a total of 2n + 2 entries, while still computing f* and still being able to print all the stations on a fastest way through the factory. ### `Answer` It's simple. When we calculate f(n),we just need f(n-1).so we just alloc f1[2] and f2[2]. ### Exercises 15.1-5 *** Professor Canty conjectures that there might exist some ei, ai,j, and ti,j values for which FASTEST-WAY produces li[j] values such that l1[j] = 2 and l2[j] = 1 for some station number j. Assuming that all transfer costs ti,j are nonnegative, show that the professor is wrong. ### `Answer` if l1[j] = 2,then f1[j-1]>f2[j-1]+t(2,j-1) ------ @1 if l2[j] = 1,then f2[j-1]>f1[j-1]+t(1,j-1) ------ @2 we add @1 and @2,find that t(1,j-1)+t(2,j-1) < 0 wrong !!!!!! *** Follow [@louis1992](https://github.com/gzc) on github to help finish this task. ================================================ FILE: C15-Dynamic-Programming/15.2.md ================================================ ### Exercises 15.2-1 *** Find an optimal parenthesization of a matrix-chain product whose sequence of dimensions is [5, 10, 3, 12, 5, 50, 6]. ### `Answer` It's very easy.see my [implementation](./Matrix-chain-multiplication.c) ### Exercises 15.2-2 *** Give a recursive algorithm MATRIX-CHAIN-MULTIPLY(A, s, i, j) that actually performs the optimal matrix-chain multiplication, given the sequence of matrices [A1, A2, ..., An], the s table computed by MATRIX-CHAIN-ORDER, and the indices i and j. (The initial call would be MATRIX-CHAIN-MULTIPLY(A, s, 1, n).) ### `Answer` we should modify PRINT_OPTIMAL_PARENS. MATRIX_CHAIN_MULTIPLY(A,s,i,j) if(i == j) return A[i] if(j == i+1) return A[i]*A[j]; else B1 = MATRIX_CHAIN_MULTIPLY(A,s,i,S[i,j]) B2 = MATRIX_CHAIN_MULTIPLY(A,s,S[i,j]+1,j) return B1*B2 ### Exercises 15.2-3 *** Use the substitution method to show that the solution to the recurrence (15.11) is Ω(2^n). ### `Answer` 当n = 1时, 有p(n) = 1成立 假设对任意的k < n都成立,即p(k) >= c2^k,则有 ![](http://latex.codecogs.com/gif.latex?p\(n\)%20=%20\\sum_{k=1}^{n-1}p\(k\)p\(n-k\)%20\\ge%20\\sum_{k=1}^{n-1}c2^kc2^{n-k}%20=%20c^2\(n-1\)2^n) 因此,对于任意的c总有N0 = 1/c+1,使得当n > N0时, 有p(n) >= c2^n. ### Exercises 15.2-4 *** Let R(i, j) be the number of times that table entry m[i, j] is referenced while computing other table entries in a call of MATRIX-CHAIN-ORDER. Show that the total number of references for the entire table is ![](http://latex.codecogs.com/gif.latex?\\sum_{i=1}^{n}\\sum_{j=1}^{n}R\(i,j\)=%20\\frac{n^3-n}{3}) (Hint: You may find equation (A.3) useful.) ### `Answer` ![](http://latex.codecogs.com/gif.latex?\\sum_{i=1}^{n}\\sum_{j=1}^{n}R\(i,j\)%20=%20\\sum_{l%20=%202}^{n}\(n-l+1\)\(l-1\)2%20=%202\\sum_{l=1}^{n-1}l\(n-l\)=%20\\frac{n^3-n}{3}) ### Exercises 15.2-5 *** Show that a full parenthesization of an n-element expression has exactly n - 1 pairs of parentheses. ### `Answer` In our previous program,wt get ((A1(A2A3))((A4A5)A6)). n = 6,and we have 5 parentheses. A pair of parentheses musts contain a operator and n elements must have n-1 operators, so a full parenthesization of an n-element expression has exactly n-1 pairs of parentheses. *** Follow [@louis1992](https://github.com/gzc) on github to help finish this task. ================================================ FILE: C15-Dynamic-Programming/15.3.md ================================================ ### Exercises 15.3-1 *** Which is a more efficient way to determine the optimal number of multiplications in a matrix- chain multiplication problem: enumerating all the ways of parenthesizing the product and computing the number of multiplications for each, or running RECURSIVE-MATRIX- CHAIN? Justify your answer. ### `Answer` 15.2节给出枚举方法的时间复杂度为 ![](http://latex.codecogs.com/gif.latex?\\Omega\(\\frac{4^n}{n^{3/2}}}\)) The following are copied from solutions made by others, sorry I do not have author's name/ 对于RECURSIVE-MATRIX- CHAIN ![](./repo/s3/1.png) ### Exercises 15.3-2 *** Draw the recursion tree for the MERGE-SORT procedure from Section 2.3.1 on an array of 16 elements. Explain why memoization is ineffective in speeding up a good divide-and- conquer algorithm such as MERGE-SORT. ### `Answer` 因为并没有重叠子问题,只能用并行去优化. ### Exercises 15.3-3 *** Consider a variant of the matrix-chain multiplication problem in which the goal is to parenthesize the sequence of matrices so as to maximize, rather than minimize, the number of scalar multiplications. Does this problem exhibit optimal substructure? ### `Answer` 是的. ### Exercises 15.3-4 *** Describe how assembly-line scheduling has overlapping subproblems. ### `Answer` 我们要求解到达S1,j最快路线,必须先知道到达S1,j-1和S2,j-1的最快路线.这就是重叠子问题. ### Exercises 15.3-5 *** As stated, in dynamic programming we first solve the subproblems and then choose which of them to use in an optimal solution to the problem. Professor Capulet claims that it is not always necessary to solve all the subproblems in order to find an optimal solution. She suggests that an optimal solution to the matrix-chain multiplication problem can be found by always choosing the matrix Ak at which to split the subproduct Ai Ai+1 Aj (by selecting k to minimize the quantity pi-1 pk pj) before solving the subproblems. Find an instance of the matrix-chain multiplication problem for which this greedy approach yields a suboptimal solution. ### `Answer` 此处求解时用了贪心策略, 每次选取矩阵Ak分裂时,使得pi-1pkpi最小. ### Exercises 15.3-6 *** Imagine that you wish to exchange one currency for another. You realize that instead of directly exchanging one currency for another, you might be better off making a series of trades through other currencies, winding up with the currency you want. Suppose that you can trade n different currencies, numbered 1,2, ... , n, where you start with currency 1 and wish to wind up with currency n. You are given, for each pair of currencies i and j , an exchange rate rij , meaning that if you start with d units of currency i , you can trade for drij units of currency j . A sequence of trades may entail a commission, which depends on the number of trades you make. Let ck be the commission that you are charged when you make k trades. Show that, if ck = 0 for all k = 1,2, ... , n, then the problem of finding the best sequence of exchanges from currency 1 to currency n exhibits optimal substructure. Then show that if commissions ck are arbitrary values, then the problem of finding the best sequence of exchanges from currency 1 to currency n does not necessarily exhibit optimal substructure. ### `Answer` let v[1...n] be an array representing the best value of exchanged currency.
Then v[i] = d, if i = 1
and max(v[k] * r[k][i]) where 1 <= k < i, if i > 2
if c[k] = 0, for all k = 1, 2 ... n
As is shown in the recursion, the solution to the main problem combines the optimal solutions to subproblems. The solution of the main problem is also optimal. Therefore, the problem exhibits the optimal substructure.
if c[k] is arbitrary, then the problem does not necessarily exhibit the optimal substructure because there is not guarantee that the subproblems give optimal solutions. *** Follow [@louis1992](https://github.com/gzc) on github to help finish this task. ================================================ FILE: C15-Dynamic-Programming/15.4.md ================================================ ### Exercises 15.4-1 *** Determine an LCS of <1, 0, 0, 1, 0, 1, 0, 1> and <0, 1, 0, 1, 1, 0, 1, 1, 0>. ### `Answer` The LCS is <1, 0, 0, 1, 1, 0> , Or It can be <1,0,1,0,1,0> ### Exercises 15.4-2 *** Show how to reconstruct an LCS from the completed c table and the original sequences X = and Y = in O(m +n) time, without using the b table. ### `Answer` PRINT_LCS(c, x, y, i, j) if i = 0 || j = 0 return if x[i] = y[j] PRINT_LCS(c, x, y, i-1, j-1) print x[i] elif c[i-1, j] >= c[i, j-1] PRINT_LCS(c, x, y, i-1, j) else PRINT_LCS(c, x, y, i, j-1) ### Exercises 15.4-3 *** Give a memoized version of LCS-LENGTH that runs in O(mn) time. ### `Answer` LCS-LENGTH(X, Y) m ← length[X] n ← length[Y] for i ← 1 to m do for j ← 1 to n do c[i,j] ← -1 end for end for return LOOKUP-LENGTH(X,Y,m,n) LOOKUP-LENGTH(X,Y,i,j) if c[i,j] > -1 then return c[i,j] end if if i = 0 or j = 0 then c[i,j] ← 0 else if X[i] = Y[j] then c[i,j] ← LOOKUP-LENGTH(X,Y,i-1,j-1)+1 else c[i,j] ← max(LOOKUP-LENGTH(X,Y,i,j-1),LOOKUP-LENGTH(X,Y,i-1,j)) end if end if return c[i,j] ### Exercises 15.4-4 *** Show how to compute the length of an LCS using only 2 · min(m, n) entries in the c table plus O(1) additional space. Then show how to do this using min(m, n) entries plus O(1) additional space. ### `Answer` 因为求解一个项c[i,j],只会用到c[i-1,j-1],c[i,j-1],c[i-1,j]. 所以运行时刻,我们只需要保存上面一行的状态和当前行的状态即可,再令X,Y这两个字符串中短的那一个放到index j.所以可以用2 · min(m, n)的空间运行算法. 那如何做到只利用min(m, n) entries plus O(1) additional space呢? 其实我们只需要用一行保存状态即可. c[i,j-1]已经保存在该行中. 然后用一个常量维护c[i-1,j-1]即可.每次更新c[i,j]的时候将old valuy保存下来,因为下次要用到. Since we need only c[i-1,j-1], c[i,j-1], c[i-1,j] to compute c[i,j], we just need to save the previous row and the current row of the dp table. We will maintain the row parallel to the shorter one of the X and Y strings. So we can run the algorithm with 2 · min(m, n) space. In fact, we only need to save only one row. c[i,j-1] is already stored in this row. Then, we use an extra variable to maintain c[i-1,j-1]. Every time c[i,j] is updated, the value of c[i-1,j] is saved into the extra variable because it will be used next time. ### Exercises 15.4-5 *** Give an O(n^2)-time algorithm to find the longest monotonically increasing subsequence of a sequence of n numbers. ### `Answer` Method 1: Given a sequence X = we wish to find the longest monotonically increasing subsequence. 1. First we sort the string X which produces sequence X'. 2. Finding the longest common subsequence of X and X' yields the longest monotonically increasing subsequence of X. The running time is O(n^2) since sorting can be done in O(nlgn) and the call to LCS-LENGTH is O(n^2). Method 2: LONGEST-INC-SEQUENCE(Arr, n) len [1...n] be a new array for i←1 to n len[i] ← 1 // length of longest increasing sequence ending at i. for i←2 to n do for j←1 to i-1 do if Arr[j] < Arr[i] len [i] ← max(len[i], len[j] +1) end if end for end for return len [n] ### Exercises 15.4-6 * *** Give an O(n lg n)-time algorithm to find the longest monotonically increasing sub-sequence of a sequence of n numbers. (Hint: Observe that the last element of a candidate subsequence of length i is at least as large as the last element of a candidate subsequence of length i - 1. Maintain candidate subsequences by linking them through the input sequence.) ### `Answer` [implementation](./lincrs.cpp) *** Follow [@louis1992](https://github.com/gzc) on github to help finish this task. ================================================ FILE: C15-Dynamic-Programming/15.5.md ================================================ ### Exercises 15.5-1 *** Write pseudocode for the procedure CONSTRUCT-OPTIMAL-BST(root) which, given the table root, outputs the structure of an optimal binary search tree. For the example in Figure 15.8, your procedure should print out the structure * k2 is the root * k1 is the left child of k2 * d0 is the left child of k1 * d1 is the right child of k1 * k5 is the right child of k2 * k4 is the left child of k5 * k3 is the left child of k4 * d2 is the left child of k3 * d3 is the right child of k3 * d4 is the right child of k4 * d5 is the right child of k5 ### `Answer` [implementation](./optimalBST.cpp) ### Exercises 15.5-2 *** Determine the cost and structure of an optimal binary search tree for a set of n = 7 keys with the following probabilities: i | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 :---:|:---:|:---:|:---:|:---:|:---:|:---: pi | | 0.04 | 0.06 | 0.08 | 0.02 | 0.10 | 0.12 | 0.14 qi | 0.06 | 0.06 | 0.06 | 0.06 | 0.05 | 0.05 | 0.05 | 0.05 ### `Answer` run my [program](./optimalBST.cpp) you will get the answer. ### Exercises 15.5-3 *** Suppose that instead of maintaining the table w[i, j], we computed the value of w(i, j) directly from equation (15.17) in line 8 of OPTIMAL-BST and used this computed value in line 10. How would this change affect the asymptotic running time of OPTIMAL-BST? ### `Answer` 时间复杂度依旧是O(n^3).虽然原来的计算方法计算w只用了O(n^2),但算法有一个三重循环. ### Exercises 15.5-4 * *** Knuth [184] has shown that there are always roots of optimal subtrees such that root[i, j - 1] ≤ root[i, j] ≤ root[i + 1, j] for all 1 ≤ i < j ≤ n. Use this fact to modify the OPTIMAL-BST procedure to run in Θ(n2) time. ### `Answer` 第9行替换为: if i = j r <- j else for r <- root[i,j-1] to root[i+1,j] *** Follow [@louis1992](https://github.com/gzc) on github to help finish this task. ================================================ FILE: C15-Dynamic-Programming/Assembly-line-sche.c ================================================ #include #include int Fastway(int *a1,int *a2,int *t1,int *t2,int *e,int *x,int n,int *l1,int *l2,int *value) { int *f1 = (int*)malloc(n*sizeof(int)); int *f2 = (int*)malloc(n*sizeof(int)); f1[0] = e[0]+a1[0]; f2[0] = e[1]+a2[0]; int i; for(i = 1;i < n;i++) { if(f1[i-1] < (f2[i-1]+t2[i-1])) { f1[i] = f1[i-1]+a1[i]; l1[i-1] = 1; }else{ f1[i] = f2[i-1]+t2[i-1]+a1[i]; l1[i-1]=2; } if(f2[i-1] < (f1[i-1]+t1[i-1])) { f2[i] = f2[i-1]+a2[i]; l2[i-1] = 2; }else{ f2[i] = f1[i-1]+t1[i-1]+a2[i]; l2[i-1] = 1; } } if((f1[n-1]+x[0]) < (f2[n-1]+x[1])) { *value = f1[n-1]+x[0]; free(f1); free(f2); return 1; }else { *value = f2[n-1]+x[1]; free(f1); free(f2); return 2; } } void PRINT_STATIONS(int *l1,int *l2,int way,int n) { printf("line%d,station%d\n",way,n); int i; for(i=(n-2);i>=0;i--) { if(way == 1) way = l1[i]; else way = l2[i]; printf("line%d,station%d\n",way,i+1); } } void reverse_PRINT_STATIONS(int *l1,int *l2,int way,int n) { int new_way; if(way == 1) new_way = l1[n-2]; else new_way = l2[n-2]; if(n >= 2) reverse_PRINT_STATIONS(l1,l2,new_way,n-1); printf("line%d,station%d\n",way,n); } int main() { int n = 6; int e[2]; e[0]=2; e[1]=4; int x[2]; x[0]=3; x[1]=2; int t1[5],t2[5]; t1[0]=2; t1[1]=3; t1[2]=1; t1[3]=3; t1[4]=4; t2[0]=2; t2[1]=1; t2[2]=2; t2[3]=2; t2[4]=1; int a1[6],a2[6]; a1[0]=7; a1[1]=9; a1[2]=3; a1[3]=4; a1[4]=8; a1[5]=4; a2[0]=8; a2[1]=5; a2[2]=6; a2[3]=4; a2[4]=5; a2[5]=7; int value; int *l1 = (int*)malloc((n-1)*sizeof(int)); int *l2 = (int*)malloc((n-1)*sizeof(int)); int way = Fastway(a1,a2,t1,t2,e,x,n,l1,l2,&value); printf("value = %d\n",value); reverse_PRINT_STATIONS(l1,l2,way,n); return 0; } ================================================ FILE: C15-Dynamic-Programming/Matrix-chain-multiplication.c ================================================ #include #include /* * according to introduction to algorithm, * we alloc array(n+1)but make use of the last n * just for convinence */ void MATRIX_CHAIN_ORDER(int *p,int n,int **m,int **s) { int i; int row = n+1; //initialize our array for(i=1;i<=n;i++) *((int*)m+row*i+i) = 0; int l; for(l=2;l<=n;l++) { for(i=1;i<=(n-l+1);i++) { int j = i+l-1; *((int*)m+row*i+j) = -1; int k; for(k=i;k<=(j-1);k++) { int tmp1 = *((int*)m+row*i+k); int tmp2 = *((int*)m+row*(k+1)+j); int q = tmp1+tmp2+p[i-1]*p[k]*p[j]; int old = *((int*)m+row*i+j); if(q using namespace std; int find(int *a, int len, int n) { int left(0),right(len),mid = (left+right)/2; while(left <= right) { if(n > a[mid]) left = mid + 1; else if(n < a[mid]) right = mid - 1; else return mid; mid = (left + right)/2; } return left; } int main() { int n, a[100], c[100], i, j, len; cin >> n; for(int i = 0;i < n;i++) cin >> a[i]; c[0] = -1; c[1] = a[0]; len = 1; for(i = 1;i <= n;i++) { j = find(c, len, a[i]); c[j] = a[i]; if(j > len) len = j; } cout << len << endl; return 0; } ================================================ FILE: C15-Dynamic-Programming/optimalBST.cpp ================================================ #include using namespace std; const int MaxVal = 9999; const int n = 5; //搜索到根节点和虚拟键的概率 double p[n + 1] = {-1,0.15,0.1,0.05,0.1,0.2}; double q[n + 1] = {0.05,0.1,0.05,0.05,0.05,0.1}; int root[n + 1][n + 1];//记录根节点 double w[n + 2][n + 2];//子树概率总和 double e[n + 2][n + 2];//子树期望代价 void optimalBST(double *p,double *q,int n) { //初始化只包括虚拟键的子树 for (int i = 1;i <= n + 1;++i) { w[i][i - 1] = q[i - 1]; e[i][i - 1] = q[i - 1]; } //由下到上,由左到右逐步计算 for (int len = 1;len <= n;++len) { for (int i = 1;i <= n - len + 1;++i) { int j = i + len - 1; e[i][j] = MaxVal; w[i][j] = w[i][j - 1] + p[j] + q[j]; //求取最小代价的子树的根 for (int k = i;k <= j;++k) { double temp = e[i][k - 1] + e[k + 1][j] + w[i][j]; if (temp < e[i][j]) { e[i][j] = temp; root[i][j] = k; } } } } } //输出最优二叉查找树所有子树的根 void printRoot() { cout << "各子树的根:" << endl; for (int i = 1;i <= n;++i) { for (int j = 1;j <= n;++j) { cout << root[i][j] << " "; } cout << endl; } cout << endl; } //打印最优二叉查找树的结构 //打印出[i,j]子树,它是根r的左子树和右子树 void printOptimalBST(int i,int j,int r) { int rootChild = root[i][j];//子树根节点 if (rootChild == root[1][n]) { //输出整棵树的根 cout << "k" << rootChild << "是根" << endl; printOptimalBST(i,rootChild - 1,rootChild); printOptimalBST(rootChild + 1,j,rootChild); return; } if (j < i - 1) return; else if (j == i - 1)//遇到虚拟键 { if (j < r) cout << "d" << j << "是" << "k" << r << "的左孩子" << endl; else cout << "d" << j << "是" << "k" << r << "的右孩子" << endl; return; } else//遇到内部结点 { if (rootChild < r) cout << "k" << rootChild << "是" << "k" << r << "的左孩子" << endl; else cout << "k" << rootChild << "是" << "k" << r << "的右孩子" << endl; } printOptimalBST(i,rootChild - 1,rootChild); printOptimalBST(rootChild + 1,j,rootChild); } int main() { optimalBST(p,q,n); printRoot(); cout << "最优二叉树结构:" << endl; printOptimalBST(1,n,-1); } ================================================ FILE: C15-Dynamic-Programming/rodcutting.cpp ================================================ #include #include #include // #define DEBUG using std::cout; using std::endl; // This is the most common recursive version of dynamic programming implementation int CutRod(const std::vector &p, const int &n) { if (n == 0) { return 0; } int q = INT8_MIN; for (auto i = 1; i <= n; ++i) { q = std::max(q, p.at(i) + CutRod(p, n - i)); } return q; } // Memoized version of cut_rod int MemoizedCutRodAux(const std::vector &p, const int &n, std::vector &r) { if (r.at(n) >= 0) { return r.at(n); } int q = 0; if (n == 0) {} else { q = INT8_MIN; for (auto i = 1; i <= n; ++i) { q = std::max(q, p.at(i) + MemoizedCutRodAux(p, n - i, r)); } } r.at(n) = q; return q; } int MemoizedCutRod(const std::vector &p, const int &n) { std::vector r(n + 1, INT8_MIN); return MemoizedCutRodAux(p, n, r); } // Cut-Rod with bottom-up method int BottomUpCutRod(const std::vector &p, const int &n) { std::vector r{0}; for (auto j = 1; j <= n; ++j) { int q = INT8_MIN; for (auto i = 1; i <= j; ++i) { q = std::max(q, p.at(i) + r.at(j - i)); } r.push_back(q); } return r.at(n); } int main() { vector p{0, 1, 5, 8, 9, 10, 17, 17, 20, 24, 30}; #ifdef DEBUG cout << "When n = 1, r = " << MemoizedCutRod(p, 1) << "." << endl; // Should output: When n = 1, r = 1. cout << "When n = 2, r = " << MemoizedCutRod(p, 2) << "." << endl; // Should output: When n = 2, r = 5. cout << "When n = 3, r = " << MemoizedCutRod(p, 3) << "." << endl; // Should output: When n = 3, r = 8. cout << "When n = 4, r = " << MemoizedCutRod(p, 4) << "." << endl; // Should output: When n = 4, r = 10. cout << "When n = 5, r = " << MemoizedCutRod(p, 5) << "." << endl; // Should output: When n = 5, r = 13. cout << "When n = 6, r = " << MemoizedCutRod(p, 6) << "." << endl; // Should output: When n = 6, r = 17. cout << "When n = 7, r = " << MemoizedCutRod(p, 7) << "." << endl; // Should output: When n = 7, r = 18. cout << "When n = 8, r = " << MemoizedCutRod(p, 8) << "." << endl; // Should output: When n = 8, r = 22. cout << "When n = 9, r = " << MemoizedCutRod(p, 9) << "." << endl; // Should output: When n = 9, r = 25. cout << "When n = 10, r = " << MemoizedCutRod(p, 10) << "." << endl; // Should output: When n = 10, r = 30. #endif cout << "When n = 9, r = " << MemoizedCutRod(p, 9) << "." << endl; // Should output: When n = 9, r = 25. return 0; } ================================================ FILE: C16-Greedy-Algorithms/16.1.md ================================================ ### Exercises 16.1-1 *** Give a dynamic-programming algorithm for the activity-selection problem, based on the recurrence (16.3). Have your algorithm compute the sizes c[i, j] as defined above and also produce the maximum-size subset A of activities. Assume that the inputs have been sorted as in equation (16.1). Compare the running time of your solution to the running time of GREEDY-ACTIVITY-SELECTOR. ### `Answer` 动态规划时间复杂度为O(n^3),贪心算法时间复杂度为O(n). DYNAMIC_ACTIVITY_SELECTOR(S): initialize c[i,j] = 0 for i <- 1 to n do for j <- 2 to n do if i >= j then c[i,j] <- 0 else for k <- i+1 to j-1 do if c[i,j] < c[i,k] + c[k,j] + 1 then c[i,j] <- c[i,k] + c[k,j] + 1 s[i,j] <- k ### Exercises 16.1-2 *** Suppose that instead of always selecting the first activity to finish, we instead select the last activity to start that is compatible with all previously selected activities. Describe how this approach is a greedy algorithm, and prove that it yields an optimal solution. ### `Answer` 最迟开始和最先结束其实是同样的思想. GREEDY-ACTIVITY-SELECTOR(s,f) n <- length[s] A <- {an} i <- n for m <- n-1 to 1 do if fm <= si then A <- A U {am} i <- m return A ### Exercises 16.1-3 *** Suppose that we have a set of activities to schedule among a large number of lecture halls. We wish to schedule all the activities using as few lecture halls as possible. Give an efficient greedy algorithm to determine which activity should use which lecture hall. (This is also known as the **interval-graph coloring problem**. We can create an interval graph whose vertices are the given activities and whose edges connect incompatible activities. The smallest number of colors required to color every vertex so that no two adjacent vertices are given the same color corresponds to finding the fewest lecture halls needed to schedule all of the given activities.) ### `Answer` Find the smallest number of lectures halls to schedule a set of activities S in.To do this efficiently move throught the activities according to starting and finishing times. Maintain two lists of lecture halls: Halls that are busy at time t and halls that are free at time t. When t is the starting time for some activity schedule this activity to a free lecture hall and move the hall to the busy list. Similarly, move the hall to the free list when the activity stops. Initially start with zero halls. If there are no halls in the free list create a new hall. The above algorithm uses the fewest number of halls possible : Assume the algorithm used m halls. Consider some activity a that was the first scheduled activity in lecture hall m. i was put in the mth hall because all of the m-1 halls were busy, that is, at the time a is scheduled there are m activities occurring simultaneously. Any algorithm must therefore use at least m halls, and the algorithm is thus optimal. The algorithm can be implemented by sorting the activities. At each start or finish time we can schedule the activities and move the halls between the lists in constant time. The total time is thus dominated by sorting and is therefore O(nlgn). ### Exercises 16.1-4 *** Not just any greedy approach to the activity-selection problem produces a maximum-size set of mutually compatible activities. Give an example to show that the approach of selecting the activity of least duration from those that are compatible with previously selected activities does not work. Do the same for the approaches of always selecting the compatible activity that overlaps the fewest other remaining activities and always selecting the compatible remaining activity with the earliest start time. ### `Answer` Show that selecting the activity with the least duration or with minimum overlap or earliest starting time does not yield an optimal solution for the activity-selection problem. Consider the figure below ![](./repo/s1/1.png) Selecting the activity with the least duration from example a will result in selecting the topmost activity and none other. Clearly, this is worse than the optimal solution obtained by selecting the two activities in the second row. The activity with the minimum overlap in example b is the middle activity in the top row. However, selecting this activity eliminates the possibility of selecting the optimal solution depicted in the second row. Selecting the activity with the earliest starting time in example c will yield only the one activity in the top row. *** Follow [@louis1992](https://github.com/gzc) on github to help finish this task. ================================================ FILE: C16-Greedy-Algorithms/16.2.md ================================================ ### Exercises 16.2-1 *** Prove that the fractional knapsack problem has the greedy-choice property. ### `Answer` Thm. The fractional knapsack problem has the greedy-choice property.

Proof:
We need to show that we can construct a global optimal solution from local optimal solutions.
To show that is to show if every time we pick out the commodity with the greatest value per pound, we will finally obtain the optimal value of all commodities under limit W.
Consider an non-empty set S, and let Cm be the commodity with the greatest value (v[m]/w[m]) per pound in S. Let A be a subset of S such that A has the greatest value under limit W and let Cj be the commodity with the greatest value (v[j]/w[j]) per pound in A. To prove the thm, we need to show, Cm must be in the subset A.
By picking out Cm, we will encounter two cases:
case1: w[m] >= W
In this case, the knapsack is full. Because Cm is the the commodity with the greatest value (v[m]/w[m]) per pound, W * (v[m]/w[m]) must be the greatest value for all W * (v[i]/w[i]), where 1 <= i <= n. Thm is already proved.
case2: w[m] < W
In this case, we know for sure that for weight w[m], v[m] is the greatest value this amount commodities can bring.
If Cm = Cj, then the thm is proved.
If Cm != Cj, then to reach v[m], the weight needed from Cj must be more than w[m] because v[m] is, as shown, the greates value for weight w[m]. That is, if we replace Cm with Cj in A, the value of knapsack actually decreases. Contradiction to the original solution being optimal. ### Exercises 16.2-2 *** Give a dynamic-programming solution to the 0–1 knapsack problem that runs in O(n W) time, where n is number of items and W is the maximum weight of items that the thief can put in his knapsack. ### `Answer` DYNAMIC-0-1-KNAPSACK(v,w,n,W) for w <- 0 to W do c[0,w] <- 0 end for for i <- 1 to n do c[i,0] <- 0 for w <- 1 to W do if wi <= w then if vi+c[i-1,w-wi] > c[i-1,w] then c[i,w] <- vi + c[i-1,w-wi] else c[i,w] <- c[i-1,w] end if else c[i,w] <- c[i-1,w] end if end for end for return c[n,W] ### Exercises 16.2-3 *** Suppose that in a 0–1 knapsack problem, the order of the items when sorted by increasing weight is the same as their order when sorted by decreasing value. Give an efficient algorithm to find an optimal solution to this variant of the knapsack problem, and argue that your algorithm is correct. ### `Answer` 最轻的先装,时间主要是排序的时间. ### Exercises 16.2-4 *** Professor Midas drives an automobile from Newark to Reno along Interstate 80. His car's gas tank, when full, holds enough gas to travel n miles, and his map gives the distances between gas stations on his route. The professor wishes to make as few gas stops as possible along the way. Give an efficient method by which Professor Midas can determine at which gas stations he should stop, and prove that your strategy yields an optimal solution. ### `Answer` The optimal strategy is the obvious greedy one. Starting with a full tank of gas, Professor Midas should go to the farthest gas station he can get to within n miles of Network. Fill up there. Then go to the farthest gas station he can get to within n miles of where he filled up, and fill up there, and so on. ### Exercises 16.2-5 *** Describe an efficient algorithm that, given a set {x1, x2, ...,xn} of points on the real line, determines the smallest set of unit-length closed intervals that contains all of the given points. Argue that your algorithm is correct. ### `Answer` Consider the following very simple algorithm: Sort the points obtaining a new array {y1,...,yn}. The first interval is given by[y1,y1+1]. If yi is the leftmost point not contained in any existing interval the next interval is [yi,yi+1] and so on. Thie greedy algorithm does the job since the rightmost element of the set must be contained in an interval and we can do no better than the interval [y1,y1+1]. Additionally, any subproblem to the optimal solution must be optimal. This is easily seen by considering the problem for the points greater than y1+1 and arguing inductively. ### Exercises 16.2-6 * *** Show how to solve the fractional knapsack problem in O(n) time. Assume that you have a solution to Problem 9-2. ### `Answer` ![](./repo/s2/1.png) ### Exercises 16.2-7 *** Suppose you are given two sets A and B, each containing n positive integers. You can choose to reorder each set however you like. After reordering, let ai be the ith element of set A, and let bi be the ith element of set B. You then receive a payoff of ![](http://latex.codecogs.com/gif.latex?\\prod_{i=1}^n%20a_i^{b_i}%20). Give an algorithm that will maximize your payoff. Prove that your algorithm maximizes the payoff, and state its running time. ### `Answer` ![](./repo/s2/2.png) *** Follow [@louis1992](https://github.com/gzc) on github to help finish this task. ================================================ FILE: C16-Greedy-Algorithms/16.3.md ================================================ ### Exercises 16.3-1 *** Prove that a binary tree that is not full cannot correspond to an optimal prefix code. ### `Answer` 要达到最优前缀编码,必须考虑到前缀的每一种情况,故必须是满二叉树. 更正式的,假设一个节点N不是满的(即它只有一个儿子),不妨设它的父节点为P,它只有左儿子L。则我们总可以将N删掉,并将L直接和P相连,得到一个新的树。这棵新树仍对应一个合法的编码,且N下面所有节点的深度都减少了1,从而新树是比原树更优的一种编码。这与原树是最优编码矛盾。因此一棵不满的二叉树一定不于最优编码对应。 ### Exercises 16.3-2 *** What is an optimal Huffman code for the following set of frequencies, based on the first 8 Fibonacci numbers? a:1 b:1 c:2 d:3 e:5 f:8 g:13 h:21 Can you generalize your answer to find the optimal code when the frequencies are the first n Fibonacci numbers? ### `Answer` ![](./repo/s3/1.png) 推广到n的情形: 设前n+1个斐波那契数,f(0) = 1, f(1) = 1, ... , f(n) = f(n-2) + f(n-1), 其对应的赫夫曼编码为H(0), H(1), ... , H(n),则由上图可归纳出: H(n) = 0, H(k) = H(k+1) + 2n-k (0 < k < n), H(0) = H(1) + 1 ### Exercises 16.3-3 *** Prove that the total cost of a tree for a code can also be computed as the sum, over all internal nodes, of the combined frequencies of the two children of the node. ### `Answer` straightforward ### Exercises 16.3-4 *** Prove that if we order the characters in an alphabet so that their frequencies are monotonically decreasing, then there exists an optimal code whose codeword lengths are monotonically increasing. ### `Answer` 设字符频度为f(1), f(2), ... , f(n),编码长度依次为d(1), d(2), ... , d(n)。由已知得f(1) >= f(2) >= ... >= f(n)。若编码长度不是递增的,则存在i,使得d(i) > d(i+1)。我们可以交换i和i+1所对应的编码。交换前的总代价为 S = f(1)d(1) + ... + f(i)d(i) + f(i+1)d(i+1) + ... + f(n)d(n) 交换后的总代价为 T = f(1)d(1) + ... + f(i)d(i+1) + f(i+1)d(i) + ... + f(n)d(n) 有T - S = (f(i) - f(i+1))(d(i+1) - d(i)) < 0 <=> T < S。所以交换后得到一个代价更小的编码。这样,我们可以一直交换,直到所有编码长度变为递增的,且其对应的编码也是最优的。 ### Exercises 16.3-5 *** Suppose we have an optimal prefix code on a set C = {0, 1, ..., n - 1} of characters and we wish to transmit this code using as few bits as possible. Show how to represent any optimal prefix code on C using only 2n - 1 + n ⌈lg n⌉ bits. (Hint: Use 2n - 1 bits to specify the structure of the tree, as discovered by a walk of the tree.) ### `Answer` 用**2n-1**位表示树的结构,内部节点用1表示,叶子节点用0表示.用nlog(n)为表示字母序列,每个字母的二进制编码长度为log(n),总共需要nlog(n)位. ### Exercises 16.3-6 *** Generalize Huffman's algorithm to ternary codewords (i.e., codewords using the symbols 0, 1, and 2), and prove that it yields optimal ternary codes. ### `Answer` 那就推广到树的结点有三个孩子结点,证明过程同引理16.3的证明. ### Exercises 16.3-7 *** Suppose a data file contains a sequence of 8-bit characters such that all 256 characters are about as common: the maximum character frequency is less than twice the minimum character frequency. Prove that Huffman coding in this case is no more efficient than using an ordinary 8-bit fixed-length code. ### `Answer` 此时生成的Huffman树是一颗满二叉树,跟固定长度编码一致. ### Exercises 16.3-8 *** Show that no compression scheme can expect to compress a file of randomly chosen 8-bit characters by even a single bit. (Hint: Compare the number of files with the number of possible encoded files.) ### `Answer` Notice that the number of possible source files S using n bit and compressed files E using n bits is 2^**n+1** - 1. Since any compression algorithm must assign each element s ∈ S to a distinct element e ∈ E the algorithm cannot hope to actually compress the source file. *** Follow [@louis1992](https://github.com/gzc) on github to help finish this task. ================================================ FILE: C16-Greedy-Algorithms/huffman/BinaryStdIn.cpp ================================================ /************************************************************************* > File Name: BinaryStdIn.cpp > Author: > Mail: > Created Time: Wed 14 Jan 2015 08:14:15 PM CST ************************************************************************/ #include "BinaryStdIn.h" BinaryStdIn::BinaryStdIn(string filename) { open(filename); } bool BinaryStdIn::open(string filename) { fin.open(filename.c_str(), std::ios::binary); if (!fin.is_open()) { cout << "Error opening file" << endl; exit (1); } fillBuffer(); return true; } bool BinaryStdIn::fillBuffer() { char ch; fin >> ch; buffer = ch; N = 8; } bool BinaryStdIn::readBool() { N--; bool bit = ((buffer >> N) & 1) == 1; if (N == 0) fillBuffer(); #ifdef DEBUG cout << "Function readBool() return : " << bit << endl; #endif return bit; } char BinaryStdIn::readChar() { if(N == 8) { char x = buffer; fillBuffer(); return x; } char x = buffer; x <<= (8-N); int oldN = N; fillBuffer(); N = oldN; unsigned char tmp = (unsigned char)buffer; x |= ( tmp >> N); #ifdef DEBUG cout << "Function readChar() return : " << x << endl; #endif return x; } int BinaryStdIn::readInt() { int x = 0; for (int i = 0; i < 4; i++) { char c = readChar(); x <<= 8; x |= c; } #ifdef DEBUG cout << "Function readInt() return : " << x << endl; #endif return x; } void BinaryStdIn::close() { fin.close(); } ================================================ FILE: C16-Greedy-Algorithms/huffman/BinaryStdIn.h ================================================ /************************************************************************* > File Name: BinaryStdIn.h > Author: > Mail: > Created Time: Wed 14 Jan 2015 08:05:13 PM CST ************************************************************************/ #ifndef _BINARYSTDIN_H #define _BINARYSTDIN_H #include #include #include using namespace std; class BinaryStdIn { ifstream fin; char buffer; int N; bool fillBuffer(); public: BinaryStdIn(){}; BinaryStdIn(string filename); bool open(string filename); bool readBool(); char readChar(); int readInt(); void close(); }; #endif ================================================ FILE: C16-Greedy-Algorithms/huffman/BinaryStdOut.cpp ================================================ /************************************************************************* > File Name: BinaryStdOut.cpp > Author: > Mail: > Created Time: Wed 14 Jan 2015 09:18:56 PM CST ************************************************************************/ #include "BinaryStdOut.h" #include using namespace std; BinaryStdOut::BinaryStdOut(string filename) { open(filename); } bool BinaryStdOut::open(string filename) { fout.open(filename.c_str(), ios::out|ios::binary); if (!fout.is_open()) { cout << "Error opening file" << endl; exit (1); } } void BinaryStdOut::clearBuffer() { if(N == 0) return; if (N > 0) buffer <<= (8 - N); fout << buffer; N = 0; buffer = 0; } void BinaryStdOut::writeBit(bool bit) { buffer <<= 1; if (bit) buffer |= 1; N++; if (N == 8) clearBuffer(); } void BinaryStdOut::writeChar(char ch) { if (N == 0) { fout << ch; return; } for (int i = 0; i < 8; i++) { bool bit = ((ch >> (8 - i - 1)) & 1 ) == 1; writeBit(bit); } } void BinaryStdOut::write(bool x) { writeBit(x); } void BinaryStdOut::write(char x) { writeChar(x); } void BinaryStdOut::write(int x) { writeChar((x >> 24) & 0xff); writeChar((x >> 16) & 0xff); writeChar((x >> 8) & 0xff); writeChar((x >> 0) & 0xff); } void BinaryStdOut::flush() { clearBuffer(); fout.flush(); } void BinaryStdOut::close() { flush(); fout.close(); cout << "-------------------- BinaryStdOut Writing Closing --------------------" << endl; } ================================================ FILE: C16-Greedy-Algorithms/huffman/BinaryStdOut.h ================================================ /************************************************************************* > File Name: BinaryStdOut.h > Author: > Mail: > Created Time: Wed 14 Jan 2015 09:12:17 PM CST ************************************************************************/ #ifndef _BINARYSTDOUT_H #define _BINARYSTDOUT_H #include #include #include using namespace std; class BinaryStdOut { char buffer; int N; ofstream fout; void clearBuffer(); void writeBit(bool bit); void writeChar(char ch); public: BinaryStdOut(){}; BinaryStdOut(string filename); bool open(string filename); void write(bool bit); void write(char ch); void write(int i); void flush(); void close(); }; #endif ================================================ FILE: C16-Greedy-Algorithms/huffman/HUFFMAN.cpp ================================================ /************************************************************************* > File Name: HUFFMAN.cpp > Author: > Mail: > Created Time: Mon 12 Jan 2015 11:48:32 PM CST ************************************************************************/ #include "HUFFMAN.h" #include using namespace std; HUFFMAN::HUFFMAN() { } HUFFMAN::~HUFFMAN() { if(!huffman_Tree) destroy(huffman_Tree); } void HUFFMAN::buildTree(string &data, string filename) { map frequency; for(string::iterator it = data.begin(); it != data.end(); it++) { frequency[*it]++; } #ifdef DEBUG cout << "-------------------- Frequencies of data --------------------" << endl; for(map::iterator it = frequency.begin(); it != frequency.end(); it++) { cout << it->first << " " << it->second << endl; } cout << "-------------------- End of frequencies --------------------" << endl; #endif priority_queue, myComparision> pq; for(map::iterator it = frequency.begin(); it != frequency.end(); it++) { Htree *node = new Htree(NULL, NULL, it->second, it->first); pq.push(node); } while(pq.size() > 1) { Htree *node1 = pq.top(); pq.pop(); Htree *node2 = pq.top(); pq.pop(); Htree *node = new Htree(node1, node2, node1->weight+node2->weight, 0); pq.push(node); } huffman_Tree = pq.top(); vector code; buildDict(huffman_Tree, code); #ifdef DEBUG cout << "-------------------- Show Encoding Dict --------------------" << endl; for(huffman_dict::iterator it = dict.begin(); it != dict.end(); it++) { cout << it->first << " "; vector code = it->second; for(vector::iterator codeit = code.begin(); codeit != code.end(); codeit++) { cout << *codeit; } cout << endl; } cout << "-------------------- End of Dict --------------------" << endl; #endif } void HUFFMAN::buildDict(Htree *node, vector &code) { if(isLeaf(node)) { dict[node->ch] = code; } else { code.push_back(false); buildDict(node->left, code); code.pop_back(); code.push_back(true); buildDict(node->right, code); code.pop_back(); } } void HUFFMAN::writeTrie(Htree *node) { if (isLeaf(node)) { binaryStdOut->write(true); binaryStdOut->write(node->ch); return; } binaryStdOut->write(false); writeTrie(node->left); writeTrie(node->right); } void HUFFMAN::encode(string &data, string filename) { cout << "-------------------- Begin Encoding Data : " << filename << " --------------------" << endl; buildTree(data, filename); binaryStdOut = new BinaryStdOut(filename); //first of all, write our huffman tree writeTrie(huffman_Tree); //Then write the total length of our data binaryStdOut->write((int)data.length()); //Finally, write our compressed data for(string::iterator it = data.begin(); it != data.end(); it++) { char ch = *it; vector code = dict[ch]; for(vector::iterator codeit = code.begin(); codeit != code.end(); codeit++) { bool v = *codeit; binaryStdOut->write(v); } } binaryStdOut->close(); delete binaryStdOut; if(!huffman_Tree) destroy(huffman_Tree); cout << endl << "-------------------- Finish Encoding Data --------------------" << endl; } Htree * HUFFMAN::readTrie() { bool isLeaf = binaryStdIn->readBool(); if (isLeaf) { return new Htree(NULL, NULL, -1, binaryStdIn->readChar()); } else { return new Htree(readTrie(), readTrie(), -1, '\0'); } } string HUFFMAN::decode(string filename) { cout << endl << "-------------------- Begin Decoding Data --------------------" << endl; binaryStdIn = new BinaryStdIn(filename); huffman_Tree = readTrie(); int length = binaryStdIn->readInt(); string data; for (int i = 0; i < length; i++) { Htree *x = huffman_Tree; while (!isLeaf(x)) { bool bit = binaryStdIn->readBool(); if (bit) x = x->right; else x = x->left; } data += x->ch; } delete binaryStdIn; if(!huffman_Tree) destroy(huffman_Tree); cout << endl << "-------------------- Finish Decoding Data --------------------" << endl; return data; } /* * delete all the nodes * prevent memory leak */ void HUFFMAN::destroy(Htree *node) { if(node->left) destroy(node->left); if(node->right) destroy(node->right); delete node; } ================================================ FILE: C16-Greedy-Algorithms/huffman/HUFFMAN.h ================================================ /************************************************************************* > File Name: HUFFMAN.h > Author: > Mail: > Created Time: Mon 12 Jan 2015 11:20:02 PM CST ************************************************************************/ #include #include #include #include #include #include #include "BinaryStdIn.h" #include "BinaryStdOut.h" using namespace std; #ifndef _HUFFMAN_H #define _HUFFMAN_H struct Htree { Htree *left; Htree *right; int weight; char ch; Htree() {left = right = NULL; weight = 0;ch = 0;} Htree(Htree *l, Htree *r, int v, char c) {left = l; right = r; weight = v; ch = c;} }; // is the node a leaf node? static inline bool isLeaf(Htree *node) { assert ( (node->left == NULL && node->right == NULL) || (node->left != NULL && node->right != NULL) ); return (node->left == NULL && node->right == NULL); } class myComparision { public: bool operator () (const Htree* t1, const Htree* t2) { return t1->weight> t2->weight; } }; class HUFFMAN { typedef map > huffman_dict; Htree *huffman_Tree; huffman_dict dict; BinaryStdOut *binaryStdOut; BinaryStdIn *binaryStdIn; void buildTree(string &data, string filename); void buildDict(Htree *node, vector &code); void destroy(Htree *node); void writeTrie(Htree *node); Htree *readTrie(); public: HUFFMAN(); ~HUFFMAN(); void encode(string &data, string filename); string decode(string filename); }; #endif ================================================ FILE: C16-Greedy-Algorithms/huffman/binary_test/main.cpp ================================================ /************************************************************************* > File Name: main.cpp > Author: > Mail: > Created Time: Wed 14 Jan 2015 10:49:50 PM CST ************************************************************************/ #include #include "../BinaryStdIn.h" #include "../BinaryStdOut.h" using namespace std; int main() { string filename = "data"; BinaryStdOut binaryStdOut; binaryStdOut.open(filename); binaryStdOut.write('h'); binaryStdOut.write('e'); binaryStdOut.write('l'); binaryStdOut.write('l'); binaryStdOut.write('o'); binaryStdOut.write(true); binaryStdOut.write(false); binaryStdOut.write(30); binaryStdOut.write('p'); binaryStdOut.close(); BinaryStdIn binaryStdIn(filename); cout << binaryStdIn.readChar() << endl; cout << binaryStdIn.readChar() << endl; cout << binaryStdIn.readChar() << endl; cout << binaryStdIn.readChar() << endl; cout << binaryStdIn.readChar() << endl; cout << binaryStdIn.readBool() << endl; cout << binaryStdIn.readBool() << endl; cout << binaryStdIn.readInt() << endl; cout << binaryStdIn.readChar() << endl; return 0; } ================================================ FILE: C16-Greedy-Algorithms/huffman/binary_test/makefile ================================================ main : main.o BinaryStdOut.o BinaryStdIn.o g++ -o main main.o BinaryStdOut.o BinaryStdIn.o main.o : main.cpp ../BinaryStdOut.h ../BinaryStdIn.h g++ -c main.cpp BinaryStdOut.o : ../BinaryStdOut.h ../BinaryStdOut.cpp g++ -c ../BinaryStdOut.cpp BinaryStdIn.o : ../BinaryStdIn.h ../BinaryStdIn.cpp g++ -c ../BinaryStdIn.cpp clean : rm main main.o BinaryStdOut.o BinaryStdIn.o ================================================ FILE: C16-Greedy-Algorithms/huffman/binarystdin_test/main.cpp ================================================ /************************************************************************* > File Name: main.cpp > Author: > Mail: > Created Time: Wed 14 Jan 2015 08:33:11 PM CST ************************************************************************/ #include #include "../BinaryStdIn.h" using namespace std; int main() { string filename = "data"; BinaryStdIn binaryStdIn(filename); cout << binaryStdIn.readInt() << endl; binaryStdIn.close(); return 0; } ================================================ FILE: C16-Greedy-Algorithms/huffman/binarystdin_test/makefile ================================================ test_BinaryStdIn : main.o BinaryStdIn.o g++ -o test_BinaryStdIn main.o BinaryStdIn.o main.o : main.cpp ../BinaryStdIn.h g++ -c main.cpp BinaryStdIn.o : ../BinaryStdIn.h ../BinaryStdIn.cpp g++ -c -DDEBUG=1 ../BinaryStdIn.cpp clean : rm test_BinaryStdIn main.o BinaryStdIn.o ================================================ FILE: C16-Greedy-Algorithms/huffman/binarystdout_test/main.cpp ================================================ /************************************************************************* > File Name: main.cpp > Author: > Mail: > Created Time: Wed 14 Jan 2015 08:33:11 PM CST ************************************************************************/ #include #include "../BinaryStdOut.h" using namespace std; int main() { string filename = "data"; BinaryStdOut binaryStdOut(filename); //write 8 true binaryStdOut.write(true); binaryStdOut.write(true); binaryStdOut.write(true); binaryStdOut.write(true); binaryStdOut.write(true); binaryStdOut.write(true); binaryStdOut.write(true); binaryStdOut.write(true); //write 8 false binaryStdOut.write(false); binaryStdOut.write(false); binaryStdOut.write(false); binaryStdOut.write(false); binaryStdOut.write(false); binaryStdOut.write(false); binaryStdOut.write(false); binaryStdOut.write(false); //4 true , 4 false binaryStdOut.write(true); binaryStdOut.write(true); binaryStdOut.write(true); binaryStdOut.write(true); binaryStdOut.write(false); binaryStdOut.write(false); binaryStdOut.write(false); binaryStdOut.write(false); binaryStdOut.write('a'); binaryStdOut.write(false); binaryStdOut.write(512); binaryStdOut.close(); return 0; } ================================================ FILE: C16-Greedy-Algorithms/huffman/binarystdout_test/makefile ================================================ test_BinaryStdOut : main.o BinaryStdOut.o g++ -o test_BinaryStdOut main.o BinaryStdOut.o main.o : main.cpp ../BinaryStdOut.h g++ -c main.cpp BinaryStdOut.o : ../BinaryStdOut.h ../BinaryStdOut.cpp g++ -c -DDEBUG=1 ../BinaryStdOut.cpp clean : rm test_BinaryStdOut main.o BinaryStdOut.o ================================================ FILE: C16-Greedy-Algorithms/huffman/huffman_test/huffman_test_client.cpp ================================================ /************************************************************************* > File Name: huffman_test_client.cpp > Author: > Mail: > Created Time: Tue 13 Jan 2015 07:59:40 PM CST ************************************************************************/ #include #include "../HUFFMAN.h" using namespace std; int main() { string data = "3248&&&^aaaaaabbbbccccdddeef"; string filename = "compressed"; HUFFMAN huffman; huffman.encode(data, filename); string rdata = huffman.decode(filename); cout << "Recover data : " << rdata << endl; return 0; } ================================================ FILE: C16-Greedy-Algorithms/huffman/huffman_test/makefile ================================================ main : huffman_test_client.o HUFFMAN.o BinaryStdIn.o BinaryStdOut.o g++ -o main huffman_test_client.o HUFFMAN.o BinaryStdIn.o BinaryStdOut.o huffman_test_client.o : huffman_test_client.cpp ../HUFFMAN.h g++ -c huffman_test_client.cpp HUFFMAN.o : ../HUFFMAN.h ../HUFFMAN.cpp ../BinaryStdIn.h ../BinaryStdOut.h g++ -c -DDEBUG=1 ../HUFFMAN.cpp BinaryStdIn.o : ../BinaryStdIn.h ../BinaryStdIn.cpp g++ -c ../BinaryStdIn.cpp BinaryStdOut.o : ../BinaryStdOut.h ../BinaryStdOut.cpp g++ -c ../BinaryStdOut.cpp clean : rm main huffman_test_client.o HUFFMAN.o BinaryStdOut.o BinaryStdIn.o ================================================ FILE: C17-Amortized-Analysis/17.1.md ================================================ ### Exercise 17.1-1 *** If the set of stack operations included a MULTIPUSH operation, which pushes k items onto the stack, would the O(1) bound on the amortized cost of stack operations continue to hold? ### `Answer` No.The time complexity of such a series of operations depends on the number of push operation. Since one MULTIPUSH needs Θ(k)time, performing n MULTIPUSH operations, each with k elements, would take Θ(kn)time, leading to amortized cost of Θ(k). ### Exercise 17.1-2 *** Show that if a DECREMENT operation were included in the k-bit counter example, n operations could cost as much as theta(nk) time. ### `Answer` In the worst case, going from 1[k-1 0's] (ie. 1000 -> 0111) takes k flips. Could do any sequence of increment and decrement from 1000 -> 0111 -> 1000 -> 0111 (n times), which is theta(nk). ### Exercise 17.1-3 *** A sequence of n operations is performed on a data structure. The ith operation costs i if i is an exact power of 2, and 1 otherwise. Use aggregate analysis to determine the amortized cost per operation. ### `Answer` Look at a sequence of n operations. Let k = floor(lg(n)). All operations cost equal to 1 + 2 * (1 - 2^k) / (1 - 2) + n - k - 1 = 2 * 2^k + n - k - 2 = O(n), than each operation cost equal to O(n) / n = O(1). ================================================ FILE: C17-Amortized-Analysis/17.2.md ================================================ ### Exercise 17.2-1 *** Suppose we perform a sequence of stack operations on a stack whose size never exceeds k. After k operations, we make a copy of the entire stack for backup purposes. Show that the cost of n stack operations, including copying the stack is O(n) by assigning suitable amortized costs to the various stack operations. ### `Answer` Amortize cost is two units for every push and two for every pop. Using one unit to pay for the operation and another to keep in the pot. Thus, after every k operations, there are at least k units in the pot. Credit never goes negative, and the amortized complexity is O(n). ### Exercise 17.2-2 *** Redo Ex 17.1-3 using an accounting method of analysis. ### `Answer` Amortized cost of every operation = 3 units. One unit is used for that operation, one is stored as credit on that element and another one is stored as credit for the 2^i th operation. **NOTE : Refer to the dynamic tables for better understanding.** ### Exercise 17.2-3 Suppose we wish not only to increment a counter but also to reset it to zero (i.e., make all bits in it 0). Counting the time to examine or modify a bit as O(1), show how to implement a counter as an array of bits so that any sequence of n INCREMENT and RESET operations takes time O(n) on an initially zero counter. (Hint: Keep a pointer to the high-order 1.) *** ### `Answer` We introduce a new field A:max to hold the index of the high-order 1 in A. Initially, A:max is set to 1, since the low-order bit of A is at index 0, and there are initially no 1’s in A. The value of A:max is updated as appropriate when the counter is incremented or reset, and we use this value to limit how much of A must be looked at to reset it. By controlling the cost of RESET in this way, we can limit it to an amount that can be covered by credit from earlier INCREMENTs. As for the counter in the book, we assume that it costs $1 to flip a bit. In addition, we assume it costs $1 to update A:max. Setting and resetting of bits by INCREMENT will work exactly as for the original counter in the book: $1 will pay to set one bit to 1; $1 will be placed on the bit that is set to 1 as credit; the credit on each 1 bit will pay to reset the bit during incrementing. In addition, we’ll use $1 to pay to update max, and if max increases, we’ll place an additional $1 of credit on the new high-order 1. (If max doesn’t increase, we can just waste that $1—it won’t be needed.) Since RESET manipulates bits at positions only up to A:max, and since each bit up to there must have become the high-order 1 at some time before the high-order 1 got up to A:max, every bit seen by RESET has $1 of credit on it. So the zeroing of bits of A by RESET can be completely paid for by the credit stored on the bits. We just need $1 to pay for resetting max. Thus charging $4 for each INCREMENT and $1 for each RESET is sufficient, so the sequence of n INCREMENT and RESET operations takes O(n) time. ================================================ FILE: C17-Amortized-Analysis/17.3.md ================================================ ### Exercises 17.3-6 *** Show how to implement a queue with two ordinary stacks (Exercise 10.1-6) so that the amortized cost of each ENQUEUE and each DEQUEUE operation is O(1). ### `Answer` First stack is used for ENQUEUE operation, second stack is used for DEQUEUE operation. In ENQUEUE operation, we push the element into first stack. In DEQUEUE operation, if second stack is not empty, pop the elements in the top of second stack. If second is empty, pop all elements in first stack, and push them into second stack. Because this procedure will reverse the order of elements, we can directly pop elements in the top of second stack. Because all elements only push and pop only twice, the amortized cost for eace ENQUEUE and DEQUEUE operation is O(1) ================================================ FILE: C17-Amortized-Analysis/17.4.md ================================================ ### Exercise 17.4-3 *** Suppose that instead of contracting a table by halving its size when its load factor drops below 1/4, we contract it by multiplying its size by 2/3 when its load factor drops below 1/3. Using the potential function $$ Φ(T) = |2 · num[T] - size[T]| $$ , show that the amortized cost of a TABLE-DELETE that uses this strategy is bounded above by a constant. ### `Answer` If the i-th deletion does not lead to a contraction, we have \hat{c_i} = c_i + Φ_i - Φ_{i-1} = 1 + (size_i - 2 num_i) - (size_{i-1} - 2 num_{i-1}) = 1 + (size_i + 2 num_i ) - (sum_i - 2( num_i + 1)) = 3 else if the i-th deletion lead to a contraction, we have equation num_{i-1} = num_i + 1 = 1/3 size_{i-1} = 1/2 size_i so \hat{c_i} = c_i + Φ_i - Φ_{i-1} =(1 + num_i) + (size_i - 2 num_i) - (size_{i-1} - 2 num_{i-1}) = 2 Q.E.D. ================================================ FILE: C18-B-Trees/18.1.md ================================================ ### Exercises 18.1-1 *** Why don't we allow a minimum degree of t = 1? ### `Answer` According to the definition, minimum degree t means every node other than the root must have at least t – 1 keys, and every internal node other than the root thus has at least t children. So, when t = 1, it means every node other than the root must have at least t – 1 = 0 key, and every internal node other than the root thus has at least t = 1 child. Thus, we can see that the minimum case doesn't exist, because no node exists with 0 key, and no node exists with only 1 child in a B-tree. ### Exercises 18.1-2 *** For what values of t is the tree of Figure 18.1 a legal B-tree? ### `Answer` According to property 5 of B-tree, every node other than the root must have at least t−1keys and may contain at most 2t−1 keys. In Figure 18.1, the number of keys of each node (except the root) is either 2 or 3. So to make it a legal B-tree, we need to guarantee that t – 1 ≤ 2 and 2 t – 1 ≥ 3, which yields 2 ≤ t ≤ 3. So t can be 2 or 3. ### Exercises 18.1-3 *** Show all legal B-trees of minimum degree 2 that represent {1, 2, 3, 4, 5} ### `Answer` The question asks for the legal trees with min degree t=2. So, each node can contain x num of keys while 1 ≤ x ≤ 3. Since each internal node that has x num of keys also has x+1 num of children (property 2), the root can only have 1 or 2 keys, the root can not have 3 keys since it must have 4 children and we only have 5 keys to draw. If the root has 1 key there are following variations. ``` 2 1 3 4 5 ``` ``` 4 1 2 3 5 ``` ``` 3 1 2 4 5 ``` If the root has 2 keys there is only one possibility. ``` 2 4 1 3 5 ``` ### Exercises 18.1-4 *** As a function of the minimum degree t, what is the maximum number of keys that can be stored in a B-tree of height h? ### `Answer` ![](./repo/s1/2.png) ### Exercises 18.1-5 *** Describe the data structure that would result if each black node in a red-black tree were to absorb its red children, incorporating their children with its own. ### `Answer` After absorbing each red node into its black parent, each black node may contain 1, 2 (1 red child), or 3 (2 red children) keys, and all leaves of the resulting tree have the same depth, according to property 5 of red-black tree (For each node, all paths from the node to descendant leaves contain the same number of black nodes). Therefore, a red-black tree will become a Btree with minimum degree t = 2, i.e., a 2-3-4 tree. *** Follow [@louis1992](https://github.com/gzc) on github to help finish this task. ================================================ FILE: C18-B-Trees/18.2.md ================================================ ### Exercises 18.2-1 *** Show the results of inserting the keys F, S, Q, K, C, L, H, T, V, W, M, R, N, P, A, B, X, Y, D, Z, E in order into an empty B-tree with minimum degree 2. Only draw the configurations of the tree just before some node must split, and also draw the final configuration. ### `Answer` ![](./repo/s2/1.png) ### Exercises 18.2-2 *** Explain under what circumstances, if any, redundant DISK-READ or DISK-WRITE operations are performed during the course of executing a call to B-TREE-INSERT. (A redundant DISK-READ is a DISK-READ for a page that is already in memory. A redundant DISK-WRITE writes to disk a page of information that is identical to what is already stored there.) ### `Answer` In order to insert the key into a full child node but without its parent being full, we need the following operations: * DISK-READ: Key placement * DISK-WRITE: Split nodes * DISK-READ: Get to the parent * DISK-WRITE: Fill parent If both were full, we'd have to do the same, but instead of the final step, repeat the above to split the parent node and write into the child nodes. With both considerations in mind, there should never be a redundant DISK-READ or DISK-WRITE on a B-TREE-INSERT. ### Exercises 18.2-3 *** Explain how to find the minimum key stored in a B-tree and how to find the predecessor of a given key stored in a B-tree. ### `Answer` Finding the minimum in a B-tree is quite similar to finding a minimum in a binary search tree. We need to find the left most leaf for the given root, and return the first key. Finding the predecessor of a given key x.keyi is according to the following rules: * If x is not a leaf, return the maximum key in the i-th child of x, which is also the maximum key of the subtree rooted at x.ci * If x is a leaf and i > 1, return the (i–1)st key of x, i.e., x.keyi–1 * Otherwise, look for the last node y (from the bottom up) and j > 0, such that x.keyi is the leftmost key in y.cj; if j = 1, return NIL since x.keyi is the minimum key in the tree; otherwise we return y.keyj–1. [implementation](./btree.cpp) ### Exercises 18.2-4 * *** Suppose that the keys {1, 2, ..., n} are inserted into an empty B-tree with minimum degree 2. How many nodes does the final B-tree have? ### `Answer` I find the answer `n - 2lg(n+1)` in the Internet, but don't know why. n | node :----:|:----: 1 | 1 2 | 1 3 | 1 4 | 3 5 | 3 6 | 4 7 | 4 8 | 5 9 | 7 10 | 8 ### Exercises 18.2-5 *** Since leaf nodes require no pointers to children, they could conceivably use a different (larger) t value than internal nodes for the same disk page size. Show how to modify the procedures for creating and inserting into a B-tree to handle this variation. ### `Answer` we could set the new t(name it t') value of leaf node = 1.5t. ### Exercises 18.2-6 *** Since leaf nodes require no pointers to children, they could conceivably use a different (larger) t value than internal nodes for the same disk page size. Show how to modify the procedures for creating and inserting into a B-tree to handle this variation. ### `Answer` ![](./repo/s2/2.png) ### Exercises 18.2-7 *** Suppose that disk hardware allows us to choose the size of a disk page arbitrarily, but that the time it takes to read the disk page is a + bt, where a and b are specified constants and t is the minimum degree for a B-tree using pages of the selected size. Describe how to choose t so as to minimize (approximately) the B-tree search time. Suggest an optimal value of t for the case in which a = 5 milliseconds and b = 10 microseconds. ### `Answer` For a B-tree with n number of keys, B-TREE-SEARCH performs at most ![\log_{t}{n}](https://render.githubusercontent.com/render/math?math=%5Clog_%7Bt%7D%7Bn%7D) disk accesses, each access takes (a + bt) IO time. Since for each internal node x, x.n < 2t, it takes at most 2t - 1 compares within each node. The final objective function is Objective: minimize ![\log_{t}{n}](https://render.githubusercontent.com/render/math?math=%5Clog_%7Bt%7D%7Bn%7D) * (IO Time of each access + ((2t - 1) * CPU time of each compare)) The problem doesn't give the corresponding CPU time for each compares but since modern computer IO operation is much more expensive than the CPU operations we can ignore the second part, hence resulting Objective: minimize ![\log_{t}{n}](https://render.githubusercontent.com/render/math?math=%5Clog_%7Bt%7D%7Bn%7D) * IO Time = ![\log_{t}{n}*(a+bt)](https://render.githubusercontent.com/render/math?math=%5Clog_%7Bt%7D%7Bn%7D*(a%2Bbt)) = ![\frac{\ln{n}}{\ln{t}}(a+bt)](https://render.githubusercontent.com/render/math?math=%5Cfrac%7B%5Cln%7Bn%7D%7D%7B%5Cln%7Bt%7D%7D(a%2Bbt)) => minimize ![\frac{a+bt}{\ln{t}}](https://render.githubusercontent.com/render/math?math=%5Cfrac%7Ba%2Bbt%7D%7B%5Cln%7Bt%7D%7D) [Taking derivative of ![\frac{a+bt}{\ln{t}}](https://render.githubusercontent.com/render/math?math=%5Cfrac%7Ba%2Bbt%7D%7B%5Cln%7Bt%7D%7D)](https://www.wolframalpha.com/input/?i=derivative+of+%28a%2Bbt%29%2Fln+t+respect+to+t&assumption=%22UnitClash%22+-%3E+%7B%22t%22%2C+%7B%22MetricTons%22%7D%7D&assumption=%7B%22C%22%2C+%22t%22%7D+-%3E+%7B%22Variable%22%7D) we get ![\frac{a+bt-bt*\ln{t}}{t*ln^{2}{t}}](https://render.githubusercontent.com/render/math?math=%5Cfrac%7Ba%2Bbt-bt*%5Cln%7Bt%7D%7D%7Bt*ln%5E%7B2%7D%7Bt%7D%7D). Since t > 1 we only need to find root of ![a+bt-bt*\ln{t}](https://render.githubusercontent.com/render/math?math=a%2Bbt-bt*%5Cln%7Bt%7D). ![a+bt=bt*\ln{t}](https://render.githubusercontent.com/render/math?math=a%2Bbt%3Dbt*%5Cln%7Bt%7D) => ![t=e^{W(\frac{a}{be})+1}](https://render.githubusercontent.com/render/math?math=t%3De%5E%7BW(%5Cfrac%7Ba%7D%7Bbe%7D)%2B1%7D) where W is the LambertW function when a = 5, b = 10, t is around [3.18](https://www.wolframalpha.com/input/?i=e%5E%281+%2B+ProductLog%281%2F%282+e%29%29%29&assumption=%22ClashPrefs%22+-%3E+%7B%22Math%22%7D), the optimal value of t is 3 in this case. *** Follow [@louis1992](https://github.com/gzc) on github to help finish this task. ================================================ FILE: C18-B-Trees/18.3.md ================================================ ### Exercises 18.3-1 *** Show the results of deleting C, P, and V , in order, from the tree of Figure 18.8(f). ### `Answer` LPTX AEJK NO QRS UV YZ LQTX AEJK NO RS UV YZ LQX AEJK NO RSTU YZ ### Exercises 18.3-2 *** Write pseudocode for B-TREE-DELETE. ### `Answer` * [C++ implementation](./btree.cpp) * [Python implementation](./btree.py) *** Follow [@louis1992](https://github.com/gzc) on github to help finish this task. ================================================ FILE: C18-B-Trees/btree.cpp ================================================ #include #include #include using namespace std; //#define DEBUG 1 /************************************************************************ * * * I provided these API * * 1. B_tree_insert(T k) * * 2. PrintTree(int kind) * * 3. get_minimum() * * 4. get_maximum() * * 5. search(T k) * * 6. bool remove(const T &key) * ************************************************************************/ template class Btree { private: static const int t = 3; struct Node { bool isLeaf; int n; T keyValue[t+1]; Node *pChild[2*t+1]; Node(bool b=true, int _n=0) : isLeaf(b), n(_n){} }; Node *bRoot; //statistics int nodeNum; public: Btree() { nodeNum = 0; B_tree_create(); } void viewStatistics() { cout << "-----------------------------Statistics-----------------------------" << endl; cout << "BUILD " << nodeNum << " nodes" << endl; cout << "-----------------------------Statistics-----------------------------" << endl; } void B_tree_insert(T k) { Node *r = bRoot; if (r->n == 2*t - 1) { Node *s = allocate_node(); bRoot = s; s->isLeaf = false; s->pChild[1] = r; B_tree_split_child(s, 1, r); B_tree_insert_nonfull(s, k); } else { B_tree_insert_nonfull(r, k); } } /* * 1 means integer * 2 means character */ void PrintTree(int kind = 1) const //打印树的关键字 { queue myqueue; myqueue.push(bRoot); int depth = 0; while(!myqueue.empty()) { queue nextlevel; cout << "depth : " << depth << endl; while(!myqueue.empty()) { Node* temp = myqueue.front(); myqueue.pop(); cout << "["; for(int i = 1;i <= temp->n;i++) { if(kind == 2)cout << (char)temp->keyValue[i] << " "; else cout << temp->keyValue[i] << " "; } cout << "]"; if(!temp->isLeaf) for(int i = 1;i <= temp->n+1;i++) nextlevel.push(temp->pChild[i]); } cout << endl; depth++; myqueue = nextlevel; } } T get_minimum() const { return B_TREE_FIND_MIN(bRoot); } T get_maximum() const { return B_TREE_FIND_MAX(bRoot); } bool search(T k) { int index = 0; Node *temp = B_tree_search(bRoot, k, &index); if(temp != nullptr) return true; return false; } //just test predecessor /* T get_root_pre() { return B_tree_find_predecessor(bRoot, 1); } */ bool remove(const T &key) //从B中删除结点key { if (!search(key)) //不存在 { return false; } if (bRoot->n == 1 && bRoot->isLeaf)//特殊情况处理 { clear(); return true; } recursive_remove(bRoot, key); return true; } void clear() //清空B树 { recursive_clear(bRoot); bRoot = nullptr; } //删除节点 void deleteNode(Node *pNode) { if (pNode != nullptr) { delete pNode; pNode = nullptr; } } private: Node *allocate_node() { Node *node = new Node(); nodeNum++; #ifdef DEBUG cout << "allocate a new node" << endl; #endif return node; } void disk_write(Node *x) const { #ifdef DEBUG cout << "write disk" << endl; #endif } void disk_read(Node *x) const { #ifdef DEBUG cout << "read disk" << endl; #endif } void B_tree_create() { bRoot = allocate_node(); disk_write(bRoot); } void B_tree_split_child(Node *x, int i, Node *y) { Node *z = allocate_node(); z->isLeaf = y->isLeaf; z->n = t - 1; for(int j = 1; j <= t-1; j++) z->keyValue[j] = y->keyValue[j+t]; if (!y->isLeaf) for(int j = 1;j <= t;j++) z->pChild[j] = y->pChild[j+t]; y->n = t - 1; for(int j = x->n+1;j >= i+1;j--) x->pChild[j+1] = x->pChild[j]; x->pChild[i+1] = z; for(int j = x->n;j >= i;j--) x->keyValue[j+1] = x->keyValue[j]; x->keyValue[i] = y->keyValue[t]; x->n++; disk_write(y); disk_write(z); disk_write(x); } void B_tree_insert_nonfull(Node *x, T k) { int i = x->n; if(x->isLeaf) { while(i >= 1 && k < x->keyValue[i]) { x->keyValue[i+1] = x->keyValue[i]; i--; } x->keyValue[i+1] = k; x->n++; disk_write(x); } else { while(i >= 1 && k < x->keyValue[i]) i--; i++; disk_read(x->pChild[i]); if(x->pChild[i]->n == 2*t - 1) { B_tree_split_child(x, i, x->pChild[i]); if(k > x->keyValue[i]) i++; } B_tree_insert_nonfull(x->pChild[i], k); } } /* *find the minimum key in btree */ T B_TREE_FIND_MIN(Node *x) const //PRE: x is a node on the B-tree T. The top level call is B-TREE-FIND-MIN(T.root). { if (x == nullptr) { cerr << "The tree is empty" << endl; return -1; } else if (x->isLeaf) //x is leaf { return x->keyValue[1]; //return the minimum key of x } else { disk_read(x->pChild[1]); return B_TREE_FIND_MIN(x->pChild[1]); } } /* *find the maximum key in btree */ T B_TREE_FIND_MAX(Node *x) const { if (x == nullptr) { cerr << "The tree is empty" << endl; return -1; } else if (x->isLeaf) //x is leaf { return x->keyValue[x->n]; //return the minimum key of x } else { disk_read(x->pChild[x->n+1]); return B_TREE_FIND_MAX(x->pChild[x->n+1]); } } T B_tree_find_predecessor(Node *x, int i) { if(!x->isLeaf) { disk_read(x->pChild[i]); return B_TREE_FIND_MAX(x->pChild[i]); } else if(i > 1){ return x->keyValue[i-1]; } else { Node *z = x; stack mystack; buildPath(bRoot, x->keyValue[i], mystack); while(1) { if(mystack.empty()) { cerr << "No predecessor"; return -1; } Node *y = mystack.top(); mystack.pop(); int j = 1; disk_read(y->pChild[1]); while(y->pChild[j] != x) { j++; disk_read(y->pChild[j]); } if(j == 1) z = y; else return y->keyValue[j-1]; } } } void buildPath(Node *x, T k, stack& mystack) { int i = 1; while(i <= x->n && k > x->keyValue[i]) i++; if (i <= x->n && k == x->keyValue[i]) return; if (x->isLeaf) return; else { disk_read(x->pChild[i]); mystack.push(x); buildPath(x->pChild[i], k, mystack); } } Node* B_tree_search(Node *x, T k, int *index) { int i = 1; while(i <= x->n && k > x->keyValue[i]) i++; if (i <= x->n && k == x->keyValue[i]) { *index = i; return x; } if (x->isLeaf) return nullptr; else { disk_read(x->pChild[i]); return B_tree_search(x->pChild[i], k, index); } } //删除树 void recursive_clear(Node *pNode) { if (pNode != nullptr) { if (!pNode->isLeaf) { for(int i = 1; i <= pNode->n + 1; ++i) recursive_clear(pNode->pChild[i]); } deleteNode(pNode); } } //递归的删除关键字 void recursive_remove(Node *pNode, const T &key) { int i = 1; while(i <= pNode->n && key > pNode->keyValue[i]) ++i; if (i <= pNode->n && key == pNode->keyValue[i])//关键字key在节点pNode中 { if (pNode->isLeaf)//pNode是个叶节点 { //从pNode中删除k pNode->n--; for (; i <= pNode->n; ++i) pNode->keyValue[i] = pNode->keyValue[i+1]; return; } else//pNode是个内节点 { Node *pChildPrev = pNode->pChild[i];//节点pNode中前于key的子节点 Node *pChildNext = pNode->pChild[i+1];//节点pNode中后于key的子节点 if (pChildPrev->n >= t)//节点pChildPrev中至少包含CHILD_MIN个关键字 { T prevKey = getPredecessor(pChildPrev); //获取key的前驱关键字 recursive_remove(pChildPrev, prevKey); pNode->keyValue[i] = prevKey; //替换成key的前驱关键字 return; } else if (pChildNext->n >= t)//节点pChildNext中至少包含CHILD_MIN个关键字 { T nextKey = getSuccessor(pChildNext); //获取key的后继关键字 recursive_remove(pChildNext, nextKey); pNode->keyValue[i] = nextKey; //替换成key的后继关键字 return; } else//节点pChildPrev和pChildNext中都只包含CHILD_MIN-1个关键字 { mergeChild(pNode, i); recursive_remove(pChildPrev, key); } } } else//关键字key不在节点pNode中 { Node *pChildNode = pNode->pChild[i];//包含key的子树根节点 if (pChildNode->n == t-1)//只有t-1个关键字 { Node *pLeft = i > 1 ? pNode->pChild[i-1] : NULL; //左兄弟节点 Node *pRight = i <= pNode->n ? pNode->pChild[i+1] : NULL;//右兄弟节点 int j; if (pLeft && pLeft->n >= t)//左兄弟节点至少有CHILD_MIN个关键字 { //父节点中i-1的关键字下移至pChildNode中 for (j = pChildNode->n+1; j > 1; --j) { pChildNode->keyValue[j] = pChildNode->keyValue[j-1]; } pChildNode->keyValue[1] = pNode->keyValue[i-1]; if (!pLeft->isLeaf) { for (j=pChildNode->n+2; j > 1; --j) //pLeft节点中合适的子女指针移植到pChildNode中 { pChildNode->pChild[j] = pChildNode->pChild[j-1]; } pChildNode->pChild[1] = pLeft->pChild[pLeft->n]; } ++pChildNode->n; pNode->keyValue[i] = pLeft->keyValue[pLeft->n];//pLeft节点中的最大关键字上升到pNode中 --pLeft->n; } else if (pRight && pRight->n >= t)//右兄弟节点至少有CHILD_MIN个关键字 { //父节点中i的关键字下移至pChildNode中 pChildNode->keyValue[pChildNode->n+1] = pNode->keyValue[i]; ++pChildNode->n; pNode->keyValue[i] = pRight->keyValue[1];//pRight节点中的最小关键字上升到pNode中 --pRight->n; for (j = 1; j <= pRight->n; ++j) { pRight->keyValue[j] = pRight->keyValue[j+1]; } if (!pRight->isLeaf) { pChildNode->pChild[pChildNode->n+1] = pRight->pChild[1];//pRight节点中合适的子女指针移植到pChildNode中 for (j = 1; j <= pRight->n+1; ++j) { pRight->pChild[j] = pRight->pChild[j+1]; } } } //左右兄弟节点都只包含CHILD_MIN-1个节点 else if (pLeft)//与左兄弟合并 { mergeChild(pNode, i-1); pChildNode = pLeft; } else if (pRight)//与右兄弟合并 { mergeChild(pNode, i); } } recursive_remove(pChildNode, key); } } //合并两个子节点 void mergeChild(Node *pParent, int index) { Node *pChild1 = pParent->pChild[index]; Node *pChild2 = pParent->pChild[index+1]; //将pChild2数据合并到pChild1 pChild1->n = 2*t-1; pChild1->keyValue[t] = pParent->keyValue[index];//将父节点index的值下移 for (int i = 1; i <= t-1; ++i) pChild1->keyValue[t+i] = pChild2->keyValue[i]; if (!pChild1->isLeaf) for (int i = 1; i <= t; ++i) pChild1->pChild[i+t] = pChild2->pChild[i]; //父节点删除index的key,index后的往前移一位 pParent->n--; for(int i = index; i <= pParent->n; ++i) { pParent->keyValue[i] = pParent->keyValue[i+1]; pParent->pChild[i+1] = pParent->pChild[i+2]; } deleteNode(pChild2); //删除pChild2 if(pParent->n == 0) { deleteNode(pParent); if (pParent == bRoot){ bRoot = pChild1; } } } T getPredecessor(Node *pNode)//找到前驱关键字 { while (!pNode->isLeaf) { pNode = pNode->pChild[pNode->n+1]; } return pNode->keyValue[pNode->n]; } T getSuccessor(Node *pNode)//找到后继关键字 { while (!pNode->isLeaf) { pNode = pNode->pChild[1]; } return pNode->keyValue[1]; } }; int main() { Btree tree; for(int i = 1;i < 20;i++) { tree.B_tree_insert(i); //cout << " n = " << i << endl; //tree.viewStatistics(); } tree.remove(19); tree.PrintTree(); //cout << "The minimum key is : " << tree.get_minimum() << endl; //cout << "The maximum key is : " << tree.get_maximum() << endl; //cout << "The pre of the root is : " << tree.get_root_pre() << endl; /* bool fff = tree.search(5); if(fff) { cout << "find 5 in the tree " << endl; } */ return 0; } ================================================ FILE: C18-B-Trees/btree.py ================================================ import bisect # See Cormen et al, chapter 18 B-Trees # This is an implementation of B-Tree with satellite information stored in internal nodes # as well as in leaves. def main(): def allocme(): nonlocal blocks idx = len(blocks) blocks.append(None) print("a new block #{} has been allocated.".format(idx)) return idx def freeme(page, root): print("the block #{} has been deallocated".format(page)) nonlocal blocks nonlocal treeroot blocks[page] = None if (treeroot != root): print("new root is: #{}".format(root)) treeroot = root def writeme(page, root, *node): # page - what allocme() returns, # treeroot - the reference to the root of the btree # *node - serialized key-value # pairs of BTree node's params (keys) print("write me to block #{}: ".format(page), node) nonlocal blocks nonlocal treeroot blocks[page] = node treeroot = root def readme(page): # page - what allocme() returns. The method must return keys, childpages # if no leaf, childpage[i] with keys < keys[i], childpages[i+1] with keys > keys[i] nonlocal blocks data = blocks[page] print("read me from block #{}: ".format(page), data[0], data[1]) return data[0], data[1] blocks = [] treeroot = 0 # check the page format # blocks.append(([('a',), ('b',)], [1])) # print(blocks) # tree = BTree(t = 4, allocate = allocme, read = readme, write = writeme) # tree.load(treeroot) # tree.insert('c', 10) # print(blocks) tree = BTree(allocate = allocme) tree.create() tree.create() tree.create() print(blocks) print(treeroot) blocks = [] treeroot = 0 tree2 = BTree(t = 2, allocate = allocme, free = freeme, read = readme, write = writeme) tree2.create() ins = tree2.insert('a', 10) print("'a' inserted:", ins) ins = tree2.insert('a', 11) print("'a' inserted:", ins) value = tree2.find('a') print("value for 'a':", value) ins = tree2.insert('a', 1) print("'a' inserted:", ins) tree2.insert('b', 2) tree2.insert('c', 3) # firstly, split root here tree2.insert('f', 6) print(blocks) print(treeroot) # insert in a non full node tree2.insert('d', 4) # insert in a full node tree2.insert('e', 5) print(blocks) print(treeroot) ins = tree2.insert('g', 7) print(ins) print(blocks) print(treeroot) ins = tree2.insert('g', 8) print(ins) print(blocks) print(treeroot) ins = tree2.insert('g', 9) print(ins) print(blocks) print(treeroot) tree2.insert('h', 10) tree2.insert('k', 11) tree2.insert('l', None) print(blocks) print(treeroot) tree3 = BTree(t = 2, allocate = allocme, free = freeme, read = readme, write = writeme) tree3.load(treeroot) tree3.insert('m', 13) print(blocks) print(treeroot) # let's find something print("find nodes") tree4 = BTree(t = 2, allocate = allocme, free = freeme, read = readme, write = writeme) tree4.load(treeroot) print("value for 'a':", tree4.find('a'), "(True, 1)") print("value for 'b':", tree4.find('b'), "(True, 2)") print("value for 'c':", tree4.find('c'), "(True, 3)") print("value for 'd':", tree4.find('d'), "(True, 4)") print("value for 'e':", tree4.find('e'), "(True, 5)") print("value for 'g':", tree4.find('g'), "(True, 9)") print("value for 'h':", tree4.find('h'), "(True, 10)") print("value for 'k':", tree4.find('k'), "(True, 11)") print("value for 'm':", tree4.find('m'), "(True, 13)") print("value for 'l':", tree4.find('l'), "(True, None)") print("value for 'f':", tree4.find('f'), "(True, 6)") print("value for 'z':", tree4.find('z'), "(False, None)") print(blocks) print(treeroot) tree4.insert('o', 14) tree4.insert('p', 15) tree4.insert('n', 16) print(blocks) print(treeroot) print("remove something") print("removing 'g'") # 3a, 3b removed = tree4.remove('g') print("removing 'g', removed:", removed) print(blocks) print(treeroot) print("removing 'e'") # 3b, 3a removed = tree4.remove('e') print("removing 'e', removed:", removed) print(blocks) print(treeroot) print("removing 'o'") # 2a removed = tree4.remove('o') print("removing 'o', removed:", removed) print(blocks) print(treeroot) print("removing 'a'") # 3a 3b (preparing for 2c, see bellow) removed = tree4.remove('a') print("removing 'a', removed:", removed) print(blocks) print(treeroot) print("removing 'h'") # 2b removed = tree4.remove('h') print("removing 'h', removed:", removed) print(blocks) print(treeroot) print("removing root") print("removing 'f'") # changing root removed = tree4.remove('f') print("removing 'f', removed:", removed) print(blocks) print(treeroot) print("removing 'z'") removed = tree4.remove('z') print("removing 'z', removed:", removed) print(blocks) print(treeroot) print("removing 'b'") removed = tree4.remove('b') print("removing 'b', removed:", removed) print(blocks) print(treeroot) print("removing 'd'") # 2c removed = tree4.remove('d') print("removing 'd', removed:", removed) print(blocks) print(treeroot) class BTree: def __init__(self, t = 2, allocate = None, free = None, read = None, write = None): """ Constructs a new btree with the minimum degree (t) = 't'. The methods of the btree are going to use the 'allocate', 'write' and 'read' methods to work with the storage the tree is persisted to. Args: ----- t (int) - the minimum degree of the btree (see the definition of b-trees in Cormen et al, 18.1) allocate (function) - a callback that allocates a new storage page to be used as a new node. free (function) - a callback that removes pages from the storage read (function) - a callback that reads pages from the storage. write (function) - a callback that writes pages into the storage. """ self.__allocate = allocate self.__free = free self.__write = write self.__read = read self.__t = t # TODO 't' can be calculated based on page-size, key-size, ptr-size and satellite-data-size self.__root = None def find(self, key): """ Takes as input a key 'key' to be searched for in this tree. If 'key' is in the tree, the method returns 'True' and the value associated with the key. Otherwise, the method returns 'False, None'. Args: ----- key - an input keey to be searched for in the tree. Returns: -------- 'found, value' where 'found' (True/False) whether the key is in the tree, 'value' is the value associated with the key 'key'. Exceptions: ----------- BTreeNotInitializedError if the method is invoked on an unitialized (created or loaded) btree. BTreeInvalidNodeError while a btree that doesn't have btree property five described in Cormen et al 18.1: (t - the minimum degree of the btree) the root may contain at most '2t - 1' keys, if the root is an internal node, it may have at most '2t' children. """ if not self.__root: raise BTreeNotInitializedError() return self.__find(self.__root, key) def create(self): """ Creates an empty root node for the tree. To build a b-tree, we first use the method to create an empty root node and then call 'insert' to add new keys. Both of these methods use an auxiliary function 'allocate', a parameter of the object 'self'. """ del self.__root self.__root = self.Node(self.__t, self.__allocate()) return def load(self, root): """ Loads the root of the tree. Before searching through the tree, the root must be loaded into the main memory otherwise a BTreeNotInitializedError will be raised. The method uses an auxiliary function 'read' to load pages into the main memory. Args: ----- root - a reference to the root in the storage. The reference must be matched with the value returned by the axiliary function 'allocate' during the invocation of the 'create' method. Exceptions: ----------- BTreeInvalidNodeError while a btree that doesn't have btree property five described in Cormen et al 18.1: (t - the minimum degree of the btree) the root may contain at most '2t - 1' keys, if the root is an internal node, it may have at most '2t' children. """ del self.__root self.__root = self.Node(self.__t, root) self.__read_page(self.__root, True) def insert(self, key, value): """ Inserts the new key 'key' and associated with it satellite data 'value' in a leaf as well as an intermediate node. So, the simplest btree (not a b+ tree) strategy is implemented. Value 'value' can be any satellite data, but take into account the data will be stored in pages increasing by themselves the minimum degree of the btree (t) and thus the fragmentation. 'None' cannot be used as a key. 'None' can be used as a value but the method 'find' returns 'None' if the key is out of the tree. Args: ----- key - a key for the being stored value 'value'. value - a value associated with the key. Returns: -------- 'True' if the key has actually been added to the tree, 'False' otherwise but the value for the key will be overwritten in any case. Exceptions: ----------- BTreeNotInitializedError if the method is invoked on an unitialized (created or loaded) btree. BTreeInvalidNodeError while a btree that doesn't have btree property five described in Cormen et al 18.1: (t - the minimum degree of the btree) every node other than the root must have at least 't - 1' keys. Every internal node other than the root thus has at least 't' children. Every node may have at most '2t - 1' keys and if it is an internal node, it may have at most '2t' children. """ if not self.__root: raise BTreeNotInitializedError() if self.__root.full(): self.__split_root() return self.__insert_in_nonfull(self.__root, key, value) def remove(self, key): """ Removes the key 'key' from the tree. Before searching through the tree, the root must be loaded into the main memory otherwise a BTreeNotInitializedError will be raised. The method uses the following auxiliary functions: 'read' to load pages into the main memory, 'write' to save changed pages into the storage, and 'free' to remove a page from the storage. Args: ----- key - a key for being removed. Returns: -------- 'True' if the key has actually been removed from the tree, 'False' otherwise. Exceptions: ----------- BTreeNotInitializedError if the method is invoked on an unitialized (created or loaded) btree. BTreeInvalidNodeError while a btree that doesn't have btree property five described in Cormen et al 18.1: (t - the minimum degree of the btree) the root may contain at most '2t - 1' keys, if the root is an internal node, it may have at most '2t' children. """ if not self.__root: raise BTreeNotInitializedError() result = self.__remove(self.__root, key) if self.__root.empty(): exroot = self.__root self.load(exroot.major().page()) # invariant: root is always in the mm self.__free_page(exroot) return result def __find(self, node, key): found, value, child = node.find(key) self.__flush_page(node) if not found and not node.leaf(): self.__read_page(child) return self.__find(child, key) return found, value def __split_root(self): exroot = self.__root newroot = self.Node(self.__t, self.__allocate(), False) newroot.insert(None, None, exroot) _, newright = self.__split_child(newroot, exroot) self.__root = newroot self.__flush_page(exroot) self.__flush_page(newright) def __insert_in_nonfull(self, node, key, value): dest = node while not dest.leaf(): _, _, child = dest.find(key) self.__read_page(child) if child.full(): median, next = self.__split_child(dest, child) if key > median: self.__flush_page(child) child = next else: self.__flush_page(next) self.__flush_page(dest) # both pages: dest and its child must be keep # loaded in the main memory during a split dest = child result = dest.insert(key, value) self.__write_page(dest) self.__flush_page(dest) return result def __split_child(self, node, child): # returns median, newnode if node.leaf(): return None, None newnode = self.Node(self.__t, self.__allocate(), child.leaf()) median = child.split(node, newnode) self.__write_page(node) self.__write_page(child) self.__write_page(newnode) return median, newnode def __remove(self, node, key): found, value, child = node.find(key) if found: if node.leaf(): # case 1 result = node.remove(key) else: self.__read_page(child) if child.hasminimum(): # case 2a newkey, newvalue = self.__remove_predecessor(child) result = node.replace(key, newkey, newvalue) else: _, follow = node.siblings(key) _, _, follow = follow self.__read_page(follow) if follow.hasminimum(): # case 2b self.__flush_page(child) newkey, newvalue = self.__remove_successor(follow) result = node.replace(key, newkey, newvalue) else: # case 2c child.join(key, value, follow) # special case, we will go into recursion thus all pages must be flushed (exc. child) self.__write_page(child) self.__free_page(follow) node.remove(key) self.__write_page(node) self.__flush_page(node) return self.__remove(child, key) self.__write_page(node) self.__flush_page(node) return result else: if node.leaf(): # do nothing, the key is out of the tree return False self.__read_page(child) if not child.hasminimum(): left, right = node.siblings(key) child = self.__extend_child(node, child, left, right) # the left sibling can be the new child self.__flush_page(node) return self.__remove(child, key) def __remove_predecessor(self, node): # returns the predecessor key,value current = node while not current.leaf(): right_child = current.minor() self.__read_page(right_child) if not right_child.hasminimum(): left_sibling = current.second_rightest() right_child = self.__extend_child(current, right_child, left = left_sibling) self.__flush_page(current) current = right_child right_key, right_value = current.remove_rightest() self.__write_page(current) self.__flush_page(current) return right_key, right_value def __remove_successor(self, node): # returns the successor key,value current = node while not current.leaf(): left_child = current.major() self.__read_page(left_child) if not left_child.hasminimum(): right_sibling = current.second_leftest() left_child = self.__extend_child(current, left_child, right = right_sibling) self.__flush_page(current) current = left_child left_key, left_value = current.remove_leftest() self.__write_page(current) self.__flush_page(current) return left_key, left_value def __extend_child(self, node, child, left = None, right = None): good_sibling = None if left: leftkey, leftval, left = left self.__read_page(left) if left and left.hasminimum(): good_sibling = left sibling_extra = left.rightest() remove_right = True # rightest() returns a reference to the right parentkey, parentval = leftkey, leftval else: if right: rightkey, rightval, right = right self.__read_page(right) if right and right.hasminimum(): if left: self.__flush_page(left) good_sibling = right sibling_extra = right.leftest() remove_right = False # leftest() returns a reference to the left parentkey, parentval = rightkey, rightval if good_sibling: # case 3a siblkey, siblval, siblchild = sibling_extra child.insert(parentkey, parentval, siblchild) node.replace(parentkey, siblkey, siblval) good_sibling.remove(siblkey, remove_right = remove_right) self.__write_page(good_sibling) self.__flush_page(good_sibling) self.__write_page(child) else: # case 3b if right: child.join(rightkey, rightval, right) node.remove(rightkey) self.__write_page(child) self.__free_page(right) if left: self.__flush_page(left) else: if left: left.join(leftkey, leftval, child) node.remove(leftkey) self.__write_page(left) self.__free_page(child) child = left # will find in the left sibling instead of child if right: self.__flush_page(right) self.__write_page(node) return child def __write_page(self, node): self.__write(node.page(), # page returned by __allocate() self.__root.page(), # special reference to root node.entries(), # key/value pairs as an array list(map(lambda n: n.page(), node.children()))) # child pages - references to the pages in the store def __read_page(self, node, root = False): # reads keys, values and references (page()) to children and builds the node in the main memory keyvalues, childpages = self.__read(node.page()) # keyvalues [0..n], childpages [0..n+1], childpages[i] for keys < key[i], # childpages[i+1] for keys > key[i], if childpages = None or [] - leaf node.read(not childpages or len(childpages) == 0, # is leaf root, # is root keyvalues, # key/value pairs list(map(lambda p: self.Node(self.__t, p), childpages))) # children def __free_page(self, node): self.__flush_page(node) self.__free(node.page(), self.__root.page()) def __flush_page(self, node): # removes the node from the main memory. We also can use LRU for the hottest nodes # convention: root should always be in the main memory if self.__root != node: node.free() class Node: def __init__(self, t, page, leaf = True): self.__t = t self.__page = page self.__leaf = leaf # len(__values) == len(__keys), by the same index, if no value for a key - None # for non leaf: len(__children) == len(__keys) + 1, idx = index in __keys for a key # __children[key] - the left child of the node for key (< key), # __children[key+1] - the right one (> key) self.__keys = [] self.__values = [] self.__children = [] def leaf(self): return self.__leaf def empty(self): return len(self.__keys) == 0 def full(self): return len(self.__keys) == 2 * self.__t - 1 def hasminimum(self): return len(self.__keys) >= self.__t def page(self): return self.__page def entries(self): return list(zip(self.__keys, self.__values)) def children(self): return self.__children def major(self): # major is the oldest children return self.__children[0] if not self.leaf() and len(self.__children) > 0 else None def minor(self): # minor is the youngest children return self.__children[-1] if not self.leaf() and len(self.__children) > 0 else None def leftest(self): # returns the first key, his value and the reference to the page with keys < the first one # (if the node is not a leaf, otherwise the reference is None). # If the node has only one key, the leftest can be equal to the rightest. key = self.__keys[0] if len(self.__keys) > 0 else None value = self.__values[0] if len(self.__values) > 0 else None child = self.major() return key, value, child def second_leftest(self): # returns the first key, his value and the reference to the page with keys > the # first one and < the key after the first one (if the node is not a leaf, otherwise the reference # is None). The difference between the 'leftest' and 'second_leftest' methods is only the reference # to a child page, the key and value are the same. key = self.__keys[0] if len(self.__keys) > 0 else None value = self.__values[0] if len(self.__values) > 0 else None child = self.__children[1] if not self.leaf() and len(self.__children) > 1 else None return key, value, child def rightest(self): # returns the last key, his value and the reference to the page with keys > the last one # (if the node is not a leaf, otherwise the reference is None). # if the node has only one key, the leftest can be equal to the rightest. key = self.__keys[-1] if len(self.__keys) > 0 else None value = self.__values[-1] if len(self.__values) > 0 else None child = self.minor() return key, value, child def second_rightest(self): # returns the last key, his value and the reference to the page with # keys < the last one and > the key just before the last one (if the node is not a leaf, # otherwise the reference is None). The difference between the 'rightest' and 'second_rightest' # methods is only the reference to a child page, the key and value are the same. key = self.__keys[-1] if len(self.__keys) > 0 else None value = self.__values[-1] if len(self.__values) > 0 else None child = self.__children[-2] if not self.leaf() and len(self.__children) > 1 else None return key, value, child def find(self, key): # binary-searches for the key 'key' in the node and returns # whether the key is in the node at all, the value associates with the key, # and the child (Node) that is staying for the 'key'. idx = self.__position(key) if idx == len(self.__keys) or self.__keys[idx] != key: value = None found = False else: value = self.__values[idx] found = True child = self.__children[idx] if not self.leaf() else None return found, value, child def siblings(self, key): # binary-searches for the key 'key' in the node and returns # the left key, his value, and immediate left sibling as a tuple # along with the right key, hist value, and immediate right sibling as another tuple # of the child that is staying for the 'key'. If there is not a sibling, returns 'None' # for the tuple this direction. idx = self.__position(key) left = (self.__keys[idx - 1], self.__values[idx - 1], self.__children[idx - 1]) if idx > 1 else None right = (self.__keys[idx], self.__values[idx], self.__children[idx + 1]) if idx + 1 < len(self.__children) else None return left, right def insert(self, key, value, right = None): result = False if key: idx = self.__position(key) if len(self.__keys) < idx + 1 or self.__keys[idx] != key: self.__keys.insert(idx, key) self.__values.insert(idx, value) result = True else: # replace the value self.__values[idx] = value else: idx = 0 if not self.leaf() and right: if len(self.__children) < idx + 2 or self.__children[idx] != right: self.__children.insert(idx + 1, right) return result def remove_leftest(self): # removes the leftest key, value and child and returns the removed key and value # the complexity is O(len(__keys)) since the first element will be removed key = value = None if len(self.__keys) > 0: key = self.__keys[0] self.__keys.pop(0) if len(self.__values) > 0: value = self.__values[0] self.__values.pop(0) if not self.leaf() and len(self.__children) > 0: self.__children.pop(0) return key, value def remove_rightest(self): # removes the rightest key, valye and child and returns the removed key and value # the compexity is O(1) since the last element will be removed key = value = None if len(self.__keys) > 0: key = self.__keys[-1] self.__keys.pop() if len(self.__values) > 0: value = self.__values[-1] self.__values.pop() if not self.leaf() and len(self.__children) > 0: self.__children.pop() return key, value def remove(self, key, remove_right = True): if not key: return False idx = self.__position(key) if len(self.__keys) < idx + 1 or self.__keys[idx] != key: return False self.__keys.pop(idx) self.__values.pop(idx) if not self.leaf(): idx = idx + 1 if remove_right else idx # if remove_right - remove the right reference otherwise - the left one self.__children.pop(idx) return True def replace(self, key, newkey, newvalue): if not key: return False idx = self.__position(key) if len(self.__keys) > idx and self.__keys[idx] == key: self.__keys[idx] = newkey self.__values[idx] = newvalue return True return False def split(self, parent, newnode): # copy a half of keys and children to newnode median_key = self.__keys[self.__t - 1] median_value = self.__values[self.__t - 1] newnode.__keys = self.__keys[self.__t:] newnode.__values = self.__values[self.__t:] self.__keys = self.__keys[:self.__t - 1] self.__values = self.__values[:self.__t - 1] if not self.leaf(): newnode.__children = self.__children[self.__t:] self.__children = self.__children[:self.__t] # insert the median and newnode to the parent node parent.insert(median_key, median_value, newnode) # if key > median_key, welcome to newnode return median_key def join(self, newkey, newvalue, right = None): # join newkey/newvalue and keys/values from the right node ('right) to the RIGHT of 'self' self.__keys.append(newkey) self.__values.append(newvalue) if right: self.__keys.extend(right.__keys) self.__values.extend(right.__values) if not self.leaf() and not right.leaf(): self.__children.extend(right.__children) def read(self, leaf, root, keyvalues, children): if len(keyvalues) > 2 * self.__t - 1: raise BTreeInvalidNodeError("a page may have at most '2t - 1' ({}) keys but has {}." .format(2 * self.__t - 1, len(keyvalues)), self.__page) if not root and len(keyvalues) < self.__t - 1: raise BTreeInvalidNodeError("the page is not the root; therefore, it may have at least "\ "'t - 1' ({}) keys but has only {}." .format(self.__t - 1, len(keyvalues)), self.__page) if not leaf: if len(children) != len(keyvalues) + 1: raise BTreeInvalidNodeError("every internal node may have a number of children ({}) "\ "exactly equals to a number of keys ({}) plus one." .format(len(children), len(keyvalues)), self.__page) self.__leaf = leaf self.__keys = list(map(lambda p: p[0], keyvalues)) self.__values = list(map(lambda p: p[1] if len(p) > 1 else None, keyvalues)) self.__children = children def free(self): del self.__keys self.__keys = [] del self.__children self.__children = [] def __position(self, key): # binary-searches for the index for the key 'key' in the array of keys (O(lg n)). return bisect.bisect_left(self.__keys, key) class BTreeNotInitializedError(Exception): def __init__(self): super(BTreeNotInitializedError, self).__init__("create a new or load an existing "\ "btree to start working with it.") class BTreeInvalidNodeError(Exception): def __init__(self, warning, page): super(BTreeInvalidNodeError, self).__init__(warning) self.__page = page def page(self): return self.__page if __name__ == "__main__": main() ================================================ FILE: C19-Binomial-Heaps/19.1.md ================================================ ### Exercises 19.1-1 *** Suppose that x is a node in a binomial tree within a binomial heap, and assume that sibling[x] ≠ NIL. If x is not a root, how does degree[sibling[x]] compare to degree[x]? How about if x is a root? ### `Answer` * If x is not a root, degree[sibling[x]] < degree[x] * If x is a root, degree[sibling[x]] > degree[x] ### Exercises 19.1-2 *** If x is a nonroot node in a binomial tree within a binomial heap, how does degree[x] compare to degree[p[x]]? ### `Answer` degree[p[x]] > degree[x] ### Exercises 19.1-3 *** Suppose we label the nodes of binomial tree Bk in binary by a postorder walk, as in Figure 19.4. Consider a node x labeled l at depth i, and let j = k - i. Show that x has j 1's in its binary representation. How many binary k-strings are there that contain exactly j 1's? Show that the degree of x is equal to the number of 1's to the right of the rightmost 0 in the binary representation of l. ### `Answer` *** Follow [@louis1992](https://github.com/gzc) on github to help finish this task. ================================================ FILE: C19-Binomial-Heaps/19.2.md ================================================ ### Exercises 19.2-1 *** Write pseudocode for BINOMIAL-HEAP-MERGE. ### `Answer` [implementation](./BinomialHeap.h) ### Exercises 19.2-2 *** Show the binomial heap that results when a node with key 24 is inserted into the binomial heap shown in Figure 19.7(d). ### `Answer` straightforward ### Exercises 19.2-3 *** Show the binomial heap that results when the node with key 28 is deleted from the binomial heap shown in Figure 19.8(c). ### `Answer` straightforward ### Exercises 19.2-4 *** Argue the correctness of BINOMIAL-HEAP-UNION using the following loop invariant: At the start of each iteration of the while loop of lines 9-21, x points to a root that is one of the following: 1. the only root of its degree, 2. the first of the only two roots of its degree, or 3. the first or second of the only three roots of its degree. Moreover, all roots preceding x's predecessor on the root list have unique degrees on the root list, and if x's predecessor has a degree different from that of x, its degree on the root list is unique, too. Finally, node degrees monotonically increase as we traverse the root list. ### `Answer` straightforward ### Exercises 19.2-5 *** Explain why the BINOMIAL-HEAP-MINIMUM procedure might not work correctly if keys can have the value ∞. Rewrite the pseudocode to make it work correctly in such cases. ### `Answer` If keys can have the value ∞, then the initial value of min can not compare with that. So we could set min to the first value of binomial heap by default. ### Exercises 19.2-6 *** Suppose there is no way to represent the key -∞. Rewrite the BINOMIAL-HEAP-DELETE procedure to work correctly in this situation. It should still take O(lg n) time. ### `Answer` ### Exercises 19.2-7 *** Discuss the relationship between inserting into a binomial heap and incrementing a binary number and the relationship between uniting two binomial heaps and adding two binary numbers. ### `Answer` It's similar, first we add one '1', if the original number in the position is '1' too, then we carry '1' to preceeding bit. ### Exercises 19.2-8 *** In light of Exercise 19.2-7, rewrite BINOMIAL-HEAP-INSERT to insert a node directly into a binomial heap without calling BINOMIAL-HEAP-UNION. ### `Answer` straightforward. just do "addtion" ### Exercises 19.2-9 *** Show that if root lists are kept in strictly decreasing order by degree (instead of strictly increasing order), each of the binomial heap operations can be implemented without changing its asymptotic running time. ### `Answer` ### Exercises 19.2-10 *** Find inputs that cause BINOMIAL-HEAP-EXTRACT-MIN, BINOMIAL-HEAP- DECREASE-KEY, and BINOMIAL-HEAP-DELETE to run in Ω(lg n) time. Explain why the worst-case running times of BINOMIAL-HEAP-INSERT, BINOMIAL-HEAP-MINIMUM, and BINOMIAL-HEAP-UNION are  but not Ω(lg n). (See Problem 3-5.) ### `Answer` *** Follow [@louis1992](https://github.com/gzc) on github to help finish this task. ================================================ FILE: C19-Binomial-Heaps/BinomialHeap.h ================================================ /** * C++: 二项堆 * * @author skywang * @date 2014/04/02 * @reference http://www.cnblogs.com/xuqiang/archive/2011/06/01/2065549.html */ #ifndef _BINOMIAL_TREE_HPP_ #define _BINOMIAL_TREE_HPP_ #include #include using namespace std; template class BinomialNode { public: T key; // 关键字(键值) int degree; // 度数 BinomialNode *child; // 左孩子 BinomialNode *parent; // 父节点 BinomialNode *next; // 兄弟节点 BinomialNode(T value):key(value), degree(0), child(NULL),parent(NULL),next(NULL) {} }; template class BinomialHeap { private: BinomialNode *mRoot; // 根结点 public: BinomialHeap(); ~BinomialHeap(); // 新建key对应的节点,并将其插入到二项堆中 void insert(T key); // 将二项堆中键值oldkey更新为newkey void update(T oldkey, T newkey); // 删除键值为key的节点 void remove(T key); // 移除二项堆中的最小节点 void extractMinimum(); // 将other的二项堆合并到当前二项堆中 void combine(BinomialHeap* other); // 获取二项堆中的最小节点的键值 T minimum(); // 二项堆中是否包含键值key bool contains(T key); // 打印二项堆 void print(); private: // 合并两个二项堆:将child合并到root中 void link(BinomialNode* child, BinomialNode* root); // 将h1, h2中的根表合并成一个按度数递增的链表,返回合并后的根节点 BinomialNode* merge(BinomialNode* h1, BinomialNode* h2); // 合并二项堆:将h1, h2合并成一个堆,并返回合并后的堆 BinomialNode* combine(BinomialNode* h1, BinomialNode* h2); // 反转二项堆root,并返回反转后的根节点 BinomialNode* reverse(BinomialNode* root); // 移除二项堆root中的最小节点,并返回删除节点后的二项树 BinomialNode* extractMinimum(BinomialNode* root); // 删除节点:删除键值为key的节点,并返回删除节点后的二项树 BinomialNode* remove(BinomialNode *root, T key); // 在二项树root中查找键值为key的节点 BinomialNode* search(BinomialNode* root, T key); // 增加关键字的值:将二项堆中的节点node的键值增加为key。 void increaseKey(BinomialNode* node, T key); // 减少关键字的值:将二项堆中的节点node的键值减小为key void decreaseKey(BinomialNode* node, T key); // 更新关键字的值:更新二项堆的节点node的键值为key void updateKey(BinomialNode* node, T key); // 获取二项堆中的最小根节点 void minimum(BinomialNode* root, BinomialNode *&prev_y, BinomialNode *&y); // 打印二项堆 void print(BinomialNode* node, BinomialNode* prev, int direction); }; /* * 构造函数 */ template BinomialHeap::BinomialHeap():mRoot(NULL) { } /* * 析构函数 */ template BinomialHeap::~BinomialHeap() { } /* * 获取二项堆中的最小根节点 * * 参数说明: * root -- 二项堆 * prev_y -- [输出参数]最小根节点y的前一个根节点 * y -- [输出参数]最小根节点 */ template void BinomialHeap::minimum(BinomialNode* root, BinomialNode *&prev_y, BinomialNode *&y) { BinomialNode *x, *prev_x; // x是用来遍历的当前节点 if (root==NULL) return ; prev_x = root; x = root->next; prev_y = NULL; y = root; // 找到最小节点 while (x != NULL) { if (x->key < y->key) { y = x; prev_y = prev_x; } prev_x = x; x = x->next; } } /* * 获取二项堆中的最小节点的键值 */ template T BinomialHeap::minimum() { BinomialNode *prev_y, *y; minimum(mRoot, prev_y, y); return y->key; } /* * 合并两个二项堆:将child合并到root中 */ template void BinomialHeap::link(BinomialNode* child, BinomialNode* root) { child->parent = root; child->next = root->child; root->child = child; root->degree++; } /* * 将h1, h2中的根表合并成一个按度数递增的链表,返回合并后的根节点 */ template BinomialNode* BinomialHeap::merge(BinomialNode* h1, BinomialNode* h2) { BinomialNode* root = NULL; //heap为指向新堆根结点 BinomialNode** pos = &root; while (h1 && h2) { if (h1->degree < h2->degree) { *pos = h1; h1 = h1->next; } else { *pos = h2; h2 = h2->next; } pos = &(*pos)->next; } if (h1) *pos = h1; else *pos = h2; return root; } /* * 合并二项堆:将h1, h2合并成一个堆,并返回合并后的堆 */ template BinomialNode* BinomialHeap::combine(BinomialNode* h1, BinomialNode* h2) { BinomialNode *root; BinomialNode *prev_x, *x, *next_x; // 将h1, h2中的根表合并成一个按度数递增的链表root root = merge(h1, h2); if (root == NULL) return NULL; prev_x = NULL; x = root; next_x = x->next; while (next_x != NULL) { if ( (x->degree != next_x->degree) || ((next_x->next != NULL) && (next_x->degree == next_x->next->degree))) { // Case 1: x->degree != next_x->degree // Case 2: x->degree == next_x->degree == next_x->next->degree prev_x = x; x = next_x; } else if (x->key <= next_x->key) { // Case 3: x->degree == next_x->degree != next_x->next->degree // && x->key <= next_x->key x->next = next_x->next; link(next_x, x); } else { // Case 4: x->degree == next_x->degree != next_x->next->degree // && x->key > next_x->key if (prev_x == NULL) { root = next_x; } else { prev_x->next = next_x; } link(x, next_x); x = next_x; } next_x = x->next; } return root; } /* * 将二项堆other合并到当前堆中 */ template void BinomialHeap::combine(BinomialHeap *other) { if (other!=NULL && other->mRoot!=NULL) mRoot = combine(mRoot, other->mRoot); } /* * 新建key对应的节点,并将其插入到二项堆中。 */ template void BinomialHeap::insert(T key) { BinomialNode* node; // 禁止插入相同的键值 if (contains(key)) { cout << "Insert Error: the key (" << key << ") is existed already!" << endl; return ; } node = new BinomialNode(key); if (node==NULL) return ; mRoot = combine(mRoot, node); } /* * 反转二项堆root,并返回反转后的根节点 */ template BinomialNode* BinomialHeap::reverse(BinomialNode* root) { BinomialNode* next; BinomialNode* tail = NULL; if (!root) return root; root->parent = NULL; while (root->next) { next = root->next; root->next = tail; tail = root; root = next; root->parent = NULL; } root->next = tail; return root; } /* * 移除二项堆root中的最小节点,并返回删除节点后的二项树 */ template BinomialNode* BinomialHeap::extractMinimum(BinomialNode* root) { BinomialNode *y, *prev_y; // y是最小节点 if (root==NULL) return root; // 找到"最小节点根y"和"它的前一个根节点prev_y" minimum(root, prev_y, y); if (prev_y == NULL) // root的根节点就是最小根节点 root = root->next; else // root的根节点不是最小根节点 prev_y->next = y->next; // 反转最小节点的左孩子,得到最小堆child; // 这样,就使得最小节点所在二项树的孩子们都脱离出来成为一棵独立的二项树(不包括最小节点) BinomialNode* child = reverse(y->child); // 将"删除最小节点的二项堆child"和"root"进行合并。 root = combine(root, child); // 删除最小节点 delete y; return root; } template void BinomialHeap::extractMinimum() { mRoot = extractMinimum(mRoot); } /* * 减少关键字的值:将二项堆中的节点node的键值减小为key。 */ template void BinomialHeap::decreaseKey(BinomialNode* node, T key) { if(key>=node->key || contains(key)) { cout << "decrease failed: the new key(" << key <<") is existed already, " << "or is no smaller than current key(" << node->key <<")" << endl; return ; } node->key = key; BinomialNode *child, *parent; child = node; parent = node->parent; while(parent != NULL && child->key < parent->key) { swap(parent->key, child->key); child = parent; parent = child->parent; } } /* * 增加关键字的值:将二项堆中的节点node的键值增加为key。 */ template void BinomialHeap::increaseKey(BinomialNode* node, T key) { if(key<=node->key || contains(key)) { cout << "decrease failed: the new key(" << key <<") is existed already, " << "or is no greater than current key(" << node->key <<")" << endl; return ; } node->key = key; BinomialNode *cur, *child, *least; cur = node; child = cur->child; while (child != NULL) { if(cur->key > child->key) { // 如果"当前节点" < "它的左孩子", // 则在"它的孩子中(左孩子 和 左孩子的兄弟)"中,找出最小的节点; // 然后将"最小节点的值" 和 "当前节点的值"进行互换 least = child; while(child->next != NULL) { if (least->key > child->next->key) { least = child->next; } child = child->next; } // 交换最小节点和当前节点的值 swap(least->key, cur->key); // 交换数据之后,再对"原最小节点"进行调整,使它满足最小堆的性质:父节点 <= 子节点 cur = least; child = cur->child; } else { child = child->next; } } } /* * 更新二项堆的节点node的键值为key */ template void BinomialHeap::updateKey(BinomialNode* node, T key) { if (node == NULL) return ; if(key < node->key) decreaseKey(node, key); else if(key > node->key) increaseKey(node, key); else cout <<"No need to update!!!" < void BinomialHeap::update(T oldkey, T newkey) { BinomialNode *node; node = search(mRoot, oldkey); if (node != NULL) updateKey(node, newkey); } /* * 查找:在二项堆中查找键值为key的节点 */ template BinomialNode* BinomialHeap::search(BinomialNode* root, T key) { BinomialNode *child; BinomialNode *parent = root; parent = root; while (parent != NULL) { if (parent->key == key) return parent; else { if((child = search(parent->child, key)) != NULL) return child; parent = parent->next; } } return NULL; } /* * 二项堆中是否包含键值key */ template bool BinomialHeap::contains(T key) { return search(mRoot, key)!=NULL ? true : false; } /* * 删除节点:删除键值为key的节点 */ template BinomialNode* BinomialHeap::remove(BinomialNode* root, T key) { BinomialNode *node; BinomialNode *parent, *prev, *pos; if (root==NULL) return root; // 查找键值为key的节点 if ((node = search(root, key)) == NULL) return root; // 将被删除的节点的数据数据上移到它所在的二项树的根节点 parent = node->parent; while (parent != NULL) { // 交换数据 swap(node->key, parent->key); // 下一个父节点 node = parent; parent = node->parent; } // 找到node的前一个根节点(prev) prev = NULL; pos = root; while (pos != node) { prev = pos; pos = pos->next; } // 移除node节点 if (prev) prev->next = node->next; else root = node->next; root = combine(root, reverse(node->child)); delete node; return root; } template void BinomialHeap::remove(T key) { mRoot = remove(mRoot, key); } /* * 打印"二项堆" * * 参数说明: * node -- 当前节点 * prev -- 当前节点的前一个节点(父节点or兄弟节点) * direction -- 1,表示当前节点是一个左孩子; * 2,表示当前节点是一个兄弟节点。 */ template void BinomialHeap::print(BinomialNode* node, BinomialNode* prev, int direction) { while(node != NULL) { if(direction==1) // node是根节点 cout << "\t" << setw(2) << node->key << "(" << node->degree << ") is "<< setw(2) << prev->key << "'s child" << endl; else // node是分支节点 cout << "\t" << setw(2) << node->key << "(" << node->degree << ") is "<< setw(2) << prev->key << "'s next" << endl; if (node->child != NULL) print(node->child, node, 1); // 兄弟节点 prev = node; node = node->next; direction = 2; } } template void BinomialHeap::print() { BinomialNode *p; if (mRoot == NULL) return ; cout << "== 二项堆( "; p = mRoot; while (p != NULL) { cout << "B" << p->degree << " "; p = p->next; } cout << ")的详细信息:" << endl; int i=0; p = mRoot; while (p != NULL) { i++; cout << i << ". 二项树B" << p->degree << ":" << endl; cout << "\t" << setw(2) << p->key << "(" << p->degree << ") is root" << endl; print(p->child, p, 1); p = p->next; } cout << endl; } #endif ================================================ FILE: C19-Binomial-Heaps/Main.cpp ================================================ /** * C 语言: 二项堆 * * @author skywang * @date 2014/04/02 */ #include #include "BinomialHeap.h" using namespace std; #define DEBUG 0 // 共7个 = 1+2+4 int a[] = {12, 7, 25, 15, 28, 33, 41}; // 共13个 = 1+4+8 int b[] = {18, 35, 20, 42, 9, 31, 23, 6, 48, 11, 24, 52, 13 }; // 验证"二项堆的插入操作" void testInsert() { int i; int alen=sizeof(a)/sizeof(a[0]); BinomialHeap* ha=new BinomialHeap(); cout << "== 二项堆(ha)中依次添加: "; for(i=0; iinsert(a[i]); } cout << endl; cout << "== 二项堆(ha)的详细信息: " << endl; ha->print(); } // 验证"二项堆的合并操作" void testUnion() { int i; int alen=sizeof(a)/sizeof(a[0]); int blen=sizeof(b)/sizeof(b[0]); BinomialHeap* ha=new BinomialHeap(); BinomialHeap* hb=new BinomialHeap(); cout << "== 二项堆(ha)中依次添加: "; for(i=0; iinsert(a[i]); } cout << endl; cout << "== 二项堆(ha)的详细信息: " << endl; ha->print(); cout << "== 二项堆(hb)中依次添加: "; for(i=0; iinsert(b[i]); } cout << endl; cout << "== 二项堆(hb)的详细信息: " << endl; hb->print(); // 将"二项堆hb"合并到"二项堆ha"中。 ha->combine(hb); cout << "== 合并ha和hb后的详细信息: " << endl; ha->print(); } // 验证"二项堆的删除操作" void testDelete() { int i; int blen=sizeof(b)/sizeof(b[0]); BinomialHeap* hb=new BinomialHeap(); cout << "== 二项堆(hb)中依次添加: "; for(i=0; iinsert(b[i]); } cout << endl; cout << "== 二项堆(hb)的详细信息: " << endl; hb->print(); // 将"二项堆hb"合并到"二项堆ha"中。 hb->remove(20); cout << "== 删除节点20后的详细信息: " << endl; hb->print(); } // 验证"二项堆的更新(减少)操作" void testDecrease() { int i; int blen=sizeof(b)/sizeof(b[0]); BinomialHeap* hb=new BinomialHeap(); cout << "== 二项堆(hb)中依次添加: "; for(i=0; iinsert(b[i]); } cout << endl; cout << "== 二项堆(hb)的详细信息: " << endl; hb->print(); // 将节点20更新为2 hb->update(20, 2); cout << "== 更新节点20->2后的详细信息: " << endl; hb->print(); } // 验证"二项堆的更新(增加)操作" void testIncrease() { int i; int blen=sizeof(b)/sizeof(b[0]); BinomialHeap* hb=new BinomialHeap(); cout << "== 二项堆(hb)中依次添加: "; for(i=0; iinsert(b[i]); } cout << endl; cout << "== 二项堆(hb)的详细信息: " << endl; hb->print(); // 将节点6更新为60 hb->update(6, 60); cout << "== 更新节点6->60后的详细信息: " << endl; hb->print(); } int main() { // 1. 验证"二项堆的插入操作" testInsert(); // 2. 验证"二项堆的合并操作" //testUnion(); // 3. 验证"二项堆的删除操作" //testDelete(); // 4. 验证"二项堆的更新(减少)操作" //testDecrease(); // 5. 验证"二项堆的更新(增加)操作" //testIncrease(); return 0; } ================================================ FILE: C21-Data-Structures-for-Disjoint-Sets/21.1.md ================================================ ### Exercises 21.1-1 *** Suppose that CONNECTED-COMPONENTS is run on the undirected graph G = (V, E), where V = {a, b, c, d, e, f, g, h, i, j, k} and the edges of E are processed in the following order: (d, i), (f, k), (g, i), (b, g), (a, h), (i, j), (d, k), (b, j), (d, f), (g, j), (a, e), (i, d). List the vertices in each connected component after each iteration of lines 3-5. ### `Answer` {a} {b} {c} {d} {e} {f} {g} {h} {i} {j} {k} 1. (d,i) => {a} {b} {c} {d i} {e} {f} {g} {h} {j} {k} 2. (f,k) => {a} {b} {c} {d i} {e} {f k} {g} {h} {j} 3. (g,i) => {a} {b} {c} {d g i} {e} {f k} {h} {j} 4. (b,g) => {a} {b d g i} {c} {e} {f k} {h} {j} 5. (a,h) => {a h} {b d g i} {c} {e} {f k} {j} 6. (i,j) => {a h} {b d g i j} {c} {e} {f k} 7. (d,k) => {a h} {b d g i j f k} {c} {e} 8. (b,j) => {a h} {b d g i j f k} {c} {e} 9. (d,f) => {a h} {b d g i j f k} {c} {e} 10. (g,j) => {a h} {b d g i j f k} {c} {e} 11. (a,e) => {a e h} {b d g i j f k} {c} ### Exercises 21.1-2 *** Show that after all edges are processed by CONNECTED-COMPONENTS, two vertices are in the same connected component if and only if they are in the same set. ### `Answer` If two vertices are not in the same set, meaning that no edge connecting the set of one vertice with the other. So two vertices are in the same connected component if and only if they are in the same set. ### Exercises 21.1-3 *** During the execution of CONNECTED-COMPONENTS on an undirected graph G = (V, E) with k connected components, how many times is FIND-SET called? How many times is UNION called? Express your answers in terms of |V|, |E|, and k. ### `Answer` * FIND-SET : 2|E| * UNION : |V| - k *** Follow [@louis1992](https://github.com/gzc) on github to help finish this task. ================================================ FILE: C21-Data-Structures-for-Disjoint-Sets/21.2.md ================================================ ### Exercises 21.2-1 *** Write pseudocode for MAKE-SET, FIND-SET, and UNION using the linked-list representation and the weighted-union heuristic. Assume that each object x has an attribute rep[x] pointing to the representative of the set containing x and that each set S has attributes head[S], tail[S], and size[S] (which equals the length of the list). ### `Answer` MAKE-SET(x): Initialize a new linked list Insert node x FIND-SET(x): return rep[x] UNION(x, y): Let smallist,biglist be the list with less and larger list according to size[x],size[y] put smallist to the tail in biglist ### Exercises 21.2-2 *** Show the data structure that results and the answers returned by the FIND-SET operations in the following program. Use the linked-list representation with the weighted-union heuristic. for i <- 1 to 16 do MAKE-SET(x_i) for i <- 1 to 15 by 2 do UNION(x_i,x_i+1) for i <- 1 to 13 by 4 do UNION(x_i, x_i+2) UNION(x1,x5) UNION(x11,x13) UNION(x1,x_10) FIND-SET(x2) FIND-SET(x9) Assume that if the sets containing xi and xj have the same size, then the operation UNION(xi, xj) appends xj's list onto xi's list. ### `Answer` All x are in the same set, all return 1. ### Exercises 21.2-3 *** Adapt the aggregate proof of Theorem 21.1 to obtain amortized time bounds of O(1) for MAKE-SET and FIND-SET and O(lg n) for UNION using the linked-list representation and the weighted-union heuristic. ### `Answer` MAKE-SET and FIND-SET can do in O(1) time, because MAKE-SET just initialize a new linked list and FIND-SET just return the representative of a linked list. Theorem 21.1 said it took O(nlogn) time to update n objects, so in average take O(logn) time. ### Exercises 21.2-4 *** Give a tight asymptotic bound on the running time of the sequence of operations in Figure 21.3 assuming the linked-list representation and the weighted-union heuristic. ### `Answer` T = O(n) + O(n) = O(n) This example is the best case, because in each UNION, we only move one element. ### Exercises 21.2-5 *** Suggest a simple change to the UNION procedure for the linked-list representation that removes the need to keep the tail pointer to the last object in each list. Whether or not the weighted-union heuristic is used, your change should not change the asymptotic running time of the UNION procedure. (Hint: Rather than appending one list to another, splice them together.) ### `Answer` For each member of the set, we will make its first field which used to point back to the set object point instead to the last element of the linked list. Then, given any set, we can find its last element by going ot the head and following the pointer that that object maintains to the last element of the linked list. This only requires following exactly two pointers, so it takes a constant amount of time. Some care must be taken when unioning these modified sets. Since the set representative is the last element in the set, when we combine two linked lists, we place the smaller of the two sets before the larger, since we need to update their set representative pointers, unlike the original situation, where we update the representative of the objects that are placed on to the end of the linked list. *** Follow [@louis1992](https://github.com/gzc) on github to help finish this task. ================================================ FILE: C21-Data-Structures-for-Disjoint-Sets/21.3.md ================================================ ### Exercises 21.3-1 *** Do Exercise 21.2-2 using a disjoint-set forest with union by rank and path compression. ### `Answer`
					1
				
			/		|	\	   				\	
			2		3    5					9
			
					|	/ \			/    /		 \
					4   6 7		   10   11		 13
					
						  |				 |	   /   \
	   					  8  			12    14   15
	   					  
	   					  						    |
	   					  						    16
### Exercises 21.3-2 *** Write a nonrecursive version of FIND-SET with path compression. ### `Answer` [implementation](./uf.cpp) ### Exercises 21.3-3 *** Give a sequence of m MAKE-SET, UNION, and FIND-SET operations, n of which are MAKE-SET operations, that takes Ω(m lg n) time when we use union by rank only. ### `Answer` [reference](http://www.cs.toronto.edu/~avner/teaching/263/A/4sol.pdf) ![](./repo/s3/1.png) ### Exercises 21.3-4 * *** Show that any sequence of m MAKE-SET, FIND-SET, and LINK operations, where all the LINK operations appear before any of the FIND-SET operations, takes only O(m) time if both path compression and union by rank are used. What happens in the same situation if only the path-compression heuristic is used? ### `Answer` In addition to each tree, we’ll store a linked list (whose set object contains a single tail pointer) with which keeps track of all the names of elements in the tree. The only additional information we’ll store in each node is a pointer `x.l` to that element’s position in the list. When we call MAKE-SET(x), we’ll also create a new linked list, insert the label of x into the list, and set `x.l` to a pointer to that label. This is all done in `O(1)`. FIND-SET will remain unchanged. UNION(x,y) will work as usual, with the additional requirement that we union the linked lists of x and y. Since we don’t need to update pointers to the head, we can link up the lists in constant time, thus preserving the runtime of UNION. Finally, PRINT-SET(x) works as follows: first, set s = FIND-SET(x). Then print the elements in the linked list, starting with the element pointed to by x. (This will be the first element in the list). Since the list contains the same number of elements as the set and printing takes `O(1)`, this operation takes linear time in the number of set members. *** Follow [@louis1992](https://github.com/gzc) on github to help finish this task. ================================================ FILE: C21-Data-Structures-for-Disjoint-Sets/problem.md ================================================ ### Problems 1 : Off-line minimum *** The off-line minimum problem asks us to maintain a dynamic set T of elements from the domain {1, 2, ..., n} under the operations INSERT and EXTRACT-MIN. We are given a sequence S of n INSERT and m EXTRACT-MIN calls, where each key in {1, 2, ..., n} is inserted exactly once. We wish to determine which key is returned by each EXTRACT-MIN call. Specifically, we wish to fill in an array extracted[1..m], where for i = 1, 2, ..., m, extracted[i] is the key returned by the ith EXTRACT-MIN call. The problem is "off-line" in the sense that we are allowed to process the entire sequence S before determining any of the returned keys. a. In the following instance of the off-line minimum problem, each INSERT is represented by a number and each EXTRACT-MIN is represented by the letter E: 4, 8, E, 3, E, 9, 2, 6, E, E, E, 1, 7, E, 5. Fill in the correct values in the extracted array. To develop an algorithm for this problem, we break the sequence S into homogeneous subsequences. That is, we represent S by I1, E, I2, E, I3, ..., Im, E, Im+1, where each E represents a single EXTRACT-MIN call and each Ij represents a (possibly empty) sequence of INSERT calls. For each subsequence Ij, we initially place the keys inserted by these operations into a set Kj , which is empty if Ij is empty. We then do the following. ``` OFF-LINE-MINIMUM(m, n) for i <- 1 to n do determine j such that i ∈ K[j] if j != m + 1 extracted[j] = i let l be the smallest value greater than j for which set K[l] exists K[l] = K[j] ∪ K[l], destroying K[j] return extracted ``` b. Argue that the array extracted returned by OFF-LINE-MINIMUM is correct. c. Describe how to implement OFF-LINE-MINIMUM efficiently with a disjoint-set data structure. Give a tight bound on the worst-case running time of your implementation. ### `Answer` a. Each E returns the smallest value before it that hasn't been returned yet, therefore the array extracted will be [4,3,2,6,8,1]. b. The algorithm determines the values returned by extract-min one by one, one extraction at a time. We shall prove that every integer ` i ` inserted in the extracted table by the algorithm is correct, and that the table is entirely filled. This shall prove that the table is entirely filled correctly at the end. Suppose the ` i-1 >= 0 ` first integers have been treated correctly, the algorithm then treats ` i `. For that, it determines ` j ` such that ` i \in K_j ` (which does exist since there are always more insertions than extractions at any given point), which means that ` i ` is inserted during the sequence ` I_j ` by definition. If ` j = m+1 ` then ` i ` is not written into the extracted array, which is indeed correct since it has been inserted after all extractions. Suppose now ` j <= m `. Therefore, the ` j `th extraction, which occurs right after ` I_j `, must return ` i ` since every value smaller than ` i ` has already been attributed a correct extraction and ` i ` could not have been returned by any previous extraction since it is inserted during ` I_j `. Afterwards, the algorithm get rids of extraction ` j `: to do that, it removes the E of index j in the sequence by merging the values inserted during ` I_j ` and ` I_l ` for ` l >j ` which has not been merged itself yet. This gives us a new sequence which is equivalent in the sense that the remaining extractions will have the same values as prior. Indeed, we know the extractions we have removed have their correct return values ` 1,...,i ` recorded in the extracted table, and the remaining values to be extracted are in the remaining sequences. The merge means that the values occuring in sequence ` I_j ` can only be returned during the extractions following ` j ` which have not yet been removed, which explains why we look for the smallest ` l > j ` for which we have not yet merged the set ` I_l `. The new sequence thus yields the same extractions, and therefore all extractions yielded by the algorithm are correct. Finally, we have to prove the array is entirely filled. Suppose ` j ` is the smallest value for which the array is not filled at any step, and let i be the correct value it should hold. Since every extraction before ` j ` is filled with the correct value, none of them are filled with ` i `. Upon treating the integer ` i `, the set ` K_j ` has not been removed (otherwise the value extracted[j] would've been filled). The integer ` i ` is in one of the insertion sequences before ` j `, so finds itself currently in one of the sets ` K_1, ..., K_j `. If it is in a set ` K_k ` other than ` K_j ` then the algorithm will now set extracted[k] to i, which is absurd since we have supposed all of the extractions before j to be filled in (and proven above that they are done so correctly). Therefore, ` i \in K_j `, and the algorithm fills in extracted[j] correctly, which concludes the proof. c. We shall make us of a disjoint-set data structure, but we add three pieces of information to the representative of each set: an integer ` j ` to denote the number of the set as a ` K_j `, and two integers "prev" and "next" to denote the previous and next sets that have not yet been merged (thus we store the sets in a doubly linked list). The way we initialize these values is obvious, with the previous of ` 0 ` being ` -1 ` and the next of ` m+1 ` being ` -1 ` (to indicate they don't exist). Determining the ` j ` at line 2 of the algorithm now simply requires a FIND operation followed by the retrieval of the number assigned to the representative (which is ` j ` by definition). Finding the smallest ` l ` is simply a matter of consulting the "next" assigned to the representative of ` K_j `, which will point to the next set we haven't deleted yet by definition (and will exist since ` j \neq m+1 ` at this point). Finally, line 6 requires us to do an union operation. For that, we first follow the integer "prev" of the representative and set the "next" of that previous set (if it exists) to the next of the representative of ` K_j `: the next set of the previous set is now the next set of the current set. Then, we do the same on the other side by setting the "prev" of the next set to the "prev" of the set we're deleting ` K_j `. Finally, we do a traditional UNION of the sets, which is correct by definition and preserves the next and prev values. All of this tinkering means that each loop of the algorithm does at most one FIND and one UNION operation. As a matter of fact, we can be more precise since there are exactly ` n ` FIND operations being carried out, and precisely ` m ` UNION operations being done (since each ` K_j ` is deleted once and exactly once during the algorithm). If we add the initial `n` makeset operations, we have an algorithm that has complexity ` O(n + (n+m)\alpha(n)) = O(n\alpha(n)) `. ================================================ FILE: C21-Data-Structures-for-Disjoint-Sets/uf.cpp ================================================ #include #include using namespace std; class UF { private: vector parent; vector rank; int count; int N; bool validate(int p) { return (p >= 0 && p < N); } public: UF(int N) : parent(N), rank(N, 0), N(N), count(N) { for (int i = 0; i < N; i++) { parent[i] = i; } } int find(int p) { if (!validate(p)) return -1; while (p != parent[p]) { parent[p] = parent[parent[p]]; // path compression by halving p = parent[p]; } return p; } int getCount() const { return count; } bool connected(int p, int q) { return find(p) == find(q); } void Union(int p, int q) { int rootP = find(p); int rootQ = find(q); if (rootP == rootQ) return; // make root of smaller rank point to root of larger rank if (rank[rootP] < rank[rootQ]) parent[rootP] = rootQ; else if (rank[rootP] > rank[rootQ]) parent[rootQ] = rootP; else { parent[rootQ] = rootP; rank[rootP]++; } count--; } ~UF() {} }; int main() { UF uf(5); cout << uf.getCount() << endl; cout << uf.connected(2, 4) << endl; uf.Union(1, 2); uf.Union(3, 4); cout << uf.getCount() << endl; uf.Union(1, 3); cout << uf.connected(2, 4) << endl; } ================================================ FILE: C22-Elementary-Graph-Algorithms/22.1.md ================================================ ### Exercises 22.1-1 *** Given an adjacency-list representation of a directed graph, how long does it take to compute the out-degree of every vertex? How long does it take to compute the in-degrees? ### `Answer` out-degree : O(E) in-degree : O(E+V) ### Exercises 22.1-2 *** Give an adjacency-list representation for a complete binary tree on 7 vertices. Give an equivalent adjacency-matrix representation. Assume that vertices are numbered from 1 to 7 as in a binary heap. ### `Answer` ~ | 1 | 2 | 3 | 4 | 5 | 6 | 7 :---:|:---:|:---:|:---:|:---:|:---:|:---:|:---: 1 | 0 | 1 | 1 | 0 | 0 | 0 | 0 2 | 1 | 0 | 0 | 1 | 1 | 0 | 0 3 | 1 | 0 | 0 | 0 | 0 | 1 | 1 4 | 0 | 1 | 0 | 0 | 0 | 0 | 0 5 | 0 | 1 | 0 | 0 | 0 | 0 | 0 6 | 0 | 0 | 1 | 0 | 0 | 0 | 0 7 | 0 | 0 | 1 | 0 | 0 | 0 | 0 ### Exercises 22.1-3 *** The **transpose** of a directed graph G = (V, E) is the graph G**T** = (V, E**T**), where E**T** = {(v, u) in V × V : (u, v) in E}. Thus, G**T** is G with all its edges reversed. Describe efficient algorithms for computing G**T** from G, for both the adjacency-list and adjacency-matrix representations of G. Analyze the running times of your algorithms. **T** means transpose. ### `Answer` * adjacency-list : 遍历所有的节点,对某个节点i,遍历其Adj,将i这个节点增加到Adj中遇到的每个点的Adj中. 需要的时间是O(V+E).
Iterate all the nodes, for each node **i**, mark its adjacency-list as **L**, then add **i** to all the nodes in L. * adjacency-matrix : 只需要将矩阵转置,需要的时间是O(V*V)
Just transpose the matrix ### Exercises 22.1-4 *** Given an adjacency-list representation of a multigraph G = (V, E), describe an O(V + E)-time algorithm to compute the adjacency-list representation of the "equivalent" undirected graph G′ = (V, E′), where E′ consists of the edges in E with all multiple edges between two vertices replaced by a single edge and with all self-loops removed. ### `Answer` * We iterate through all the vertices in the multigraph. * For each of the vertices we iterate through their multigraph adjacency list. * While iterating through the multigraph adjacency list of a vertex u, we add the neighbor to the new adjacency list of u and u to the new adjacency list of the neighbor, if the neighbor is not vertex u itself and if the neighbor is not already present in the new adjacency list of u. * The new adjacency list array is a representation of the required undirected graph. [reference](http://vlsicad.ucsd.edu/courses/cse101-w15/hw/hw1_solutions.pdf) ### Exercises 22.1-5 *** The **square** of a directed graph G=(V,E) is the graph G2 =(V,E2) such that (u,w) in E2 if and only if for some v in V, both(u,v) in E and(v,w) in E.That is,G2 contains an edge between u and w whenever G contains a path with exactly two edges between u and w. Describe efficient algorithms for computing G2 from G for both the adjacency-list and adjacency-matrix representations of G. Analyze the running times of your algorithms. ### `Answer` G2 for an adjacency matrix: - Computing G2 may be done in O(V^3) time by matrix multiplication: for i = 1 to V for j = 1 to V { G2[i][j] = 0; for k = 1 to V if (g[i][k] == 1 && g[k][j] == 1) { G2[i][j] == 1; break; } } G2 for an adjacency list: Procedure G-Square (V[G], E[G]) V[G2] <- V[G] for each u ∈ V[G] for each v ∈ Adj[u] for each w ∈ Adj[v] E[G2] <- {(u, w)} ∪ E[G2] Run time = O(V^3) ### Exercises 22.1-6 *** When an adjacency-matrix representation is used, most graph algorithms require time Ω(V2), but there are some exceptions. Show that determining whether a directed graph G contains a **universal sink**-a vertex with in-degree |V| - 1 and out-degree 0-can be determined in time O(V), given an adjacency matrix for G. ### `Answer` [reference](http://www.csie.ntu.edu.tw/~r95122/alg07spr/alg07spr_hw1sol.pdf) 从Matrix[1,1]开始,如果当前位置是0,则往右走一步;如果是1,则往下走一步;一直走到最后一行或者最后一列再停下.再检查该位置是否满足**universal sink**的定义. If vertex i is a universal sink according to the definition, the i-th row of the adjacency-matrix will be all “0”, and the i-th column will be all “1” except the aii entry, and clearly there is only one such vertex. We then describe an algorithm to find out if a universal sink really exist. Starts from a11. If current entry aij = 0 then j = j + 1 (take one step right); if aij = 1 then i = i +1 (take one step down). In this way, it will stop at an entry akn of the last row or ank of the last column (n = |V|, 1 ≦ k ≦|V|). Check if vertex k satisfies the definition of universal sink (check for kth row to contain V zeros and kth column to contain k-1 1s, because the column in adajacency matrix defines in degree of a vertex), if yes then we found it, if no then there is no universal sink. Since we always make a step right or down, and checking if a vertex is a universal sink can be done in O(V), the total running time is O(V). If there is no universal sink, this algorithm won’t return any vertex. If there is a universal sink u, the path starts from a11 will definitely meet u-th column or u-th row at some entry. Once it’s on track, it can’t get out of the track and will finally stop at the right entry. ### Exercises 22.1-7 *** The incidence matrix of a directed graph G = (V, E) is a |V| × |E| matrix B = (bij) such that -1 if edge j leaves vertex i bij = 1 if edge j enters vertex i 0 otherwise Describe what the entries of the matrix product B BT represent, where BT is the transpose of B. T means transpose ### `Answer` Assume we have a simple graph with 3 vertex {X,Y,Z} and two edges{X->Y,X->Z}. The incidence matrix B for the above graph is as follows. XY XZ X -1 -1 B = Y 1 0 Z 0 1 The matrix BT is as follows. X Y Z BT = XY -1 1 0 XZ -1 0 1 The matrix product BBT is as follows. X Y Z X 2 -1 -1 BBT = Y -1 1 0 Z -1 0 1 BBT(u,v) = degree of u = indegree + outdegree if u = v −(number of edges connecting u and v) if u != v [reference](http://vlsicad.ucsd.edu/courses/cse101-w15/hw/hw1_solutions.pdf) ### Exercises 22.1-8 *** Suppose that instead of a linked list, each array entry Adj[u] is a hash table containing the vertices v for which (u, v) in E. If all edge lookups are equally likely, what is the expected time to determine whether an edge is in the graph? What disadvantages does this scheme have? Suggest an alternate data structure for each edge list that solves these problems. Does your alternative have disadvantages compared to the hash table? ### `Answer` [reference](http://stackoverflow.com/questions/9667571/graph-what-are-the-disadvantages-if-i-replace-each-linked-list-in-adjacency-li) I think the expected time is O(1), because I just go Hashtable t = Adj[u], then return t.get(v); I think the disadvantage is that Hashtable will take more spaces then linked list. It could be a binary search tree. In an adjacency matrix, each vertex is followed by an array of V elements. This O(V)-space cost leads to fast (O(1)-time) searching of edges. In an adjacency list, each vertex is followed by a list, which contains only the n adjacent vertices. This space-efficient way leads to slow searching (O(n)). A hash table is a compromise between the array and the list. It uses less space than V, but requires the handle of collisions in searching. A binary search tree is another compromise -- the space cost is minimum as that of lists, and the time cost in searching is O(lg n). Disadvantage for BST could be that search/insert/delete operations all need O(lgn) time, while those operations for hash table is O(1) time. *** Follow [@louis1992](https://github.com/gzc) on github to help finish this task. ================================================ FILE: C22-Elementary-Graph-Algorithms/22.2.md ================================================ ### Exercises 22.2-1 *** Show the d and π values that result from running breadth-first search on the directed graph of Figure 22.2(a), using vertex 3 as the source. ### `Answer` ![](./repo/s2/1.png) ### Exercises 22.2-2 *** Show the d and π values that result from running breadth-first search on the undirected graph of Figure 22.3, using vertex u as the source. ### `Answer` ![](./repo/s2/2.png) ### Exercises 22.2-3 *** What is the running time of BFS if its input graph is represented by an adjacency matrix and the algorithm is modified to handle this form of input? ### `Answer` 用邻接矩阵表示的话,遍历所有的边的时间由O(E)变为O(V^2),因此总运行时间是O(V+V^2) If the graph is represented by adjancency matrix, then the time of iterating all the edge becomes O(V^2), so the running time is O(V+V^2). ### Exercises 22.2-4 *** Argue that in a breadth-first search, the value d[u] assigned to a vertex u is independent of the order in which the vertices in each adjacency list are given. Using Figure 22.3 as an example, show that the breadth-first tree computed by BFS can depend on the ordering within adjacency lists. ### `Answer` While traversing adjacency list of a vertex, all the neighbour of the vertex is going to be in the same level, That's why d[u] of any vertex u is independent of the order of the adjacency list. Suppose that vertex **A** and **B** are in same level and vertex **C** is reachable from both **A** and **B**. Then in the BFS tree, whether there will be an edge from **A** to **C** or **B** to **C** depends on the order of **A** and **B**. ### Exercises 22.2-5 *** Give an example of a directed graph G = (V, E), a source vertex s in V , and a set of tree edges Eπ in E such that for each vertex v in V,the unique path in the graph(V,Eπ) from s to v is a shortest path in G, yet the set of edges Eπ cannot be produced by running BFS on G, no matter how the vertices are ordered in each adjacency list. ### `Answer` Following is the example, the bold edges are in Eπ. Then can not be produced by BFS. ![](./repo/s2/3.png) ### Exercises 22.2-6 *** There are two types of professional wrestlers: "good guys" and "bad guys." Between any pair of professional wrestlers, there may or may not be a rivalry. Suppose we have n professional wrestlers and we have a list of r pairs of wrestlers for which there are rivalries. Give an O(n + r)-time algorithm that determines whether it is possible to designate some of the wrestlers as good guys and the remainder as bad guys such that each rivalry is between a good guy and a bad guy. If is it possible to perform such a designation, your algorithm should produce it. ### `Answer` Bipartite Graph problem. Run DFS or BFS, the start node we could mark as white. Each time, we encounter a node, if it is not currently colored, we should mark it as the opposite color of current node. Else, check the color with current node. If same, then it is not bipartite graph. ### Exercises 22.2-7 *** The **diameter** of a tree T =(V, E) is given by max d(u,v) that is, the diameter is the largest of all shortest-path distances in the tree. Give an efficient algorithm to compute the diameter of a tree, and analyze the running time of your algorithm. ### `Answer` ##### Solution 1 : For all node v, run BFS each, choose the longest shortest path. Or use floyd algorithm to calculate all p-p shortest path. BFS running time = O(V*(V+E)) floyd running time = O(V^3)
##### Solution 2: Run BFS twice. For the first time, arbitrarily choose a vertex as the source. The second time, let the vertex with largest d[] be the source.
##### Solution 3: The diameter of a tree can computed in a bottom-up fashion using a recursive solution. If x is a node with a depth d(x) in the tree then the diameter D(x) must be: D(x) = max{maxi{D(x.childi)}, maxij{d(x.childi) + d(x.childj)} + 2}, if x is an internal node 0 , if x is a leaf Since the diameter must be in one of the subtrees or pass through the root and the longest path from the root must be the depth. The depth can easily be computed at the same time. Using dynamic programming we obtain a linear solution. Actually, the problem can also be solved by computing the longest shortest path from an arbi- trary node. The node farthest away will be the endpoint of a diameter and we can thus compute the longest shortest path from this node to obtain the diameter. See relevant litterature for a proof of this. ### Exercises 22.2-8 *** Let G = (V, E) be a connected, undirected graph. Give an O(V + E)-time algorithm to compute a path in G that traverses each edge in E exactly once in each direction. Describe how you can find your way out of a maze if you are given a large supply of pennies. ### `Answer` We can perform a DFS like procedure where each edge has a state among the following: never visited, visited once, visited twice. We never visit an edge that has been visited twice. To ensure we explore all edges we will prioritize edges that have never been traversed over edges that have been traversed once. This way, we will explore edges the second time during backtracking phase of DFS. Every time we visit an unexplored edge(u, v) we add (u, v) to our path. Every time we backtrack from vertex v to vertex u we add (v, u) to our path. Given a large supply of pennies, we will use pennies to mark state of an edge as suggested above. We put a penny down on each edge every time we travel it and we will never travel an edge with already two pennies placed. Alternatively we can use absence of penny, head face up, tail face up for keeping track of edges that haven't been explored, explored once and explored twice respectively. *** Follow [@louis1992](https://github.com/gzc) on github to help finish this task. ================================================ FILE: C22-Elementary-Graph-Algorithms/22.3.md ================================================ ### Exercises 22.3-1 Make a 3-by-3 chart with row and column labels WHITE, GRAY, and BLACK. In each cell (i, j), indicate whether, at any point during a depth-first search of a directed graph, there can be an edge from a vertex of color i to a vertex of color j. For each possible edge, indicate what edge types it can be. Make a second such chart for depth-first search of an undirected graph. ### `Answer` Directed (i,j) | White | Gray | Black :----:|:----:|:----:|:----: White | TBFC | BC | C Gray | TF | TFB | TFC Black | | B | TFBC Undirected (i,j) | White | Gray | Black :----:|:----:|:----:|:----: White | TB | TB | Gray | TB | TB | TB Black | | TB | TB ### Exercises 22.3-2 *** Show how depth-first search works on the graph of Figure 22.6. Assume that the for loop of lines 5-7 of the DFS procedure considers the vertices in alphabetical order, and assume that each adjacency list is ordered alphabetically. Show the discovery and finishing times for each vertex, and show the classification of each edge. ![](./repo/s3/1.png) ### `Answer` ![](./repo/s3/2.png) Tree edges: (q, s), (s, v), (v, w), (q, t), (t, x), (x, z), (t, y), (r, u) Back edges: (w, s), (z, x), (y, q) Forward edges: (q, w) Cross edges: (r, y), (u, y) [reference](http://test.scripts.psu.edu/users/d/j/djh300/cmpsc465/notes-4985903869437/solutions-to-some-homework-exercises-as-shared-with-students/4-solutions-clrs-22.pdf) ### Exercises 22.3-3 *** Show the parenthesis structure of the depth-first search shown in Figure 22.4. ### `Answer` ![](./repo/s3/3.png) ### Exercises 22.3-4 *** Show that edge (u, v) is 1. a tree edge or forward edge if and only if d[u] < d[v] < f[v] < f[u], 2. a back edge if and only if d[v] < d[u] < f[u] < f[v], and 3. a cross edge if and only if d[v] < f[v] < d[u] < f[u]. ### `Answer` First, you have to show the two following lemma: 1. u is an ancestor of v ⇔ d[u] < d[v] < f[v] < f[u]. 2. u is a decendant of v ⇔ d[v] < d[u] < f[u] < f[v]. Therefore, a. (u, v) is a tree edge or forward edge ⇔ u is an ancestor of v ⇔ d[u] < d[v] < f[v] < f[u]. b. (u, v) is a back edge ⇔ u is a decendant of v ⇔ d[v] < d[u] < f[u] < f[v] c. (u, v) is a cross edge ⇔ v has been finished when exploring (u, v) ⇔ d[v] < f[v] < d[u] < f[u] ### Exercises 22.3-5 *** Show that in an undirected graph, classifying an edge (u, v) as a tree edge or a back edge according to whether (u, v) or (v, u) is encountered first during the depth-first search is equivalent to classifying it according to the priority of types in the classification scheme. ### `Answer` 其实就是将无向图变成了有向图,将无向图的一条边变成有向图的两条边. ### Exercises 22.3-6 *** Rewrite the procedure DFS, using a stack to eliminate recursion. ### `Answer` DFS-VISIT(u) STACK.push(u) while !STACK.empty u <- STACK.top() if COLOR[u] = GRAY: COLOR[u] <- BLACK f[u] <- time <- time+1 STACK.pop() continue if COLOR[u] = WHITE: COLOR[u] <- GRAY d[u] <- time <- time+1 for each v in Adj[u] from tail downto head do if color[v] = WHITE then π[v] <- u STACK.push(v) ### Exercises 22.3-7 *** Give a counterexample to the conjecture that if there is a path from u to v in a directed graph G, and if d[u] < d[v] in a depth-first search of G, then v is a descendant of u in the depth-first forest produced. ### `Answer` 如下图,假设s是起点,我们先走u,再走v.那么满足d[u] < d[v] 并且 u -> s -> v有一条路径.v并不是u的子孙节点. In the image below, consider the Adjacent list (Adj[s]) alphabetically ordered. Performing DFS starting at _s_, we have: | v | d | f | | --- | --- | --- | | s | 1 | 6 | | u | 2 | 3 | | v | 4 | 5 | So d[u] < d[v], but _v_ is not a descendant of _u_ in the depth-first forest. ### Exercises 22.3-8 *** Give a counterexample to the conjecture that if there is a path from u to v in a directed graph G, then any depth-first search must result in d[v] < f[u]. ### `Answer` ![](./repo/s3/4.png) We perform a DFS starting at vertex s. We then discover vertex u. Since the only edge out of u is (u, s), and s has been found, we finish u. Next, we discover and finish v. Finally, we finish s. ### Exercises 22.3-9 *** Modify the pseudocode for depth-first search so that it prints out every edge in the directed graph G, together with its type. Show what modifications, if any, must be made if G is undirected. ### `Answer` 根据练习22.3-4的结论进行即可. ### Exercises 22.3-10 *** Explain how a vertex u of a directed graph can end up in a depth-first tree containing only u, even though u has both incoming and outgoing edges in G. ### `Answer` If the DFS first searches the vertices in the other ends of the outgoing edges, then searches u, and then searches the vertices in the other ends of the incoming edges. ### Exercises 22.3-11 *** Show that a depth-first search of an undirected graph G can be used to identify the connected components of G, and that the depth-first forest contains as many trees as G has connected components. More precisely, show how to modify depth-first search so that each vertex v is assigned an integer label cc[v] between 1 and k, where k is the number of connected components of G, such that cc[u] = cc[v] if and only if u and v are in the same connected component. ### `Answer` A directed graph G = (V, E) is **singly connected** if  implies that there is at most one simple path from u to v for all vertices u, v in V. Give an efficient algorithm to determine whether or not a directed graph is singly connected. Since the connectivity implies reaching a vertex from any other vertex in the graph, using this idea we can follow the process in below pseudo code to number the vertices to the connected componenet they belong: numberingConnectedComponenets(G): connected_component = 1 for every u vertex in G markVertex(G.u, connected_component) connected_component += 1 markVertex(G.u , connected_component) //similar to DFS for every vertex v in G.adj[u] v.number = connected_component markVertex(G.v, connected_component) ### Exercises 22.3-12 *** Show that a depth-first search of an undirected graph G can be used to identify the connected components of G, and that the depth-first forest contains as many trees as G has connected components. More precisely, show how to modify depth-first search so that each vertex v is assigned an integer label cc[v] between 1 and k, where k is the number of connected components of G, such that cc[u] = cc[v] if and only if u and v are in the same connected component. ### `Answer` For each vertex u ∈ V , perform a DFS on the given graph G. Check if there are any foward edges or cross edges (in the same component) in any one of the searches. If no such edges exist, then the graph is singly connected, else not. Time complexity: O(|V |(|V | + |E|)). The graph is singly connected even with back edges existed. Since back edges implies there is a path u v and v u, which is consistent with the definition of single connectedness. *** Follow [@louis1992](https://github.com/gzc) on github to help finish this task. ================================================ FILE: C22-Elementary-Graph-Algorithms/22.4.md ================================================ ### Exercises 22.4-1 Show the ordering of vertices produced by TOPOLOGICAL-SORT when it is run on the dag of Figure 22.8, under the assumption of Exercise 22.3-2. ### `Answer` ![](./repo/s4/1.png) ### Exercises 22.4-2 *** Give a linear-time algorithm that takes as input a directed acyclic graph G = (V, E) and two vertices s and t, and returns the number of paths from s to t in G. For example, in the directed acyclic graph of Figure 22.8, there are exactly four paths from vertex p to vertex v: pov, por yv, posr yv, and psr yv. (Your algorithm only needs to count the paths, not list them.) ### `Answer` Add a field to the vertex representation to hold an integer count. Initially, set vertex t’s count to 1 and other vertices’ count to 0. Start running DFS with s as the start vertex. When t is discovered, it should be immediately marked as finished (BLACK), without further processing starting from it. Subsequently, each time DFS finishes a vertex v, set v’s count to the sum of the counts of all vertices adjacent to v. When DFS finishes vertex s, stop and return the count computed for s. ### Exercises 22.4-3 *** Give an algorithm that determines whether or not a given undirected graph G = (V, E) contains a cycle. Your algorithm should run in O(V) time, independent of |E|. ### `Answer` An undirected graph is acyclic (i.e., a forest) iff a DFS yields no back edges. Since back edges are those edges (u, v) connecting a vertex u to an ancestor v in a depth-first tree, so no back edges means there are only tree edges, so there is no cycle. So we can simply run DFS. If find a back edge, there is a cycle. The complexity is O(V ) instead of O(E + V ). Since if there is a back edge, it must be found before seeing |V | distinct edges. This is because in a acyclic (undirected ) forest, |E| ≤ |V | - 1 ### Exercises 22.4-4 *** Prove or disprove: If a directed graph G contains cycles, then TOPOLOGICAL-SORT (G) produces a vertex ordering that minimizes the number of "bad" edges that are inconsistent with the ordering produced. ### `Answer` This conclusion is not true. Selecting different starting points, topological ordering (according to the depth-first traversal implementation on the book) will give different sequences, and the number of bad edges cannot be guaranteed to be the least. First explain the meaning of the bad side: Assuming that the sequence of nodes generated by topological sorting is a, b, c, d, then for the sequence the directed edges d -> a are bad edges, a -> d is not. Consider the following counterexample: For the following figure G: a / ^\ / \\ V \V b ------> c If starting from point a, getting sequence a, b, c, there is 1 bad edge c -> a If starting from point b and getting the sequence b, c, a, there are 2 bad edges a -> c, a -> b So the conclusion is not true ### Exercises 22.4-5 *** Another way to perform topological sorting on a directed acyclic graph G = (V, E) is to repeatedly find a vertex of in-degree 0, output it, and remove it and all of its outgoing edges from the graph. Explain how to implement this idea so that it runs in time O(V + E). What happens to this algorithm if G has cycles? ### `Answer` First, run DFS or BFS to count the in and out of each point in O(V+E) time, and then maintain this information when deleting edges. The one with a 0 input each time Click and delete the edge and maintain the information, so there are E edges and V points, so O(V) output and O(E) deletion are performed. So the total running time is O(V+E). ) If the graph has loops, then sometimes there may be no points with a degree of zero. *** Follow [@louis1992](https://github.com/gzc) on github to help finish this task. ================================================ FILE: C22-Elementary-Graph-Algorithms/22.5.md ================================================ ### Exercises 22.5-1 How can the number of strongly connected components of a graph change if a new edge is added? ### `Answer` * 如果增加的是一个强连通分量里面的边,那么不会影响. * 如果增加的是不同连通分量之间的边,那么可能不会减少,也可能会减少,最多减少到1. ###### English * If an edge is added in an SCC, the number of SCCs will remain the same. * If an edge is added outside of an SCC, in a graph with n > 0 SCCs, the reduction in SCCs can be between 0 and (n-1). Example: ![22.5-1](./repo/s5/2.png) ### Exercises 22.5-2 *** Show how the procedure STRONGLY-CONNECTED-COMPONENTS works on the graph of Figure 22.6. Specifically, show the finishing times computed in line 1 and the forest produced in line 3. Assume that the loop of lines 5-7 of DFS considers vertices in alphabetical order and that the adjacency lists are in alphabetical order. ### `Answer` first, we need the finishing times: ~ | q | r | s | t | u | v | w | x | y | z :---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---: d | 1 | 17 | 2 | 8 | 18 | 3 | 4 |9 | 13 | 10 f | 16| 20 | 7 | 15 | 19 | 6 | 5 |12 | 14 | 11 Then, we need to compute the transpose: ![](./repo/s5/1.png) Finally, we need the finishing times of the transpose with respect to the original finishing times: ~ | q | r | s | t | u | v | w | x | y | z :---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---: d | 5 | 1 | 15 | 7 | 3 | 17 | 16 | 11 | 6 | 12 f | 10| 2 | 20 | 8 | 4 | 18 | 19 |14 | 9 | 13 Giving us the following components: {r} -> {u} -> {q, y, t} -> {x, z} -> {s, w, v} [reference](http://student.csuci.edu/~douglas.holmes253/Assignment6.html) ### Exercises 22.5-3 *** Professor Deaver claims that the algorithm for strongly connected components can be simplified by using the original (instead of the transpose) graph in the second depth-first search and scanning the vertices in order of increasing finishing times. Is the professor correct? ### `Answer` Consider vertex1 to vertex0 vertex0 to vertex2 and vertex0 to vertex1 For this algorithm, the first DFS will give a list 1 2 0 for the second DFS. All vertices will be incorrectly reported to be in the same SCC. For the CLRS algorithm, the first DFS will give a list 0 2 1 for the second DFS. After reversing edges, the correct SCCs {0, 1} and {2} will be reported. ### Exercises 22.5-4 *** Prove that for any directed graph G, we have ((GT)SCC)T = GSCC. That is, the transpose of the component graph of GT is the same as the component graph of G. ### `Answer` Since the strongly connected relationship is an equivalence relation, G and GT will always have the same strongly connected components. If two vertices X and Y are not strongly connected, then there is a unique path from X to Y in G iff there is a unique path from Y to X in GT. A similar property will also hold for the component graph and transpose of the component graph, i.e. If two vertices X and Y are not strongly connected in G, then here is a unique path from SCC(G,X) to SCC(G,Y) in (G)SCC iff there is a unique path from SCC(GT,Y) to SCC(GT,X) in (GT)SCC). ### Exercises 22.5-5 *** Give an O(V + E)-time algorithm to compute the component graph of a directed graph G = (V, E). Make sure that there is at most one edge between two vertices in the component graph your algorithm produces. ### `Answer` 先执行STRONGLY-CONNECTED-COMPONENTS过程,然后对每个节点赋予[1,k]中的一个值,即生成的k个强联通分量.第k个强联通分量里面的所有节点的值为k. 然后遍历每一个节点i,对Adj[i]的每一个节点j, 如果k[i]和k[j]以前没有边,则加上. ###### English First execute the STRONGLY-CONNECTED-COMPONENTS process, and then assign each node a value in [1, k], that is, the generated k strongly connected components. The value of all nodes in the kth strongly connected components is k. Then Traversing each node i, for each node j of Adj[i], if k[i] and k[j] have no edges before, then add. ### Exercises 22.5-6 *** Given a directed graph G = (V, E), explain how to create another graph G′ = (V, E′) such that (a) G′ has the same strongly connected components as G, (b) G′ has the same component graph as G, and (c) E′ is as small as possible. Describe a fast algorithm to compute G′. ### `Answer` 1. 先对原图G生成k个联通分量和一个SCC子图. 2. 遍历k个所有的联通分量. 对每个联通分量,必然有一个能够到所有点的回路,只添加该回路到新的图.假设第i个联通分量有5个节点a,b,c,d,e,我们只需要添加边a->b,b->c,c->d,d->e,e->a即可. 3. 选SCC子图的边,加到新的图. 节点可以任意选择. ###### English 1. Generate k connectivity components and an SCC submap for the original graph G. 2. Traverse all k connected components. For each connected component, there must be a loop that can reach all points, and only add the loop to the new graph. Suppose the i-th connected component has 5 nodes a, b, c , d, e, we only need to add the edges a->b, b->c, c->d, d->e, e->a. 3. Select the edge of the SCC submap and add it to the new graph. The nodes can be arbitrarily selected. ### Exercises 22.5-7 *** A directed graph G = (V, E) is semiconnected if, for all pairs of vertices u, v ∈ V, we have u ~> v or v ~> u. Give an efficient algorithm to determine whether or not G is semiconnected. Prove that your algorithm is correct, and analyze its running time. ### `Answer` 强连通组件算法, 按第 2 次 DFS(GT) 强连通组件生成顺序编号, 假设是 SCC1, SCC2, ..., SCCn 如果存在边 (SCC1, SCC2), (SCC2, SCC3), ..., (SCCn-1, SCCn) , 即组件间边形成线性的链, 则图 G 是 semiconnected. 稍后详细解释. 1. 强连通组件图算法, 时间 O(V+E) 2. 按强连通组件 SCC 顺序编号, 假设是SCC1, SCC2, ..., SCCn, 并求出组件 SCC 的邻接表, 这步骤可以在 (1) 中完成, 所以时间 O(V+E) 3. 遍历 SCC 邻接表, 如果对于范围 i = 1, 2, ..., n-1, 强连通组件SCCi.adj 中存在 SCCi+1 , 既含有边 (SCCi, SCCi+1), 强连通组件图是线性链, 则 G = (V, E) 是 semiconnected, 时间 O(V+E). 详细解释, 按算法理论的支持, SCCi+1 不存在到SCCi 的边, 如果没有 (SCCi, SCCi+1), 则两者均无法到达对方, 即不符合 G 是 semiconnected 的概念. 算法时间复杂度 O(V+E) ###### English Strongly connected component algorithm, numbered according to the second DFS (GT) strong connected component generation sequence, assuming SCC1, SCC2, ..., SCCn If there are edges (SCC1, SCC2), (SCC2, SCC3), ..., (SCCn-1, SCCn), that is, the edges between the components form a linear chain, Then the graph G is semiconnected. I will explain it in detail later. 1. Strongly connected component graph algorithm, time O(V+E) 2. According to the SCC sequence of the strong connected component, assume SCC1, SCC2, ..., SCCn, and find the adjacency list of the component SCC. This step can be done in (1), so time O(V+E) 3. Traverse the SCC adjacency list, if there is SCCi+1 in the strongly connected component SCCi.adj for the range i = 1, 2, ..., n-1, Contains both edges (SCCi, SCCi+1), strongly connected component graphs are linear chains, then G = (V, E) is semiconnected, time O(V+E). Detailed explanation, according to the support of algorithm theory, SCCi+1 does not exist to the side of SCCi, if not (SCCi, SCCi+1), Then neither can reach the other party, that is, it does not conform to the concept that G is semiconnected. Algorithm time complexity O(V+E) [reference](http://blog.csdn.net/anye3000/article/details/9791213) *** Follow [@louis1992](https://github.com/gzc) on github to help finish this task. ================================================ FILE: C22-Elementary-Graph-Algorithms/README.md ================================================ UNSOLVED ================================================ FILE: C22-Elementary-Graph-Algorithms/elementary_graph_algo.py ================================================ from collections import defaultdict, deque from Queue import Queue import unittest class Graph: def __init__(self, adj_list): """ Process the graph with the given adjacent list. :param adj_list: The given adjacent list. A dictionary with parent node as keys and values as set of the adjacent nodes. """ self.adj_list = adj_list def bfs(self, start): node_q = Queue() node_q.put(start) visited = set() while not node_q.empty(): node = node_q.get() # FIFO if node not in visited: yield node visited.add(node) node_q.queue.extend(self.adj_list[node] - visited) def bfs_path(self, start, end): node_q = Queue() node_q.put((start, [start])) visited = {start} while not node_q.empty(): parent, path = node_q.get() for child in self.adj_list[parent]: if child not in visited: if child == end: yield path + [child] else: visited.add(child) node_q.put((child, path + [child])) def connected_component(self): count = 0 visited = set() connected_components = [] for node in self.adj_list.keys(): if node not in visited: count += 1 bfs_nodes = list(self.bfs(node)) for node in bfs_nodes: visited.add(node) connected_components.append(bfs_nodes) return count, connected_components def dfs(self, start, adj_list, visited=None): node_s = deque() node_s.append(start) if visited is None: visited = set() while node_s: node = node_s.pop() if node not in visited: yield node visited.add(node) node_s.extend(adj_list[node] - visited) def strongly_connected_component(self): """ Kosaraju's Algorithm. """ finish_time_stack = [] strongly_connected_components = [] visited = set() def dfs_util(start): """ Subroutine to compute the finishing time stack. Iterative approach is preferred to avoid exceeding recursion depth. """ node_s = deque() node_s.append(start) pop_set = set() # for memoization of the processed elements. while node_s: node = node_s[-1] visit_next = None # for boundary condition. if node not in visited: visited.add(node) visit_next = self.adj_list[node] - visited node_s.extend(visit_next) if not visit_next: node = node_s.pop() if node not in pop_set: finish_time_stack.append(node) pop_set.add(node) # d.keys() creates a static list of the dictionary keys. # Otherwise, we get a pretty neat exception while processing # large graphs. # "RuntimeError: dictionary changed size during iteration". for node in self.adj_list.keys(): if node not in visited: dfs_util(node) # Graph transpose can also be done while reading the file for optimisation. adj_list_invert = defaultdict(set) for head, tail in self.adj_list.items(): for node in tail: adj_list_invert[node].add(head) visited = set() while finish_time_stack: start = finish_time_stack.pop() if start not in visited: newly_visited = set(self.dfs(start, adj_list_invert, visited)) visited.update(newly_visited) strongly_connected_components.append(newly_visited) return strongly_connected_components class GraphTest(unittest.TestCase): def setUp(self): self.graph = Graph({'A': set(['B', 'C']), 'B': set(['A', 'D', 'E']), 'C': set(['A', 'F']), 'D': set(['B']), 'E': set(['B', 'F', 'G']), 'F': set(['C', 'E']), 'G': set()}) def test_bfs(self): self.assertEqual(list(self.graph.bfs('A')), ['A', 'C', 'B', 'F', 'E', 'D', 'G']) def test_bfs_path(self): self.assertEqual(list(self.graph.bfs_path('A', 'E')), [['A', 'B', 'E'], ['A', 'C', 'F', 'E']]) def test_connected_component(self): self.graph = Graph({'A': set(['B', 'C']), 'B': set(['C', 'A']), 'C': set(['A', 'B']), 'D': set(), 'E': set(['F']), 'F': set(['E'])}) self.assertEqual(self.graph.connected_component(), (3, [['A', 'C', 'B'], ['E', 'F'], ['D']])) def test_dfs(self): self.graph = Graph({'A': set(['B']), 'B': set(['C', 'D']), 'C': set(['A']), 'D': set('E'), 'E': set()}) self.assertEqual(list(self.graph.dfs('A', self.graph.adj_list)), ['A', 'B', 'D', 'E', 'C']) def test_strongly_connected_components(self): self.graph = Graph({1: set([2]), 2: set([3,4,6]), 3: set([1,4]), 4: set([5]), 5: set([4]), 6: set([5,7]), 7: set([6,8]), 8: set([5,7])}) self.assertEqual(self.graph.strongly_connected_component(), [{1,3,2}, {6,7,8}, {5,4}]) if __name__ == "__main__": unittest.main() ================================================ FILE: C22-Elementary-Graph-Algorithms/exercise_code/EulerTour.cpp ================================================ /************************************************************************* > File Name: EulerTour.cpp > Author: Louis1992 > Mail: zhenchaogan@gmail.com > Blog: http://gzc.github.io > Created Time: Thu Jul 16 13:54:43 2015 ************************************************************************/ #include #include using namespace std; #define MAX_VERTEX 10005 typedef struct { int end_vertex; int visited; } Edge; int Nvertex; int Nedge; vector vertex[MAX_VERTEX]; static void input() { int i, u, v; Edge tmp; tmp.visited = 0; cin >> Nvertex >> Nedge; for (i = 0; i < Nedge; ++i) { cin >> u >> v; tmp.end_vertex = u; vertex[v].push_back(tmp); tmp.end_vertex = v; vertex[u].push_back(tmp); } } static void euler(int x) { int i; for (i = vertex[x].size() - 1; i >= 0; --i) { if (!vertex[x][i].visited) { vertex[x][i].visited = 1; euler(vertex[x][i].end_vertex); } } cout << x << endl; } int main() { input(); euler(1); return 0; } ================================================ FILE: C22-Elementary-Graph-Algorithms/problem.md ================================================ ### Problems 3 : Euler tour *** An Euler tour of a connected, directed graph G = (V, E) is a cycle that traverses each edge of G exactly once, although it may visit a vertex more than once. a. Show that G has an Euler tour if and only if in-degree (v) = out-degree (v) for each vertex v in V. b. Describe an O(E)-time algorithm to find an Euler tour of G if one exists. (Hint: Merge edge-disjoint cycles.) ### `Answer` **a.** 只有入度=出度,才能进来一次又出去;否则,不对称,是不可能访问全部的边的. **b.** [implementation](./exercise_code/EulerTour.cpp) ### Problems 4 : Reachability Let G = (V, E) be a directed graph in which each vertex u in V is labeled with a unique integer L(u) from the set {1, 2,..., |V|}. For each vertex u in V, let  be the set of vertices that are reachable from u. Define min(u) to be the vertex in R(u) whose label is minimum, i.e., min(u) is the vertex v such that L(v) = min {L(w) : w in R(u)}. Give an O(V + E)-time algorithm that computes min(u) for all vertices u in V. ### `Answer` For every vertex v in G Mark v undiscovered For every undiscovered vertex v in G, starting with the lowest L-value R(v) = L(v) Mark v discovered. Perform Reverse-DFS from v. For every undiscovered vertex u we encounter on this DFS: R(u) = R(v) Mark u discovered. [reference](http://www.fongboy.com/classes/cs180/hw7-sol.pdf) *** Follow [@louis1992](https://github.com/gzc) on github to help finish this task. ================================================ FILE: C23-Minimum-Spanning-Trees/23.1.md ================================================ ### Exercises 23.1-1 *** Let (u, v) be a minimum-weight edge in a graph G. Show that (u, v) belongs to some minimum spanning tree of G. ### `Answer` 过程GENERIC-MST的第一步.我们可以选择这样一条割,u在割的一边,v在另一边.此时,u-v就是一条通过割的轻边,把它加进来是安全的. In the first step of GENERIC-MST, we could choose such a cut, node u is on one side, node v is on another side. Then(u, v) is a light-edge through this cut. So, it is safe to add (u, v) ### Exercises 23.1-2 *** Professor Sabatier conjectures the following converse of Theorem 23.1. Let G = (V, E) be a connected, undirected graph with a real-valued weight function w defined on E. Let A be a subset of E that is included in some minimum spanning tree for G, let (S, V - S) be any cut of G that respects A, and let (u, v) be a safe edge for A crossing (S, V - S). Then, (u, v) is a light edge for the cut. Show that the professor's conjecture is incorrect by giving a counterexample. ### `Answer` ![](./repo/s1/1.jpg) 对于该 cut 来说, 虽然 (A, C) 是安全的, 但不是最轻边. For this cut, although (A, C) is safe, but is it not the lightest. ### Exercises 23.1-3 *** Show that if an edge (u, v) is contained in some minimum spanning tree, then it is a light edge crossing some cut of the graph. ### `Answer` 在这个MST里边,我们先去掉u-v这条边.然后画一个只穿过u-v的割(这个割肯定存在).此时,我们的策略是选择一条轻边,既然u-v之前在这个MST里边,那么u-v就是一条轻边. In this MST, we remove (u,v) and draw a cut cross (u,v). Now, our strategy is choose a light-weight edge, because (u,v) is in this MST originally, then (u,v) is a light edge. ### Exercises 23.1-4 *** Give a simple example of a graph such that the set of edges {(u, v) : there exists a cut (S, V - S) such that (u, v) is a light edge crossing (S, V - S)} does not form a minimum spanning tree. ### `Answer` 当三角形三条边权重相同时,每条边在某种 cut 中均是最轻,即结果中存在环,所以不是最小生成树. For example, if we have a triangle with equal weight. In each cut, there will be two equal edge e1 and e2. If we choose e1 into MST, though e2 is also a light weight but it is not in the MST. ### Exercises 23.1-5 *** Let e be a maximum-weight edge on some cycle of G = (V, E). Prove that there is a minimum spanning tree of G′ = (V, E -{e}) that is also a minimum spanning tree of G. That is, there is a minimum spanning tree of G that does not include e. ### `Answer` 因为e是该回路上的最大权边,因此选择这个回路上的其他边都优于选择e.所以G′和G肯定会有一个不含e的MST. Becase e is the maximum-weight edge in this circle. So other edges in the circle are all better than e. As a result, G and G' must have a MST not containging e. ### Exercises 23.1-6 *** Show that a graph has a unique minimum spanning tree if, for every cut of the graph, there is a unique light edge crossing the cut. Show that the converse is not true by giving a counterexample. ### `Answer` 假设存在两个最小生成树 T 和 T'. 对任意一条边 e 属于 T, 如果从 T 中移除 e, 则 T 变得不连通, 形成 cut (S, V - S), 根据练习 23.1-3 可知, e 是穿过 cut(S, V - S) 最轻边. 假设边 x 属于 T', 并穿过 cut (S, V - S), 则 x 同样是最轻边. 由于穿过 cut(S, V - S) 的最轻边唯一. 既 e 和 x 是同一条边. 所以 e 也属于 T', 由于我们选择 e 是任意的, 所有在 T 中的边, 同样在 T' 中. 即最小生成树唯一. Assuming there are two MSTs called T and T'. For any edge e in T, if we remove e from T, then T becomes unconnected and we have a cut(S, V - S). According to exercise 23.1-3, e is the light edge through cut(S, V - S). If edge x is in T' and through cut(S, V - S), then x is also a light weight. Because the light edge is unique. So e and x is the same edge, e is also in T'. Because we choose e at random, of all edges in T, also in T'. As a result, the MST is unique. 将条件和结论调换则不成立, 如下. If inverse, then does not hold. See the picture. ![](./repo/s1/2.jpg) ### Exercises 23.1-7 *** Argue that if all edge weights of a graph are positive, then any subset of edges that connects all vertices and has minimum total weight must be a tree. Give an example to show that the same conclusion does not follow if we allow some weights to be nonpositive. ### `Answer` 假设边的子集 T 中存在环, 则某两点之间存在多条通路, 移除其中一条通路, 子集 A' 仍然连通所有点. 因为边的权重为正, 既 w(A') < w(A), 结论与条件矛盾, 所以 T 是树. Assuming any subset of edges contain circles, then there must be points u,v, the path from u to v is not unique. If we remove a path, subset A' also connects all the points. Because the weight is positive, w(A') < w(A). There is a confliction, because we can produce smaller graph. So no circles, it must be a tree. 如果边的权重准许为负, 则子集 T 不一定是树, 图中三条边总权重最小, 如下. If some weights could be nonpositive, see picture below. It is a graph with total minimum weights. ![](./repo/s1/3.jpg) ### Exercises 23.1-8 *** Let T be a minimum spanning tree of a graph G, and let L be the sorted list of the edge weights of T. Show that for any other minimum spanning tree T′ of G, the list L is also the sorted list of edge weights of T′. ### `Answer` 假设最小生成树有 n 条边, 存在两个最小生成树 T 和 T', 用 w(e) 表示边的权值. T 权值递增排列 w(a1) <= w(a2) <= ... w(an) T' 权值递增排列 w(b1) <= w(b2) <= ... w(bn) 假设 i 是两个列表中, 第一次出现边不同的位置, 既 ai ≠ bi, 先假定 w(ai) >= w(bi). 情况1, 如果 T 中含有边 bi, 由于 ai 和 bi 在列表 i 位置之前都是相同的, 若含有 bi 则一定在 i 位置后, 既有 j > i 使得 w(aj) = w(bi). 得到 w(bi) = w(aj) >= w(ai) >= w(bi), 既 w(bi) = w(aj) = w(ai), 故 i 位置处边的权值相同. 情况2, 如果 T 不包含边 bi, 则把 bi 加到 T 中, 会在某处形成一个圈. 由于 T 是最小生成树, 圈内任何一条边的权值都小于等于 w(bi), 另外这个圈中必定存在 aj 不在 T' 中, 得出 w(aj) <= w(bi) 且 j > i. 因此 w(bi) <= w(ai) <= w(aj) <= w(bi), 既 w(bi) = w(aj) = w(ai), 故 i 位置处边的权值仍相同. Assuming MST contains n edges, there existing two MST T and T', w(e) stand for the weight of edge e.
For T w(a1) <= w(a2) <= ... w(an)
For T' w(b1) <= w(b2) <= ... w(bn)
Also assuming i is the first occuring index where ai ≠ bi, let's assume w(ai) >= w(bi). **Condition 1** : If bi is in T, because the preceeding edges befor ai and bi are same, if T containing bi then bi must after ai. So there existing j > i, w(aj) = w(bi). Which produce w(bi) = w(aj) >= w(ai) >= w(bi). So w(ai) = w(bi). **Condition 2** : If bi is not in T, then adding bi to T will produce a circle. Because T is MST, any weights of nodes in this circle will be less or equal than w(bi). Besides, in this circles, there must exiting aj which not in T' and have w(aj) <= w(bi), j > i. So, w(bi) <= w(ai) <= w(aj) <= w(bi), w(bi) = w(aj) = w(ai). ### Exercises 23.1-9 *** Let T be a minimum spanning tree of a graph G = (V, E), and let V′ be a subset of V. Let T′ be the subgraph of T induced by V′, and let G′ be the subgraph of G induced by V′. Show that if T′ is connected, then T′ is a minimum spanning tree of G′. ### `Answer` 用 cut (V', V - V') 分割图 G, 该 cut 一定不影响 T', 且 T' 是 T 的子集, 所以 T' 对于 G' 是安全的. 如果 T' 是连通的, 则 T' 一定是 G' 的最小生成树. We use cut(V', V - V') to cut graph. This cut will not influence T' and T' is the subset of T, so to G', T' is safe. if T′ is connected, then T′ is a minimum spanning tree of G. ### Exercises 23.1-10 *** Given a graph G and a minimum spanning tree T , suppose that we decrease the weight of one of the edges in T . Show that T is still a minimum spanning tree for G. More formally, let T be a minimum spanning tree for G with edge weights given by weight function w. Choose one edge (x, y) ∈ T and a positive number k, and define the weight function w' by ![](./repo/s1/4.jpg) Show that T is a minimum spanning tree for G with edge weights given by w′. ### `Answer` We prove by cut. Originally, (x,y) is the light edge in a certain cut(V1, V2). Decreasing the weight of (x,y), (x,y) is still a light edge. So T is a minimum spanning tree for G with edge weights given by w′. ### Exercises 23.1-11 * Given a graph G and a minimum spanning tree T , suppose that we decrease the weight of one of the edges not in T . Give an algorithm for finding the minimum spanning tree in the modified graph. ### `Answer` 假设 (u, v) 不在最小生成树 T 中, 减小 (u, v) 权值后, 形成新的最小生成树 T'. 可能的情况是 T' 包含 (u, v) 或者 T' = T 保持不变. 算法只需寻找 T 中 u -> v 路径中权值最重边 x, 如果该边权值大于 (u, v), 则 T' = T - x + (u, v). 如果 (u, v) 权值大于 x, 则 T' = T. 路径可用 DFS 算法求得, 从 u 开始 v 结束. 因为 T 是最小生成树, 所以路径唯一, 时间 O(V+E). If(u,v) is not in MST, decrease the weight of(u, v), we may form a new MST T'. **Condition 1** : if the weightest edge e in path from u->v is greater than edge(u,v).Then we can replace e with (u,v). **Condition 2** : if the weightest edge e in path from u->v is less or equal than edge(u,v).Then we need not change, *** Follow [@louis1992](https://github.com/gzc) on github to help finish this task. 本节部分答案参考自[这里](http://blog.csdn.net/anye3000/article/details/12091125) ================================================ FILE: C23-Minimum-Spanning-Trees/23.2.md ================================================ ### Exercises 23.2-1 *** Kruskal's algorithm can return different spanning trees for the same input graph G, depending on how ties are broken when the edges are sorted into order. Show that for each minimum spanning tree T of G, there is a way to sort the edges of G in Kruskal's algorithm so that the algorithm returns T. ### `Answer` 产生不同的MST的原因就在于当有几条相同的边时选择的顺序不一样. The reason why there may be several different MST is that we have several choices on same weighted edge. Given a minimum spanning tree T we wish to sort the edges in Kruskal’s algorithm such that it produces T. For each edge e in T simply make sure that it preceeds any other edge not in T with weight w(e). ### Exercises 23.2-2 *** Suppose that the graph G = (V, E) is represented as an adjacency matrix. Give a simple implementation of Prim's algorithm for this case that runs in O(V^2) time. ### `Answer` MST-PRIM(G, w, r) for each u ∈ V[G] do key[u] <- ∞ π[u] <- NIL key[r] <- 0 Q <- V[G] while !isEmpty(Q) do u <- EXTRACT-MIN(Q) for i <- 1 to n do if MATRIX[u][i] == 1 and i ∈ Q and w(u,i) < key[i] then π[i] <- u key[i] <- w(u, i) ### Exercises 23.2-3 *** Is the Fibonacci-heap implementation of Prim's algorithm asymptotically faster than the binary-heap implementation for a sparse graph G = (V, E), where |E| = Θ(V)? What about for a dense graph, where |E| = Θ(V2)? How must |E| and |V| be related for the Fibonacci-heap implementation to be asymptotically faster than the binary-heap implementation? ### `Answer` Consider the running times of Prims algorithm implemented with either a binary heap or a Fibon-nacci heap. Suppose |E| = Θ(V) then the running times are: • Binary: O(ElgV) = O(V lgV) • Fibonnacci: O(E+VlgV)=O(VlgV) If |E| = Θ(V2) then: • Binary:O(ElgV)=O(V2lgV) • Fibonnacci: O(E + V lg V) = O(V2) The Fibonnacci heap beats the binary heap implementation of Prims algorithm when |E| = ω(V) since O(E+VlgV) = O(VlgV) t for |E| = O(VlgV) but O(ElgV) = ω(VlgV) for |E| = ω(V ). For |E| = ω(V lg V ) the Fibonnacci version clearly has a better running time than the ordinary version. ### Exercises 23.2-4 *** Suppose that all edge weights in a graph are integers in the range from 1 to |V|. How fast can you make Kruskal's algorithm run? What if the edge weights are integers in the range from 1 to W for some constant W? ### `Answer` If w is a constant we can use **counting sort** • Sorting the edges: O(E lg E) time. • O(E) operations on a disjoint-set forest taking O(Eα(V)). The sort dominates and hence the total time is O(E lg E). Sorting using counting sort when the edges fall in the range 1, . . . , |V | yields O(V + E) = O(E) time sorting. The total time is then O(Eα(V )). If the edges fall in the range 1, . . . , W for any constant W we still need to use Ω(E) time for sorting and the total running time cannot be improved further. ### Exercises 23.2-5 *** Suppose that all edge weights in a graph are integers in the range from 1 to |V|. How fast can you make Prim's algorithm run? What if the edge weights are integers in the range from 1 to W for some constant W? ### `Answer` The running time of Prims algorithm is composed : • O(V) initialization. • O(V · time for EXTRACT-MIN). • O(E · time for DECREASE-KEY). If the edges are in the range 1, . . . , |V| the Van Emde Boas priority queue can speed up EXTRACT- MIN and DECREASE-KEY to O(lg lg V) thus yielding a total running time of O(V lg lg V +E lg lg V) = O(E lg lg V ). If the edges are in the range from 1 to W we can implement the queue as an array [1...W+1] where the ith slot holds a doubly linked list of the edges with weight i. The (W+1)st slot contains ∞. EXTRACT-MIN now runs in O(W) = O(1) time since we can simply scan for the first nonempty slot and return the first element of that list. DECREASE-KEY runs in O(1) time as well since it can be implemented by moving an element from one slot to another. ### Exercises 23.2-6 * *** Suppose that the edge weights in a graph are uniformly distributed over the half-open interval [0, 1). Which algorithm, Kruskal's or Prim's, can you make run faster? ### `Answer` Kruskal, using bucket sort. ### Exercises 23.2-7 * *** Suppose that a graph G has a minimum spanning tree already computed. How quickly can the minimum spanning tree be updated if a new vertex and incident edges are added to G? ### `Answer` 如果只有一条边,只需要将这个顶点和这条边加进去. 如果有k(k > 1)条边,那么需要删去k-1条边. 假设新节点是v,那么从v必然有一些回路. 遍历k-1次,每次都能找到一个回路,从该回路中删除一条权值最大的边. If there is only one edge, just add this edge. If there are k(k > 1) edges, then we need to remmove k-1 edges. We can find cycles by Union-Find and remove the weightest edge in an union. This algorithm need (k-1) passes. ### Exercises 23.2-8 *** Professor Toole proposes a new divide-and-conquer algorithm for computing minimum spanning trees, which goes as follows. Given a graph G = (V, E), partition the set V of vertices into two sets V1 and V2 such that |V1| and |V2| differ by at most 1. Let E1 be the set of edges that are incident only on vertices in V1, and let E2 be the set of edges that are incident only on vertices in V2. Recursively solve a minimum-spanning-tree problem on each of the two subgraphs G1 = (V1, E1) and G2 = (V2, E2). Finally, select the minimum-weight edge in E that crosses the cut (V1, V2), and use this edge to unite the resulting two minimum spanning trees into a single spanning tree. Either argue that the algorithm correctly computes a minimum spanning tree of G, or provide an example for which the algorithm fails. ### `Answer` We argue that the algorithm fails. Consider the graph G below. We partition G into V1 and V2 as follows: V1 = {A, B}, V2 = {C, D}. E1 = {(A, B)}. E2 = {(C, D)}. The set of edges that cross the cut is Ec = {(A, C), (B, D)}. ![](./repo/s2/1.png) Now, we must recursively find the minimum spanning trees of G1 and G2. We can see that in this case, MST(G1) = G1 and MST(G2) = G2. The minimum spanning trees of G1 and G2 are shown below on the left. The minimum weighted edge of the two edges across the cut is edge (A, C). So (A, C) is used to connect G1 and G2. This is the minimum spanning tree returned by Professor Borden’s algorithm. It is shown below and to the right. ![](./repo/s2/2.png) We can see that the minimum-spanning tree returned by Professor Borden’s algorithm is not the minimum spanning tree of G, therefore, this algorithm fails. [reference](http://test.scripts.psu.edu/users/d/j/djh300/cmpsc465/notes-4985903869437/solutions-to-some-homework-exercises-as-shared-with-students/4-solutions-clrs-23.pdf) *** Follow [@louis1992](https://github.com/gzc) on github to help finish this task. 本节部分答案参考自[这里](http://blog.csdn.net/anye3000/article/details/12091125) ================================================ FILE: C24-Single-Source-Shortest-Paths/24.1.md ================================================ ### Exercises 24.1-1 *** Run the Bellman-Ford algorithm on the directed graph of Figure 24.4, using vertex z as the source. In each pass, relax edges in the same order as in the figure, and show the d and π values after each pass. Now, change the weight of edge (z, x) to 4 and run the algorithm again, using s as the source. ### `Answer` ~ | s | t | x | y | z :---:|:---:|:---:|:---:|:---:|:---: d | 2 | 4 | 6 | 9 | 0 π | z | x | y | s | Ø ~ | s | t | x | y | z :---:|:---:|:---:|:---:|:---:|:---: d | 0 | 0 | 4 | 7 | -2 π | Ø | x | z | s | t but with weight 4 of edge (z, x), BELLMAN-FORD algorithm returns false since there is a negative cycle. To be specific, edge (t, z), (z, x), (x, t) create the negative cycle. ### Exercises 24.1-2 *** Prove Corollary 24.3. ### `Answer` 如果没有通路,则无法进行松弛. ### Exercises 24.1-3 *** Given a weighted, directed graph G = (V, E) with no negative-weight cycles, let m be the maximum over all pairs of vertices u, v ∈ V of the minimum number of edges in a shortest path from u to v. (Here, the shortest path is by weight, not the number of edges.) Suggest a simple change to the Bellman-Ford algorithm that allows it to terminate in m + 1 passes, even if m is not known in advance. ### `Answer` We can simply implement this optimization of BELLMAN-FORD algorithm by remebering if `v` was `relaxed or not`. If `v` is `relaxed` then we wait to see if `v` was `udpated` (which means being relaxed again). If `v` was not `updated`, then we would stop. **P.S. More explanation:** Because the greatest number of edges on any shortest path from the source is m, then the path-relaxation property tells us that after m iterations of BELLMAN-FORD, every vertex v has achieved its shortest-path weight in v.d. By the upper-bound property, after m iterations, no d values will ever change. Therefore, no d values will change in the (m+1)st iteration. Because we do not know m in advance, we cannot make the algorithm iterate exactly m times and then terminate. But if we just make the algorithm stop when nothing changes any more, it will stop after m + 1 iterations. ### Exercises 24.1-4 *** Modify the Bellman-Ford algorithm so that it sets d[v] to -∞ for all vertices v for which there is a negative-weight cycle on some path from the source to v. ### `Answer` Bellman-Ford-New(G, w, s) Initialize-Single-Source(G, s) for i <- 1 to |V[G]| - 1 do for each edge (u,v) ∈ E[G] do Relax[u, v, w] for each edge (u, v) ∈ E[G] do if d[v] > d[u] + w(u, v) d[v] = -∞ for each v such that d[v] = -∞ do Follow-And-Mark-Pred(v) Follow-And-Mark-Pred(v) if π[v] != nil and d[π[v]] != -∞ do d[π[v]] = -∞ Follow-And-Mark-Pred(π[v]) else return [reference](http://beyondabstraction.net/school/cs441/hw10.pdf) ### Exercises 24.1-5 * *** Let G = (V, E) be a weighted, directed graph with weight function w : E → R. Give an O(V E)-time algorithm to find, for each vertex v ∈ V , the value δ*(v) = min{u∈V} {δ(u, v)}. ### `Answer` 假设图 G 中没有权值为负的环路. 修改 RELAX 如下: ``` MOD_RELAX(u, v, w) min = w(u, v) + ((u.d < 0) ? u.d : 0) if v.d > min v.d = min ``` 即更新 v.d 时考虑 u 为源点或者 u 为其他最短路径上某结点这两种情况. 然后运行修改过的 Bellman-Ford 算法: ``` MOD_BELLMAN_FORD(G, w) for each v ∈ G.V v.d = Infinity for i = 1 to |G.V| - 1 for each edge(u, v) ∈ G.E MOD_RELAX(u, v, w) ``` 最后 v.d 即为所求. 算法的运行时间显然为 O(VE) ### Exercises 24.1-6 * *** Suppose that a weighted, directed graph G = (V, E) has a negative-weight cycle. Give an efficient algorithm to list the vertices of one such cycle. Prove that your algorithm is correct. ### `Answer` 在 BF 算法找到负权回路上的一个点以后,从该点出发,顺着前驱逆向遍历,即可得到负权回路。 由于是负权回路,所以在 BF 算法的松弛过程中,一定会反复松弛负权回路中的所有边,即在 BF 算法的松弛过程结束后,负权回路中的顶点的前驱也一定是负权回路中的点。 [reference](http://www.stolerman.net/studies/cs521/cs521_fall_2011_sol4.pdf) *** Follow [@louis1992](https://github.com/gzc) on github to help finish this task. 本节部分答案参考自[这里](http://blog.csdn.net/anye3000/article/details/12091125) ================================================ FILE: C24-Single-Source-Shortest-Paths/24.2.md ================================================ ### Exercises 24.2-1 *** Run DAG-SHORTEST-PATHS on the directed graph of Figure 24.5, using vertex r as the source. ### `Answer` straightforward. ### Exercises 24.2-2 *** Suppose we change line 3 of DAG-SHORTEST-PATHS to read 3 for the first |V| - 1 vertices, taken in topologically sorted order Show that the procedure would remain correct. ### `Answer` 因为最后一次对结果没有影响. ### Exercises 24.2-3 *** The PERT chart formulation given above is somewhat unnatural. It would be more natural for vertices to represent jobs and edges to represent sequencing constraints; that is, edge (u, v) would indicate that job u must be performed before job v. Weights would then be assigned to vertices, not edges. Modify the DAG-SHORTEST-PATHS procedure so that it finds a longest path in a directed acyclic graph with weighted vertices in linear time. ### `Answer` PERT(G) topologically sort the vertices of G INITIALIZE(G) for each vertex u, taken in topologically sorted order do for each vertex v ∈ Adj[u] do RELAX(u,v) INITIALIZE(G) for each vertex v ∈ V[G] do d[v] = w[v] π[v] = NIL RELAX(u, v) if(d[v] < d[u] + w[v]) d[v] = d[u] + w[v] π[v] = u ### Exercises 24.2-4 *** Give an efficient algorithm to count the total number of paths in a directed acyclic graph. Analyze your algorithm. ### `Answer` DAG-PATHS(G, s) topologically sort the vertices of G INITIALIZE(G, s) for each vertex u, taken in topologically sorted order do for each vertex v ∈ Adj[u] do c[v] = c[v] + c[u] INITIALIZE(G, s) for each vertex v ∈ V[G] do c[v] = 0 c[s] = 1 *** Follow [@louis1992](https://github.com/gzc) on github to help finish this task. 本节部分答案参考自[这里](http://blog.csdn.net/anye3000/article/details/12091125) ================================================ FILE: C24-Single-Source-Shortest-Paths/24.3.md ================================================ ### Exercises 24.3-1 *** Run Dijkstra's algorithm on the directed graph of Figure 24.2, first using vertex s as the source and then using vertex z as the source. In the style of Figure 24.6, show the d and π values and the vertices in set S after each iteration of the while loop. ### `Answer` ![](./repo/s3/1.png) ![](./repo/s3/2.png) ![](./repo/s3/3.png) ### Exercises 24.3-2 *** Give a simple example of a directed graph with negative-weight edges for which Dijkstra's algorithm produces incorrect answers. Why doesn't the proof of Theorem 24.6 go through when negative-weight edges are allowed? ### `Answer` Dijkstra算法的原理是:每次新拓展一个最近的点,更新与其相邻的点的距离,当所有边的权值都为正时,由于不会存在一个距离更短的没拓展过的点,所以这个点的距离永远不会被改变,因而保证了算法的正确性.但是这个原理有一个限制,就是边权不能为负. ### Exercises 24.3-3 *** Suppose we change line 4 of Dijkstra's algorithm to the following. **4** while |Q| > 1 This change causes the while loop to execute |V | - 1 times instead of |V | times. Is this proposed algorithm correct? ### `Answer` 完全正确,其他点已经做过relax,不可能再更新了.否则就破坏了这个算法的基本性质. ### Exercises 24.3-4 *** We are given a directed graph G = (V, E) on which each edge (u, v) ∈ E has an associated value r(u, v), which is a real number in the range 0 ≤ r(u, v) ≤ 1 that represents the reliability of a communication channel from vertex u to vertex v. We interpret r(u, v) as the probability that the channel from u to v will not fail, and we assume that these probabilities are independent. Give an efficient algorithm to find the most reliable path between two given vertices. ### `Answer` 令w(u,v) = lg(r(u,v)),即可转化用Dijkstra算法解决. ### Exercises 24.3-5 *** Let G = (V, E) be a weighted, directed graph with weight function w : E → {1, 2, ..., W } for some positive integer W , and assume that no two vertices have the same shortest-path weights from source vertex s. Now suppose that we define an unweighted, directed graph G' = (V U V', E') by replacing each edge (u, v) ∈ E with w(u, v) unit-weight edges in series. How many vertices does G' have? Now suppose that we run a breadth-first search on G'. Show that the order in which vertices in V are colored black in the breadth-first search of G' is the same as the order in which the vertices of V are extracted from the priority queue in line 5 of DIJKSTRA when run on G. ### `Answer` 图 G 与 图 G' 的区别如下图所示: ![](./repo/s3/24.3-5.png) 由图可知, 对应图 G 的每一条边 (u, v), 图 G' 都会新增 w(u, v) - 1 个结点, 因此图 G' 的结点数为 ![](./repo/s3/24.3-5-equal.png) 对于遍历顺序的证明: 假设在图 G 上运行 DIJKSTRA 算法时, 结点 u, v 先后从优先队列中取出. 由题意, 从源点 s 到其他各点的最短路径互不相同, 所以可以肯定 d(u) < d(v). 又因为在图 G' 上进行广度优先遍历时, 结点 u 会在第 d[u] 步染成黑色, 结点 v 则在第 d[v] 步. 因此图 G' 上结点的染色顺序与在图 G 上运行 DIJKSTRA 算法时从优先队列取出的顺序相同. ### Exercises 24.3-6 *** Let G = (V, E) be a weighted, directed graph with weight function w : E → {0, 1, ..., W } for some nonnegative integer W . Modify Dijkstra's algorithm to compute the shortest paths from a given source vertex s in O(W V + E) time. ### `Answer` 用一个二维数组A[wv]实现优先级队列.具有长度为d的节点就在A[d]的list中. EXTRACT-MIN总共需要O(WV)的时间. 因为跑完整个算法相当于遍历了一遍数组. DECREASE-KEY总共需要O(E)的时间. 每次检查一条边做relax,只需要根据d移动到数组A的对应位置. ### Exercises 24.3-7 *** Modify your algorithm from Exercise 24.3-6 to run in O((V + E) lg W ) time. (Hint: How many distinct shortest-path estimates can there be in V - S at any point in time?) ### `Answer` 用binary heap实现priority queue. ### Exercises 24.3-8 *** Suppose that we are given a weighted, directed graph G = (V, E) in which edges that leave the source vertex s may have negative weights, all other edge weights are nonnegative, and there are no negative-weight cycles. Argue that Dijkstra's algorithm correctly finds shortest paths from s in this graph. ### `Answer` 这种情况下不会破坏已经更新的点的距离. *** Follow [@louis1992](https://github.com/gzc) on github to help finish this task. 本节部分答案参考自[这里](http://blog.csdn.net/anye3000/article/details/12091125) ================================================ FILE: C24-Single-Source-Shortest-Paths/24.4.md ================================================ ### Exercises 24.4-1 *** Find a feasible solution or determine that no feasible solution exists for the following system of difference constraints: x1 -x2 ≤1 x1 -x4 ≤-4 x2 -x3 ≤2 x2 -x5 ≤7 x2 -x6 ≤5 x3 -x6 ≤10 x4 -x2 ≤2 x5 -x1 ≤-1 x5 -x4 ≤3 x6 -x3 ≤-8 ### `Answer` (-5, -3, 0, -1, -6, -8) ### Exercises 24.4-2 *** Find a feasible solution or determine that no feasible solution exists for the following system of difference constraints: x1 -x2 ≤4 x1 -x5 ≤5 x2 -x4 ≤-6 x3 -x2 ≤1 x4 -x1 ≤3 x4 -x3 ≤5 x4 -x5 ≤10 x5 -x3 ≤-4 x5 -x4 ≤-8 ### `Answer` 没有解,因为x4 -> x2 -> x3 -> x5 -> x1 -> x4形成了一个负权回路. ### Exercises 24.4-3 *** Can any shortest-path weight from the new vertex v0 in a constraint graph be positive? Explain. ### `Answer` 不可能为正数,因为最大已经是0了.不可能大于0. ### Exercises 24.4-4 *** Express the single-pair shortest-path problem as a linear program. ### `Answer` 将G视为约束图,得到差分约束Ax<=b。设起点为i,终点为j,求解目标为 (xj - xi) 的最大值。 ### Exercises 24.4-5 *** Show how to modify the Bellman-Ford algorithm slightly so that when it is used to solve a system of difference constraints with m inequalities on n unknowns, the running time is O(nm). ### `Answer` V0这个顶点和他的n条权值为0的边其实是没有意义的,一开始初始化的时候可以对所有的点v,令d[v] = 0. ### Exercises 24.4-6 *** Suppose that in addition to a system of difference constraints, we want to handle **equality constraints** of the form xi = xj + bk. Show how the Bellman-Ford algorithm can be adapted to solve this variety of constraint system. ### `Answer` xi >= xj + bk xi <= xj + bk ### Exercises 24.4-7 *** Show how a system of difference constraints can be solved by a Bellman-Ford-like algorithm that runs on a constraint graph without the extra vertex v0. ### `Answer` 同练习24.4-5 ### Exercises 24.4-8 * *** Let Ax ≤ b be a system of m difference constraints in n unknowns. Show that the Bellman- Ford algorithm, when run on the corresponding constraint graph, maximizes **x1+x2+...+xn** subject to Ax≤b and xi ≤0 for all xi. ### `Answer` Bellman-Ford 处理线性规划问题的算法:添加虚拟的顶点v0到约束图,并设置该节点到其他节点的边权重均为0,之后以 v0 为源使用 Bellman-Ford 算法,得出的 v0 到各点的最小距离即为满足条件的解 xi,此结果已经最大化了x1+x2+...xn. 正确性证明:(反证法) 假设存在另一组解 (y1,y2...yn),其和更大,则至少存在一个 yi>xi。由于xi对应了约束图上 v0 顶点到 vi 顶点的最小距离,我们设该最小路径通过的顶点分别是 v1,v2...vi,我们有: 不等式1: (根据 y 是满足约束条件的一组解) y2-y1 < w(v1,v2), y3-y2 < w(v2,v3) ... yi-yi-1 < w(vi-1,vi) 不等式2: (根据 y1<0 的约束) yi < yi-y1 等式: (根据 v1 到 vi 是求出的最短路径) xi = w(v0,v1)+w(v1,v2)+...+w(v(i-1),vi) 注意该等式右边第一项是0. 结合以上三个式子,我们有: yi < yi-y1 < yi-y_(i-1)+y_(i-1)...-y2+y2-y1 < w(v1,v2)+w(v2,v3)+...+w(v_(i-1),v_i) = xi 与假设矛盾,因此 x 就是和最大的解,证毕。 ### Exercises 24.4-9 * *** Show that the Bellman-Ford algorithm, when run on the constraint graph for a system Ax ≤ b of difference constraints, minimizes the quantity (max {xi} - min {xi}) subject to Ax ≤ b. Explain how this fact might come in handy if the algorithm is used to schedule construction jobs. ### `Answer` 根据上题,Bellman-Ford 算法在约束图上给出的解为 x1,x2...x3。设其中的最小值为 xi。依旧考虑 v0 到 vi 的这一情况下的最短路径,记路过的顶点为 v1, v2... vi-1。首先由于 w(v0,v1)=0, 则 x1=0。(因为最短路径的子路径仍旧是最短路径)。而由于距 v0 的最短距离不可能为正数,则 x1 即为解 x 中的最大值。此时我们有数据区间 x1-xi=-xi=-[w(v1,v2)+w(v2,v3)+...w(vi-1,vi)]。 考虑存在另一组满足约束的解 y1,y2...yi。那么 y1-yi = -(y2-y1)-(y3-y2)-...-(yi-yi-1) > -w(v1,v2)-w(v2,v3)-...-w(vi-1,vi) = -xi。解 y 的极差一定不小于 y1-yi,而该值大于原解 x。由此得证 x 已经是满足要求的极差最小的解。 考虑每个 x 是一个任务完成的时刻,在一定约束下,我们总是希望完成任务的总时长最小,此即为最小化极差的应用。 ### Exercises 24.4-10 *** Suppose that every row in the matrix A of a linear program Ax ≤ b corresponds to a difference constraint, a single-variable constraint of the form xi ≤ bk, or a single-variable constraint of the form -xi ≤ bk. Show how to adapt the Bellman-Ford algorithm to solve this variety of constraint system. ### `Answer` 新造一个节点u,令约束条件变成xi - xu ≤ bk. 并且初始化d(u) = 0. ### Exercises 24.4-11 *** Give an efficient algorithm to solve a system Ax ≤ b of difference constraints when all of the elements of b are real-valued and all of the unknowns xi must be integers. ### `Answer` 把所有的b向下取整。因为 xi - xj ≤ b 同时 xi - xj 是整数,可得 xi - xj 一定小于等于b向下取整。 ### Exercises 24.4-12 * *** Give an efficient algorithm to solve a system Ax ≤ b of difference constraints when all of the elements of b are real-valued and a specified subset of some, but not necessarily all, of the unknowns xi must be integers. ### `Answer` 仍然采用最短路径算法,在松弛过程中,若有必要(x.v为整数),取 v.d = {(u.d + w(u, v)) 向下取整}。 证明: 假设这个算法得出的解(最短路径)不满足约束条件,更具体的,xi - xj > bm。 因为我们在松弛过程中,由于向下取整,导致实际取的 v.d ≤ u.d + w(u, v),而如果取 v.d = u.d + w(u, v) 结果是满足约束条件的,可知一定是j点得最短路径取小了。 另一方面,原来的约束 xi - xj ≤ bm 在约束图中是点j到点i的路径。如果i点在j点之前已经取得最短路径,对j点的循环过程会导致i点最短路径更短,可知i点一定在j点之后取得最短路径。根据松弛过程可知,算出的i点的最短路径一定满足约束,即 xi - xj ≤ bm。 与假设矛盾。 因此,按照该方法算出的结果一定是一个可行解。 *** Follow [@louis1992](https://github.com/gzc) on github to help finish this task. 本节部分答案参考自[这里](http://blog.csdn.net/anye3000/article/details/12091125) ================================================ FILE: C24-Single-Source-Shortest-Paths/README.md ================================================ UNSOLVED [24.4.4](./24.4.md#exercises-244-4) [24.4.9](./24.4.md#exercises-244-9) [24.4.11](./24.4.md#exercises-244-11) [24.4.12](./24.4.md#exercises-244-12) ================================================ FILE: C25-All-Pairs-Shortest-Paths/25.1.md ================================================ ### Exercises 25.1-1 *** Run SLOW-ALL-PAIRS-SHORTEST-PATHS on the weighted, directed graph of Figure 25.2, showing the matrices that result for each iteration of the loop. Then do the same for FASTER-ALL-PAIRS-SHORTEST-PATHS. ### `Answer` straightforward. ### Exercises 25.1-2 *** Why do we require that wii = 0 for all 1≤i≤n? ### `Answer` 为了保证递归定义式25.2的正确性. ### Exercises 25.1-3 *** What does the matrix 0 ∞ ∞ ... ∞ ∞ 0 ∞ ... ∞ L(0) = ∞ ∞ 0 ... ∞ . . . . . . . . . . . . . . . ∞ ∞ ∞ ... ∞ used in the shortest-paths algorithms correspond to in regular matrix multiplication? ### `Answer` 单位矩阵 ### Exercises 25.1-4 *** Show that matrix multiplication defined by EXTEND-SHORTEST-PATHS is associative. ### `Answer` A brute force verification will work. Here is the proof. [Proof of the exercise 25.1-4.pdf](https://github.com/gzc/CLRS/files/456393/Proof.of.the.exercise.25.1-4.pdf) ### Exercises 25.1-5 *** Show how to express the single-source shortest-paths problem as a product of matrices and a vector. Describe how evaluating this product corresponds to a Bellman-Ford-like algorithm (see Section 24.1). ### `Answer` 向量就是所有节点对矩阵L的一行(行号为单源起点),W依然是权重矩阵。矩阵乘法中,向量与W的第i列相乘对应算法第3,4行中对 (?, i) 这类的边进行松弛。 ### Exercises 25.1-6 *** Suppose we also wish to compute the vertices on shortest paths in the algorithms of this section. Show how to compute the predecessor matrix Π from the completed matrix L of shortest-path weights in O(n3) time. ### `Answer` FING-Π(L, w) for i <- 1 to n for j <- 1 to n for k <- 1 to n do if L(i,k)+w(k,j) = L(i,j) do Π(i,j) = k ### Exercises 25.1-7 *** The vertices on shortest paths can also be computed at the same time as the shortest-path weights. Let us define  to be the predecessor of vertex j on any minimum-weight path from i to j that contains at most m edges. Modify EXTEND-SHORTEST-PATHS and SLOW- ALL-PAIRS-SHORTEST-PATHS to compute the matrices Π(1), Π(2),..., Π(n-1) as the matrices L(1), L(2),..., L(n-1) are computed. ### `Answer` 这个改动很简单. 就是更新l(ij)的时候同时更新Π,类似于relax松弛操作. 所以伪代码暂时就不写啦. ### Exercises 25.1-8 *** The FASTER-ALL-PAIRS-SHORTEST-PATHS procedure, as written, requires us to store ⌈lg(n - 1)⌉ matrices, each with n2 elements, for a total space requirement of Θ(n2 lg n). Modify the procedure to require only Θ(n2) space by using only two n × n matrices. ### `Answer` 这道题跟pow(2, n)一样. pow(2,n) res <- 1 temp <- 2 while(n > 0) if n%2 = 1 res *= temp n-- else temp *= 2 n /= 2 return res 这里的res和temp就是对应的两个n*n矩阵. ### Exercises 25.1-9 *** Modify FASTER-ALL-PAIRS-SHORTEST-PATHS so that it can detect the presence of a negative-weight cycle. ### `Answer` 只需要查看最后的L(n-1)矩阵.如果对角线上的元素有负值,就说明有负权回路. 如果是有三个节点,ABC,由A到B为-1,B到C为-1,C到A为-1,最后的L(n-1)矩阵的对角线不会是负值。因为算法考虑的是最多n-1条边的情况,而负环的最大可能性是n条边,所以如果不多循环一次的话是无法检测出的;另外也可以多循环一次,查看两个矩阵是否有变化,这样是O(n^2),而仅仅是多循环一次检查对角线的话只用O(n) ### Exercises 25.1-10 *** Give an efficient algorithm to find the length (number of edges) of a minimum-length negative-weight cycle in a graph. ### `Answer` 跟练习25.1-9差不多,我们可以根据L矩阵的对角线判断存不存在负权回路.如果L(m)是第一次对角线出现负值,那么m就是我们的值. *** Follow [@louis1992](https://github.com/gzc) on github to help finish this task. ================================================ FILE: C25-All-Pairs-Shortest-Paths/25.2.md ================================================ ### Exercises 25.2-1 *** Run the Floyd-Warshall algorithm on the weighted, directed graph of Figure 25.2. Show the matrix D(k) that results for each iteration of the outer loop. ### `Answer` straightforward. ### Exercises 25.2-2 *** Show how to compute the transitive closure using the technique of Section 25.1. ### `Answer` 将EXTEND-SHORTEST-PATHS中第7行的min换成**OR**,+换成**AND** ### Exercises 25.2-3 *** Modify the FLOYD-WARSHALL procedure to include computation of the Π(k) matrices according to equations (25.6) and (25.7). Prove rigorously that for all i ∈ V , the predecessor subgraph Gπ,i is a shortest-paths tree with root i. ### `Answer` 证明分三步: 1. 无环。 每次循环过程中,一定有 dij(k) <= dij(k - 1)。 首先证明如果检验dij(k)之后有 πij(k) = l,则 dij(k) >= dil(k) + wlj。 检验dij(k)的过程中, 若 dij(k - 1) <= dik(k - 1) + dkj(k - 1),有 dij(k) = dij(k - 1),πij(k) = πij(k - 1) 都为l,l 存在于 (1, 2, ... , k - 1) 中。 k - 1 次循环在此时已经完成,故有 dij(k) = dij(k - 1) = dil(k - 1) + wlj >= dil(k) + wlj。 若 dij(k - 1) > dik(k - 1) + dkj(k - 1),有 dij(k) = dik(k - 1) + dkj(k - 1),必然有 πij(k) = πkj(k - 1) 都为l,l 存在于 (1, 2, ... , k - 1) 中。 k - 1 次循环在此时已经完成,故有 dij(k) = dik(k - 1) + dkj(k - 1) = dik(k - 1) + dkl(k - 1) + wlj,又因为dil(k - 1) <= dik(k - 1) + dkl(k - 1)(dil(k - 1) 是最短路径权重),有 dij(k) >= dil(k - 1) + wlj >= dil(k) + wlj。 现在假设Gπ,i中存在环路 (v0, v1, ... , vs),vs = v0,πip(k) = p - 1,p = 1, 2, ... , s。不失一般性,假设 πis(k) = s - 1 是形成环的最后一步,这一步之前 πis(k) != s - 1。那么这一步之前一定有 dij(k) > dil(k) + wlj。此时,将所有 (v0, v1, ... , vs)对应的式子累加起来,有 Σdij(k) > Σdil(k) + Σwlj,j = 1, 2, ... , s,l = 0, 1, ... , s - 1,有Σwlj < 0,j = 1, 2, ... , s,l = 0, 1, ... , s - 1,这与Floyd-Wallshall算法基本假设 不存在权重为负的环 矛盾。 2. Gπ,i 是一棵以 i 为根的有根树。 因为 πij 记录了从 i 到 j 的路径中j的前驱点,Gπ,i 只取 πij 不为空的点,根据归纳法容易证明 Gπ,i 中存在 i 到 Gπ,i 中任意点的简单路径。下面证明这种简单路径唯一。 假设 i 到 j 有不止一条简单路径,设其中两条为 i~>p~>x->z~>j 和 i~>p~>y->z~>j,对于z点,有 πiz = x 同时 πiz = y,有 x = y,这两条路径为一条。同样的方法可以证明所有路径其实是一条,与假设矛盾。 3. Gπ,i 包含的一定是 i 到每个点的最短路径。根据算法过程可知正确。 ### Exercises 25.2-4 *** As it appears above, the Floyd-Warshall algorithm requires Θ(n3) space, since we compute for i, j, k = 1, 2,...,n. Show that the following procedure, which simply drops all the superscripts, is correct, and thus only Θ(n2) space is required. ![](./repo/s2/1.png) ### `Answer` 当然是正确的,因为这个动态规划只需要保存上一个状态.也就是要计算当前这个状态只需要借助上一个状态. ### Exercises 25.2-5 *** Suppose that we modify the way in which equality is handled in equation (25.7): ![](./repo/s2/2.png) Is this alternative definition of the predecessor matrix Π correct? ### `Answer` 感觉正确呀. ### Exercises 25.2-6 *** How can the output of the Floyd-Warshall algorithm be used to detect the presence of a negative-weight cycle? ### `Answer` 只需要在正常的Floyd-Warshall算法完成后再多跑一个循环,如果有一个值还能更新则说明有负权回路。 或者,权重矩阵D对角线上出现了负值。 ### Exercises 25.2-7 *** Another way to reconstruct shortest paths in the Floyd-Warshall algorithm uses values Φij(k) for i, j, k = 1, 2,..., n, where Φij(k) is the highest-numbered intermediate vertex of a shortest path from i to j in which all intermediate vertices are in the set {1, 2,..., k}. Give a recursive formulation for  Φij(k) , modify the FLOYD-WARSHALL procedure to compute the Φij(k) values, and rewrite the PRINT-ALL-PAIRS-SHORTEST-PATH procedure to take the matrix Φ = (Φij(n)) as an input. How is the matrix Θ like the s table in the matrix-chain multiplication problem of Section 15.2? ### `Answer` Φij(k-1) 如果dij(k-1) <= dik(k-1) + dkj(k-1) Φij(k) = k otherwise PRINT-ALL-PAIRS-SHORTEST-PATH(Φ,i,j) if i == j then print i else if Φ(i,j) = -1 then print "no path from 'i' to 'j' exists" else PRINT-ALL-PAIRS-SHORTEST-PATH(Φ,i,Φ(i, j)) PRINT-ALL-PAIRS-SHORTEST-PATH(Φ,Φ(i, j),j) ### Exercises 25.2-8 *** Give an O(V E)-time algorithm for computing the transitive closure of a directed graph G = (V, E). ### `Answer` 对每个节点都跑一次DFS. 一共V个点,E条边,所以是O(VE)的时间. ### Exercises 25.2-9 *** Suppose that the transitive closure of a directed acyclic graph can be computed in f(|V|,|E|) time, where f is a monotonically increasing function of |V| and |E|. Show that the time to compute the transitive closure `G* = (V, E*)` of a general directed graph G = (V, E) is f(|V|,|E|) + O(V + E*). ### `Answer` 首先随便选择一个点开始DFS,搜索中如果遇到灰色的点说明有环,把这一次搜索用到的边 (u, v) 记录下来,并从E中删除。这个操作复杂度 `O(V + E) <= O(V + E*)`。这个操作之后有环图变成了无环图,运行f(|V|, |E|)算法,得到不完整的传递闭包。 结束后,遍历被删除的边,连接u和v,以及所有v能到达的点。由于记录 (u, v) 不会重复,遍历过程最多把不完整的传递闭包每条边遍历一遍,外加被删除的边(这些边一定存在于`E*`中),因此复杂度为`O(E*)`。 因此,这个算法总的复杂度为`f(|V|, |E|) + O(V + E*)`。 *** Follow [@louis1992](https://github.com/gzc) on github to help finish this task. ================================================ FILE: C25-All-Pairs-Shortest-Paths/25.3.md ================================================ ### Exercises 25.3-1 *** Use Johnson’s algorithm to find the shortest paths between all pairs of vertices in the graph of Figure 25.2. Show the values of h and w' computed by the algorithm. ### `Answer` | v | 1 | 2 | 3 | 4 | 5 | 6 | |---|---|---|---|---|---|---| | h(v) | -5 | -3 | 0 | -1 | -6 | -8 | W'(u,v) = W(u,v) + h(u) - h(v) | (u,v) | (1,5) | (2,1) | (2,4) | (3,2) | (3,6) | (4,1) | (4,5) | (5,2) | (6,2) | (6,3) | |---|---|---|---|---|---|---|---|---|---|---| | w(u,v) | -1 | 1 | 2 | 2 | -8 | -4 | 3 | 7 | 5 | 10 | | w'(u,v) | 0 | 3 | 0 | 5 | 0 | 0 | 8 | 4 | 0 | 2 | ### Exercises 25.3-2 *** What is the purpose of adding the new vertex s to V , yielding V' ? ### `Answer` No matter where we start Bellmanford's algorithm, some of those vertex costs will be infinite. Johnson’s algorithm avoids this problem by adding a new vertex s to the graph, with zero-weight edges going from s to every other vertex, but no edges going back into s. This addition doesn’t change the shortest paths between any other pair of vertices, because there are no paths into s. ### Exercises 25.3-3 *** Suppose that w(u,v) >= 0 for all edges (u,v) belonging to E. What is the relationship between the weight functions w and w'? ### `Answer` In such a case, the h(v) will be simply 0 for all the vertices v belonging to the graph because the shortest path from s to any vertex would be the direct path to that vertex from s and the weight of all those edges is 0. Hence, w' = w. ### Exercises 25.3-4 *** Professor Greenstreet claims that there is a simpler way to reweight edges than the method used in Johnson’s algorithm. Letting w* = min{w(u,v)} over all the edges, just define w'(u,v) = w(u,v) - w* for all the edges. What is wrong with the professor’s method of reweighting? ### `Answer` It changes shortest paths. Consider the following graph. V = {s; x; y; z} and there are 4 edges: w(s, x) = 2, w(x, y) = 2, w(s, y) = 5, and w(s, z) = 10. So we’d add 10 to every weight to make w'. With w, the shortest path from s to y is s --> x --> y, with weight 4. With w', y the shortest path from s to y is s --> y, with weight 15. (The path s --> x --> y has weight 24). The problem is that by just adding the same amount to every edge, you penalize paths with more edges, even if their weights are low. ### Exercises 25.3-5 *** Suppose that we run Johnson’s algorithm on a directed graph G with weight function w. Show that if G contains a 0-weight cycle c, then w'(u,v) = 0 for every edge (u,v) in c. ### `Answer` Let there be two nodes a,b in the cycle c. Assume that w(a,b) + h(a) - h(b) > 0 This means that w(b,a) + h(b) - h(a) < 0 But this is a contradiction as there are no negative edges after reweighting in the Johnson's algorithm. ### Exercises 25.3-6 *** Professor Michener claims that there is no need to create a new source vertex in line 1 of JOHNSON . He claims that instead we can just use G' = G and let s be any vertex. Give an example of a weighted, directed graph G for which incorporating the professor’s idea into JOHNSON causes incorrect answers. Then show that if G is strongly connected (every vertex is reachable from every other vertex), the results returned by JOHNSON with the professor’s modification are correct. ### `Answer` ![example](repo/s3/25_3_6.gif) In the example, if vertex 2, 3 or 4 is chosen as s, then the vertex 1 will be unreachable. On adding an extra edge in the example, from vertex 4 to vertex 1, the graph becomes strongly connected and all the vertices become reachable. ================================================ FILE: C25-All-Pairs-Shortest-Paths/Floyd_Warshall.cpp ================================================ /************************************************************************* > File Name: Floyd_Warshall.cpp > Author: Louis1992 > Mail: zhenchaogan@gmail.com > Blog: http://gzc.github.io > Created Time: Tue Nov 3 17:10:56 2015 ************************************************************************/ #include #include #define V 5 #define INF 99999 using namespace std; int dist[V][V]; int Pre[V][V]; void printSolution(); void printPre(); void floydWarshell (int graph[][V]) { int i, j, k; for (i = 0; i < V; i++) { for (j = 0; j < V; j++) { dist[i][j] = graph[i][j]; if(i == j || graph[i][j] == INF) Pre[i][j] = INF; else Pre[i][j] = i+1; } } for (k = 0; k < V; k++) { for (i = 0; i < V; i++) { for (j = 0; j < V; j++) { if (dist[i][k] + dist[k][j] < dist[i][j]) { dist[i][j] = dist[i][k] + dist[k][j]; Pre[i][j] = Pre[k][j]; } } } } printSolution(); printPre(); } void printSolution() { printf ("Following matrix shows the shortest distances" " between every pair of vertices \n"); for (int i = 0; i < V; i++) { for (int j = 0; j < V; j++) { if (dist[i][j] == INF) printf("%7s", "INF"); else printf ("%7d", dist[i][j]); } printf("\n"); } } void printPre() { printf ("Following matrix shows the Pre \n"); for (int i = 0; i < V; i++) { for (int j = 0; j < V; j++) { if (Pre[i][j] == INF) printf("%7s", "INF"); else printf ("%7d", Pre[i][j]); } printf("\n"); } } void findPath(int start, int end) { vector paths; paths.push_back(end); while(Pre[start-1][end-1] != start) { paths.push_back(Pre[start-1][end-1]); end = Pre[start-1][end-1]; } paths.push_back(start); printf("The path : "); for(int i = paths.size()-1;i >= 0;i--) printf("%i ", paths[i]); printf("\n"); } int main() { int graph[V][V] = { {0, 3, 8, INF, -4}, {INF, 0, INF, 1, 7}, {INF, 4, 0, INF,INF}, {2, INF, -5, 0, INF}, {INF, INF, INF, 6, 0} }; floydWarshell(graph); findPath(2, 5); return 0; } ================================================ FILE: C25-All-Pairs-Shortest-Paths/README.md ================================================ UNSOLVED [25.1.4](./25.1.md#exercises-251-4) [25.1.5](./25.1.md#exercises-251-5) [25.2.3](./25.2.md#exercises-252-3) [25.2.9](./25.2.md#exercises-252-9) ================================================ FILE: C26-Flow-networks/26.1.md ================================================ ### Exercises 26.1-1 *** Using the definition of a flow, prove that if (u, v) ∉ E and (v, u) ∉ E then f(u, v) = f(v, u) = 0. ### `Answer` f(u,v) <= c(u,v) f(u,v) + f(v, u) = 0 c(u,v) = 0 So f(u,v) = f(v,u) = 0 ### Exercises 26.1-2 *** Prove that for any vertex v other than the source or sink, the total positive flow entering v must equal the total positive flow leaving v. ### `Answer` Capacity constraint: For all u, v ∈ V, we require f(u, v) <= c(u, v) Skew Symmetry: For all u, v ∈ V, we require f(u, v) <= -f(u, v) Flow conservation: For all u, v ∈ V – {s, t}, we require Σ f(u, v) = 0 ### Exercises 26.1-3 *** Extend the flow properties and definitions to the multiple-source, multiple-sink problem. Show that any flow in a multiple-source, multiple-sink flow network corresponds to a flow of identical value in the single-source, single-sink network obtained by adding a supersource and a supersink, and vice versa. ### `Answer` In Figure 26.2, we add a supersource s and add a directed edge (s, si) with capacity c(s, si) = ∞ for each i = 1, 2, . . . , m. We also create a new supersink t and add a directed edge (ti, t) with capacity c(ti, t) = ∞ for each i = 1, 2, . . . , n. The single source s simply provides as much flow as desired for the multiple sources si, and the single sink t likewise consumes as much flow as desired for the multiple sinks ti. Because the virutal edges of s and t can consume as much flows as they want, they don't influence the actual edges. ### Exercises 26.1-4 *** Prove Lemma 26.1. ### `Answer` Pretty obvious, several vertices make up a set. Apply each f on on vertices and combine them. ### Exercises 26.1-5 *** For the flow network G = (V, E) and flow f shown in Figure 26.1(b), find a pair of subsets X, Y for all u, v ∈ V. If f1 and f2 are flows in G, which of the three flow properties must the flow sum f1 + f2 satisfy, and which might it violate? V for which f(X, Y) = - f(V - X, Y). Then, find a pair of subsets X, Y ∈ V for which f (X, Y) ≠ - f(V - X, Y). ### `Answer` X = {v1, v2} , Y = {v3} , f(X, Y) = - f(V - X, Y) X = {s} , Y = {t} , f(X, Y) ≠ - f(V - X, Y) ### Exercises 26.1-6 *** Given a flow network G = (V,E),let *f1* and *f2* be functions from *V×V* to **R**.The **flow sum** *f1* + *f2* is the function from *V* × *V* to **R** defined by (26.4) (f1+f2)(u,v) = f1(u,v) + f2(u,v) for all u, v ∈ V. If f1 and f2 are flows in G, which of the three flow properties must the flow sum f1 + f2 satisfy, and which might it violate? ### `Answer` Flow conservation, may violate Capacity constraint ### Exercises 26.1-7 *** Let f be a flow in a network, and let α be a real number. The *scalar flow product*, denoted α f, is a function from V × V to **R defined by (αf)(u, v) = α · f (u, v). Prove that the flows in a network form a **convex set**. That is, show that if f1 and f2 are flows, thensoisαf1 +(1-α)f2 forallαintherange0≤α≤1. ### `Answer` UNSOLVED ### Exercises 26.1-8 *** State the maximum-flow problem as a linear-programming problem. ### `Answer` Haven't focus on linear-programming yet. ### Exercises 26.1-9 *** Professor Adam has two children who, unfortunately, dislike each other. The problem is so severe that not only do they refuse to walk to school together, but in fact each one refuses to walk on any block that the other child has stepped on that day. The children have no problem with their paths crossing at a corner. Fortunately both the professor's house and the school are on corners, but beyond that he is not sure if it is going to be possible to send both of his children to the same school. The professor has a map of his town. Show how to formulate the problem of determining if both his children can go to the same school as a maximum-flow problem. ### `Answer` Each edge has weight 1, to see if the max flow is bigger than 1. *** Follow [@louis1992](https://github.com/gzc) on github to help finish this task. ================================================ FILE: C26-Flow-networks/26.2.md ================================================ I get some answers from the web but I forgot the link, pretty sorry. ### Exercises 26.2-1 *** In Figure 26.1(b), what is the flow across the cut ({s, v2, v4}, {v1, v3, t})? What is the capacity of this cut? ### `Answer` ![](./repo/s2/1.png) Net flow across the cut is f(s, v1) + f(v2,v1) + f(v2,v3) + f(v3,v4) + f(v4,t) = 11 + 1 + -4 + 7 + 4 = 19 and its capacity is = 16 + 14 +7 + 4 = 41 ### Exercises 26.2-2 *** Show the execution of the Edmonds-Karp algorithm on the flow network of Figure 26.1(a). ### `Answer` ![](./repo/s2/2.png) ### Exercises 26.2-3 *** In the example of Figure 26.5, what is the minimum cut corresponding to the maximum flow shown? Of the augmenting paths appearing in the example, which two cancel flow? ### `Answer` c and d. ### Exercises 26.2-4 *** Prove that for any pair of vertices u and v and any capacity and flow functions c and f, we have cf(u, v) + cf(v, u) = c(u, v) + c(v, u). ### `Answer` UNSOLVED ### Exercises 26.2-5 *** Recall that the construction in Section 26.1 that converts a multisource, multisink flow network into a single-source, single-sink network adds edges with infinite capacity. Prove that any flow in the resulting network has a finite value if the edges of the original multisource, multisink network have finite capacity. ### `Answer` Because we could find a minimum cut which only include finite weight edges. ### Exercises 26.2-6 *** Suppose that each source si in a multisource, multisink problem produces exactly pi units of flow, so that f(si, V) = pi. Suppose also that each sink tj consumes exactly qj units, so that f(V, tj) = qj, where Σi pi = Σj qj. Show how to convert the problem of finding a flow f that obeys these additional constraints into the problem of finding a maximum flow in a single-source, single-sink flow network. ### `Answer` v is the virtual start point. If f(si, V) = q, then c(v, si) = q. ### Exercises 26.2-7 *** Prove Lemma 26.3. ### `Answer` UNSOLVED ### Exercises 26.2-8 *** Show that a maximum flow in a network G = (V, E) can always be found by a sequence of at most |E| augmenting paths. (Hint: Determine the paths after finding the maximum flow.) ### `Answer` [reference](http://www.cise.ufl.edu/class/cot5405sp08/assignments/hw4Sol.pdf) The solution to this question is fairly straight forward. First you find the maximum flow. Then you could get the minimum cut of the network. We also know that you saturate one edge on the minimum cut each time. Thus, the upper bound is simply |E|. ### Exercises 26.2-9 *** The **edge connectivity** of an undirected graph is the minimum number k of edges that must be removed to disconnect the graph. For example, the edge connectivity of a tree is 1, and the edge connectivity of a cyclic chain of vertices is 2. Show how the edge connectivity of an undirected graph G = (V, E) can be determined by running a maximum-flow algorithm on at most |V| flow networks, each having O(V) vertices and O(E) edges. ### `Answer` Each edge has same weight, find the minimum cut. ### Exercises 26.2-10 *** Suppose that a flow network G = (V, E) has symmetric edges, that is, (u, v) ∈ E if and only if (v, u) ∈ E. Show that the Edmonds-Karp algorithm terminates after at most |V| |E|/4 iterations. (Hint: For any edge (u, v), consider how both δ(s, u) and δ(v, t) change between times at which (u, v) is critical.) ### `Answer` From the time (u, v) becomes critical to the time when it NEXT becomes crticial, the distance of u from source increases by at least 2, and the distance of v to sink increases by at least 2. The distance from source to u is at most |V|-2, so it can be critical for at most |V|/4 times. Given |E| edges, thus the result is |V||E|/4. *** Follow [@louis1992](https://github.com/gzc) on github to help finish this task. ================================================ FILE: C26-Flow-networks/26.3.md ================================================ ### Exercises 26.3-1 *** Run the Ford-Fulkerson algorithm on the flow network in Figure 26.8(b) and show the residual network after each flow augmentation. Number the vertices in L top to bottom from 1 to 5 and in R top to bottom from 6 to 9. For each iteration, pick the augmenting path that is lexicographically smallest. ### `Answer` ![](./repo/s3/1.png) ### Exercise 26.3-5 *** We say that bipartie graph G = (V, E), where V = L ∪ R is d-regular if every vertex v ∈ V has degree exactly d. Every d-regular bipartie graph has |L|=|R|. Prove that every d-regular bipartite graph has a matching of cardinality |L| by arguing that a minimum cut of the corresponding flow network has capacity |L|. ### `Answer` Consider the corresponding network flow graph G' to `k`-regular graph G: *** Follow [@louis1992](https://github.com/gzc) on github to help finish this task. ================================================ FILE: C26-Flow-networks/maxflow/FlowEdge.cpp ================================================ /************************************************************************* > File Name: FlowEdge.cpp > Author: Louis1992 > Mail: zhenchaogan@gmail.com > Blog: http://gzc.github.io > Created Time: Fri Nov 13 08:19:03 2015 ************************************************************************/ #ifndef FLOW_EDGE_H #define FLOW_EDGE_H #include using namespace std; class FlowEdge { int v; // from int w; // to double capacity; // capacity double flow; // flow public: FlowEdge() { this->v = -1; this->w = -1; this->capacity = 0; this->flow = 0.0; } FlowEdge(int v, int w, double capacity) { this->v = v; this->w = w; this->capacity = capacity; this->flow = 0.0; } FlowEdge(int v, int w, double capacity, double flow) { this->v = v; this->w = w; this->capacity = capacity; this->flow = flow; } FlowEdge(const FlowEdge &e) { this->v = e.v; this->w = e.w; this->capacity = e.capacity; this->flow = e.flow; } int from() { return this->v; } int to() { return this->w; } double getCapacity() { return this->capacity; } double getFlow() { return this->flow; } int other(int vertex) { if (vertex == v) return this->w; else return this->v; } double residualCapacityTo(int vertex) { if (vertex == this->v) return this->flow; // backward edge else return this->capacity - this->flow; // forward edge } void addResidualFlowTo(int vertex, double delta) { if (vertex == v) this->flow -= delta; // backward edge else this->flow += delta; // forward edge } friend ostream& operator << (ostream& out, FlowEdge& fe) { out << to_string(fe.v) << "->" << to_string(fe.w) << "\t" << to_string(fe.flow) << "\t" << to_string(fe.capacity) << endl; return out; } }; #endif ================================================ FILE: C26-Flow-networks/maxflow/FlowNetwork.cpp ================================================ /************************************************************************* > File Name: FlowNetwork.cpp > Author: Louis1992 > Mail: zhenchaogan@gmail.com > Blog: http://gzc.github.io > Created Time: Fri Nov 13 08:43:38 2015 ************************************************************************/ #ifndef Flow_Network_H #define Flow_Network_H #include #include #include #include #include "FlowEdge.cpp" using namespace std; class FlowNetwork { int V; int E; vector > adj; public: FlowNetwork(int V) { this->V = V; this->E = 0; for (int v = 0; v < V; v++) { list temp; adj.push_back(temp); } } FlowNetwork(ifstream& in) { int k, e; in >> k; new(this) FlowNetwork(k); in >> e; for (int i = 0; i < e; i++) { int v,w; in >> v; in >> w; double capacity; in >> capacity; FlowEdge fe(v, w, capacity); addEdge(fe); } } int getV() { return this->V; } int getE() { return this->E; } void addEdge(FlowEdge e) { int v = e.from(); int w = e.to(); adj[v].push_back(e); adj[w].push_back(e); this->E++; } list getadj(int v) { return adj[v]; } void addResidualFlowTo(FlowEdge e, int v, double bottle) { for(list::iterator it = adj[e.from()].begin(); it != adj[e.from()].end(); it++) { if(it->from() == e.from() && it->to() == e.to()) { it->addResidualFlowTo(v, bottle); break; } } } list edges() { list lists; for (int v = 0; v < this->V; v++) for (FlowEdge e : getadj(v)) { if (e.to() != v) lists.push_back(e); } return lists; } friend ostream& operator << (ostream& out, FlowNetwork& fn) { out << to_string(fn.V) << " " << to_string(fn.E) << endl; for (int v = 0; v < fn.V; v++) { out << to_string(v) << ": "; for (FlowEdge e : fn.getadj(v)) { if (e.to() != v) out << e << " "; } out << endl; } return out; } }; #endif ================================================ FILE: C26-Flow-networks/maxflow/FordFulkerson.cpp ================================================ /************************************************************************* > File Name: FordFulkerson.cpp > Author: Louis1992 > Mail: zhenchaogan@gmail.com > Blog: http://gzc.github.io > Created Time: Fri Nov 13 10:04:58 2015 ************************************************************************/ #include #include #include #include #include #include #include #include #include "FlowNetwork.cpp" #include "FlowEdge.cpp" using namespace std; /** * Implementation with Edmonds-Karp */ class FordFulkerson { const double FLOATING_POINT_EPSILON = 1E-11; bool *marked; // marked[v] = true iff s->v path in residual graph FlowEdge *edgeTo; // edgeTo[v] = last edge on shortest residual s->v path double value; // current value of max flow // return excess flow at vertex v double excess(FlowNetwork G, int v) { double excess = 0.0; for (FlowEdge e : G.getadj(v)) { if (v == e.from()) excess -= e.getFlow(); else excess += e.getFlow(); } return excess; } // check optimality conditions bool check(FlowNetwork G, int s, int t) { // check that flow is feasible if (!isFeasible(G, s, t)) { cerr << "Flow is infeasible" << endl; return false; } // check that s is on the source side of min cut and that t is not on source side if (!inCut(s)) { cerr << "source " << s << " is not on source side of min cut" << endl; return false; } if (inCut(t)) { cerr << "sink " << t << " is on source side of min cut" << endl; return false; } // check that value of min cut = value of max flow double mincutValue = 0.0; for (int v = 0; v < G.getV(); v++) { for (FlowEdge e : G.getadj(v)) { if ((v == e.from()) && inCut(e.from()) && !inCut(e.to())) mincutValue += e.getCapacity(); } } if (abs(mincutValue - value) > FLOATING_POINT_EPSILON) { cerr << "Max flow value = " << value << ", min cut value = " << mincutValue << endl; return false; } return true; } // return excess flow at vertex v bool isFeasible(FlowNetwork G, int s, int t) { // check that capacity constraints are satisfied for (int v = 0; v < G.getV(); v++) { for (FlowEdge e : G.getadj(v)) { if (e.getFlow() < -FLOATING_POINT_EPSILON || e.getFlow() > e.getCapacity() + FLOATING_POINT_EPSILON) { cerr << "Edge does not satisfy capacity constraints: " << e << endl; return false; } } } // check that net flow into a vertex equals zero, except at source and sink if (abs(value + excess(G, s)) > FLOATING_POINT_EPSILON) { cerr << "Excess at source = " << excess(G, s) << endl; cerr << "Max flow = " << value << endl; return false; } if (abs(value - excess(G, t)) > FLOATING_POINT_EPSILON) { cerr << "Excess at sink = " << excess(G, t) << endl; cerr << "Max flow = " << value << endl; return false; } for (int v = 0; v < G.getV(); v++) { if (v == s || v == t) continue; else if (abs(excess(G, v)) > FLOATING_POINT_EPSILON) { cerr << "Net flow out of " << v << " doesn't equal zero" << endl; return false; } } return true; } // is there an augmenting path? // if so, upon termination edgeTo[] will contain a parent-link representation of such a path // this implementation finds a shortest augmenting path (fewest number of edges), // which performs well both in theory and in practice bool hasAugmentingPath(FlowNetwork G, int s, int t) { if(edgeTo != nullptr) delete edgeTo; if(marked != nullptr) delete marked; edgeTo = new FlowEdge[G.getV()]; marked = new bool[G.getV()]; fill(marked, marked+G.getV(), false); // breadth-first search queue myqueue; myqueue.push(s); marked[s] = true; while (!myqueue.empty() && !marked[t]) { int v = myqueue.front(); myqueue.pop(); for (FlowEdge e : G.getadj(v)) { int w = e.other(v); // if residual capacity from v to w if (e.residualCapacityTo(w) > 0) { if (!marked[w]) { edgeTo[w] = e; marked[w] = true; myqueue.push(w); } } } } // is there an augmenting path? return marked[t]; } public: FordFulkerson(FlowNetwork G, int s, int t) { // while there exists an augmenting path, use it value = excess(G, t); while (hasAugmentingPath(G, s, t)) { // compute bottleneck capacity double bottle = DBL_MAX; for (int v = t; v != s; v = edgeTo[v].other(v)) { bottle = min(bottle, edgeTo[v].residualCapacityTo(v)); } // augment flow for (int v = t; v != s; v = edgeTo[v].other(v)) { G.addResidualFlowTo(edgeTo[v], v, bottle); } value += bottle; } // check optimality conditions //assert (check(G, s, t) == true); } double getvalue() { return value; } bool inCut(int v) { return marked[v]; } }; int main() { // create flow network with V vertices and E edges int V = 6; int E = 10; int s = 0, t = V-1; ifstream in("input.txt"); FlowNetwork G (in); // compute maximum flow and minimum cut FordFulkerson maxflow(G, s, t); cout << "Max flow from " << s << " to " << t << endl; for (int v = 0; v < G.getV(); v++) { for (FlowEdge e : G.getadj(v)) { if ((v == e.from()) && e.getFlow() > 0) cout << " " << e << endl; } } // print min-cut cout << "Min cut: " << endl; for (int v = 0; v < G.getV(); v++) { if (maxflow.inCut(v)) cout << v << " "; } cout << endl; cout << "Max flow value = " << maxflow.getvalue() << endl; } ================================================ FILE: C26-Flow-networks/maxflow/input.txt ================================================ 6 10 0 1 16 0 2 13 1 2 10 2 1 4 1 3 12 3 2 9 2 4 14 4 3 7 3 5 20 4 5 4 ================================================ FILE: C26-Flow-networks/maxflow/makefile ================================================ all: testnetwork testflowedge maxflow .PHONY:all maxflow: FordFulkerson.o g++ -o maxflow -std=c++11 FordFulkerson.o FordFulkerson.o : FordFulkerson.cpp g++ -c -std=c++11 FordFulkerson.cpp testnetwork: testFlowNetwork.o FlowNetwork.o g++ -o testnetwork -std=c++11 testFlowNetwork.o FlowNetwork.o testFlowNetwork.o : testFlowNetwork.cpp g++ -c -std=c++11 testFlowNetwork.cpp FlowNetwork.o : FlowNetwork.cpp g++ -c -std=c++11 FlowNetwork.cpp testflowedge: testFlowEdge.o FlowEdge.o g++ -o testflowedge testFlowEdge.o FlowEdge.o testFlowEdge.o : testFlowEdge.cpp g++ -c testFlowEdge.cpp FlowEdge.o : FlowEdge.cpp g++ -c FlowEdge.cpp clean: rm testflowedge rm testFlowEdge.o rm FlowEdge.o rm testFlowNetwork.o rm FlowNetwork.o rm testnetwork rm FordFulkerson.o rm maxflow ================================================ FILE: C26-Flow-networks/maxflow/readme.md ================================================ [reference](http://algs4.cs.princeton.edu/64maxflow/FordFulkerson.java.html) ================================================ FILE: C26-Flow-networks/maxflow/testFlowEdge.cpp ================================================ /************************************************************************* > File Name: testFlowEdge.cpp > Author: Louis1992 > Mail: zhenchaogan@gmail.com > Blog: http://gzc.github.io > Created Time: Fri Nov 13 08:27:56 2015 ************************************************************************/ #include #include "FlowEdge.cpp" using namespace std; int main() { FlowEdge e(12, 23, 3.14); cout << e; return 1; } ================================================ FILE: C26-Flow-networks/maxflow/testFlowNetwork.cpp ================================================ /************************************************************************* > File Name: testFlowNetwork.cpp > Author: Louis1992 > Mail: zhenchaogan@gmail.com > Blog: http://gzc.github.io > Created Time: Fri Nov 13 09:16:10 2015 ************************************************************************/ #include #include #include "FlowNetwork.cpp" using namespace std; int main() { ifstream in("input.txt"); FlowNetwork G(in); cout << G; return 0; } ================================================ FILE: C31-Number-Theoretic-Algorithms/31.1.md ================================================ ### Exercises 31.1-1 *** Prove that there are infinitely many primes. (Hint: how that none of the primes p1, p2, ..., pk divide (p1 p2 ··· pk) + 1.) ### `Answer` The hint tells us everything. If we have finite prime numbers{p1,p2,p3,...,pk}. We show that A = (p1 p2 ··· pk) + 1 is neither a composite number nor prime number. * Becauce we have finite prime numbers, then A is not prime. * On the other hand, A mod p1 = 1;A mod p2 = 1;...;A mod pk = 1. So, A is not composite. As a result, we have infinite many primes. ### Exercises 31.1-2 *** Prove that if a | b and b | c, then a | c. a | b means b = k1a b | c means c = k2b So, c = k2b = k1k2a which means a | c ### `Answer` If p[i] dismatch T[j],next time trace back to j+1;That is,compare from p[0] and T[j+1]. ### Exercises 31.1-3 *** Prove that if p is prime and 0 < k < p, then gcd(k, p) = 1. ### `Answer` Obvious. ### Exercises 31.1-4 *** Prove Corollary 31.5. ### `Answer` ab = kn b = n(k/a), because gcd(n,a) = 1,k/a is an integer b = k'n, k' = k/a so n | b ### Exercises 31.1-5 *** Prove that if p is prime and 0 < k < p, then  p | (p k). Conclude that for all integers a, b, and primes p, (a+b)p ≡ ap +bp (modp). ### `Answer` The first is pretty obvious using polynomial expansion. If solve the first, the second is easy too. (a+b)^p mod p = a^p + b^p + a^1*b^(p-1)(p 1) + ... mod p = a^p + b^p mod p ### Exercises 31.1-6 *** Prove that if a and b are any integers such that a | b and b > 0, then (x mod b) mod a = x mod a for any x. Prove, under the same assumptions, that x ≡ y mod b) implies x ≡ y (mod a) for any integers x and y. ### `Answer` Assume x = ra + b , b = ka x mod a = b x mod b = (ra+b)%(ka) = (r%k)a+b x mod b mod a = (r%k)a % a + b % a = b ### Exercises 31.1-7 *** For any integer k > 0, we say that an integer n is a kth power if there exists an integer a such that ak = n.We say that n > 1 is a nontrivial power if it is a kth power for some integer k > 1. Show how to determine if a given β-bit integer n is a nontrivial power in time polynomial in β. ### `Answer` I only have naive idea : iterate every number. ### Exercises 31.1-8 *** Prove equations (31.6)–(31.10). ### `Answer` straightforward ### Exercises 31.1-9 *** Show that the gcd operator is associative. That is, prove that for all integers a, b, and c, gcd(a, gcd(b, c)) = gcd(gcd(a, b), c). ### `Answer` Assume a = p1^i1 * p2^i2 * ... * pk^ik Assume b = p1^j1 * p2^j2 * ... * pk^jk Assume c = p1^l1 * p2^l2 * ... * pk^lk gcd(a, gcd(b, c)) = gcd(gcd(a, b), c) = p1^min(i1,j2,k1) * p2^min(i2,j2,k2) * ... *pk^min(ik,jk,lk) ### Exercises 31.1-10 *** Prove Theorem 31.8. ### `Answer` If the way is not unique, then some combinations of primes are equal to others. However, gcd(prime1, prime2) = 1, so it is not possible. ### Exercises 31.1-11 *** Give efficient algorithms for the operations of dividing a β-bit integer by a shorter integer and of taking the remainder of a β-bit integer when divided by a shorter integer. Your algorithms should run in time O(β2). ### `Answer` UNSOLVED ### Exercises 31.1-12 *** Give an efficient algorithm to convert a given β-bit (binary) integer to a decimal representation. Argue that if multiplication or division of integers whose length is at most β takes time M(β), then binary-to-decimal conversion can be performed in time Θ(M(β) lg β).  (Hint: Use a divide-and-conquer approach, obtaining the top and bottom halves of the result with separate recursions.) ### `Answer` [implementation](./exercise_code/binary2decimal.py) *** Follow [@louis1992](https://github.com/gzc) on github to help finish this task ================================================ FILE: C31-Number-Theoretic-Algorithms/31.2.md ================================================ ### Exercises 31.2-1 *** Prove that equations (31.11) and (31.12) imply equation (31.13). ### `Answer` straightforward ### Exercises 31.2-2 *** Compute the values (d, x, y) that the call EXTENDED-EUCLID(899, 493) returns. ### `Answer` (29, -6, 11) ### Exercises 31.2-3 *** Prove that for all integers a, k, and n, gcd(a, n) = gcd(a + kn, n). ### `Answer` gcd(a+kn,n) = gcd(n, (a+kn) mod n) = gcd(n, a) ### Exercises 31.2-4 *** Rewrite EUCLID in an iterative form that uses only a constant amount of memory (that is, stores only a constant number of integer values). ### `Answer` [implementation](./euclid.py) ### Exercises 31.2-5 *** If a > b ≥ 0, show that the invocation EUCLID(a, b) makes at most 1 + logφ b recursive calls. Improve this bound to 1 + logφ(b/ gcd(a, b)). ### `Answer` φ^(k+1) / Root(5) < F(k+1) < b k + 1 < 1/2logφ 5 + logφ b k <= logφ b + 1 The second is pretty simple, before executing, we divide gcd(a,b) and get the answer. ### Exercises 31.2-6 *** What does EXTENDED-EUCLID(Fk+1, Fk) return? Prove your answer correct. ### `Answer` a | b | 0 :----:|:----:|:----: 2 | 1 | (1,0,1) 3 | 2 | (1,1,-1) 5 | 3 | (1,-1,2) 8 | 5 | (1,2,-3) 13 | 8 | (1,-3,5) (1,1,2,3,5,8,13,...) we can conclude that * if k is odd, the answer is (1, F_k-2, -F_k-1) * if k is even, the answer is (1, -F_k-2, F_k-1) If k is odd, Fk+1 * Fk-2 - Fk * Fk-1 = Fk * Fk-2 + Fk-1 * Fk-2 - Fk * Fk-1 = Fk-1Fk-2 + Fk-2Fk-2 + Fk-1Fk-2 - Fk-1Fk-1 - Fk-1Fk-2 = Fk-2Fk-2 + Fk-1Fk-2 - Fk-1Fk-1 = 1(by mathematical induction we can obtain the result) if k is even, the prove is the same. ### Exercises 31.2-7 *** Define the gcd function for more than two arguments by the recursive equation gcd(a0, a1, ..., an) = gcd(a0, gcd(a1, a2, ..., an)). Show that the gcd function returns the same answer independent of the order in which its arguments are specified. Also show how to find integers x0, x1, ..., xn such that gcd(a0, a1, ..., an) = a0x0 + a1x1 + ··· + anxn. Show that the number of divisions performed by your algorithm is O(n + lg(max {a0, a1, ..., an})). ### `Answer` UNSOLVED ### Exercises 31.2-8 *** Define the gcd function for more than two arguments by the recursive equation gcd(a0, a1, ..., an) = gcd(a0, gcd(a1, a2, ..., an)). Show that the gcd function returns the same answer independent of the order in which its arguments are specified. Also show how to find integers x0, x1, ..., xn such that gcd(a0, a1, ..., an) = a0x0 + a1x1 + ··· + anxn. Show that the number of divisions performed by your algorithm is O(n + lg(max {a0, a1, ..., an})). ### `Answer` [implementation](./exercise_code/lcm.py) ### Exercises 31.2-9 *** Prove that n1, n2, n3, and n4 are pairwise relatively prime if and only if gcd(n1n2, n3n4) = gcd(n1n3, n2n4) = 1. Show more generally that n1, n2, ..., nk are pairwise relatively prime if and only if a set of ⌈lg k⌉ pairs of numbers derived from the ni are relatively prime. ### `Answer` a) k=4 case =>) Trivial. <=) can be proved by proof by contraposition . Assume n1, n2, n3, and n4 are not pairwise relatively prime, then there exists a pair i, j (1 <=i < j <= 4) such that gcd(ni, nj) > 1. If (i, j) = (1, 3), (1, 4), (2, 3), or (2, 4), then gcd(n1n2, n3n4) > 1. If (i, j) = (1, 2) or (3, 4), then gcd(n1n3, n2n4) > 1. Therefore, at least one of pair of gcd must be greater than 1. b) General case Assume the given integers are n0, ..., n(k-1). (The indices are changed.) Then, we can construct ⌈lg k⌉ pairs {(xt, yt)} where xt is the product of { nj | t-th bit of j is 0 } and yt is the product of { nj | t-th bit of j is 1 }. For example, with the following k=4 case,we get (x1, y1) = (n0n2, n1n3), (x2, y2) = (n0n1, n2n3). 0 : 00 1 : 01 2 : 10 3 : 11 Proof : =>) Any prime p is a divisor of at most one of {nj}, so p is also a divisor of at most one of xi and yi for each i. <=) can be proved by proof by contraposition proof by contraposition. Assume n0, n1, …, n(k-1) are not pairwise relatively prime, then there exists a pair i, j (0 <= i < j < k) such that gcd(ni, nj) = r > 1. Because i and j are different and less than k, there exists t (<= ⌈lg k⌉) such that the t-th bits of i and j are different. Therefore, we can get gcd(xt, yt) >= gcd(ni, nj) = r > 1. *** Follow [@louis1992](https://github.com/gzc) on github to help finish this task ================================================ FILE: C31-Number-Theoretic-Algorithms/31.7.md ================================================ ### Exercises 31.7-1 *** Consider an RSA key set with `p = 11`, `q = 29`, `n = 319`, and `e = 3`. What value of `d` should be used in the secret key? What is the encryption of the message `M = 100`? ### `Answer` We follow the procedure of RSA cryptosystem. 1. `n = pq = 319` 2. `phi(n) = (p-1)(q-1) = 280` 3. For fixed `e = 3`, apply Euclidean algorithm to find its inverse modulo `(p-1)(q-1)`. We find `1 = 280 - 3 x 93` and the inverse of `e` is `-93 mod 280 = 187` 4. Hence, `(e, n) = (3, 319)` is the public key and `(d, n) = (187, 319)` is the private key. 5. For `M = 100`, the cipher text is `100^3 mod 319 = 254`. ================================================ FILE: C31-Number-Theoretic-Algorithms/euclid.py ================================================ #!/usr/bin/env python # coding=utf-8 def gcd(a, b): while b != 0: tmp = b b = a % b a = tmp return a print gcd(69,99) ================================================ FILE: C31-Number-Theoretic-Algorithms/exercise_code/binary2decimal.py ================================================ #!/usr/bin/env python # coding=utf-8 def b2d(binary, base): l = len(binary) if l == 1: return int(binary)*base; mid = l/2 high = binary[:mid] low = binary[mid:] return b2d(high, base*(2**(l-mid)))+b2d(low, base) binary = "1111" print b2d(binary,1) ================================================ FILE: C31-Number-Theoretic-Algorithms/exercise_code/lcm.py ================================================ #!/usr/bin/env python # coding=utf-8 def gcd(a, b): while b != 0: tmp = b b = a % b a = tmp return a def lcm(items): while True: if len(items) == 1: return items[0] else: a = items[-1] b = items[-2] del items[-1] del items[-1] items.append(a*b/gcd(a,b)) print lcm([9,12,15]) ================================================ FILE: C31-Number-Theoretic-Algorithms/extended_euclid.py ================================================ #!/usr/bin/env python # coding=utf-8 def ee(a, b): if b == 0: return (a, 1, 0) res = ee(b, a % b) return (res[0], res[2], res[1] - a/b*res[2]) print ee(99, 78) print ee(899, 493) print ee(2,1) print ee(3,2) print ee(5,3) print ee(8,5) print ee(13,8) ================================================ FILE: C32-String-Matching/32.1.md ================================================ ### Exercises 32.1-1 *** Show the comparisons the naive string matcher makes for the pattern P = 0001 in the text T = 000010001010001. ### `Answer` straightforward. ### Exercises 32.1-2 *** Suppose that all characters in the pattern P are different. Show how to accelerate NAIVE- STRING-MATCHER to run in time O(n) on an n-character text T. ### `Answer` If p[i] dismatch T[j],next time trace back to j+1;That is,compare from p[0] and T[j+1]. ### Exercises 32.1-3 *** Suppose that pattern P and text T are randomly chosen strings of length m and n, respectively, from the d-ary alphabet Σd = {0, 1, . . . , d - 1}, where d ≥ 2. Show that the expected number of character-to-character comparisons made by the implicit loop in line 4 of the naive algorithm is ![](./repo/s1/1.png) over all executions of this loop. (Assume that the naive algorithm stops comparing characters for a given shift once a mismatch is found or the entire pattern is matched.) Thus, for randomly chosen strings, the naive algorithm is quite efficient. ### `Answer` It's a probability problem. assume we have to compare (n-m+1) times. a(n) b(n) each time: compare 1 1-1/d 2 1/d*(1-1/d) 3 (1/d)^2*(1-1/d) ... ... m (1/d)^(m-1)*(1-1/d) then wo can get the T(time) = (n-m+1)*[a(1)*b(1)+a(2)*b(2)+,,,+a(m)*b(m)] So we get the answer. ### Exercises 32.1-4 *** Suppose we allow the pattern P to contain occurrences of a gap character ⋄ that can match an arbitrary string of characters (even one of zero length). For example, the pattern ab⋄ba⋄c occurs in the text cabccbacbacab as ![](./repo/s1/2.png) Note that the gap character may occur an arbitrary number of times in the pattern but is assumed not to occur at all in the text. Give a polynomial-time algorithm to determine if such a pattern P occurs in a given text T , and analyze the running time of your algorithm. ### `Answer` To determine if a pattern P with gap characters exists in T partition P into substrings P1 , . . . , Pk determined by the gap characters. Search for P1 and if found continue searching for P2 and so on. This clearly find a pattern if one exists. *** Follow [@louis1992](https://github.com/gzc) on github to help finish this task ================================================ FILE: C32-String-Matching/32.2.md ================================================ ### Exercises 32.2-1 *** Working modulo q = 11, how many spurious hits does the Rabin-Karp matcher encounter in the text T = 3141592653589793 when looking for the pattern P = 26? ### `Answer` 15,59,92. So there are 3. ### Exercises 32.2-2 *** How would you extend the Rabin-Karp method to the problem of searching a text string for an occurrence of any one of a given set of k patterns? Start by assuming that all k patterns have the same length. Then generalize your solution to allow the patterns to have different lengths. ### `Answer` 如果k个模式都是等长的,那么算法修改不大.用这些模式模p.然后跑正常的RK算法.遇到有出现在其中的就去匹配一下. 如果不等长,可以按长度划分跑多次RK. ### Exercises 32.2-3 *** Show how to extend the Rabin-Karp method to handle the problem of looking for a given m × m pattern in an n × n array of characters. (The pattern may be shifted vertically and horizontally, but it may not be rotated.) ### `Answer` 核心思想是reduce 2d to 1d. 对长度为n每一列,我们都能根据RK算法计算出(n-m+1)个hash值.然后对每一行的连续的m个hash值,又能够根据RK的hash算法新算出一个值. 也就是说,原来的RK是把算出长度为m的模式的hash值.而现在是先计算出m个长度为m的hash值,再对这m个hash值再一次hash. ### Exercises 32.2-4 *** Alice has a copy of a long n-bit file A = , and Bob similarly has an n-bit file B = . Alice and Bob wish to know if their files are identical. To avoid transmitting all of A or B, they use the following fast probabilistic check. Together, they select a prime q > 1000n and randomly select an integer x from {0, 1, . . . , q - 1}. Then, Alice evaluates ![](./repo/s2/1.png) and Bob similarly evaluates B(x). Prove that if A ≠ B, there is at most one chance in 1000 that A(x) = B(x), whereas if the two files are the same, A(x) is necessarily the same as B(x). (Hint: See Exercise 31.4-4.) ### `Answer` UNSOLVED *** Follow [@louis1992](https://github.com/gzc) on github to help finish this task ================================================ FILE: C32-String-Matching/32.3.md ================================================ ### Exercises 32.3-1 *** Construct the string-matching automaton for the pattern P = aabab and illustrate its operation on the text string T = aaababaabaababaab. ### `Answer` run my program [FA.c](./FA.c) you will get the answer. ### Exercises 32.3-2 *** Draw a state-transition diagram for a string-matching automaton for the pattern ababbabbababbababbabb over the alphabet Σ = {a, b}. ### `Answer` run my program [FA.c](./FA.c) then you can draw the diagram. ### Exercises 32.3-3 *** We call a pattern P nonoverlappable if Pk ⊐ Pq implies k = 0 or k = q. Describe the state- transition diagram of the string-matching automaton for a nonoverlappable pattern. ### `Answer` 这样子的模式产生的要么指向下一个状态,要么重新回到状态0. ### Exercises 32.3-4 * *** Given two patterns P and P′, describe how to construct a finite automaton that determines all occurrences of either pattern. Try to minimize the number of states in your automaton. ### `Answer` UNSOLVED ### Exercises 32.3-5 *** Given a pattern P containing gap characters (see Exercise 32.1-4), show how to build a finite automaton that can find an occurrence of P in a text T in O(n) matching time, where n = |T|. ### `Answer` We can construct the finite automaton corresponding to a pattern P using the same idea as in exercise 32.1−4. Partition P into substrings P1, . . . , Pk determined by the gap characters. Construct finite automatons for each Pi and combine sequentially, i.e., the accepting state of Pi, i ∈ [1, k) is no longer accepting but has a single transition to Pi+1. *** Follow [@louis1992](https://github.com/gzc) on github to help finish this task ================================================ FILE: C32-String-Matching/32.4.md ================================================ ### Exercises 32.4-1 *** Compute the prefix function π for the pattern ababbabbabbababbabb when the alphabet is Σ = {a, b}. ### `Answer` run my program [KMP.c](./KMP.c) you will get the answer. ### Exercises 32.4-2 *** Give an upper bound on the size of π*[q] as a function of q. Give an example to show that your bound is tight. ### `Answer` Since π[q] < q we trivially have |π∗[q]| <= q. This bound is tight as illustrated by the string a^q. Here π[q] = q − 1, π(1)[q] = q − 2, and so on resulting in π∗[q] = {q − 1, . . . , 0}. ### Exercises 32.4-3 *** Explain how to determine the occurrences of pattern P in the text T by examining the π function for the string PT (the string of length m + n that is the concatenation of P and T). ### `Answer` The indices in which P occurs in PT can be determined as the set M = {q | m ∈ π∗[q] and q >= 2m}. ### Exercises 32.4-4 *** Show how to improve KMP-MATCHER by replacing the occurrence of π in line 7 (but not line 12) by π′, where π′ is defined recursively for q = 1, 2, . . . , m by the equation 0 if π[q] = 0, π'[q] = π'[π[q]] if π[q] != 0 and p[π[q]+1] = p[q+1] π[q] if π[q] != 0 and p[π[q]+1] != p[q+1] Explain why the modified algorithm is correct, and explain in what sense this modification constitutes an improvement. ### `Answer` 本质上和原算法是一样的,就是可以快速的推进,按最大的距离推进. ### Exercises 32.4-5 *** Give a linear-time algorithm to determine if a text T is a cyclic rotation of another string T′. For example, arc and car are cyclic rotations of each other. ### `Answer` [implementation](./exercise_code/str_spin.c) ### Exercises 32.4-6 * *** Give an efficient algorithm for computing the transition function δ for the string-matching automaton corresponding to a given pattern P. Your algorithm should run in time O(m |Σ|). (Hint: Prove that δ(q, a) = δ(π[q], a) if q = m or P[q + 1] ≠ a.) ### `Answer` UNSOLVED *** Follow [@louis1992](https://github.com/gzc) on github to help finish this task ================================================ FILE: C32-String-Matching/BF.c ================================================ #include #include /* * if find,return first offset * else return -1 */ int BF_match(char *text,char *pattern) { size_t n = strlen(text); size_t m = strlen(pattern); int i; int end = n-m; for(i = 0; i <= end; i++) { int j; char *p_text = text + i; for(j = 0;j < m;j++) { if(*(p_text+j) == *(pattern+j)) continue; else break; } if(j == m) return i; } return -1; } int main() { char *s1 = "abcabcabcd"; char *s2 = "abcd"; int offset = BF_match(s1,s2); printf("offset is %d\n",offset); return 0; } ================================================ FILE: C32-String-Matching/BM.c ================================================ #include #include #include int max(int a,int b) { if(a > b) return a; return b; } /* * from http://www.cnblogs.com/dsky/archive/2012/05/04/2483190.html */ int BM_match(char* pSrc, int nSrcSize, char* pSubSrc, int nSubSrcSize) { //1.坏字符数组 int bcSkip[256]; int i; for( i = 0; i < 256; i++) { bcSkip[i] = nSubSrcSize; } for (i = 0; i < nSubSrcSize - 1; i++) { bcSkip[pSubSrc[i]] = nSubSrcSize - i - 1; } //2.好后缀数组 int* suffix =(int*)malloc(nSubSrcSize*sizeof(int)); suffix[nSubSrcSize - 1] = nSubSrcSize; for ( i = nSubSrcSize - 2; i >= 0; i--) { int k = i; while( k >= 0 && pSubSrc[k] == pSubSrc[nSubSrcSize-1-i+k] ) { k--; } suffix[i] = i - k; } int* gsSkip = (int*)malloc(nSubSrcSize*sizeof(int)); for (i = 0; i < nSubSrcSize; i++) { gsSkip[i] = nSubSrcSize; } for ( i = nSubSrcSize - 1; i >= 0; i--) { if (suffix[i] == i + 1) { int j; for (j = 0; j < nSubSrcSize - 1 - i; ++j) { if (gsSkip[j] == nSubSrcSize) gsSkip[j] = nSubSrcSize - 1 - i; } } } for ( i = 0; i <= nSubSrcSize - 2; ++i) { gsSkip[nSubSrcSize - 1 - suffix[i]] = nSubSrcSize - 1 - i; } int nPos = 0; while (nPos <= nSrcSize - nSubSrcSize) { int j = nSubSrcSize - 1; while(j >= 0 && pSubSrc[j] == pSrc[j + nPos]) { j--; } if (j < 0) break; else { nPos += max(gsSkip[j], bcSkip[pSrc[j + nPos]]-(nSubSrcSize - 1 - j) ); } } free(gsSkip); return (nPos > nSrcSize - nSubSrcSize)? -1 : nPos; } int main() { char *s1 = "abcabcabcd"; char *s2 = "abcd"; int l1 = strlen(s1); int l2 = strlen(s2); int offset = BM_match(s1,l1,s2,l2); printf("offset is %d\n",offset); return 0; } ================================================ FILE: C32-String-Matching/FA.c ================================================ #include #include #include #define true 1 #define false 0 int min(int a,int b) { if(a > b) return b; return a; } //check whether Pk is suffix of Pq int suffix(char *pattern,int k,int q,char ch) { int i; if(pattern[k-1] != ch) return false; for(i = 0;i < k-1;i++) if(pattern[i] != pattern[q-k+i+1]) return false; return true; } /* * construct our FA */ void compute_transition_function(char *pattern,int *array,int numchars) { int m = strlen(pattern); int q; for(q = 0; q <= m; q++) { int chars; for(chars = 0; chars < numchars; chars++) { int k = min(m,q+1); while(k) { char ch = 'a'+chars; if(suffix(pattern,k,q,ch)) break; k--; } array[q*numchars+chars] = k; } } } int FA_match(char *text,int *array,int numchars,int receive) { int n = strlen(text); int q = 0; int i; for(i = 0;i < n;i++) { int index = numchars*q+text[i]-'a'; q = array[index]; if(q == receive) return i+1-receive; } return -1; } int main() { char *text = "aaababaabaababaab"; char *pattern = "aabab"; const int num_chars_alphabet = 2; int length = strlen(pattern); int *array = (int*)malloc(sizeof(int)*num_chars_alphabet*(length+1)); compute_transition_function(pattern,array,num_chars_alphabet); //This prints a chart showing present state and next states given //the corresponding inputs. printf("This is our chart\nstate\t"); for(char j = 'a'; j < 'a'+num_chars_alphabet; j++) printf("%c\t", j); printf("\n"); int i; for(i = 0;i <= length; i++) { printf("%d\t",i); int j; for(j = 0; j < num_chars_alphabet; j++) printf("%d\t",array[i*num_chars_alphabet+j]); printf("\n"); } int offset = FA_match(text,array,num_chars_alphabet,length); printf("offset is %d\n",offset); free(array); return 0; } ================================================ FILE: C32-String-Matching/KMP.c ================================================ #include #include #include /* * calculate our next array */ int* compute_prefix_function(char *pattern) { int m = strlen(pattern); int *next = (int*)malloc(m*sizeof(int)); next[0]=0; int k = 0; int q; for(q = 1;q < m;q++) { while(k > 0 && (pattern[k] != pattern[q]) ) k = next[k-1]; if (pattern[k] == pattern[q]) k++; next[q] = k; } return next; } int KMP_match(char *text,char *pattern) { int n = strlen(text); int m = strlen(pattern); int *next = compute_prefix_function(pattern); int q = 0; int i; for(i = 0;i < n;i++) { while(q > 0 && (pattern[q] != text[i]) ) q = next[q-1]; if (pattern[q] == text[i]) q++; if(q == m) return i+1-m; } free(next); return -1; } int main() { char *s1 = "bababaababababca"; char *s2 = "ababababca"; int offset = KMP_match(s1,s2); printf("offset is %d\n",offset); return 0; } ================================================ FILE: C32-String-Matching/README.md ================================================ UNSOLVED [32.2.4](./32.2.md#exercises-322-4) [32.3.4](./32.3.md#exercises-323-4) [32.4.6](./32.4.md#exercises-324-6) ================================================ FILE: C32-String-Matching/RK.c ================================================ #include #include #include //we find a prime 11!+1 a seven-digit prime int factorial(int n) { if(n == 1) return 1; else return n*factorial(n-1); } /* * d is the base * and q is the prime wo choose * if find,return first offset * else return -1 */ int RK_match(char *text,char *pattern,int d,int q) { size_t n = strlen(text); size_t m = strlen(pattern); size_t h = (size_t)pow(d,m-1); int p = 0; int t = 0; int k = 96; //'a' is 1; 'b' is 2 and so on int i; for(i = 0; i < m; i++) { p = (d*p+pattern[i]-k)%q; t = (d*t+text[i]-k)%q; } for(i = 0; i <= (n-m); i++) { /* * we made it a Las Vegas algorithm * to check if the string really same? */ if(p == t) { int j; char *p_text = text+i; for(j = 0;j < m;j++) { if(*(p_text+j) == *(pattern+j)) continue; else break; } if(j == m) return i; } //calculate the next num if(i < (n-m)) { int s = (d*(t-(text[i]-k)*h))+text[i+m]-k; t = s%q; } } return -1; } ================================================ FILE: C32-String-Matching/exercise_code/str_spin.c ================================================ #include #include #include #define true 1 #define false 0 int f(char *s1,char *s2) { if(strlen(s1) != strlen(s2)) return false; int n = strlen(s1); char *tmp = (char*)malloc(2*n*sizeof(char)); strcpy(tmp,s1); strcpy(tmp+n,s1); printf("tmp = %s\n",tmp); if((strstr(tmp,s2)) != NULL) { free(tmp); return true; } free(tmp); return false; } int main() { char *s1 = "abc"; char *s2 = "cab"; if(f(s1,s2)) printf("YES!"); else printf("NO!"); return 0; } ================================================ FILE: C33-Computational-Geometry/33.1.md ================================================ ### Exercises 33.1-1 *** Prove that if p1 × p2 is positive, then vector p1 is clockwise from vector p2 with respect to the origin (0, 0) and that if this cross product is negative, then p1 is counterclockwise from p2. ### `Answer` cross-product是一个三维的概念,根据右手规则,若p1在p2的顺时针方向,右手握住拇指出纸面,这时候的面积是正的;否则,面积是负的. ### Exercises 33.1-2 *** Professor Powell proposes that only the x-dimension needs to be tested in line 1 of ON- SEGMENT. Show why the professor is wrong. ### `Answer` 如何一条直线是竖直的话,不仅要检查x值,还要检查y值. If a line is vertical, it's not enough to check x-value, y-valuealso needs checking. ### Exercises 33.1-3 *** The polar angle of a point p1 with respect to an origin point p0 is the angle of the vector p1 - p0 in the usual polar coordinate system. For example, the polar angle of (3, 5) with respect to (2, 4) is the angle of the vector (1, 1), which is 45 degrees or π/4 radians. The polar angle of (3, 3) with respect to (2, 4) is the angle of the vector (1, -1), which is 315 degrees or 7π/4 radians. Write pseudocode to sort a sequence [p1, p2, ..., pn] of n points according to their polar angles with respect to a given origin point p0. Your procedure should take O(n lg n) time and use cross products to compare angles. ### `Answer` Here is the basic idea : if we could define the compare function in points by cross product, we could adopt methods like quicksort to sort the points. * if p1 > 0, p2 < 0, then p1 < p2. * else if p1 < 0, p2 > 0, then p1 > p2. * else if crossproduct(p1, p2) > 0, then p1 < p2 * else p1 > p2 Once we define this compare function, we could call **sort** in the `algorithm` standard library and pass this function as a parameter. Here is my [implementation](./exercise_code/polarCMP.cpp) ### Exercises 33.1-4 *** Show how to determine in O(n2 lg n) time whether any three points in a set of n points are collinear. ### `Answer` Show how to determine if three point are collinear in a set of n points. For each point p0 sort the n − 1 other points according to the polar angle with respect to p. If two points p1 and p2 have the same polar angle then p0, p1 and p2 are collinear. This can approach can be implemented in O(n2 lg n). For conveience, we will ignore the case in which two points may have same coordinate. Here is my [implementation](./exercise_code/colinear.cpp) ### Exercises 33.1-5 *** A polygon is a piecewise-linear, closed curve in the plane. That is, it is a curve ending on itself that is formed by a sequence of straight-line segments, called the sides of the polygon. A point joining two consecutive sides is called a vertex of the polygon. If the polygon is simple, as we shall generally assume, it does not cross itself. The set of points in the plane enclosed by a simple polygon forms the interior of the polygon, the set of points on the polygon itself forms its boundary, and the set of points surrounding the polygon forms its exterior. A simple polygon is convex if, given any two points on its boundary or in its interior, all points on the line segment drawn between them are contained in the polygon's boundary or interior. Professor Amundsen proposes the following method to determine whether a sequence {p0, p1, ..., pn-1} of n points forms the consecutive vertices of a convex polygon. Output "yes" if the set {angle pi pi+1 pi+2 : i = 0, 1, ..., n - 1}, where subscript addition is performed modulo n, does not contain both left turns and right turns; otherwise, output "no." Show that although this method runs in linear time, it does not always produce the correct answer. Modify the professor's method so that it always produces the correct answer in linear time. ### `Answer` For example, Point(0,0)(1,1)(2,2) form a line not a polygon. But it does not contain both left turns and right turns so Professor Amundsen will say "yes". So, if these is no left turn or right turn, then say "no". Only if left turn occurs or right turn occurs will we say "yes". [implementation](./exercise_code/convex_polygon.cpp) ### Exercises 33.1-6 *** Given a point p0 = (x0, y0), the right horizontal ray from p0 is the set of points {pi = (xi, yi) : xi ≥ x0 and yi = y0}, that is, it is the set of points due right of p0 along with p0 itself. Show how to determine whether a given right horizontal ray from p0 intersects a line segment p1p2 in O(1) time by reducing the problem to that of determining whether two line segments intersect. ### `Answer` The tricky part is how to find an `end` point in the ray. We follow this strategy : The y-coordinate of the end point is the same as p0(y0). We name `xmax = max(p1.x, p2.x)`, to prevent p0 and end-point are same, we set end.x = xmax + 1. Next, we can apply original algorithm on p0p`end` and p1p2. It is my [implementation](./exercise_code/ray_intersection.cpp). ### Exercises 33.1-7 *** One way to determine whether a point p0 is in the interior of a simple, but not necessarily convex, polygon P is to look at any ray from p0 and check that the ray intersects the boundary of P an odd number of times but that p0 itself is not on the boundary of P. Show how to compute in Θ(n) time whether a point p0 is in the interior of an n-vertex polygon P. (Hint: Use Exercise 33.1-6. Make sure your algorithm is correct when the ray intersects the polygon boundary at a vertex and when the ray overlaps a side of the polygon.) ### `Answer` The difficulty is how to deal with the case when the ray intersects the polygon boundary at a vertex and when the ray overlaps a side of the polygon. I adopt this strategy : when the case occurs, if the line is below my ray(that is to say, one of the y-coordinate < p0.y, and the other >= p0.y) the increase the interaction times else do not. see my [implementation](./exercise_code/pointpolygon.cpp) [reference](http://blog.csdn.net/hjh2005/article/details/9246967) ### Exercises 33.1-8 *** Show how to compute the area of an n-vertex simple, but not necessarily convex, polygon in Θ(n) time. (See Exercise 33.1-5 for definitions pertaining to polygons.) ### `Answer` To calculate the area of an arbitary polygon, we can divide the polygon into a number of triangles. ![Area](./area.png) Then we calculate the area of each triangle and add them. Here is the [code](./exercise_code/area.cpp). *** Follow [@louis1992](https://github.com/gzc) on github to help finish this task ================================================ FILE: C33-Computational-Geometry/Graham_Scan.py ================================================ #!/usr/bin/env python # -*- coding: utf-8 -*- import math import unittest __author__ = 'tushar-rishav' class Point: def __init__(self, x, y): """ Create a (X, Y) coordinate object vector. """ self.x = x self.y = y def __eq__(self, p): return p.x == self.x and p.y == self.y def __sub__(self, p): """ Return the difference of two vector. """ return Point(self.x - p.x, self.y - p.y) def __mul__(self, p): """ Return cross product of two vector. """ return self.x * p.y - self.y * p.x def __dict__(self): return {'X': self.x, 'Y': self.y} @classmethod def dist(cls, P, Q=None): """ Distance between two given points P and Q. Q is origin by default. """ if Q is None: Q = Point(0,0) return math.sqrt((P.x - Q.x)**2 + (P.y - Q.y)**2) @classmethod def dot(cls, P, Q): """ Return dot product of two vector: Q.P """ return P.x * Q.x + P.y * Q.y @classmethod def direction(cls, P, Q, R): """ Find the direction of rotation of angle pqr. CCW is positive and CW is negative. """ return (P-Q) * (R-Q) @classmethod def angle(cls, P, Q, R=None): """ Find the angle pqr if either q and r are distinct or R is None otherwise angle between PQ and X axis is returned. :param P, Q, R: The coordinate of the three points. :return: Angle in radian rounded with a precision of 5. """ if R is None or Q == R: # A point on line parallel to X axis and passing through Q. R = Point(abs(P.x + Q.x) / 2.0, 0) if P.x + Q.x else Point(1, 0) if P != Q and Q != R: acute = math.acos(cls.dot(P-Q, R-Q) / (cls.dist(P-Q) * cls.dist(R-Q))) return (round(2*math.pi - acute, 5) if (P-Q).y < 0 else round(acute, 5)) raise Exception("Invalid Points") @classmethod def polar_sort(cls, p0, *P): """ Sort a sequence of n points according to their polar angles w.r.t a given original point p0. Time Complexity: O(n log(n)) :param p0: The reference point. :param P: A sorted sequence of tuple of Point object and its angle. Sorting is done by angle. """ point_and_angle = map(lambda p: (p, cls.angle(p, p0)), P) return sorted(point_and_angle, key = lambda p_tuple: p_tuple[1]) class ConvexHull: def __init__(self, *P): self._input = P def graham_scan(self): def find_p0(): min_p = self._input[0] for p in self._input: if p.y <= min_p.y: if p.x <= min_p.x: min_p = p return min_p def filter_farthest(rp, p0): p_min = rp[0] result = [rp[0]] for p in rp: if p[1] == p_min[1]: if Point.dist(p[0], p0) > Point.dist(p_min[0], p0): result[-1] = p p_min = p else: p_min = p result.append(p_min) return result p0 = find_p0() remaining_p = filter(lambda p: p != p0, self._input) remaining_p = Point.polar_sort(p0, *remaining_p) remaining_p = filter_farthest(remaining_p, p0) point_stack = [] point_stack.append(p0) point_stack.append(remaining_p[0][0]) point_stack.append(remaining_p[1][0]) for i in xrange(2, len(remaining_p)): while (Point.direction(remaining_p[i][0], point_stack[-1], point_stack[-2]) < 0): point_stack.pop() point_stack.append(remaining_p[i][0]) return point_stack class ConvexHullTest(unittest.TestCase): def test_graham_scan(self): ch = ConvexHull(Point(0, 0), Point(1,0), Point(1,1), Point(5, 5), Point(0,2), Point(0,6), Point(-1,1)) self.assertEqual(ch.graham_scan(), [Point(0,0), Point(1,0), Point(5,5), Point(0,6), Point(-1, 1)]) class PointTest(unittest.TestCase): def test_polar_sort(self): p1 = Point(0, 0); p2 = Point(5, 5) p3 = Point(0, 5); p4 = Point(5, 0) self.assertEqual(Point.polar_sort(p1, p2, p3, p4), [(p4, 0), (p2, round(math.pi/4, 5)), (p3, round(math.pi/2, 5))]) def test_angle(self): p1 = Point(1, 1) p2 = Point(0, 0) p4 = Point(2, 0) p3 = Point(-1, 1) p5 = Point(-1, -1) self.assertEqual(Point.angle(p1, p2, p4), round(math.pi/4 ,5)) self.assertEqual(Point.angle(p3, p2, p4), round(3*math.pi/4 ,5)) self.assertEqual(Point.angle(p3, p2), round(3*math.pi/4 ,5)) self.assertEqual(Point.angle(p5, p2, p4), round(5*math.pi/4 ,5)) if __name__ == "__main__": unittest.main() ================================================ FILE: C33-Computational-Geometry/exercise_code/area.cpp ================================================ #include using namespace std; struct Point{ float x,y; }; // O(1) float area(Point a, Point b, Point c){ return (a.x*(b.y-c.y) + b.x*(c.y - a.y) + c.x*(a.y - b.y))/2.0f; } // O(N) float calculateAreaOfHull(Point* hull,int N){ float sum = 0; for(int i = 1; i < N - 1; i++) sum += abs(area(hull[0],hull[i],hull[i+1])); // O(1) return sum; } int main(){ // Square with 1 unit area Point test1[4] = {{0,0},{0,1},{1,0},{1,1}}; cout< File Name: colinear.cpp > Author: Louis1992 > Mail: zhenchaogan@gmail.com > Blog: http://gzc.github.io > Created Time: Sat Aug 8 12:11:59 2015 ************************************************************************/ #include #include #include using namespace std; const int num = 3; class Point { public: float x; float y; Point(float _x, float _y):x(_x),y(_y){} Point():x(0.0),y(0.0){} Point operator +(const Point &that) const { return Point(x+that.x, y+that.y); } Point operator -(const Point &that) const { return Point(x-that.x, y-that.y); } Point& operator =(const Point &that) { if (this == &that) return *this; x = that.x; y = that.y; return *this; } void reverse() { this->x = -x; this->y = -y; } friend ostream& operator << (ostream& os, const Point &p) { os << "x: " << p.x << " y: " << p.y << endl; return os; } }; bool myfunc(const Point &p1, const Point &p2) { if (p1.y > 0 && p2.y < 0) return true; else if(p1.y < 0 && p2.y > 0) return false; if ((p1.x*p2.y - p2.x*p1.y) < 0) return false; return true; } float crossProduct(const Point &p1, const Point &p2) { return p1.x*p2.y - p2.x*p1.y; } int main() { Point p1(0,0); Point p2(-1,-1); Point p3(2,2); Point points[num] = {p1,p2,p3}; Point pointsback[num]; copy (points, points+num, pointsback); for(int i = 0;i < num;i++) { copy(pointsback, pointsback+num, points); bool flag[num] = {false}; for(int j = i+1;j < num;j++) { points[j] = points[j] - points[i]; if (points[j].y < 0) { flag[j] = true; points[j].reverse(); } } sort(points+i+1, points+num, myfunc); for(int j = i+1; j < num-1;j++) { if (abs(crossProduct(points[j],points[j+1])) <= 1e-8) { cout << points[i]; if (flag[j]) { flag[j] = false; points[j].reverse(); } cout << points[j]; if (flag[j+1]) { flag[j+1] = false; points[j+1].reverse(); } cout << points[j+1]; cout << "are colinear" << endl; } } } return 0; } ================================================ FILE: C33-Computational-Geometry/exercise_code/convex_polygon.cpp ================================================ /************************************************************************* > File Name: convex_polygon.cpp > Author: Louis1992 > Mail: zhenchaogan@gmail.com > Blog: http://gzc.github.io > Created Time: Thu Aug 6 17:03:30 2015 ************************************************************************/ #include #include #include using namespace std; class Point { public: float x; float y; Point(float _x, float _y):x(_x),y(_y){} Point operator +(const Point &that) const { return Point(x+that.x, y+that.y); } Point operator -(const Point &that) const { return Point(x-that.x, y-that.y); } Point& operator =(const Point &that) { if (this == &that) return *this; x = that.x; y = that.y; return *this; } }; float crossProduct(const Point &p1, const Point &p2) { return p1.x*p2.y - p2.x*p1.y; } float direction(const Point &pi, const Point &pj, const Point &pk) { return crossProduct(pk-pi, pj-pi); } bool isConvexPolygon(Point points[], int n) { bool f1,f2; f1 = f2 = false; for(int i = 0;i < n;i++) { Point p1 = points[i]; Point p2 = points[(i+1)%n]; Point p3 = points[(i+2)%n]; float d = direction(p2, p1, p3); if(d > 0 && f2) return false; if(d < 0 && f1) return false; if(d > 0) f1 = true; if(d < 0) f2 = true; } if(!f1 && !f2) return false; return true; } int main() { Point p1(0,0); Point p2(1,1); Point p3(2,2); Point points[3] = {p1,p2,p3}; bool fff = isConvexPolygon(points, 3); if(fff) cout << "yes" << endl; else cout << "no" << endl; return 0; } ================================================ FILE: C33-Computational-Geometry/exercise_code/pointpolygon.cpp ================================================ /************************************************************************* > File Name: pointpolygon.cpp > Author: Louis1992 > Mail: zhenchaogan@gmail.com > Blog: http://gzc.github.io > Created Time: Tue Aug 11 18:53:57 2015 ************************************************************************/ #include #include #include using namespace std; class Point; float direction(const Point &, const Point &, const Point &); bool onSegmant(const Point &, const Point &, const Point &); class Point { public: float x; float y; Point(float _x, float _y):x(_x),y(_y){} Point operator +(const Point &that) const { return Point(x+that.x, y+that.y); } Point operator -(const Point &that) const { return Point(x-that.x, y-that.y); } Point& operator =(const Point &that) { if (this == &that) return *this; x = that.x; y = that.y; return *this; } bool inTheLine(Point p1, Point p2) { if ( abs(direction(*this, p1, p2)) < 1e-8 && onSegmant(p1, p2, *this)) return true; return false; } }; float crossProduct(const Point &p1, const Point &p2) { return p1.x*p2.y - p2.x*p1.y; } float direction(const Point &pi, const Point &pj, const Point &pk) { return crossProduct(pk-pi, pj-pi); } bool onSegmant(const Point &pi, const Point &pj, const Point &pk) { if (min(pi.x, pj.x) <= pk.x && max(pi.x, pj.x) >= pk.x && min(pi.y, pj.y) <= pk.y && max(pi.y, pj.y) >= pk.y) return true; return false; } bool segmentsInterect(const Point &p1, const Point &p2, const Point &p3, const Point &p4) { float d1 = direction(p3, p4, p1); float d2 = direction(p3, p4, p2); float d3 = direction(p1, p2, p3); float d4 = direction(p1, p2, p4); if ( ((d1 > 0 && d2 < 0) || (d1 < 0 && d2 > 0)) && ((d3 > 0 && d4 < 0) || (d3 < 0 && d4 > 0)) ) return true; else if (abs(d1) < 1e-8 && onSegmant(p3, p4, p1)) return true; else if (abs(d2) < 1e-8 && onSegmant(p3, p4, p2)) return true; else if (abs(d3) < 1e-8 && onSegmant(p1, p2, p3)) return true; else if (abs(d4) < 1e-8 && onSegmant(p1, p2, p4)) return true; return false; } /* 1 means in the polygon 0 means in the boundary -1 means out of the polygon */ int point_polygon_pos(Point p0, Point polygon[], int n) { int num(0); for(int i = 0;i < n;i++) { Point p1 = polygon[i]; Point p2 = polygon[(i+1)%n]; if (p0.inTheLine(p1, p2)) return 0; float rightmost = max(p1.x, p2.x); rightmost += 1.0; Point end(rightmost, p0.y); bool fff = segmentsInterect(p0, end, p1, p2); if(fff && ( (p1.y < p0.y && p2.y >= p0.y) || (p2.y < p0.y && p1.y >= p0.y))) num++; } return num%2 == 1; } int main() { Point p0(1, 1); Point p1(0, 0); Point p2(2, 0); Point p3(2, 2); Point p4(0, 2); Point Square[4] = {p1, p2, p3, p4}; int cond = point_polygon_pos(p0, Square, 4); cout << cond << endl; return 0; } ================================================ FILE: C33-Computational-Geometry/exercise_code/polarCMP.cpp ================================================ /************************************************************************* > File Name: polarCMP.cpp > Author: Louis1992 > Mail: zhenchaogan@gmail.com > Blog: http://gzc.github.io > Created Time: Thu Aug 6 17:03:30 2015 ************************************************************************/ #include #include #include using namespace std; class Point { public: float x; float y; Point(float _x, float _y):x(_x),y(_y){} Point operator +(const Point &that) const { return Point(x+that.x, y+that.y); } Point operator -(const Point &that) const { return Point(x-that.x, y-that.y); } Point& operator =(const Point &that) { if (this == &that) return *this; x = that.x; y = that.y; return *this; } friend ostream& operator << (ostream& os, const Point &p) { os << "x: " << p.x << " y: " << p.y << endl; return os; } }; bool myfunc(const Point &p1, const Point &p2) { if (p1.y > 0 && p2.y < 0) return true; else if(p1.y < 0 && p2.y > 0) return false; if ((p1.x*p2.y - p2.x*p1.y) < 0) return false; return true; } int main() { Point p0(1,1); Point p1(3,0); Point p2(2,2); Point p3(0.5,3); Point p4(-2,2); Point p5(-1,-2); Point points[5] = {p1,p2,p3,p4,p5}; for(int i = 0;i < 5;i++) points[i] = points[i] - p0; sort(points, points+5, myfunc); for(int i = 0;i < 5;i++) cout << (points[i]+p0); return 0; } ================================================ FILE: C33-Computational-Geometry/exercise_code/ray_intersection.cpp ================================================ /************************************************************************* > File Name: ray_intersection.cpp > Author: Louis1992 > Mail: zhenchaogan@gmail.com > Blog: http://gzc.github.io > Created Time: Thu Aug 6 17:03:30 2015 ************************************************************************/ #include #include #include using namespace std; class Point { public: float x; float y; Point(float _x, float _y):x(_x),y(_y){} Point operator +(const Point &that) const { return Point(x+that.x, y+that.y); } Point operator -(const Point &that) const { return Point(x-that.x, y-that.y); } Point& operator =(const Point &that) { if (this == &that) return *this; x = that.x; y = that.y; return *this; } }; float crossProduct(const Point &p1, const Point &p2) { return p1.x*p2.y - p2.x*p1.y; } float direction(const Point &pi, const Point &pj, const Point &pk) { return crossProduct(pk-pi, pj-pi); } bool onSegmant(const Point &pi, const Point &pj, const Point &pk) { if (min(pi.x, pj.x) <= pk.x && max(pi.x, pj.x) >= pk.x && min(pi.y, pj.y) <= pk.y && max(pi.y, pj.y) >= pk.y) return true; return false; } bool segmentsInterect(const Point &p1, const Point &p2, const Point &p3, const Point &p4) { float d1 = direction(p3, p4, p1); float d2 = direction(p3, p4, p2); float d3 = direction(p1, p2, p3); float d4 = direction(p1, p2, p4); if ( ((d1 > 0 && d2 < 0) || (d1 < 0 && d2 > 0)) && ((d3 > 0 && d4 < 0) || (d3 < 0 && d4 > 0)) ) return true; else if (abs(d1) < 1e-8 && onSegmant(p3, p4, p1)) return true; else if (abs(d2) < 1e-8 && onSegmant(p3, p4, p2)) return true; else if (abs(d3) < 1e-8 && onSegmant(p1, p2, p3)) return true; else if (abs(d4) < 1e-8 && onSegmant(p1, p2, p4)) return true; return false; } int main() { Point p0(2,0); Point p1(3,-1); Point p2(3,1); float rightmost = max(p1.x, p2.x); rightmost += 1.0; Point end(rightmost, p0.y); cout << segmentsInterect(p0, end, p1, p2) << endl; return 0; } ================================================ FILE: C33-Computational-Geometry/twoline.cpp ================================================ /************************************************************************* > File Name: twoline.cpp > Author: Louis1992 > Mail: zhenchaogan@gmail.com > Blog: http://gzc.github.io > Created Time: Thu Aug 6 17:03:30 2015 ************************************************************************/ #include #include #include using namespace std; class Point { public: float x; float y; Point(float _x, float _y):x(_x),y(_y){} Point operator +(const Point &that) const { return Point(x+that.x, y+that.y); } Point operator -(const Point &that) const { return Point(x-that.x, y-that.y); } Point& operator =(const Point &that) { if (this == &that) return *this; x = that.x; y = that.y; return *this; } }; float crossProduct(const Point &p1, const Point &p2) { return p1.x*p2.y - p2.x*p1.y; } float direction(const Point &pi, const Point &pj, const Point &pk) { return crossProduct(pk-pi, pj-pi); } bool onSegmant(const Point &pi, const Point &pj, const Point &pk) { if (min(pi.x, pj.x) <= pk.x && max(pi.x, pj.x) >= pk.x && min(pi.y, pj.y) <= pk.y && max(pi.y, pj.y) >= pk.y) return true; return false; } bool segmentsInterect(const Point &p1, const Point &p2, const Point &p3, const Point &p4) { float d1 = direction(p3, p4, p1); float d2 = direction(p3, p4, p2); float d3 = direction(p1, p2, p3); float d4 = direction(p1, p2, p4); if ( ((d1 > 0 && d2 < 0) || (d1 < 0 && d2 > 0)) && ((d3 > 0 && d4 < 0) || (d3 < 0 && d4 > 0)) ) return true; else if (abs(d1) < 1e-8 && onSegmant(p3, p4, p1)) return true; else if (abs(d2) < 1e-8 && onSegmant(p3, p4, p2)) return true; else if (abs(d3) < 1e-8 && onSegmant(p1, p2, p3)) return true; else if (abs(d4) < 1e-8 && onSegmant(p1, p2, p4)) return true; return false; } int main() { Point p1(0,0); Point p2(5,0); Point p3(2,2); Point p4(2,-2); cout << segmentsInterect(p1,p2,p3,p4) << endl; cout << segmentsInterect(p1,p3,p2,p4) << endl; return 0; } ================================================ FILE: C35-Approximation-Algorithms/35.1.md ================================================ ### Exercises 35.1-1 *** Give an example of a graph for which APPROX-VERTEX-COVER always yields a suboptimal solution. ### `Answer` A graph with two node u,v and an edge(u,v). The optimal is either u or v. By running APPROX-VERTEX-COVER we get u and v. It is always a suboptimal solution. ### Exercises 35.1-2 *** Let A denote the set of edges that were picked in line 4 of APPROX-VERTEX-COVER. Prove that the set A is a maximal matching in the graph G. ### `Answer` It is obvious. Because in line 4, we randomly choose an edge (u,v) and delete all edges incident on either u or v. The remaining graph becomes a subproblem. *** Follow [@louis1992](https://github.com/gzc) on github to help finish this task ================================================ FILE: C35-Approximation-Algorithms/35.2-5.md ================================================ # Exercise 35.2-5 *** Suppose that the vertices for an instance of the traveling-salesman problem are points in the plane and that the cost *c(u,v)* is the euclidean distance between points *u* and *v*. Show that an optimaol tour never crosses itself. ## Solution Cost function based on euclidean distance satisfies the triangle-inequality, s.t.: ``` c(u,v) <= c(u,w) + c(w,v) ``` Assume: The tour corsses itself. Let *(u,v)* and *(w,x)* be corssing edges, *u -> v -> w -> x* the assumed optimal tour and P the crossing point of *(u,v)* and *(w,x)*. Based on the triangle-inequality its: ```c(x,v) <= c(x,P) + c(P,v) ``` Thus we can derive: ```c(u,P) + c(P,w) + c(x,P) + c(P,v) + c(u,w) + c(w,v) >= c(u,w) + c(x,v) * c(u,x) + c(w,v)``` Based on our definition its ```c(u,P) + c(P,v) = c(u,v)``` and ```c(x,P) + c(P,w) = c(x,w)``` Therefore its ``` c(u,v) + c(v,w) >= c(u,w) + c(x,v)``` which denotes in combination with (u,x) and (v,w) a shorter tour then the original one. Contradiction. q.e.d ================================================ FILE: LICENSE ================================================ The MIT License (MIT) Copyright (c) 2015 Zhenchao Gan Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ # Solutions to CLRS. Solutions to *Introduction to Algorithms* by Charles E. Leiserson, Clifford Stein, Ronald Rivest, and Thomas H. Cormen (CLRS). ![](https://images-na.ssl-images-amazon.com/images/I/51n%2B3GEQvYL._SX433_BO1,204,203,200_.jpg) ## Contributor 1. [Soyn](https://github.com/Soyn) 2. [idf](https://github.com/idf) 3. [W4anD0eR96](https://github.com/W4anD0eR96) 4. [knight42](https://github.com/knight42) 5. [ajinkyakolhe112](https://github.com/ajinkyakolhe112) 6. [an-yun](https://github.com/an-yun) 7. [y1y](https://github.com/y1y) 8. [RepapLataf](https://github.com/RepapLataf) 9. [Ghost---Shadow](https://github.com/Ghost---Shadow) 10. [wonjunetai](https://github.com/wonjunetai) 11. [suensky](https://github.com/suensky) 12. [xwu64](https://github.com/xwu64) 13. [ryuxin](https://github.com/ryuxin) 14. [Puriney](https://github.com/Puriney) 15. [wild-flame](https://github.com/wild-flame) 16. [zhangysh1995](https://github.com/zhangysh1995) 17. [DarthUjj](https://github.com/DarthUjj) 18. [VMatrix1900](https://github.com/VMatrix1900) 19. [Jingru](https://github.com/Jingru) 20. [prasook-jain](https://github.com/prasook-jain) 21. [Mundhey](https://github.com/Mundhey) 22. [Cokile](https://github.com/Cokile) 23. [wuchichung](https://github.com/wuchichung) 24. [saurabhvyas](https://github.com/saurabhvyas) 25. [codemukul95](https://github.com/codemukul95) 26. [JasonQSY](https://github.com/JasonQSY) 27. [imbrobits](https://github.com/imbrobits) 28. [zhanglanqing](https://github.com/zhanglanqing) 29. [tushar-rishav](https://github.com/tushar-rishav) 30. [ravgill](https://github.com/ravgill) 31. [Mad_Kingu](https://github.com/Mad-Kingu) 32. [kotoz](https://github.com/Kottoz) If I miss your name here, please pull a request to me to fix. You maybe interested in another repo [gitstats](https://github.com/gzc/gitstats) which generates repo contribution of CLRS. ## This repo needs your help. If you are interested in this project, you could complete problems which are marked **"UNSOLVED"** in the following list. Or if you are interested in certain chapters that have not been completed, you could fork this project and issue a pull request to this repo. Appreciate your efforts. 如果你感兴趣,可以完成没有完成的题(下面有个UNSOLVED列表),或者如果你对某章节感兴趣想要完成,可以fork这个项目然后pull request进这个repo。 In order to speed up this project, we will ignore any hard problems (for instance, problems in the very end of each chapter) and review them when finishing mediocre problems. Moreover, we will only focus on sections that are interesting. You could also help to finish these hard problems. If a problem is too easy to solve, we'll mark it as **straightforward** in order to speed up the progress. ***
Chapter Section
Part I: Foundations
I 1 2 p
II 1 2 3 p
III 1 2 p
IV 1 2 3 4 p
V 1 2 3 4 p
Part II: Sorting and Order Statistics
VI 1 2 3 4 5 p
VII 1 2 3 4 p
VIII 1 2 3 4 p
IX 1 2 3 p
Part III: Data Structures
X 1 2 3 4 p
XI 1 2 3 4 5 p
XII 1 2 3
XIII 1 2 3 4 p
XIV 1 2 3 p
Part IV: Advanced Design and Analysis Techniques
XV 1 2 3 4 5
XVI 1 2 3
XVII 1 2
Part V: Advanced Data Structures
XVIII 1 2 3
XIX 1 2
XXI 1 2 3
Part VI: Graph Algorithms
XXII 1 2 3 4 5 p
XXIII 1 2
XXIV 1 2 3 4
XXV 1 2 3
XXVI 1 2 3
Part VII: Selected Topics
XXXI 1 2
XXXII 1 2 3 4
XXXIII 1
XXXV 1
*** ## Data Structure&algorithm implementation ### BASIC * [Heap](./C06-Heapsort/heap.cpp) * [Priority Queue](./C06-Heapsort/p_queue.h) * [Quicksort](./C07-Quicksort/quicksort.py) * [Radixsort](./C08-Sorting-in-Linear-Time/exercise_code/radixSort.cpp) * [Counting Sort](./C08-Sorting-in-Linear-Time/exercise_code/in_place_counting_sort.py) * [K-th Finding](./C09-Medians-and-Order-Statistics/worst-case-linear-time.cpp) * [Deque(py)](./C10-Elementary-Data-Structures/exercise_code/deque.py) [Deque(c++)](./C10-Elementary-Data-Structures/exercise_code/deque.cpp) ### DIVIDE and CONQUER * [Karatsuba](./other/Karatsuba) ### TREE/ADVANCED * [BST](./C12-Binary-Search-Trees/BSTree.h) * [RBT](./C13-Red-Black-Trees/rbtree.cpp) * [Btree](./C18-B-Trees/btree.cpp) * [BinomialHeap](./C19-Binomial-Heaps/BinomialHeap.h) [Driver](././C19-Binomial-Heaps/Main.cpp) * [SegmentTree](./other/segmentTree.cpp) * [Trie](./other/trie.cpp) * [UnionFind](./C21-Data-Structures-for-Disjoint-Sets/uf.cpp) ### DYNAMIC/GREEDY * [Matrix Chain](./C15-Dynamic-Programming/Matrix-chain-multiplication.c) * [Huffman](./C16-Greedy-Algorithms/huffman) ### GRAPH * [Kosaraju's Algorithm ( strongly connected components )](./C22-Elementary-Graph-Algorithms/elementary_graph_algo.py#L70) * [Maximum Flow](./C26-Flow-networks/maxflow) * [Floyd-Warshall](./C25-All-Pairs-Shortest-Paths/Floyd_Warshall.cpp) ### GEOMETRY * [LineIntersection](./C33-Computational-Geometry/twoline.cpp) * Convex Hull * [Graham Scan](./C33-Computational-Geometry/Graham_Scan.py) ### STRING * [BruteForce](./C32-String-Matching/BF.c) * [KMP](./C32-String-Matching/KMP.c) * [DFA](./C32-String-Matching/FA.c) ### UTILITY * [Split string by delimiter in C++](./other/stringSpilit.cpp) # UNSOLVED [31.1.11](./C31-Number-Theoretic-Algorithms/31.1.md#exercises-311-11) [31.2.7](./C31-Number-Theoretic-Algorithms/31.2.md#exercises-312-7) [31.2.9](./C31-Number-Theoretic-Algorithms/31.2.md#exercises-312-9) [32.2.4](./C32-String-Matching/32.2.md#exercises-322-4) [32.3.4](./C32-String-Matching/32.3.md#exercises-323-4-) [32.4.6](./C32-String-Matching/32.4.md#exercises-324-6-) *** Follow [@louis1992](https://github.com/gzc) on github to help finish this task. You can also subscribe my [youtube channel](https://www.youtube.com/channel/UCAvvkYnRNyObcHzOCaVgSrQ). **Disclaimer**: the solutions in this repository are crowdsourced work, and in any form it neither represents any opinion of nor affiliates to the authors of Introduction to Algorithms or the MIT press. ================================================ FILE: other/Karatsuba/Karatsuba.cpp ================================================ #include #include #include using namespace std; int makeEqualLength(string &str1, string &str2) { int len1 = str1.size(); int len2 = str2.size(); if (len1 < len2) { for (int i = 0 ; i < len2 - len1 ; i++) str1 = '0' + str1; return len2; } else if (len1 > len2) { for (int i = 0 ; i < len1 - len2 ; i++) str2 = '0' + str2; } return len1; } string add(string &first, string &second) { int length = max(first.length(), second.length()); string result(length,'0'); int carry = 0; int i1 = first.length()-1; int i2 = second.length()-1; for (int i = length-1 ; i >= 0 ; i--) { int firstBit = i1 >= 0? first.at(i1) - '0' : 0; int secondBit = i2 >= 0? second.at(i2) - '0' : 0; int sum = (firstBit ^ secondBit ^ carry)+'0'; result[i] = sum; carry = (firstBit&secondBit) | (secondBit&carry) | (firstBit&carry); i1--; i2--; } if (carry) result = '1' + result; return result; } string multiplyiSingleBit(string &a, string &b) { return to_string((a[0] - '0')*(b[0] - '0')); } string sub(string num1, string num2) { string s(max(num1.length(),num2.length()),'0'); reverse(num1.begin(), num1.end()); reverse(num2.begin(), num2.end()); bool lend(false); for(int i = 0;i < num1.length();i++) { int tmp = (num1[i] - '0') - ( i >= num2.length()? 0 : (num2[i] - '0') ); if(lend) tmp--; if(tmp < 0) { tmp += 2; lend = true; } else lend = false; s[i] = ('0'+tmp); } reverse(s.begin(), s.end()); while(s[0] == '0' && s.length() > 1) s = s.substr(1); return s; } string multiply(string &X, string &Y) { int n = makeEqualLength(X, Y); if (n == 0) return "0"; if (n == 1) return multiplyiSingleBit(X, Y); int fh = n/2; int sh = (n-fh); string Xl = X.substr(0, fh); string Xr = X.substr(fh, sh); string Yl = Y.substr(0, fh); string Yr = Y.substr(fh, sh); string P1 = multiply(Xl, Yl); string P2 = multiply(Xr, Yr); string temp1 = add(Xl, Xr); string temp2 = add(Yl, Yr); string P3 = multiply(temp1, temp2); int numof0 = 2*sh; string zeros(numof0,'0'); string zeros2(sh,'0'); while(P1.length() > 1 && P1[0] == '0') P1 = P1.substr(1); while(P2.length() > 1 && P2[0] == '0') P2 = P2.substr(1); while(P3.length() > 1 && P3[0] == '0') P3 = P3.substr(1); string t1 = sub(P3,P1); string t2 = sub(t1,P2); string tt1 = P1+zeros; string tt2 = t2+zeros2; string t3 = add(tt1, tt2); string res = add(t3,P2); while(res.length() > 1 && res[0] == '0') res = res.substr(1); return res; } int main(int argc, char **argv) { /* string str = string(argv[1]); int k = stoi(str); k = (int)pow(2,k); string s(k,'1'); multiply(s,s); return 0;*/ ifstream in("input.txt"); ofstream out("output.txt"); int n; in >> n; string num1,num2; in >> num1; in >> num2; string res = multiply(num1,num2); out << res; in.close(); out.close(); return 0; } ================================================ FILE: other/Karatsuba/main.cpp ================================================ #include #include #include using namespace std; string multiply(string &num1, string &num2) { string sum(num1.size() + num2.size(), '0'); for (int i = num1.size() - 1; 0 <= i; --i) { int carry = 0; for (int j = num2.size() - 1; 0 <= j; --j) { int tmp = (sum[i + j + 1] - '0') + (num1[i] - '0') * (num2[j] - '0') + carry; sum[i + j + 1] = tmp % 2 + '0'; carry = tmp / 2; } sum[i] += carry; } size_t startpos = sum.find_first_not_of("0"); if (string::npos != startpos) { return sum.substr(startpos); } return "0"; } int main(int argc, char **argv) { /* string str = string(argv[1]); int k = stoi(str); k = (int)pow(2,k); string s(k,'1'); multiply(s,s); return 0; */ ifstream in("input.txt"); ofstream out("output.txt"); int n; in >> n; string num1,num2; in >> num1; in >> num2; string res = multiply(num1,num2); out << res; in.close(); out.close(); return 0; } ================================================ FILE: other/Karatsuba/makefile ================================================ all: longmultiplication karatsuba .PHONY:all longmultiplication: main.o g++ -o longmultiplication main.o; rm main.o main.o : main.cpp g++ -c -O3 main.cpp karatsuba: Karatsuba.o g++ -o karatsuba Karatsuba.o; rm Karatsuba.o Karatsuba.o : Karatsuba.cpp g++ -c -O3 Karatsuba.cpp ================================================ FILE: other/segmentTree.cpp ================================================ #include #include using namespace std; struct SegmentTreeNode { int start, end, sum; SegmentTreeNode *left, *right; SegmentTreeNode(int start, int end, int sum) { this->start = start; this->end = end; this->sum = sum; this->left = this->right = nullptr; } }; class NumArray { SegmentTreeNode* root; SegmentTreeNode* build(int start, int end, const vector& A) { if (start > end) return nullptr; else if (start == end) { SegmentTreeNode *tree = new SegmentTreeNode(start, end, A[start]); return tree; } else { SegmentTreeNode *left = build(start, (start+end)/2, A); SegmentTreeNode *right = build((start+end)/2+1, end, A); SegmentTreeNode *tree = new SegmentTreeNode(start, end, left->sum + right->sum); tree -> left = left; tree -> right = right; return tree; } } void modify(SegmentTreeNode *root, int i, int val) { if (root->start == root->end) { root->sum = val; return; } if (i <= root->left->end) modify(root->left, i, val); else modify(root->right, i, val); root->sum = root->left->sum + root->right->sum; } int query(SegmentTreeNode *root, int i, int j) { if (root->start == root->end) return root->sum; if (root->start == i && root->end == j) return root->sum; if(j <= root->left->end) { return query(root->left, i, j); } else if (i >= root->right->start) { return query(root->right, i, j); } else { return query(root->left, i, root->left->end) + query(root->right, root->right->start, j); } } public: NumArray(vector &nums) { root = build(0, nums.size()-1, nums); } void update(int i, int val) { modify(root, i, val); } int sumRange(int i, int j) { return query(root, i, j); } }; int main() { vector nums{1, 2, 3}; NumArray numArray(nums); cout << numArray.sumRange(0, 1) << endl; numArray.update(1, 10); cout << numArray.sumRange(1, 2) << endl; } ================================================ FILE: other/stringSpilit.cpp ================================================ vector split(const string &s, char delim) { vector elems; stringstream ss(s); string item; while (getline(ss, item, delim)) { if (item.length() > 0) { elems.push_back(item); } } return elems; } ================================================ FILE: other/trie.cpp ================================================ struct TrieNode { TrieNode *nodes[26]; bool word; // Initialize your data structure here. TrieNode(): word(false) { memset(nodes, 0, sizeof(nodes)); } }; class Trie { public: Trie() { root = new TrieNode(); } // Inserts a word into the trie. void insert(const string& s) { TrieNode *tmp = root; for(char ch : s) { int index = ch - 'a'; if(tmp->nodes[index] == nullptr) { tmp->nodes[index] = new TrieNode(); } tmp = tmp->nodes[index]; } tmp->word = true; } // Returns if the word is in the trie. bool search(const string& key) const { TrieNode *tmp = root; for(char ch : key) { int index = ch - 'a'; if(tmp->nodes[index] == nullptr) { return false; } tmp = tmp->nodes[index]; } return tmp->word; } // Returns if there is any word in the trie // that starts with the given prefix. bool startsWith(const string& prefix) const { TrieNode *tmp = root; for(char ch : prefix) { int index = ch - 'a'; if(tmp->nodes[index] == nullptr) { return false; } tmp = tmp->nodes[index]; } return true; } private: TrieNode* root; };