• 沒有找到結果。

n X k=1 ln k ≤ n X k=1 ln n = n ln n (2) for n ≥ 1, we can take c2= 1 and the constraint N ≥ 1 is trivial

N/A
N/A
Protected

Academic year: 2022

Share "n X k=1 ln k ≤ n X k=1 ln n = n ln n (2) for n ≥ 1, we can take c2= 1 and the constraint N ≥ 1 is trivial"

Copied!
9
0
0

加載中.... (立即查看全文)

全文

(1)

Data Structure and Algorithm Midterm Reference Solution

TA email: dsa1@csie.ntu.edu.tw

Problem 1. To prove log2n! = Θ(n log n), it suffices to show ∃N ∈ N, c1, c2> 0 such that

c1n ln n ≤ ln n! ≤ c2n ln n (1)

for all n ≥ N , since constants can be ignored when considering asymptotic complexity.

For the second inequality, since

ln n! =

n

X

k=1

ln k ≤

n

X

k=1

ln n = n ln n (2)

for n ≥ 1, we can take c2= 1 and the constraint N ≥ 1 is trivial.

For the trickier first inequality, calculus come in handy! Remember that the upper sum is greater than the area under the curve y = ln x, i.e.

ln n! =

n

X

k=1

ln k ≥ Z n

1

ln xdx = (x ln x − x)|n1 = n ln n − n + 1 (3)

for n ≥ 1, with the help of integration by parts. Now we want n ln n − n + 1 ≥ c1n ln n, or equivalently (1 − c1)n ln n ≥ n − 1, for n ≥ N . If we take c1= 0.5 (actually all 0 < c1< 1 are fine, and note that different c1result in different N0below), then what we want becomes n ln n ≥ 2n−2, for n ≥ N . We already know (and are familiar with) that n ln n = ω(n), so there must be some N0∈ N such that n ln n ≥ 2n − 2, for n ≥ N0. The formal arguments are not shown here.

To sum up, take c1= 0.5, c2= 1, N = N0, and with (2), (3) we have (1), which completes the proof.

Remark 1. Another simpler way to prove log2n! = Ω(n log n) (the first inequality) is by

log n! = log n + log(n − 1) + · · · + logn

2 + · · · + log 1

≥ log n + log(n − 1) + · · · + logn 2

≥ logn

2 + logn

2 + · · · + logn 2

=n 2 logn

2 = Ω(n log n).

Remark 2. This problem can be solved easily with Stirling’s formula, which this margin is too narrow to contain.

(2)

Problem 2. (a) Code provided as follow:

1 int i s _ l i n k e d _ l i s t _ w i t h _ l o o p ( s t r u c t l i s t _ n o d e * h e a d ) {

2 l i s t _ n o d e * o n e _ s t e p = h e a d ;

3 l i s t _ n o d e * t w o _ s t e p s = h e a d ;

4 w h i l e( o n e _ s t e p != N U L L && t w o _ s t e p s != N U L L ) {

5 o n e _ s t e p = o n e _ s t e p - > n e x t ;

6 t w o _ s t e p s = t w o _ s t e p s - > n e x t ;

7 if( t w o _ s t e p s == N U L L ) r e t u r n 0;

8 t w o _ s t e p s = t w o _ s t e p s - > n e x t ;

9 if( o n e _ s t e p != N U L L && o n e _ s t e p == t w o _ s t e p s )

10 r e t u r n 1;

11 }

12 r e t u r n 0;

13 }

(b) • Suppose the linked list is without loop. Then, for each one step and two steps will at most go through O(N ) nodes then encounter NULL pointer.

• Suppose the linked list has a loop. Assume that the size of loop is C and total number of nodes is N . Then, for each iteration, the number of steps between one step and two steps will differ by one. Therefore, after K iterations, their difference will be exactly K. Then, after N − C iterations, both of them will go into the loop. Once their steps’

difference is a multiple of C, they will be on the same node. We need at most C more iterations to reach a multiple of C. Therefore, total time complexity is O(N −C)+O(C) = O(N ).

(c) We modify the code of (a) to provide more information to solve this problem. That is, the number of step go through by one step. For convenience, we encode the step by multiplying it by two. Then, we can still have the information that the linked list with loop or not from least significant bit.

1 int i s _ l i n k e d _ l i s t _ w i t h _ l o o p ( s t r u c t l i s t _ n o d e * h e a d ) {

2 l i s t _ n o d e * o n e _ s t e p = h e a d ;

3 l i s t _ n o d e * t w o _ s t e p s = h e a d ;

4 int s t e p = 0;

5 w h i l e( o n e _ s t e p != N U L L && t w o _ s t e p s != N U L L ) {

6 s t e p = s t e p + 1;

7 o n e _ s t e p = o n e _ s t e p - > n e x t ;

8 t w o _ s t e p s = t w o _ s t e p s - > n e x t ;

9 if( t w o _ s t e p s == N U L L ) {

10 w h i l e( o n e _ s t e p != N U L L ) {

11 s t e p = s t e p + 1;

12 o n e _ s t e p = o n e _ s t e p - > n e x t ;

13 }

14 r e t u r n 0 + s t e p * 2;

15 }

2

(3)

16 t w o _ s t e p s = t w o _ s t e p s - > n e x t ;

17 if( o n e _ s t e p != N U L L && o n e _ s t e p == t w o _ s t e p s )

18 r e t u r n 1 + s t e p * 2;

19 }

20 r e t u r n 0 + s t e p * 2;

21 }

22 int c h a i n _ l e n g t h ( s t r u c t l i s t _ n o d e * h e a d ) {

23 int r e s u l t = i s _ l i n k e d _ l i s t _ w i t h _ l o o p ( h e a d ) ;

24 if( ( r e s u l t & 1 ) == 0 )

25 r e t u r n r e s u l t / 2;

26 int s t e p _ o f _ o n e = r e s u l t / 2;

27 l i s t _ n o d e * o n e _ s t e p = h e a d ;

28 l i s t _ n o d e * t w o _ s t e p = h e a d ;

29 for( int i = 0 ; i < s t e p _ o f _ o n e ; i ++ )

30 t w o _ s t e p = t w o _ s t e p - > n e x t ;

31 for( int i = 0 ; i < s t e p _ o f _ o n e ; i ++ ) {

32 if( o n e _ s t e p == t w o _ s t e p ) {

33 r e t u r n i ;

34 }

35 o n e _ s t e p = o n e _ s t e p - > n e x t ;

36 t w o _ s t e p = t w o _ s t e p - > n e x t ;

37 }

38 r e t u r n -1; // this should not happen

39 }

(4)

Problem 3. Space-efficient doubly linked list.

(a) (6%)

1 f i r s t _ n o d e - > x o r _ p o i n t e r = (int) N U L L ^ (int) (& n e x t _ n o d e )

2 m i d d l e _ n o d e - > x o r _ p o i n t e r = (int) (& p r e v _ n o d e ) ^ (int) (& n e x t _ n o d e )

3 l a s t _ n o d e - > x o r _ p o i n t e r = (int) (& p r e v _ n o d e ) ^ (int) N U L L

Note that XORing (int)NULL can be removed, since a bit remains the same after being XORed with 0.

Each node’s xor pointer pointer stores the XOR value of its previous node’s address and its next node’s address. During traversal, whatever the direction is, calculating the XOR value of the xor pointer and its previous encountered node’s address results in the next node’s address.

(b) (9%)

1 int n u m b e r _ o f _ n o d e s (s t r u c t d b _ l i s t _ n o d e * h e a d ) {

2 s t r u c t d b _ l i s t _ n o d e * prev , * n e x t ;

3 p r e v = N U L L ;

4 int c o u n t = 0;

5 w h i l e( h e a d != N U L L ) {

6 n e x t = (s t r u c t d b _ l i s t _ n o d e *) ((int) head - > x o r _ p o i n t e r ^ (int ) p r e v ) ;

7 p r e v = h e a d ;

8 h e a d = n e x t ;

9 c o u n t ++;

10 }

11 r e t u r n c o u n t ;

12 }

Note that the int type should be replaced by long long int type, if the computer architecture is 64-bit instead of 32-bit, or you can use uintptr t type, which automatically detects the length of address, to avoid this problem.

4

(5)

Problem 4.

(a) (5%)

Because insertion sort runs in O(n2) worst-case time for sorting n elements, sort a sequence of k elements runs in O(k2) worst-case time. For sorting nk sublists, the total running time is O(nk∗ k2)

= O(nk) (b) (10%)

By problem 4.(a), we know that the running time of insertion sort part is O(nk) (the bottom of the tree). The running time for sorting n elements from each level to higher level in merge sort is O(n) (because in each level there are n elements). And we have nk sublists, so the number of levels of the tree are log2nk. The running time of merge sort part is O(n ∗ lognk), and the time complexity of the hybrid algorithm is O(nk + n lognk).

(c) (10%)

Our target is to find the optimal k so that intersion sort can run faster than merge sort. At fisrt, we can find that sorting k elements by insertion sort is faster than sorting by merge sort. When the value of k grows and reaches some value, the running time of insertion sort is larger than the running time of merge sort.

In practice, we can choose any platform and compare the running time of merge sort and insertion sort by trying various values of k (e.g. k from 2 to n). The optimal value of k is the largest list length on which insertion sort is faster than merge sort.

(6)

Problem 5.

(a) (5%)

(1), (2), (4): valid; (3), (5): invalid.

For each list, split it in two lists: one with numbers smaller than 363 and one with bigger (in order). The smaller list should be sorted ascending, the bigger should be descending. For example, in case (1), the bigger list is 401, 398, and 397. It is sorted descending. The smaller list is 2, 252, 330, and 344. It is sorted ascending. Therefore, case (1) is valid. You can use the same method for case (2) and (4) and found that they are all valid, too.

In case (3), the bigger list is 925, 911, and 912. It is not sorted descending, so it is invalid. In case (5), the smaller list is 278, 347, 299, and 358. It is not sorted ascending, so it is invalid.

(b) (5%)

Given that x has two child nodes, x’s successor must be in x’s right subtree because

The x’s successor must in the x’s right subtree. Since x have right child, there exist one node greater than x. For the other nodes which is not in the x’s right subtree, if it is greater than x, then it must greater than any node in subtree of x, so there is no any node can between x and the smallest node in the x’right subtree, hence that the smallest node in x’right subtree is x’s successor. If x’s succesor has a left child, the left child is greater than x since it is in x’s right subtree and smaller then x’s successor, we can find a contradiction that there is a node between x and x’s successor, so x’s successor have no left child. Similar, the smallest node in x’s left subtree is x’s predecessor and if it have right child will lead to the same contradiction.

(c) (10%)

1 s t r u c t b s t _ n o d e * b s t _ s u c c e s s o r (s t r u c t b s t _ n o d e * x ) {

2 b s t _ n o d e * s u c c e s s o r = N U L L ; w

3 if ( x - > r i g h t != N U L L ) {

4 s u c c e s s o r = x - > r i g h t ;

5 w h i l e( s u c c e s s o r - > l e f t != N U L L ) {

6 s u c c e s s o r = s u c c e s s o r - > l e f t ;

7 }

8 } e l s e {

9 w h i l e ( x - > p != N U L L ) {

10 if ( x - > p - > d a t a > x - > d a t a ) {

11 s u c c e s s o r = x - > p ;

12 b r e a k;

13 }

14 x = x - > p ;

15 }

16 }

17 r e t u r n s u c c e s s o r ;

18 }

6

(7)

Problem 6. String compression.

First of all, we can see that string AB (directly concatenate string A and string B) is a valid choice of S. Knowing the answer is no more then |A| + |B| (|S| denotes the length of string S), let’s focus on finding shorter S.

If |S| = x < |A| + |B|,

A[1] ... A[x − |B| + 1] ... A[|A|]

S[1] ... S[x − |B| + 1] ... S[|A|] ... S[x]

B[1] ... B[|A| + |B| − x] ... B[|B|]

There should be a string A’s suffix equals to a string B’s prefix.

If the string A’s suffix with length y is equal to the string B’s prefix with length y, then one can construct a string with length |A| + |B| − y that is a valid choice of S.

The task becomes to find the longest A’s suffix that is also a string B’s prefix.

One can get 30% of scores by enumerating every A’s suffix and directly comparing it with B’s prefix that has the same length.

To get 100% of scores, one possible way is using Rabin-Karp Algorithm to compare a pair of A’s suffix and B’s prefix, which get time complexity to O(|A| + |B|).

Another solution uses the property of Prefix function from Knuth-Morris-Pratt Algorithm.

For any string C, the prefix function π[|C|] is the largest number k < |C| such that the suffix of C with length k is a prefix of C. This seems similar to our task here. But we got two strings here, how to manage this problem?

Concatenate them!

Let’s make C = B$A where $ is a character not being any of the lowercase English letters.

Because of the strange character $, π[|C|] will not exceed min{|A|, |B|}, which is actually matching A’s suffix to B’s prefix. So we got another solution with time complexity O(|A| + |B|).

(8)

Problem 7. Post-order Sequence Verification.

Overview

Basically, any sequence of numbers could be a post-order sequence of a binary tree. However, there are some characteristics that one must possess to be a post-order sequence of a binary search tree.

The definition of post-order is outputting the content of a binary tree in the following order:

(1) output the left sub-tree, (2) output the right sub-tree, and (3) output the root. Now, consider the characteristics of a binary search tree. The left sub-tree contains all nodes that are smaller than root’s value, and the right sub-tree contains all those larger than root’s value.

Therefore, a post-order sequence, when we take the last number as the value of root, the rest of the sequence can be divided into two parts, where the first part is all the numbers smaller than root’s value, i.e., left sub-tree, and the numbers in the second part would be all greater than root’s value, forming the right sub-tree.

Algorithm

Based on the observation and deduction above, we can now develop a more structured set of steps for our post-order sequence verification:

• Take the last number as root’s value.

• Try split the rest of the sequence into two parts.

– If successful, recursively check the two sub-sequence.

– Otherwise, it’s not a valid post-order sequence.

Sample Code

The following code snippet implements the algorithm above. The function check() would check a sub-sequence of the given integer array, sequence, specified by start and end, both inclusive.

The return value of check() is the height of the tree formed by the specified sub-sequence if it is possible, -1 otherwise. Additionally, the height of the tree formed by current sub-sequence is: 1 + the height of its higher sub-tree, either left or right.

1 f u n c t i o n c h e c k ( s e q u e n c e , start , end ) :

2 # the t e r m i n a l c o n d i t i o n 8

(9)

3 if s t a r t > end :

4 r e t u r n 0

5 # get the v a l u e of r o o t

6 r o o t _ v a l u e = s e q u e n c e [ end ]

7 # f i n d the p o s i t i o n of s e p a r a t i o n

8 for i = s t a r t to end :

9 if s e q u e n c e [ i ] > r o o t _ v a l u e :

10 b r e a k

11 s e p a r a t e _ i n d e x = i

12 for i = s e p a r a t e _ i n d e x to end :

13 if seq [ i ] < r o o t _ v a l u e :

14 r e t u r n -1 # not s e p a r a b l e

15 # r e c u r s i v e c h e c k i n g

16 l e f t _ s u b = c h e c k ( s e q u e n c e , start , s e p a r a t e _ i n d e x - 1)

17 r i g h t _ s u b = c h e c k ( s e q u e n c e , s e p a r a t e _ i n d e x , end - 1)

18 if l e f t _ s u b == -1 or r i g h t _ s u b == -1:

19 r e t u r n -1

20 e l s e:

21 r e t u r n max ( l e f t _ s u b , r i g h t _ s u b ) + 1

參考文獻

相關文件

[r]

[r]

Determine whether the series is absolutely convergent, conditionally convergent, or diver- gent... However, any conditionally convergent series can be rearranged to give a

More precisely, it is the problem of partitioning a positive integer m into n positive integers such that any of the numbers is less than the sum of the remaining n − 1

For periodic sequence (with period n) that has exactly one of each 1 ∼ n in any group, we can find the least upper bound of the number of converged-routes... Elementary number

The minimal ellipse that can enclosed the cirlce is tangent to the circle, and the tangent point (x, y) has one possible solution for y variable.. This is our constrain

I will give no credit for wildly incorrect answers which are obviously only there in the hopes of getting partial credit.. Page 2

But P na n = P(−1) n−1 is divergent because the limit of its partial sum does