<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Blog Khoa Học Máy Tính &#187; Thuật Toán</title>
	<atom:link href="http://www.procul.org/blog/category/thu%e1%ba%adt-toan/feed/" rel="self" type="application/rss+xml" />
	<link>http://www.procul.org/blog</link>
	<description>Tầm nhìn ta thật ngắn mà đã thấy bao thứ  để làm -- Alan Turing</description>
	<lastBuildDate>Wed, 08 Feb 2012 12:29:56 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	
		<item>
		<title>Thuật toán tồi tệ nhất trong lịch sử nhân loại</title>
		<link>http://www.procul.org/blog/2012/02/07/thu%e1%ba%adt-toan-t%e1%bb%93i-t%e1%bb%87-nh%e1%ba%a5t-trong-l%e1%bb%8bch-s%e1%bb%ad-nhan-lo%e1%ba%a1i/</link>
		<comments>http://www.procul.org/blog/2012/02/07/thu%e1%ba%adt-toan-t%e1%bb%93i-t%e1%bb%87-nh%e1%ba%a5t-trong-l%e1%bb%8bch-s%e1%bb%ad-nhan-lo%e1%ba%a1i/#comments</comments>
		<pubDate>Wed, 08 Feb 2012 02:40:53 +0000</pubDate>
		<dc:creator>NQH</dc:creator>
				<category><![CDATA[Lập trình]]></category>
		<category><![CDATA[Thuật Toán]]></category>
		<category><![CDATA[Fibonacci]]></category>
		<category><![CDATA[Phân tích thuật toán]]></category>
		<category><![CDATA[thuật toán]]></category>

		<guid isPermaLink="false">http://www.procul.org/blog/?p=4273</guid>
		<description><![CDATA[Đặt tít để câu view. Số là học kỳ này tôi dạy lớp cấu trúc dữ liệu. Đây là lớp undergrad đầu tiên tôi dạy từ trước đến nay. Lớp cho sinh viên năm 1 năm 2. Có bài giảng này mới viết, có vẻ thích hợp cho blog nên đăng lại đây. Bạn đọc [...]]]></description>
			<content:encoded><![CDATA[<blockquote><p>Đặt tít để câu view. Số là học kỳ này tôi dạy lớp cấu trúc dữ liệu. Đây là lớp undergrad đầu tiên tôi dạy từ trước đến nay. Lớp cho sinh viên năm 1 năm 2. Có bài giảng này mới viết, có vẻ thích hợp cho blog nên đăng lại đây. Bạn đọc  nào rảnh thì góp ý giùm. </p></blockquote>
<hr />
<h3>1. The Fibonacci numbers</h3>
<p>The Fibonacci numbers are the numbers in the sequence 0,1,1,2,3,5,8,&#8230; defined by <code>F[0] = 0</code>, <code>F[1]=1</code>, and <code>F[n] = F[n-1]+F[n-2]</code> for integers <code>n &ge; 2</code>. These numbers appeared in Indian mathematics, probably as early as 200BC. The numbers, however, are named after the Italian mathematician Fibonacci, also known as <a href="http://en.wikipedia.org/wiki/Fibonacci">Leonardo of Pisa</a>. Fibonacci wanted to study an idealized model of rabbit population growth (where they never die), and the <code>n</code>th Fibonacci number is the number of rabbits in the population in the <code>n</code>th month starting with a single pair of rabbits in the first month.</p>
<p>Using the method of generating functions, it is not hard to show that <img src='http://s.wordpress.com/latex.php?latex=F%5Bn%5D%20%3D%20%5Cfrac%7B%5Cphi%5En-%28-1%2F%5Cphi%29%5En%7D%7B%5Csqrt%205%7D&#038;bg=ffffff&#038;fg=000000&#038;s=0' alt='F[n] = \frac{\phi^n-(-1/\phi)^n}{\sqrt 5}' title='F[n] = \frac{\phi^n-(-1/\phi)^n}{\sqrt 5}' class='latex' />, where <img src='http://s.wordpress.com/latex.php?latex=%5Cphi%20%3D%20%5Cfrac%7B1%2B%5Csqrt%205%7D%7B2%7D&#038;bg=ffffff&#038;fg=000000&#038;s=0' alt='\phi = \frac{1+\sqrt 5}{2}' title='\phi = \frac{1+\sqrt 5}{2}' class='latex' /> is the <a href="http://en.wikipedia.org/wiki/Golden_ratio">Golden ratio</a>. Using this formula to compute the <code>n</code>th Fibonacci number is possible, but quite messy due to round-off errors and the complexity of floating point computation in general. So, let us try to write a program to compute <code>F[n]</code> without doing floating point computation.</p>
<p><span id="more-4273"></span></p>
<h4>1.1. The worst algorithm in the history of humanity</h4>
<pre class="brush: cpp; title: ; notranslate">
/**
 * -----------------------------------------------------------------------
 *  the most straightforward implementation of Fibonacci comp. algo.
 * -----------------------------------------------------------------------
 */
unsigned long long fib1(unsigned long  n) {
    if (n &lt;= 1) return n;
    return fib1(n-1) + fib1(n-2);
}
</pre>
<p>The above algorithm for computing <code>F[n]</code> turns out to be extremely bad. How do I know that? Well, on my laptop which runs 2.53 GHz Intel Core 2 Duo with 4GB of memory, I ran a little experiment. Computing up to <code>F[37]</code> took under 1 second. Things start to get interesting for <code>F[38]</code> and on.</p>
<table>
<tr>
<td>n
<td>37
<td>38
<td>39
<td>40
<td>41
<td>42
<td>43
<td>44
<td>45
<td>46
<td>47<br />
</tr>
<tr>
<td># secs
<td>1
<td>2
<td>2
<td>3
<td>5
<td>7
<td>13
<td>22
<td>31
<td>53
<td>104<br />
</tr>
</table>
<p><a href="http://cse250.files.wordpress.com/2012/02/fib1.png"><img src="http://cse250.files.wordpress.com/2012/02/fib1.png" alt="" title="fib1" width="600" height="463" class="aligncenter size-full wp-image-347" /></a></p>
<p>Looks like the number of seconds is doubled for each additional 1 we add to n. We wouldn&#8217;t be able to compute <code>F[110]</code> this way. Remember that the age is the universe is probably less than 15 billion years, which is less than <img src='http://s.wordpress.com/latex.php?latex=2%5E%7B60%7D&#038;bg=ffffff&#038;fg=000000&#038;s=0' alt='2^{60}' title='2^{60}' class='latex' /> seconds. Now, wouldn&#8217;t you agree that the above algorithm is the worst algorithm in the history of the universe? </p>
<p>The shape of the run-time function, in terms of <code>n</code>, looks exponential. Is there a mathematical justification for this? Yes! We should have at least done some back-of-the-envelope estimate on the run-time of this algorithm before even writing the code. </p>
<ul>
<li> Note that when we say &#8220;function&#8221;, sometimes we mean a C++ funtion, and sometimes we mean a mathematical function. A C++ function might be used to compute a mathematical function, but not always. What kind of functions we mean should be clear from context. In this case, the C++ function <code>fib1()</code> is used to compute the mathematical function <code>F[n]</code>.
<li> <code>fib1()</code> contains calls to itself, with smaller parameters. These are <a href="http://en.wikipedia.org/wiki/Recursion_(computer_science)">recursive calls</a>, and are one of the most important techniques of algorithmic design. Recursion, if used in the right way, makes the code very clean and easy to understand. That comes at a price: each time we call a function the operating system has to reserve some memory space for that function on a function stack that the operating system maintains. This operation takes both time and space (i.e. memory). Some compilers/interpreters are very smart and they might be able to apply a technique called <a href="http://blogs.msdn.com/b/chrsmith/archive/2008/08/07/understanding-tail-recursion.aspx">tail recursion</a> to save the use the time/space imposed by some class of recursive function calls. But you&#8217;ll need to take CSE305 to understand the technique. Anyhow, this is not the case here.
<li> Now, let&#8217;s consider the call to <code>fib1(6)</code>. Our program spends some time before making the two recursive calls: compare 6 with 2 in the <code>if</code> statement, computing 6-1 and 6-2, etc. So, there is a fixed amount of work done before the two recursive calls are performed. Then, after the two recursive calls return their values, some amount of work is done to get their sum and return. Overall, let <code>T[n]</code> denote the amount of time it takes the call <code>fib1(n)</code> to compute <code>F[n]</code>, then we can estimate <code>T[6]</code> by
<pre>T[6] = c + T[5] + T[4]</pre>
<p>where <code>c</code> is a constant, probably in the order of miliseconds. Using exactly the same reasoning, we can continue:</p>
<pre>
T[6] = c + T[5] + T[4]
     = c + (c + T[4] + T[3]) + (c + T[3] + T[2])
     = 3c + T[4] + 2T[3] + T[2]
     = 3c + (c + T[3] + T[2]) + 2(c + T[2] + T[1]) + (c + T[1] + T[0])
     = 7c + T[3] + 3T[2] + 3T[1] + T[0]
     = 7c + (c + T[2] + T[1]) + 3(c + T[1] + T[0]) + 3T[1] + T[0]
     = 11c + T[2] + 7T[1] + 4T[0]
     = 11c + (c + T[1] + T[0]) + 7T[1] + 4T[0]
     = 12c + 8T[1] + 5T[0]
</pre>
<p>Now, <code>T[1]</code> and <code>T[0]</code> are slightly different because only the <code>if</code> statement is executed; let&#8217;s assume that it takes <code>d</code> units of time. Overall, we have <code>T[6] = 12c + 13d</code>. But we want to estimate <code>T[n]</code>, not just <code>T[6]</code>. How do we do that?
</ul>
<h4>1.2. First technique for run-time estimation: guess and induct</h4>
<p>Our first strategy is to guess a formula for <code>T[n]</code> and then prove it by induction. We can easily get the formulas for <code>T[n]</code> for <code>n &le; 6</code> by iterating &#8220;bottom-up&#8221;:</p>
<pre>
T[0] = d
T[1] = d
T[2] = c + 2d
T[3] = 2c + 3d
T[4] = 4c + 5d
T[5] = 7c + 8d
T[6] = 12c + 13d
...
T[n] = c + T[n-1] + T[n-2]
</pre>
<p>The coefficients of <code>d</code> looks like &#8230; the Fibonacci sequence, and the coefficients of <code>c</code> is just one off. Our conjecture is then:</p>
<pre>T[n] = (F[n+1]-1)c + F[n+1]d     (*)</pre>
<p>Let&#8217;s prove it by induction. The base cases are <code>T[0] = d</code> and <code>T[1] = d</code>, obviously hold! Assume (*) holds for all <code>T[m]</code> with <code>m &le; n-1</code>; in particular, assume we knew that</p>
<pre>
T[n-1] = (F[n]-1)c + F[n]d
T[n-2] = (F[n-1]-1)c + F[n-1]d
</pre>
<p>Then, we have</p>
<pre>
T[n] = c + T[n-1] + T[n-2]                           // from fib1()'s definition
     = c + (F[n]-1)c + F[n]d + (F[n-1]-1)c + F[n-1]d // induction hypotheses
     = (F[n] + F[n-1] -1)c + (F[n] + F[n-1])d        // regrouping
     = (F[n+1]-1)c + F[n+1]d                         // Fibonacci's sequence
</pre>
<p>Ah hah! We have a precise formula for the time it takes to run <code>fib1(n)</code>. However, the formula still looks a little ugly. Let <code>B = max(c,d)</code> and <code>S = min(c,d)</code>, then it follows that</p>
<pre>(2F[n+1]-1) S &le; T[n] &le; (2F[n+1]-1) B</pre>
<p>where <code>B</code> and <code>S</code> are off by a few miliseconds, depending on the speed of your computer. In fact, the values of <code>B,S</code> are also in the order of miliseconds. If your computer is twice as fast then probably <code>B,S</code> are twice as small. Now, if we use the golden-ratio formula for <code>F[n]</code> then because <img src='http://s.wordpress.com/latex.php?latex=%281%2F%5Cphi%29%5En%20%5Cto%200&#038;bg=ffffff&#038;fg=000000&#038;s=0' alt='(1/\phi)^n \to 0' title='(1/\phi)^n \to 0' class='latex' /> as <img src='http://s.wordpress.com/latex.php?latex=n%20%5Cto%20%5Cinfty&#038;bg=ffffff&#038;fg=000000&#038;s=0' alt='n \to \infty' title='n \to \infty' class='latex' />, we can estimate</p>
<p><img src='http://s.wordpress.com/latex.php?latex=F%5Bn%2B1%5D%20%3D%20%5Cfrac%7B%5Cphi%5E%7Bn%2B1%7D-%28-1%2F%5Cphi%29%5E%7Bn%2B1%7D%7D%7B%5Csqrt%205%7D%20%5Capprox%20%5Cphi%5E%7Bn%2B1%7D%2F%5Csqrt%7B5%7D&#038;bg=ffffff&#038;fg=000000&#038;s=0' alt='F[n+1] = \frac{\phi^{n+1}-(-1/\phi)^{n+1}}{\sqrt 5} \approx \phi^{n+1}/\sqrt{5}' title='F[n+1] = \frac{\phi^{n+1}-(-1/\phi)^{n+1}}{\sqrt 5} \approx \phi^{n+1}/\sqrt{5}' class='latex' />.</p>
<p>Overall, there are two constants <code>C &le; D</code> such that</p>
<p><img src='http://s.wordpress.com/latex.php?latex=C%5Cphi%5En%20%5Cleq%20T%5Bn%5D%20%5Cleq%20D%5Cphi%5En&#038;bg=ffffff&#038;fg=000000&#038;s=0' alt='C\phi^n \leq T[n] \leq D\phi^n' title='C\phi^n \leq T[n] \leq D\phi^n' class='latex' />.</p>
<p>Faster computers reduce those constants but they are limited by the speed of light, and the bulk of the run time of <code>fib1()</code> lies in the exponential factor <img src='http://s.wordpress.com/latex.php?latex=%5Cphi%5En&#038;bg=ffffff&#038;fg=000000&#038;s=0' alt='\phi^n' title='\phi^n' class='latex' />. This explains the exponential-like curve we saw above. </p>
<h4>1.3. Asymptotic analysis</h4>
<p>Let&#8217;s play &#8220;devil&#8217;s advocate&#8221; and assume that we have an extremely fast computer which allows for <code>C</code> equals 1 nanosecond (10 to the minus 9). Recall that the golden ratio is smaler than 1.6. Then, <code>fib1(140)</code> takes <em>at least</em> <img src='http://s.wordpress.com/latex.php?latex=10%5E%7B-9%7D%281.6%29%5E%7B140%7D%20%5Cgeq%203.77%20%5Ccdot%2010%5E%7B19%7D&#038;bg=ffffff&#038;fg=000000&#038;s=0' alt='10^{-9}(1.6)^{140} \geq 3.77 \cdot 10^{19}' title='10^{-9}(1.6)^{140} \geq 3.77 \cdot 10^{19}' class='latex' /> seconds. The number of seconds since the big-bang is currently <a href="http://en.wikipedia.org/wiki/Age_of_the_universe">estimated to be</a> <img src='http://s.wordpress.com/latex.php?latex=4.336%5Ccdot%2010%5E%7B17%7D&#038;bg=ffffff&#038;fg=000000&#038;s=0' alt='4.336\cdot 10^{17}' title='4.336\cdot 10^{17}' class='latex' />. If we had a computer which is a million times faster, does it help in running <code>fib1(140)</code>? Absolutely not.</p>
<p><a href="http://en.wikipedia.org/wiki/Analysis_of_algorithms">Asymptotic analysis of algorithms</a> refers to a technique of doing back-of-the-envelope estimation of an algorithm&#8217;s run-time and/or space requirement, without worrying about the speed of the computer that the algorithm is running on. As we have seen above, if an algorithm is very bad then running it on a super-computer does not help much. We might be able to compute, say, <code>fib1(120)</code> but <code>fib1(160)</code> is certainly impossible. The factor <img src='http://s.wordpress.com/latex.php?latex=%5Cphi%5En&#038;bg=ffffff&#038;fg=000000&#038;s=0' alt='\phi^n' title='\phi^n' class='latex' /> is the correct measure of the run-time &#8220;quality&#8221; of <code>fib1()</code>, while the constant factor is not. We can always reduce the constant factor by running the algorithm on a faster computer, but we can only change the <img src='http://s.wordpress.com/latex.php?latex=%5Cphi%5En&#038;bg=ffffff&#038;fg=000000&#038;s=0' alt='\phi^n' title='\phi^n' class='latex' /> factor &#8212; the dominating factor &#8212; by designing a new algorithm.</p>
<p>What we first need is a formal way to state an intuitive statement such as &#8220;this algorithm intrinsically runs in time <img src='http://s.wordpress.com/latex.php?latex=%5Cphi%5En&#038;bg=ffffff&#038;fg=000000&#038;s=0' alt='\phi^n' title='\phi^n' class='latex' />&#8220;. Let <img src='http://s.wordpress.com/latex.php?latex=f%2C%20g%3A%20%5Cmathbb%20N%20%5Cto%20%5Cmathbb%20R%5E%2B&#038;bg=ffffff&#038;fg=000000&#038;s=0' alt='f, g: \mathbb N \to \mathbb R^+' title='f, g: \mathbb N \to \mathbb R^+' class='latex' /> be any two functions from the natural numbers to non-negative real numbers. </p>
<h6>Big-O</h6>
<p>We write</p>
<p><img src='http://s.wordpress.com/latex.php?latex=f%28n%29%20%3D%20O%28g%28n%29%29&#038;bg=ffffff&#038;fg=000000&#038;s=0' alt='f(n) = O(g(n))' title='f(n) = O(g(n))' class='latex' /> (read: f(n) is big-O of g(n))</p>
<p>if and only if there is a constant <img src='http://s.wordpress.com/latex.php?latex=C&#038;bg=ffffff&#038;fg=000000&#038;s=0' alt='C' title='C' class='latex' /> and a natural number <img src='http://s.wordpress.com/latex.php?latex=n_0&#038;bg=ffffff&#038;fg=000000&#038;s=0' alt='n_0' title='n_0' class='latex' /> such that</p>
<img src='http://s.wordpress.com/latex.php?latex=f%28n%29%20%5Cleq%20C%20%5Ccdot%20g%28n%29%2C%20%5Ctext%7B%20for%20all%20%7D%20n%20%5Cgeq%20n_0&#038;bg=ffffff&#038;fg=000000&#038;s=0' alt='f(n) \leq C \cdot g(n), \text{ for all } n \geq n_0' title='f(n) \leq C \cdot g(n), \text{ for all } n \geq n_0' class='latex' />
<p>In English, the function <img src='http://s.wordpress.com/latex.php?latex=f%28n%29&#038;bg=ffffff&#038;fg=000000&#038;s=0' alt='f(n)' title='f(n)' class='latex' /> is bounded above by some constant scaling of <img src='http://s.wordpress.com/latex.php?latex=g%28n%29&#038;bg=ffffff&#038;fg=000000&#038;s=0' alt='g(n)' title='g(n)' class='latex' /> when <img src='http://s.wordpress.com/latex.php?latex=n&#038;bg=ffffff&#038;fg=000000&#038;s=0' alt='n' title='n' class='latex' /> is sufficiently large.</p>
<p><a href="http://cse250.files.wordpress.com/2012/02/bigograph.gif"><img src="http://cse250.files.wordpress.com/2012/02/bigograph.gif" alt="" title="bigOGraph" width="453" height="261" class="aligncenter size-full wp-image-354" /></a></p>
<p>In the <code>fib1()</code> example, we have <img src='http://s.wordpress.com/latex.php?latex=T%5Bn%5D%20%3D%20O%28%5Cphi%5En%29&#038;bg=ffffff&#038;fg=000000&#038;s=0' alt='T[n] = O(\phi^n)' title='T[n] = O(\phi^n)' class='latex' /> <strong>and</strong> <img src='http://s.wordpress.com/latex.php?latex=%5Cphi%5En%20%3D%20O%28T%5Bn%5D%29&#038;bg=ffffff&#038;fg=000000&#038;s=0' alt='\phi^n = O(T[n])' title='\phi^n = O(T[n])' class='latex' />. The first relation <img src='http://s.wordpress.com/latex.php?latex=T%5Bn%5D%20%3D%20O%28%5Cphi%5En%29&#038;bg=ffffff&#038;fg=000000&#038;s=0' alt='T[n] = O(\phi^n)' title='T[n] = O(\phi^n)' class='latex' /> says that the growth-rate of T[n] is at most as much as the growth-rate of <img src='http://s.wordpress.com/latex.php?latex=%5Cphi%5En&#038;bg=ffffff&#038;fg=000000&#038;s=0' alt='\phi^n' title='\phi^n' class='latex' /> (up to a constant factor), and the second relation <img src='http://s.wordpress.com/latex.php?latex=%5Cphi%5En%20%3D%20O%28T%5Bn%5D%29&#038;bg=ffffff&#038;fg=000000&#038;s=0' alt='\phi^n = O(T[n])' title='\phi^n = O(T[n])' class='latex' /> states that the growth-rate of <img src='http://s.wordpress.com/latex.php?latex=%5Cphi%5En&#038;bg=ffffff&#038;fg=000000&#038;s=0' alt='\phi^n' title='\phi^n' class='latex' /> is also bounded by the growth rate of <img src='http://s.wordpress.com/latex.php?latex=T%5Bn%5D&#038;bg=ffffff&#038;fg=000000&#038;s=0' alt='T[n]' title='T[n]' class='latex' />. In other words, the shape of the graph for T[n] is very similar to that of the shape of the graph for <img src='http://s.wordpress.com/latex.php?latex=%5Cphi%5En&#038;bg=ffffff&#038;fg=000000&#038;s=0' alt='\phi^n' title='\phi^n' class='latex' />. In fact, we have seen that T[n] is sandwiched between two copies of <img src='http://s.wordpress.com/latex.php?latex=%5Cphi%5En&#038;bg=ffffff&#038;fg=000000&#038;s=0' alt='\phi^n' title='\phi^n' class='latex' />, which are different by some constant ratio.</p>
<h6>Big-Omega and Theta</h6>
<p>We write <img src='http://s.wordpress.com/latex.php?latex=f%28n%29%20%3D%20%5COmega%28g%28n%29%29&#038;bg=ffffff&#038;fg=000000&#038;s=0' alt='f(n) = \Omega(g(n))' title='f(n) = \Omega(g(n))' class='latex' /> if and only if <img src='http://s.wordpress.com/latex.php?latex=g%28n%29%20%3D%20O%28f%28n%29%29&#038;bg=ffffff&#038;fg=000000&#038;s=0' alt='g(n) = O(f(n))' title='g(n) = O(f(n))' class='latex' />. In other words, <img src='http://s.wordpress.com/latex.php?latex=f%28n%29%20%3D%20%5COmega%28g%28n%29%29&#038;bg=ffffff&#038;fg=000000&#038;s=0' alt='f(n) = \Omega(g(n))' title='f(n) = \Omega(g(n))' class='latex' /> if and only if there exists a constant C and some number <img src='http://s.wordpress.com/latex.php?latex=n_0&#038;bg=ffffff&#038;fg=000000&#038;s=0' alt='n_0' title='n_0' class='latex' /> such that</p>
<img src='http://s.wordpress.com/latex.php?latex=f%28n%29%20%5Cgeq%20C%20g%28n%29%2C%20%5Ctext%7B%20for%20all%20%7D%20n%20%5Cgeq%20n_0&#038;bg=ffffff&#038;fg=000000&#038;s=0' alt='f(n) \geq C g(n), \text{ for all } n \geq n_0' title='f(n) \geq C g(n), \text{ for all } n \geq n_0' class='latex' />
<p>In the <code>fib1()</code> example, <img src='http://s.wordpress.com/latex.php?latex=T%5Bn%5D%20%3D%20%5COmega%28%5Cphi%5En%29&#038;bg=ffffff&#038;fg=000000&#038;s=0' alt='T[n] = \Omega(\phi^n)' title='T[n] = \Omega(\phi^n)' class='latex' />.</p>
<p>If <img src='http://s.wordpress.com/latex.php?latex=f%28n%29%20%3D%20O%28g%28n%29%29&#038;bg=ffffff&#038;fg=000000&#038;s=0' alt='f(n) = O(g(n))' title='f(n) = O(g(n))' class='latex' /> <strong>and</strong> <img src='http://s.wordpress.com/latex.php?latex=f%28n%29%20%3D%20%5COmega%28g%28n%29%29&#038;bg=ffffff&#038;fg=000000&#038;s=0' alt='f(n) = \Omega(g(n))' title='f(n) = \Omega(g(n))' class='latex' />, then we say that <img src='http://s.wordpress.com/latex.php?latex=f%28n%29&#038;bg=ffffff&#038;fg=000000&#038;s=0' alt='f(n)' title='f(n)' class='latex' /> is Theta of <img src='http://s.wordpress.com/latex.php?latex=g%28n%29&#038;bg=ffffff&#038;fg=000000&#038;s=0' alt='g(n)' title='g(n)' class='latex' /> and write <img src='http://s.wordpress.com/latex.php?latex=f%28n%29%20%3D%20%5CTheta%28g%28n%29%29&#038;bg=ffffff&#038;fg=000000&#038;s=0' alt='f(n) = \Theta(g(n))' title='f(n) = \Theta(g(n))' class='latex' />. In the <code>fib1()</code> example, we have <img src='http://s.wordpress.com/latex.php?latex=T%5Bn%5D%20%3D%20%5CTheta%28%5Cphi%5En%29&#038;bg=ffffff&#038;fg=000000&#038;s=0' alt='T[n] = \Theta(\phi^n)' title='T[n] = \Theta(\phi^n)' class='latex' />. When <img src='http://s.wordpress.com/latex.php?latex=f%28n%29%20%3D%20%5CTheta%28g%28n%29%29&#038;bg=ffffff&#038;fg=000000&#038;s=0' alt='f(n) = \Theta(g(n))' title='f(n) = \Theta(g(n))' class='latex' />, they have the same growth-rate as n gets large. Two algorithms which run in times <img src='http://s.wordpress.com/latex.php?latex=f%28n%29&#038;bg=ffffff&#038;fg=000000&#038;s=0' alt='f(n)' title='f(n)' class='latex' /> and <img src='http://s.wordpress.com/latex.php?latex=g%28n%29&#038;bg=ffffff&#038;fg=000000&#038;s=0' alt='g(n)' title='g(n)' class='latex' /> on an input of size n are considered to have the same asymptotic running time.</p>
<p>If <img src='http://s.wordpress.com/latex.php?latex=f%28n%29%20%3D%20O%28g%28n%29%29&#038;bg=ffffff&#038;fg=000000&#038;s=0' alt='f(n) = O(g(n))' title='f(n) = O(g(n))' class='latex' />, then the algorithm with <img src='http://s.wordpress.com/latex.php?latex=f%28n%29&#038;bg=ffffff&#038;fg=000000&#038;s=0' alt='f(n)' title='f(n)' class='latex' /> run time is at least as good as the algorithm with <img src='http://s.wordpress.com/latex.php?latex=g%28n%29&#038;bg=ffffff&#038;fg=000000&#038;s=0' alt='g(n)' title='g(n)' class='latex' /> run time. When <img src='http://s.wordpress.com/latex.php?latex=f%28n%29%20%3D%20O%28g%28n%29%29&#038;bg=ffffff&#038;fg=000000&#038;s=0' alt='f(n) = O(g(n))' title='f(n) = O(g(n))' class='latex' /> we think of <img src='http://s.wordpress.com/latex.php?latex=g%28n%29&#038;bg=ffffff&#038;fg=000000&#038;s=0' alt='g(n)' title='g(n)' class='latex' /> as an <em>asymptotic upper bound</em> for <img src='http://s.wordpress.com/latex.php?latex=f%28n%29&#038;bg=ffffff&#038;fg=000000&#038;s=0' alt='f(n)' title='f(n)' class='latex' />.</p>
<p>Conversely, when <img src='http://s.wordpress.com/latex.php?latex=f%28n%29%20%3D%20%5COmega%28g%28n%29%29&#038;bg=ffffff&#038;fg=000000&#038;s=0' alt='f(n) = \Omega(g(n))' title='f(n) = \Omega(g(n))' class='latex' /> we say <img src='http://s.wordpress.com/latex.php?latex=g%28n%29&#038;bg=ffffff&#038;fg=000000&#038;s=0' alt='g(n)' title='g(n)' class='latex' /> is an <em>asymptotic lower bound</em> for <img src='http://s.wordpress.com/latex.php?latex=f%28n%29&#038;bg=ffffff&#038;fg=000000&#038;s=0' alt='f(n)' title='f(n)' class='latex' />. We will consider more example functions later. Let us now get back to the Fibonacci number computation problem and try to improve <code>fib1()</code>, with the knowledge that the running time of <code>fib1()</code> is in the (asymptotic) order of <img src='http://s.wordpress.com/latex.php?latex=%5Cphi%5En&#038;bg=ffffff&#038;fg=000000&#038;s=0' alt='\phi^n' title='\phi^n' class='latex' />: an exponential run time. In fact, <code>fib1()</code> also has an exponential space complexity because of all the recursive calls.</p>
<p><strong>Exercise</strong>: suppose for each function call the operating system pushes x bytes of memory onto a &#8220;function stack&#8221;. The memory will be released when the function returns (and thus its function stack space will be popped out of the function stack). Roughly how many bytes are used by a call to <code>fib1(n)</code>?</p>
<h3>2. Better algorithms for Fibonacci sequence</h3>
<h4>2.1. A slightly better solution</h4>
<pre class="brush: cpp; title: ; notranslate">
/**
 * -----------------------------------------------------------------------
 *  a better implementation with a linear time/space
 * -----------------------------------------------------------------------
 */
unsigned long long fib2(unsigned long  n) {
    if (n &lt;= 1) return n;
    vector&lt;unsigned long long&gt; A;
    A.push_back(0); A.push_back(1);
    for (int i=2; i&lt;=n; i++) {
        A.push_back(A[i-1]+A[i-2]);
    }
    return A[n];
}
</pre>
<p>This algorithm uses a vector. Initially the vector is empty, and each time we are able to compute <code>A[i]</code> using two previous entries <code>A[i-1]</code> and <code>A[i-2]</code> we append the vector with <code>A[i]</code>. If we assume the <code>push_back</code> operation takes a constant amount of time (machine dependent, but still a constant), then the total running time is <code>O(n)</code>, and the total space required is also <code>O(n)</code>. (Here, we also assume that the memory space needed to store a vector of <code>n</code> elements is about <code>O(n)</code>. We will see later in the course when we implement the vector class that both the assumptions are valid.)</p>
<p>There is no recursion in this case. The <code>for</code> loop has about <code>n-1</code> steps, each step takes a constant amount of work. Thus, the overall running time is <img src='http://s.wordpress.com/latex.php?latex=T_2%5Bn%5D%20%3D%20%5CTheta%28n%29&#038;bg=ffffff&#038;fg=000000&#038;s=0' alt='T_2[n] = \Theta(n)' title='T_2[n] = \Theta(n)' class='latex' />. How good is this run time compared to the <img src='http://s.wordpress.com/latex.php?latex=%5CTheta%28%5Cphi%5En%29&#038;bg=ffffff&#038;fg=000000&#038;s=0' alt='\Theta(\phi^n)' title='\Theta(\phi^n)' class='latex' /> we had earlier? Here&#8217;s a table of run time:</p>
<table>
<tr>
<td>n
<td>10<sup>6</sup>
<td>10<sup>7</sup>
<td>10<sup>8</sup>
<td>10<sup>9</sup><br />
</tr>
<tr>
<td># secs
<td>1
<td>1
<td>9
<td>eats up all my CPU/RAM<br />
</tr>
</table>
<p>This is dramatically better. We were only able to get up to the tens, and now n can be as large as a hundred million. Well, mathematically it is exponentially better. However, when <code>n = 10<sup>9</sup></code> and beyond, my computer becomes extremely slow. The <code>vector</code> class is sort of like an array but the memory space for it has to be dynamically allocated. Those operations&#8217; cost catch up to us eventually, even when they are hidden behind the constant in the big-O notation. Furthermore, when we consume so much memory, the assumption that each <code>push_back</code> takes constant time is no longer valid! Even if each element in the vector is only 8 bytes long (<code>sizeof(unsigned long long)</code> in my MacBook Pro), we need at least <code>8GB</code> of memory space and my laptop has only <code>4GB</code> RAM in total. Remember that in a 64-bit machine like mine, theoretically we can have up to <code>2<sup>64</sup> &#126; 18*10<sup>18</sup> </code> bytes of memory. (Question: how large must your computer case be to hold that much of RAM?) So, with only 4GB of RAM, a lot of swapping in and out of memory is done and the operations become increasingly slow. I had to kill the process at <code>n=10<sup>9</sup></code>.</p>
<p>Now, suppose we keep the same algorithm but we change the data structure to something lighter than the <code>vector</code>, namely we use an array:</p>
<pre class="brush: cpp; title: ; notranslate">
unsigned long long fib2(unsigned long n) {
    if (n &lt;= 1) return n;
    unsigned long long* A = new unsigned long long[n];
    A[0] = 0; A[1] = 1;
    for (int i=2; i&lt;=n; i++) {
        A[i] = A[i-1]+A[i-2];
    }
    unsigned long long ret = A[n];
    delete [] A;
    return ret;
}
</pre>
<table>
<tr>
<td>n
<td>10<sup>6</sup>
<td>10<sup>7</sup>
<td>10<sup>8</sup>
<td>10<sup>9</sup><br />
</tr>
<tr>
<td># secs
<td>1
<td>1
<td>1
<td>got a segmentation fault!<br />
</tr>
</table>
<p>So we did get quite a dramatic improvement at <code>n = 10<sup>8</sup></code>, but at <code>n = 10<sup>9</sup></code> we got a segmentation fault. This example illustrates 2 points. First, the data structure choice matters &#8212; even beyond asymptotic analysis. Second, some of the assumptions we made in asymptotic analysis which holds true for &#8220;small&#8221; <code>n</code> does not hold true for large <code>n</code> when space consumption is taken into account. A linear-space algorithm on huge inputs is still pretty bad.</p>
<h4>2.2. A much better solution</h4>
<p>Now, to compute F[n] we only need to hold the previous two values (constant space!). This simple observation leads to the following solution. This strategy is actually called <a href="http://en.wikipedia.org/wiki/Dynamic_programming">dynamic programming</a>, something you will learn about in CSE331 and CSE431. Please take those courses! They will make you a better computer engineer, a better computer scientist. I guarantee it.</p>
<pre class="brush: cpp; title: ; notranslate">
/**
 * -----------------------------------------------------------------------
 *  a better algorithm with a linear time, constant space
 * -----------------------------------------------------------------------
 */
unsigned long long fib3(unsigned long n) {
    if (n &lt;= 1) return n;
    unsigned long long a=0, b=1, temp;
    unsigned long i;
    for (unsigned long i=2; i&lt;= n; i++) {
        temp = a + b; // F[i] = F[i-2] + F[i-1]
        a = b;        // a = F[i-1]
        b = temp;     // b = F[i]
    }
    return temp;
}
</pre>
<p>The data for this <code>fib3()</code> algorithm are as follows.</p>
<table>
<tr>
<td>n
<td>10<sup>8</sup>
<td>10<sup>9</sup>
<td>10<sup>10</sup>
<td>10<sup>11</sup><br />
</tr>
<tr>
<td># secs
<td>1
<td>3
<td>35
<td>359<br />
</tr>
</table>
<p>The run-time still exhibits the exponential behavior, but this time it is exponential in the exponents 8, 9, 10, 11, etc. That makes sense, because a run time of <code>n</code> is exponential in <code>log(n)</code>. Note also that we don&#8217;t get the correct answer because the <code>10<sup>8</sup></code>th Fibonacci number is <strong>way</strong> larger than the largest number representable by an <code>unsigned long long</code>. But, that&#8217;s not our concern here. We want to analyze the running time of the algorithms. And, theoretically, if we had a better data type (an Integer class you can build on your own) then the algorithms will work.</p>
<h4>2.3. An even faster solution</h4>
<p>You might have thought <code>fib3()</code> is the best one can hope for, and you would be very wrong. The best we can hope for is <strong>exponentially faster</strong> than <code>fib3()</code>, and thus <strong>double-exponentially faster</strong> than <code>fib1()</code>. Now the title &#8220;the worst algorithm in the history of humanity&#8221; doesn&#8217;t sound far-fetch, does it? To design a good algorithm,we will often need to know a fair bit of mathematics, and use our knowledge in a smart way. Knowing math alone is not going to make it. Mathematicians, even the very best ones, aren&#8217;t necessarily good algorithm designers.</p>
<p>Here&#8217;s the key to the new algorithm:</p>
<img src='http://s.wordpress.com/latex.php?latex=%5Cbegin%7Bbmatrix%7D%201%20%26%201%5C%5C%201%20%26%200%5Cend%7Bbmatrix%7D%20%5Cbegin%7Bbmatrix%7D%201%20%5C%5C%200%5Cend%7Bbmatrix%7D%20%3D%20%5Cbegin%7Bbmatrix%7D%201%20%5C%5C%201%5Cend%7Bbmatrix%7D%2C%20%5Cbegin%7Bbmatrix%7D%201%20%26%201%5C%5C%201%20%26%200%5Cend%7Bbmatrix%7D%20%5Cbegin%7Bbmatrix%7D%201%20%5C%5C%201%5Cend%7Bbmatrix%7D%20%3D%20%5Cbegin%7Bbmatrix%7D%202%20%5C%5C%201%5Cend%7Bbmatrix%7D%2C%20%5Cbegin%7Bbmatrix%7D%201%20%26%201%5C%5C%201%20%26%200%5Cend%7Bbmatrix%7D%20%5Cbegin%7Bbmatrix%7D%202%20%5C%5C%201%5Cend%7Bbmatrix%7D%20%3D%20%5Cbegin%7Bbmatrix%7D%203%20%5C%5C%202%5Cend%7Bbmatrix%7D&#038;bg=ffffff&#038;fg=000000&#038;s=0' alt='\begin{bmatrix} 1 &amp; 1\\ 1 &amp; 0\end{bmatrix} \begin{bmatrix} 1 \\ 0\end{bmatrix} = \begin{bmatrix} 1 \\ 1\end{bmatrix}, \begin{bmatrix} 1 &amp; 1\\ 1 &amp; 0\end{bmatrix} \begin{bmatrix} 1 \\ 1\end{bmatrix} = \begin{bmatrix} 2 \\ 1\end{bmatrix}, \begin{bmatrix} 1 &amp; 1\\ 1 &amp; 0\end{bmatrix} \begin{bmatrix} 2 \\ 1\end{bmatrix} = \begin{bmatrix} 3 \\ 2\end{bmatrix}' title='\begin{bmatrix} 1 &amp; 1\\ 1 &amp; 0\end{bmatrix} \begin{bmatrix} 1 \\ 0\end{bmatrix} = \begin{bmatrix} 1 \\ 1\end{bmatrix}, \begin{bmatrix} 1 &amp; 1\\ 1 &amp; 0\end{bmatrix} \begin{bmatrix} 1 \\ 1\end{bmatrix} = \begin{bmatrix} 2 \\ 1\end{bmatrix}, \begin{bmatrix} 1 &amp; 1\\ 1 &amp; 0\end{bmatrix} \begin{bmatrix} 2 \\ 1\end{bmatrix} = \begin{bmatrix} 3 \\ 2\end{bmatrix}' class='latex' />
<p>and, in general,</p>
<img src='http://s.wordpress.com/latex.php?latex=%5Cbegin%7Bbmatrix%7D%201%20%26%201%5C%5C%201%20%26%200%5Cend%7Bbmatrix%7D%20%5Cbegin%7Bbmatrix%7D%20F%5Bn-1%5D%20%5C%5C%20F%5Bn-2%5D%5Cend%7Bbmatrix%7D%20%3D%20%5Cbegin%7Bbmatrix%7D%20F%5Bn%5D%20%5C%5C%20F%5Bn-1%5D%5Cend%7Bbmatrix%7D&#038;bg=ffffff&#038;fg=000000&#038;s=0' alt='\begin{bmatrix} 1 &amp; 1\\ 1 &amp; 0\end{bmatrix} \begin{bmatrix} F[n-1] \\ F[n-2]\end{bmatrix} = \begin{bmatrix} F[n] \\ F[n-1]\end{bmatrix}' title='\begin{bmatrix} 1 &amp; 1\\ 1 &amp; 0\end{bmatrix} \begin{bmatrix} F[n-1] \\ F[n-2]\end{bmatrix} = \begin{bmatrix} F[n] \\ F[n-1]\end{bmatrix}' class='latex' />
<p>Thus, we have</p>
<img src='http://s.wordpress.com/latex.php?latex=%5Cbegin%7Bbmatrix%7D%20F%5Bn%2B1%5D%20%5C%5C%20F%5Bn%5D%5Cend%7Bbmatrix%7D%20%3D%20%5Cbegin%7Bbmatrix%7D%201%20%26%201%5C%5C%201%20%26%200%5Cend%7Bbmatrix%7D%5En%20%5Cbegin%7Bbmatrix%7D%201%20%5C%5C%200%5Cend%7Bbmatrix%7D&#038;bg=ffffff&#038;fg=000000&#038;s=0' alt='\begin{bmatrix} F[n+1] \\ F[n]\end{bmatrix} = \begin{bmatrix} 1 &amp; 1\\ 1 &amp; 0\end{bmatrix}^n \begin{bmatrix} 1 \\ 0\end{bmatrix}' title='\begin{bmatrix} F[n+1] \\ F[n]\end{bmatrix} = \begin{bmatrix} 1 &amp; 1\\ 1 &amp; 0\end{bmatrix}^n \begin{bmatrix} 1 \\ 0\end{bmatrix}' class='latex' />
<p>So, we can compute both <code>F[n+1], F[n]</code> by computing a matrix power <img src='http://s.wordpress.com/latex.php?latex=%5Cbegin%7Bbmatrix%7D%201%20%26%201%5C%5C%201%20%26%200%5Cend%7Bbmatrix%7D%5En&#038;bg=ffffff&#038;fg=000000&#038;s=0' alt='\begin{bmatrix} 1 &amp; 1\\ 1 &amp; 0\end{bmatrix}^n' title='\begin{bmatrix} 1 &amp; 1\\ 1 &amp; 0\end{bmatrix}^n' class='latex' /> and multiply the result with the vector <img src='http://s.wordpress.com/latex.php?latex=%5Cbegin%7Bbmatrix%7D%201%20%5C%5C%200%20%5Cend%7Bbmatrix%7D&#038;bg=ffffff&#038;fg=000000&#038;s=0' alt='\begin{bmatrix} 1 \\ 0 \end{bmatrix}' title='\begin{bmatrix} 1 \\ 0 \end{bmatrix}' class='latex' />. How do we compute the <code>n</code>th power of a matrix quickly? </p>
<h6>The repeated squaring trick</h6>
<p>In fact, how do we even compute the <code>n</code>th power of an integer quickly, say <code>3<sup>n</sup></code>. Here is a straightforward implementation:</p>
<pre class="brush: cpp; title: ; notranslate">
const int base=3;
/**
 * -----------------------------------------------------------------------
 *  a straightforward algorithm for computing base^n
 * -----------------------------------------------------------------------
 */
unsigned long long power1(unsigned long n) {
    unsigned long i;
    unsigned long long ret=1;
    for (unsigned long i=0; i&lt;n; i++)
        ret *= base;
    return ret;
}
</pre>
<p>The above algorithm runs in time <code>O(n)</code> because it takes <code>n</code> steps and each step takes about a constant amount of time. (See pitfall below.). If you try to run the above for <code>n = 10<sup>10</sup></code> it takes about 44 seconds. It turns out we can do much better! The idea is not to multiply one <code>base</code> at a time. For example, if we want to compute <code>2<sup>16</sup></code>, we can compute <code>2<sup>8</sup></code> somehow and then square the result instead of multiplying the result with <code>2</code> 8 times. To compute <code>2<sup>8</sup></code>, we square <code>2<sup>4</sup></code> and so on. The following algorithm should be self-explanatory:</p>
<pre class="brush: cpp; title: ; notranslate">
const int base=3;

/**
 * -----------------------------------------------------------------------
 *  a recursive algorithm using the repeated squaring trick
 * -----------------------------------------------------------------------
 */
unsigned long long power2(unsigned long n) {
    unsigned long long ret;
    if (n == 0) return 1;
    if (n % 2 == 0) {
        ret = power2(n/2);
        return ret * ret;
    } else {
        ret = power2((n-1)/2);
        return base * ret * ret;
    }
}
</pre>
<p>The above algorithm on <code>n = 10<sup>19</sup></code> takes &#8230; less than 1 second. I could not test for <code>n = 10<sup>20</sup></code> because that would be larger than the largest <code>unsigned long</code> (64-bit ingeger). This is really remarkable! The <code>power1</code> algorithm runs in linear time, obviously. The <code>power2</code> algorithm is not that easy to analyze. </p>
<p>Let <code>T[n]</code> denote the run-time of algorithm <code>power2</code> on input <code>n</code>. When <code>n</code> is even, let&#8217;s say the time it takes is some constant <code>c</code> plus the time is take to compute <code>power2(n/2)</code>. Similarly, when <code>n</code> is odd, let&#8217;s say it takes about <code>d</code> units of time (should be miliseconds) plus <code>T[(n-1)/2]</code>. Also, suppose <code>T[0] == e</code>. So we have the following recurrence relation:</p>
<pre>
T[0] = e
T[n] = c + T[n/2]     // if n is even
T[n] = d + T[(n-1)/2] // if n is odd
</pre>
<p>We will apply the guess and induct strategy. When <code>n</code> is even, the binary representation of <code>n/2</code> is the binary representation of <code>n</code> shifted to the right 1 bit. When <code>n</code> is odd, the binary representation of <code>(n-1)/2</code> is also the 1-bit shift of the binary representation of <code>n</code>. Hence, each bit 1 in the binary representation of <code>n</code> accounts for d units and each bit 0 accounts for c units. Overall, it follows that </p>
<p><code>T[n] = O(# bits in the binary representation of n) = O(log n)</code>.</p>
<h6>Back to the Fibonacci number problem</h6>
<p>Well, we will use exactly the same idea. We can now compute <code>F[10<sup>19</sup>]</code> in under 1 second! Again, I didn&#8217;t try larger numbers because we run out of space for <code>unsigned long</code>. Here&#8217;s the full code listing for all <code>fibi</code> routines:</p>
<pre class="brush: cpp; title: ; notranslate">
// fib.cpp: several routines to compute the n'th Fibonacci number
// usage: fib i n, where i is from 1 to 4, which picks an algorithm
//        and n is the order of the Fibonacci number we want to compute
#include &lt;iostream&gt;
#include &lt;sstream&gt;
#include &lt;vector&gt;
using namespace std;

struct Matrix { // a 2x2 matrix
    unsigned long long a11;
    unsigned long long a12;
    unsigned long long a21;
    unsigned long long a22;
};

const Matrix base = {1, 1, 1, 0};

unsigned long long fib1(unsigned long); // straightforward implementation
unsigned long long fib2(unsigned long);
unsigned long long fib3(unsigned long);
unsigned long long fib4(unsigned long); // repeated squaring

int main(int argc, char* argv[]) {
    if (argc != 3) return 0;
    int algo = atoi(argv[1]);
    unsigned long long (*fib)(unsigned long); // pointer to a fibi
    switch (algo) {
        case 1: fib = &amp;fib1; break;
        case 2: fib = &amp;fib2; break;
        case 3: fib = &amp;fib3; break;
        case 4: fib = &amp;fib4; break;
        default: return 0;
    }
    stringstream ss(argv[2]);
    unsigned long n;
    ss &gt;&gt; n;
    cout &lt;&lt; &quot;Fib[&quot; &lt;&lt; n &lt;&lt; &quot;] = &quot; &lt;&lt; fib(n) &lt;&lt; endl;
    return 0;
}

/**
 * -----------------------------------------------------------------------
 *  the most straightforward algorithm of Fibonacci comp. algo.
 * -----------------------------------------------------------------------
 */
unsigned long long fib1(unsigned long n) {
    if (n &lt;= 1) return n;
    return fib1(n-1) + fib1(n-2);
}

/**
 * -----------------------------------------------------------------------
 *  a better algorithm with a linear time/space
 * -----------------------------------------------------------------------
 */
unsigned long long fib2(unsigned long n) {
    /*
    // this is one implementation option
    if (n &lt;= 1) return n;
    vector&lt;unsigned long long&gt; A;
    A.push_back(0); A.push_back(1);
    for (unsigned long i=2; i&lt;=n; i++) {
        A.push_back(A[i-1]+A[i-2]);
    }
    return A[n];
    */

    // this one should be faster
    if (n &lt;= 1) return n;
    unsigned long long* A = new unsigned long long[n];
    A[0] = 0; A[1] = 1;
    for (unsigned long i=2; i&lt;=n; i++) {
        A[i] = A[i-1]+A[i-2];
    }
    unsigned long long ret = A[n];
    delete [] A;
    return ret;
}

/**
 * -----------------------------------------------------------------------
 *  a better algorithm with a linear time, constant space
 * -----------------------------------------------------------------------
 */
unsigned long long fib3(unsigned long n) {
    if (n &lt;= 1) return n;
    unsigned long long a=0, b=1, temp;
    unsigned long i;
    for (unsigned long i=2; i&lt;= n; i++) {
        temp = a + b; // F[i] = F[i-2] + F[i-1]
        a = b;        // a = F[i-1]
        b = temp;     // b = F[i]
    }
    return temp;
}

/**
 * -----------------------------------------------------------------------
 *  multiply two matrices
 * -----------------------------------------------------------------------
 */
Matrix matrix_mult(Matrix x, Matrix y) {
    Matrix temp;
    temp.a11 = x.a11*y.a11 + x.a12*y.a21;
    temp.a12 = x.a11*y.a12 + x.a12*y.a22;
    temp.a21 = x.a21*y.a11 + x.a22*y.a21;
    temp.a22 = x.a21*y.a12 + x.a22*y.a22;
    return temp;
}

/**
 * -----------------------------------------------------------------------
 *  compute base^n using repeated squaring
 * -----------------------------------------------------------------------
 */
Matrix matrix_power(unsigned long n) {
    Matrix unit = {1, 0, 0, 1};
    Matrix temp;
    if (n == 0) return unit;
    if (n%2 == 0) {
        temp = matrix_power(n/2);
        return matrix_mult(temp, temp);
    } else {
        temp = matrix_power((n-1)/2);
        return matrix_mult(temp, matrix_mult(temp, base));
    }
}

/**
 * -----------------------------------------------------------------------
 *  a better implementation with a logarithmic time, constant space
 * -----------------------------------------------------------------------
 */
unsigned long long fib4(unsigned long n) {
    if (n &lt;= 1) return n;
    Matrix mat = matrix_power(n-1);
    return mat.a11;
}
</pre>
<h3>3. A pitfall in our assumption</h3>
<p>We have assumed that multiplying two numbers take constant time. This is generally true if those numbers are small enough to store in some fixed number of bytes. This assumption is no longer true when we want to compute the <code>10<sup>19</sup></code>th Fibonacchi number, which requires at least <img src='http://s.wordpress.com/latex.php?latex=%5Clog%28F%5B10%5E%7B19%7D%5D%29&#038;bg=ffffff&#038;fg=000000&#038;s=0' alt='\log(F[10^{19}])' title='\log(F[10^{19}])' class='latex' /> bits to store. In general, since the Fibonacci numbers are in the order of <img src='http://s.wordpress.com/latex.php?latex=%5Cphi%5En&#038;bg=ffffff&#038;fg=000000&#038;s=0' alt='\phi^n' title='\phi^n' class='latex' />, the number <code>F[n]</code> requires <img src='http://s.wordpress.com/latex.php?latex=%5COmega%28n%29&#038;bg=ffffff&#038;fg=000000&#038;s=0' alt='\Omega(n)' title='\Omega(n)' class='latex' /> bits to store. And thus, adding <code>F[n]</code> and <code>F[n-1]</code> will take time <code>O(n)</code>. Let&#8217;s not delve into this level of details. The hand-wavy analysis above should give you a good idea of the type of asymptotic analysis to come in this course. We will generally not work with numbers that large, so the assumption that arithmetic operations can be done in constant time is a good one for the rest of the semester.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.procul.org/blog/2012/02/07/thu%e1%ba%adt-toan-t%e1%bb%93i-t%e1%bb%87-nh%e1%ba%a5t-trong-l%e1%bb%8bch-s%e1%bb%ad-nhan-lo%e1%ba%a1i/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>Thuật toán bẻ ghi</title>
		<link>http://www.procul.org/blog/2012/02/05/thu%e1%ba%adt-toan-b%e1%ba%bb-ghi/</link>
		<comments>http://www.procul.org/blog/2012/02/05/thu%e1%ba%adt-toan-b%e1%ba%bb-ghi/#comments</comments>
		<pubDate>Sun, 05 Feb 2012 14:27:25 +0000</pubDate>
		<dc:creator>NQH</dc:creator>
				<category><![CDATA[Thuật Toán]]></category>
		<category><![CDATA[Cấu trúc dữ liệu]]></category>
		<category><![CDATA[Dijkstra]]></category>
		<category><![CDATA[stack]]></category>
		<category><![CDATA[thuật toán]]></category>
		<category><![CDATA[Thuật toán bẻ ghi]]></category>
		<category><![CDATA[Đố]]></category>

		<guid isPermaLink="false">http://www.procul.org/blog/?p=4237</guid>
		<description><![CDATA[Loài người viết các biểu thức toán học dùng ký hiệu trung tố (infix notation) đã vài nghìn năm: [4+3*(2+7)/3-2]*3. Ký hiệu trung tố có bất lợi lớn là cần nhiều dấu ngoặc để phân biệt các biểu thức khác nhau: (4+2)*3 &#8800; 4+2*3. Ký hiệu tiền tố, còn gọi là ký hiệu Ba Lan [...]]]></description>
			<content:encoded><![CDATA[<p>Loài người viết các biểu thức toán học dùng ký hiệu trung tố (<a href="http://en.wikipedia.org/wiki/Infix_notation">infix notation</a>) đã vài nghìn năm: <code>[4+3*(2+7)/3-2]*3</code>. Ký hiệu trung tố có bất lợi lớn là cần nhiều dấu ngoặc để phân biệt các biểu thức khác nhau: <code> (4+2)*3 &#8800; 4+2*3</code>. Ký hiệu tiền tố, còn gọi là ký hiệu Ba Lan (<a href="http://en.wikipedia.org/wiki/Polish_notation">Polish notation</a>), giải phóng chúng ta khỏi gông xiềng nô lệ của các dấu ngoặc. Ký hiệu này do nhà logic học người Ba Lan <a href="http://en.wikipedia.org/wiki/Jan_Łukasiewicz">Jan Łukasiewicz</a> nghĩ ra khoảng năm 1924. Trong ký hiệu tiền tố thì ta luôn viết toán tử trước (hai) toán hạng. Ví dụ, biểu thức <code>[4+3*(2+7)/3-2]*3</code> dùng ký hiệu tiền tố có thể viết bằng <code>* - + 4 / * 3 + 2 7 3 2 3</code>. </p>
<p>Đến những năm 1950, 1960 thì người ta mới nghĩ đến ký hiệu hậu tố (<a href="http://en.wikipedia.org/wiki/Reverse_Polish_notation">postfix notation</a>), hay còn gọi là ký hiệu Ba Lan ngược. Ở ký hiệu hậu tố thì toán tử được viết sau (các) toán hạng. Dijkstra là một trong những người nghĩ ra và thiết kế thuật toán cho ký hiệu hậu tố đầu tiên. Lý do là máy tính thì hay đọc input từ trái sang phải, mà ký hiệu hậu tố làm cho việc định trị một biểu thức rất tiện lợi: ta định trị mỗi toán tử ngay sau khi toán tử được đọc từ input. Khi biết thời điểm ra đời của các ký hiệu tiền tố và hậu tố, tôi đã tương đối ngạc nhiên; trước đó cứ nghĩ là ký hiệu tiền tố và hậu tố phải ra đời sớm hơn như vậy.</p>
<p>Định trị một biểu thức viết bằng ký hiệu hậu tố là bài toán (lập trình) cơ bản nhất của lớp cấu trúc dữ liệu đầu tiên: ta đọc input từ trái sang phải, khi thấy toán hạng thì nhấn (push) vào chồng (<a href="http://en.wikipedia.org/wiki/Stack_(data_structure)">stack</a>), nếu thấy toán tử thì định trị toán từ dùng 2 toán hạng trên đỉnh chồng, bật (pop) hai toán hạng này ra và nhấn kết quả vào chồng. </p>
<p>Định trị một biểu thức viết bằng ký hiệu trung tố không đơn giản như vậy. Dijkstra thiết kế một thuật toán gọi là <a href="http://en.wikipedia.org/wiki/Shunting_yard_algorithm">thuật toán bẻ ghi</a> (shunting-yard algorithm), chuyển từ biểu thức trung tố thành biểu thức hậu tố. Tất nhiên, sau đó ta có thể định trị biểu thức hậu tố dùng thuật toán trên. Nếu ta chỉ cần định trị biểu thức trung tố thôi thì không cần phải chuyển trực tiếp thành hậu tố rồi mới định trị. Thuật toán bẻ ghi có thể định trị biểu thức trung tố dùng hai chồng: một <em>chồng toán tử</em> và môt <em>chồng toán hạng</em>, như sau. Ta quét input từ trái sang phải, đọc từng token một (đây là tác vụ cơ bản của một <a href="http://matt.might.net/articles/standalone-lexers-with-lex/">lexical analyzer</a>). Mỗi token là một toán hạng, toán tử, hoặc dấu ngoặc. Để đơn giản ta giả sử chỉ có một loại ngoặc <code>()</code>, và chỉ có các toán tử nhị phân <code>+-*/</code>. Cộng trừ có trật tự ưu tiên nhỏ hơn hẳn nhân chia.</p>
<p>Trước hết xét trường hợp không có dấu ngoặc. Gọi <code>tok</code> là input token đang xét. </p>
<ul>
<li> Nếu <code>tok</code> là một toán hạng thì ta nhấn nó vào chồng toán hạng.
<li> Nếu <code>tok</code> là một toán tử <code>op</code> thì
<ul>
<li> Trong khi vẫn còn toán tử  <code>op'</code> trên đỉnh chồng toán tử, với trật tự ưu tiên lớn hơn hoặc bằng <code>op</code> thì ta bật <code>op'</code> ra khỏi chồng toán tử, bật 2 toán hạng ra khỏi chồng toán hạng, tính kết quả của <code>op'</code> áp dụng vào 2 toán hạng, nhấn kết quả vào chồng toán hạng. Nếu không có đủ toán hạng thì ta có lỗi cú pháp.
<li> Nhấn <code>op</code> vào chồng toán tử.
   </ul>
</ul>
<p>Cuối cùng, khi đã hết input thì ta định trị tất cả các toán tử trong chồng toán tử, từ đỉnh chồng xuống. Nếu cuối cùng chỉ còn một toán hạng trong chồng toán hạng thì đó là kết quả. Nếu còn khác 1 toán hạng thì trả về lỗi cú pháp. </p>
<p>Trong trường hợp có dấu ngoặc thì ta xem dấu ngoặc là một toán tử. Khi thấy dấu mở ngoặc thì nhấn vào chồng toán tử. Khi thấy dấu đóng ngoặc thì định trị toàn bộ các toán tử trên chồng toán tử cho đến dấu mở ngoặc và ấn kết quả vào chồng toán hạng. Nói chung là thay <code>(expression)</code> bằng một toán hạng trong chồng toán hạng. Nếu không thấy dấu mở ngoặc tương ứng thì ta có lỗi cú pháp. </p>
<p>Thuật toán bẻ ghi thật là đơn giản, dễ thương, cơ bản, và cực kỳ hữu dụng. Không may là nó không phát hiện được mọi loại lỗi cú pháp. Ví dụ: đưa cho thuật toán trên biểu thức <code>(3 4 +)*2</code> nó sẽ trả về <code>14</code>. </p>
<p><strong>Hỏi:</strong> sửa thuật toán như thể nào để nó phát hiện được loại lỗi cú pháp trên? Để đơn giản, ta vẫn giả sử chỉ có các toán tử nhị phân <code>+-*/</code>. </p>
]]></content:encoded>
			<wfw:commentRss>http://www.procul.org/blog/2012/02/05/thu%e1%ba%adt-toan-b%e1%ba%bb-ghi/feed/</wfw:commentRss>
		<slash:comments>3</slash:comments>
		</item>
		<item>
		<title>Polytime và polydata</title>
		<link>http://www.procul.org/blog/2011/10/25/polytime-va-polydata/</link>
		<comments>http://www.procul.org/blog/2011/10/25/polytime-va-polydata/#comments</comments>
		<pubDate>Tue, 25 Oct 2011 16:51:54 +0000</pubDate>
		<dc:creator>Nguyễn Xuân Long</dc:creator>
				<category><![CDATA[Lý thuyết tính toán]]></category>
		<category><![CDATA[Lý thuyết thông tin]]></category>
		<category><![CDATA[Thuật Toán]]></category>
		<category><![CDATA[Toán Ứng Dụng]]></category>
		<category><![CDATA[Xác suất & thống kê]]></category>
		<category><![CDATA[học máy]]></category>
		<category><![CDATA[lỗi xấp xỉ]]></category>
		<category><![CDATA[lỗi ước lượng]]></category>
		<category><![CDATA[thống kê]]></category>
		<category><![CDATA[xác suất]]></category>

		<guid isPermaLink="false">http://www.procul.org/blog/?p=3929</guid>
		<description><![CDATA[Mấy hôm nay đọc một số bài viết về việc học mô hình hỗn hợp (mixture models). Đây là lĩnh vực kinh điển trong thống kê, nhưng vẫn tiếp tục là một lĩnh vực mở đang được quan tâm trong thống kê, học máy cũng như thuật toán. [Tôi cũng vừa upload bài mới trên [...]]]></description>
			<content:encoded><![CDATA[<p>Mấy hôm nay đọc một số bài viết về việc học mô hình hỗn hợp (mixture models). Đây là lĩnh vực kinh điển trong thống kê, nhưng vẫn tiếp tục là một lĩnh vực mở đang được quan tâm trong thống kê, học máy cũng như thuật toán. [Tôi cũng vừa upload <a href= "http://arxiv.org/abs/1109.3250"> bài mới trên arxiv</a> về lĩnh vực này.]</p>
<p>Có một số khác biệt thú vị về tiêu chuẩn học/ước lượng bởi các cộng đồng khác nhau. Dân khmt đặc biệt là về thuật toán thì quan tâm đến làm sao tìm ra được thuật toán chạy có thời gian đa thức, và cần số lượng mẫu <img src='http://s.wordpress.com/latex.php?latex=%20n%20&#038;bg=ffffff&#038;fg=000000&#038;s=0' alt=' n ' title=' n ' class='latex' /> cũng là đa thức. Đa thức đối với kích cỡ của đầu vào của bài toán (ở đây sẽ là chiều của mô hình = số lượng tham số), và đa thức đối với <img src='http://s.wordpress.com/latex.php?latex=%201%2F%5Cepsilon%20&#038;bg=ffffff&#038;fg=000000&#038;s=0' alt=' 1/\epsilon ' title=' 1/\epsilon ' class='latex' />. Epsilon ở đây là lỗi ước lượng cho phép. Ví dụ như <a href= "http://research.microsoft.com/en-us/um/people/adum/publications/2010-Efficiently_Learning_Mixtures_of_Two_Gaussians.pdf"> bài báo này </a>, <a href="http://projecteuclid.org/DPubS?service=UI&#038;version=1.0&#038;verb=Display&#038;handle=euclid.aoap/1106922321"> bài này </a>.</p>
<p>Tôi không hiểu tại sao <img src='http://s.wordpress.com/latex.php?latex=%201%2F%5Cepsilon%20&#038;bg=ffffff&#038;fg=000000&#038;s=0' alt=' 1/\epsilon ' title=' 1/\epsilon ' class='latex' /> và số lượng tham số lại &#8220;nghiễm nhiên&#8221; để ngang hàng nhau trong việc phân tích asymptotic. Nếu giới hạn cho polynomial asymptotic một cách chung chung thì chắc không sao &#8212; có nhiều ví dụ cho ta thấy điều đó. Phân biệt giữa độ phức tạp exponential và phức tạp polynomial là vấn đề truyền thống trong KHMT. Điều này cũng có nghĩa là trong khmt sự phân biệt giữa các tốc độ đa thức còn chưa được chú ý nhiều (<img src='http://s.wordpress.com/latex.php?latex=%20%281%2F%5Cepsilon%29%5E2%20&#038;bg=ffffff&#038;fg=000000&#038;s=0' alt=' (1/\epsilon)^2 ' title=' (1/\epsilon)^2 ' class='latex' /> vs. <img src='http://s.wordpress.com/latex.php?latex=%20%281%2F%5Cepsilon%29%5E3&#038;bg=ffffff&#038;fg=000000&#038;s=0' alt=' (1/\epsilon)^3' title=' (1/\epsilon)^3' class='latex' /> chẳng hạn.) Kỳ thực so với thống kê thì các kết quả về phức tạp mẫu bên computational learning theory còn khá đơn giản, vì họ chưa chú trọng đến độ đa thức. </p>
<p>Trong thống kê hay học máy thống kê thì quan tâm chính là độ hội tụ của ước lượng (convergence of estimation error) đối với số mẫu. <img src='http://s.wordpress.com/latex.php?latex=%20%5Cepsilon%20%3D%20O%28n%5E%7B-1%2F2%7D%29%20&#038;bg=ffffff&#038;fg=000000&#038;s=0' alt=' \epsilon = O(n^{-1/2}) ' title=' \epsilon = O(n^{-1/2}) ' class='latex' /> là một tốc độ kinh điển trong thống kê tham số hữu hạn chiều (khi đó, ta cũng có <img src='http://s.wordpress.com/latex.php?latex=%20n%20%3D%20%281%2F%5Cepsilon%29%5E%7B2%7D%20&#038;bg=ffffff&#038;fg=000000&#038;s=0' alt=' n = (1/\epsilon)^{2} ' title=' n = (1/\epsilon)^{2} ' class='latex' />). Trong các vấn đề ước lượng khác thì tốc độ hội tụ có thể rất khác, <img src='http://s.wordpress.com/latex.php?latex=%20n%5E%7B-%5Cdelta%7D%20&#038;bg=ffffff&#038;fg=000000&#038;s=0' alt=' n^{-\delta} ' title=' n^{-\delta} ' class='latex' /> hoặc rất chậm theo tốc độ logarithm, <img src='http://s.wordpress.com/latex.php?latex=%5Blog%281%2Fn%29%5D%5E%7B%5Cdelta%7D%20&#038;bg=ffffff&#038;fg=000000&#038;s=0' alt='[log(1/n)]^{\delta} ' title='[log(1/n)]^{\delta} ' class='latex' />, chẳng hạn. Lý thuyết minimax, nếu có, cho ta biết giá trị tốt nhất có thể của delta đạt được bởi một thuật toán tốt nhất có thể được. </p>
<p>Tuy nhiên bên thống kê chưa quan tâm đến sự phức tạp về thời gian của thuật toán. Có thể nói các kết quả về sự phức tạp của thuật toán ở bên thống kê hầu như không tồn tại.</p>
<p>Có thể tiếp cận vấn đề này theo hai hướng.</p>
<p>Một là tạm thời tách rời hai sự phức tạp khác nhau này ra. Thực ra cái này đã được làm ở một chừng mực nào đó: Đây chính là sự tách rời giữa lỗi xấp xỉ (approximation error) và lỗi ước lượng thống kê (estimation error). Thông thường lỗi xấp xỉ cũng cho ta biết tốc độ tốt nhất của một giải thuật tính xấp xỉ một đại lượng nào đó (không ngẫu nhiên), tuy điều này không phải luôn rõ ràng. Dân thống kê mới chỉ chú tâm đến cái thứ hai (lỗi ước lượng thống kê). Còn phân tích về lỗi xấp xỉ thì là địa hạt của những người nghiên cứu về lý thuyết xấp xỉ. Chỉ việc bê kết quả bên lý thuyết xấp xỉ sang áp dụng là xong. </p>
<p>Hướng này hay ở chỗ nó tách ra hai khái niệm mà bản thân chúng đều không đơn giản. Nhưng nếu không có kết quả về xấp xỉ cho một lớp mô hình cần quan tâm thì chịu. Điều này không phải là hy hữu, vì sự chú ý và động cơ của các ngành có nhiều khác nhau. Ví dụ, đân làm xấp xỉ có vẻ không quan tâm nhiều đến các biến rời rạc hoặc các dạng hàm số định nghĩa cho tập hợp (như hàm phân bố xác suất).</p>
<p>Hướng thứ hai là không tách rời hai dạng lỗi kể trên. Qua đó ta có thể nghiên cứu về sự tương tác giữa phức tạp về mẫu và phức tạp về thuật toán. Đặc biệt, sự tương tác giữa các mức phức tạp đa thức của cả mẫu và thời gian thuật toán. Tôi nghĩ đây là một trong những vấn đề thú vị trong lý thuyết học máy hiện nay. </p>
]]></content:encoded>
			<wfw:commentRss>http://www.procul.org/blog/2011/10/25/polytime-va-polydata/feed/</wfw:commentRss>
		<slash:comments>7</slash:comments>
		</item>
		<item>
		<title>PM 3: Thuật toán Boyer-Moore</title>
		<link>http://www.procul.org/blog/2011/08/31/pm-3-thu%e1%ba%adt-toan-boyer-moore/</link>
		<comments>http://www.procul.org/blog/2011/08/31/pm-3-thu%e1%ba%adt-toan-boyer-moore/#comments</comments>
		<pubDate>Thu, 01 Sep 2011 02:04:47 +0000</pubDate>
		<dc:creator>NQH</dc:creator>
				<category><![CDATA[Lập trình]]></category>
		<category><![CDATA[Thuật Toán]]></category>
		<category><![CDATA[Boyer-Moore]]></category>
		<category><![CDATA[Pattern matching]]></category>
		<category><![CDATA[String matching]]></category>
		<category><![CDATA[thuật toán]]></category>

		<guid isPermaLink="false">http://www.procul.org/blog/?p=3615</guid>
		<description><![CDATA[PM 2: Pattern matching bằng automaton đơn định PM 4: Thuật Toán Apostolico-Crochemore Thuật toán KMP được thiết kế khoảng 1969-1970, mặc dù bài báo KMP phải đến 1977 mới ra bản in. Năm 1974, Robert S. Moyer và J. Strother Moore (và độc lập với họ, cả R. W. Gosper) đã đề xuất một [...]]]></description>
			<content:encoded><![CDATA[<ul>
<li>PM 2: <a href="http://www.procul.org/blog/2011/08/26/pm-2-pattern-matching-b%e1%ba%b1ng-automaton-d%c6%a1n-d%e1%bb%8bnh/">Pattern matching bằng automaton đơn định</a>
<li>PM 4: Thuật Toán Apostolico-Crochemore
</ul>
<p><a href="http://www.procul.org/blog/2011/04/02/pm-1-thu%E1%BA%ADt-toan-knuth-morris-pratt/">Thuật toán KMP</a> được thiết kế khoảng 1969-1970, mặc dù <a href="http://www.procul.org/blog/wp-content/uploads/2011/04/KMP77.pdf">bài báo KMP</a> phải đến 1977 mới ra bản in. Năm 1974, Robert S. Moyer và <a href="http://www.cs.utexas.edu/~moore/">J. Strother Moore</a> (và độc lập với họ, cả R. W. Gosper) đã đề xuất một ý tưởng có khả năng cải tiến thời gian chạy của thuật toán. Ta vẫn trượt pattern từ trái sang phải, nhưng với mỗi vị trí của pattern thì ta quét các ký tự trong pattern từ ký tự cuối ngược về ký tự đầu. Nếu ký tự cuối đã không khớp thì ta có thể quyết định trượt ngay lập tức, đôi khi một khoảng xa. Hiện nay các biến thể của <a href="http://www.cs.utexas.edu/users/moore/publications/fstrpos.pdf">thuật toán Boyer-Moore</a> (mà lẽ ra cũng có thể gọi là thuật toán Boyer-Moore-Gosper) rất phổ dụng trong thực tế, ví dụ <a href="http://lists.freebsd.org/pipermail/freebsd-current/2010-August/019310.html">nó được dùng</a> trong chương trình <code>grep</code> của GNU. Hoặc <a href="http://www.gnu.org/s/hello/manual/libc/Regular-Expressions.html">thư viện <code>regexp</code></a> duyệt các biểu thức chính quy của GNU <a href="http://lists.freebsd.org/pipermail/freebsd-current/2010-August/019348.html">cũng dùng</a> ý tưởng này.</p>
<p><span id="more-3615"></span></p>
<p><strong>1. Ý tưởng chính</strong></p>
<p>Giả sử ta đang khớp mẫu <code>p</code> ở vị trí <code>j</code> của chuỗi nhập <code>s</code>. Ta sẽ bắt đầu ngược bằng cách so sánh <code>p[i]</code> với <code>s[j+i]</code> với <code>i</code> đi từ <code>m-1</code> xuống <code>0</code>. Giả sử đến một giá trị <code>i</code> nào đó ta gặp hai ký tự khác nhau như hình dưới đây.</p>
<p><a href="http://www.procul.org/blog/wp-content/uploads/2011/08/bm_sp.png"><img class="aligncenter size-full wp-image-3624" title="bm_sp" src="http://www.procul.org/blog/wp-content/uploads/2011/08/bm_sp.png" alt="" width="350" height="72" /></a></p>
<p>Đoạn màu tím là giống nhau.</p>
<p><strong>1.1. Luật matching shift</strong></p>
<p>Bây giờ ta sẽ phải dịch chuỗi <code>p</code> đi một đoạn. Có hai trường hợp.</p>
<ul>
<li>Thứ nhất, nếu đoạn màu tím cũng xuất hiện ở một chỗ khác trong <code>p</code> thì ta dời <code>p</code> về bên phải một đoạn <em>ngắn nhất</em> cho đến một tái xuất hiện của đoạn màu tím.
<div id="attachment_3627" class="wp-caption aligncenter" style="width: 360px"><a href="http://www.procul.org/blog/wp-content/uploads/2011/08/ms.png"><img class="size-full wp-image-3627" title="ms" src="http://www.procul.org/blog/wp-content/uploads/2011/08/ms.png" alt="" width="350" height="105" /></a><p class="wp-caption-text">Hình 1: Luật matching shift.</p></div>
<p>Giả sử ta dịch <code>p</code> đi một đoạn có độ dài <code>k</code>. Nếu <code>p[i-k] == b</code> thì ta lại có một mis-match mới. Do đó, không những ta khớp với đoạn màu tím, mà còn phải tìm cách khớp sao cho ký tự bên trái đoạn màu tím mới <em>khác</em> với ký tự bên trái của đoạn màu tím cũ. Ý tưởng này giống như cải tiến của thuật toán KMP từ thuật toán MP. Boyer và Moore nói rằng cái mẹo này là do <a href="http://eecs.umich.edu/~kuipers/">Ben Kuipers</a> gợi ý cho họ.</p>
<p>Tóm lại, nếu tồn tại trong <code>p</code> một đoạn màu tím mà ký tự bên trái của nó khác với ký tự bên trái của đoạn màu tím hậu tố, thì ta dời <code>p</code> đi một khoảng cách ngắn nhất để khớp đến một đoạn màu tím như vậy. Dĩ nhiên, khoảng cách di dời phải là dương.</li>
<li>Thứ hai, giả sử không tồn tại đoạn màu tím thoả mãn điều kiện trên. Trong trường hợp này ta phải dời <code>p</code> đi xa hơn nữa, cho đến khi một tiền tố dài nhất của <code>p</code> trùng với một hậu tố của đoạn màu tím. (Tiền tố dài nhất để cho độ xê dịch ngắn nhất có thể có.)
<p><div id="attachment_3628" class="wp-caption aligncenter" style="width: 360px"><a href="http://www.procul.org/blog/wp-content/uploads/2011/08/ms2.png"><img class="size-full wp-image-3628" title="ms2" src="http://www.procul.org/blog/wp-content/uploads/2011/08/ms2.png" alt="" width="350" height="116" /></a><p class="wp-caption-text">Hình 2: Luật matching shift</p></div></li>
</ul>
<p>Luật &#8220;trượt&#8221; <code>p</code> như trên gọi là luật &#8220;matching shift&#8221;. Để hiện thực nó ta phải tính một dãy matching shift <code>ms[0..m-1]</code> định nghĩa như sau:</p>
<p><code>ms[i] = min{ k &gt; 0 such that<br />
( (k &le; i) and (p[i-k+1..m-k-1] == p[i+1..m-1]) and (p[i-k] != p[i]) )<br />
OR<br />
( (m-1 &ge; k &gt; i) and (p[0..m-1-k] == p[k..m-1]) )<br />
OR<br />
( k == m )}<br />
</code></p>
<p><strong>1.2. Luật occurrence shift</strong></p>
<p>Một quan sát tương đối hiển nhiên nữa là ta phải dịch <code>p</code> đến vị trí nào đó sao cho ký tự bên trái đoạn màu tím <em>giống</em> như ký tự bên trái đoạn màu tím của <code>s</code>.</p>
<div id="attachment_3635" class="wp-caption aligncenter" style="width: 360px"><a href="http://www.procul.org/blog/wp-content/uploads/2011/08/os.png"><img class="size-full wp-image-3635" title="os" src="http://www.procul.org/blog/wp-content/uploads/2011/08/os.png" alt="" width="350" height="105" /></a><p class="wp-caption-text">Hình 3: Luật occurence shift</p></div>
<p>Ta gọi luật này là luật occurrence shift. Để tính chiều dài phép dịch này, ta tính dãy <code>os[0..|Σ|-1]</code> gồm |Σ| phần tử, sao cho với mỗi ký tự <code>a</code> ta có <code>os[a] = max( {-1} U { x | p[x] == a })</code>. Cụ thể hơn, nếu tồn tại <code>0 ≤ x ≤ m-1</code> sao cho <code>p[x] == a</code> thì ta chọn <code>x</code> lớn nhất như vậy, và gán <code>os[a] = x</code>. Nếu không có ký tự <code>a</code> trong chuỗi <code>p</code> thì ta đặt <code>os[a] = -1</code></p>
<p>Giả sử ta đang quét <code>s</code> đến ký tự <code>a</code>, và ở vị trí <code>i</code> của mẫu <code>p</code>, thì theo luật occurrence shift ta nên dịch <code>p</code> đi một đại lượng bằng <code>i - os['a']</code></p>
<p><strong>1.3. Thuật toán Boyer-Moore</strong></p>
<p>Thuật toán BM nói rằng ta nên dịch <code>p</code> đi một khoảng cách bằng <code>max</code> của <code>i - os['a']</code> (do luật occurrence shift cho phép) và <code>ms[i]</code> (do luật matching shift cho phép). Giả sử ta đã viết hai thủ tục <code>occ_shift</code> và <code>matching_shift</code> để tính các chuỗi <code>os</code> và <code>ms</code> thì thuật toán Boyer-Moore có thể viết như sau:</p>
<pre class="brush: python; title: ; notranslate">
def bm(s, p): # the Boyer-Moore algorithm
    n = len(s); m = len(p)
    os = occ_shift(p)
    ms = matching_shift(p)
    j = 0
    while (j &lt;= n-m):
        i = m-1
        while (i &gt;= 0) and (p[i] == s[j+i]): i = i-1
        if (i == -1):
            print &quot;Match at position &quot;, j
            i = 0
        j = j + max(ms[i], i-os[s[i+j]])
</pre>
<p><strong>2. Tính các chuỗi <code>ms</code> và <code>os</code></strong></p>
<p>Cái occurrence shift dễ tính, nên ta viết nó trước:</p>
<pre class="brush: python; title: ; notranslate">
def occ_shift(p): # compute the occurrance shift array
    m  = len(p)
    atoz  = map(chr, range(97, 123)) # list from 'a' to 'z'
    os = dict.fromkeys(atoz, -1)     # 26 characters, all os['c'] = -1 at first
    for i in range(m):
        os[p[i]] = i
    return os
</pre>
<p>Cái matching shift thì làm sao? Ta quan sát luật này một chút. Trước hết, xét Hình 1. Do <code>p[i-k] != p[i]</code>, phần màu tím là <em>hậu tố chung dài nhất</em> của <code>p[0..m-1]</code> và <code>p[0..m-k-1]</code>. Nhớ rằng phần màu tím có chiều dài là <code>m-i-1</code>. Còn trong Hình 2 thì ta có <em>hậu tố chúng dài nhất</em> của <code>p[0..m-1]</code> và <code>p[0..m-k-1]</code> là bằng với <code>m-k</code>. Với mỗi số <code>0 &le; x &le; m-1</code>, gọi <code>lcs[x]</code> là hậu tố chung dài nhất (longest common suffix) của <code>p[0..x]</code> và <code>p[0..m-1]</code>. Từ quan sát trên, ta có thể viết lại định nghĩa của chuỗi <code>ms[0..m-1]</code> như sau.</p>
<p><code>ms[i] = min{ k &gt; 0 such that<br />
( (k &le; i) and (lcs[m-k-1] == m-i-1) )<br />
OR<br />
( (m-1 &ge; k &gt; i) and (lcs[m-k-1] == m-k)<br />
OR<br />
( k == m)}<br />
</code></p>
<p>Giả sử ta viết được một thủ tục <code>suffix(p)</code> trả về chuỗi <code>lcs</code>, thì ta có thể dùng nó để viết đoạn chương trình tính chuỗi <code>ms</code> tương đối dễ dàng trong thời gian tuyến tính.</p>
<pre class="brush: python; title: ; notranslate">
def matching_shift(p): # compute the matching shift array
    m = len(p)
    i = 0
    ms = [0]*m
    lcs = suffix(p)
    for k in range(1, m+1):
        if (k == m) or (lcs[m-1-k] == m-k):
            while (i &lt; k):
                ms[i] = k
                i = i+1
    for k in range(m-1, 0, -1):
        ms[m-1-lcs[m-1-k]] = k
    return ms
</pre>
<p>Cuối cùng, thủ tục <code>suffix()</code> cũng hiện thực được trong thời gian tuyến tính như sau. Đây là một bài tập quy hoạch động rất tốt, tôi sẽ không giải thích thêm.</p>
<pre class="brush: python; title: ; notranslate">
def suffix(p): # compute the lcs array
    m = len(p);
    lcs = [0]*m
    lcs[m-1] = m
    l = r = m-1

    for i in range(m-2, -1, -1): # i from m-2 downto 0
        if ((l &lt; i) and (lcs[i+m-1-r] &lt; i-l)):
            lcs[i] = lcs[i+m-1-r]
        else:
            l = min(l, i)
            r = i
            while ((l &gt;=0) and (p[l] == p[l+m-1-i])): l = l-1
            lcs[i] = r-l

    return lcs
</pre>
<p><strong>3. Thời gian chạy</strong></p>
<p>Thời gian xây dựng các chuỗi <code>os, ms, lcs</code>, đều là tuyến tính. </p>
<p>Dễ thầy rằng, nếu <img src='http://s.wordpress.com/latex.php?latex=s%20%3D%20a%5En&#038;bg=ffffff&#038;fg=000000&#038;s=0' alt='s = a^n' title='s = a^n' class='latex' /> và <img src='http://s.wordpress.com/latex.php?latex=p%20%3D%20a%5Em&#038;bg=ffffff&#038;fg=000000&#038;s=0' alt='p = a^m' title='p = a^m' class='latex' /> với một ký tự <img src='http://s.wordpress.com/latex.php?latex=a&#038;bg=ffffff&#038;fg=000000&#038;s=0' alt='a' title='a' class='latex' /> nào đó thì thời gian chạy của Boyer-Moore là rất tồi: <img src='http://s.wordpress.com/latex.php?latex=O%28mn%29&#038;bg=ffffff&#038;fg=000000&#038;s=0' alt='O(mn)' title='O(mn)' class='latex' />. Nhưng trên thực tế nó thường chạy nhanh hơn KMP vì hai lý do. Về mặt lý thuyết thì Boyer-Moore đã chứng minh, với một phân bố input nhất định thì thời gian chạy kỳ vọng của thuật toán là nhỏ, cần ít lệnh hơn KMP. Về mặt thực tế, những biến thể của BM được dùng &#8220;công nghiệp&#8221; còn có thêm vài cái mẹo khác để giảm thời gian chạy. Có rất nhiều biến thể của Boyer-Moore, chạy nhanh hơn BM căn bản hoặc là trên lý thuyết hoặc là trên thực tế hoặc cả hai. Họ đều cố gắng cải tiến thời gian chạy xuống còn tuyến tính:</p>
<ul>
<li> APOSTOLICO A., GIANCARLO R., 1986, <a href="http://docs.lib.purdue.edu/cgi/viewcontent.cgi?article=1456&#038;context=cstech&#038;sei-redir=1#search=%22Boyer-Moore-Galil%20string%20searching%20strategies%20revisited%22">The Boyer-Moore-Galil string searching strategies revisited</a>, SIAM Journal on Computing 15(1):98-105.
<li> (Turbo BM) CROCHEMORE, M., CZUMAJ A., GASIENIEC L., JAROMINEK S., LECROQ T., PLANDOWSKI W., RYTTER W., 1992, Deux méthodes pour accélérer l&#8217;algorithme de Boyer-Moore, in Théorie des Automates et Applications, Actes des 2e Journées Franco-Belges, D. Krob ed., Rouen, France, 1991, pp 45-63, PUR 176, Rouen, France.
<li> COLUSSI L., 1994, <a href="http://dl.acm.org/citation.cfm?id=182529">Fastest pattern matching in strings</a>, Journal of Algorithms. 16(2):163-189.
<li> SUNDAY D.M., 1990, <a href="http://dl.acm.org/citation.cfm?id=79184">A very fast substring search algorithm</a>, Communications of the ACM . 33(8):132-142. (Cải tiến này không nhanh hơn trên lý thuyết worst-case nhưng chạy rất nhanh trên thực tế!)
<li> <a href="http://igm.univ-mlv.fr/~lecroq/string/node24.html#SECTION00240">Turbo Reverse Factor</a>.
</ul>
]]></content:encoded>
			<wfw:commentRss>http://www.procul.org/blog/2011/08/31/pm-3-thu%e1%ba%adt-toan-boyer-moore/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>PM 2: Pattern matching bằng automaton đơn định</title>
		<link>http://www.procul.org/blog/2011/08/26/pm-2-pattern-matching-b%e1%ba%b1ng-automaton-d%c6%a1n-d%e1%bb%8bnh/</link>
		<comments>http://www.procul.org/blog/2011/08/26/pm-2-pattern-matching-b%e1%ba%b1ng-automaton-d%c6%a1n-d%e1%bb%8bnh/#comments</comments>
		<pubDate>Sat, 27 Aug 2011 03:46:57 +0000</pubDate>
		<dc:creator>NQH</dc:creator>
				<category><![CDATA[Lập trình]]></category>
		<category><![CDATA[Thuật Toán]]></category>
		<category><![CDATA[automaton]]></category>
		<category><![CDATA[Pattern matching]]></category>
		<category><![CDATA[String matching]]></category>

		<guid isPermaLink="false">http://www.procul.org/blog/?p=3568</guid>
		<description><![CDATA[PM 1: Thuật toán Knuth-Morris-Pratt PM 3: Thuật toán Boyer-Moore 1. Phép xây dựng DFA cơ bắp Chúng ta đang xét bài toán tìm tất cả các xuất hiện của chuỗi p[0..m-1] trong chuỗi s[0..n-1], với bộ ký tự là &#931;. Thuật toán KMP thật ra cũng chỉ là mô phỏng một automaton đơn định [...]]]></description>
			<content:encoded><![CDATA[<ul>
<li><a href="http://www.procul.org/blog/2011/04/02/pm-1-thu%E1%BA%ADt-toan-knuth-morris-pratt/">PM 1: Thuật toán Knuth-Morris-Pratt</a>
<li><a href="http://www.procul.org/blog/2011/08/31/pm-3-thu%e1%ba%adt-toan-boyer-moore/">PM 3: Thuật toán Boyer-Moore</a>
</ul>
<p><strong>1. Phép xây dựng DFA cơ bắp</strong></p>
<p>Chúng ta đang xét bài toán tìm tất cả các xuất hiện của chuỗi <code>p[0..m-1]</code> trong chuỗi <code>s[0..n-1]</code>, với bộ ký tự là &Sigma;. Thuật toán KMP thật ra cũng chỉ là mô phỏng một automaton đơn định (<a href="http://en.wikipedia.org/wiki/Deterministic_finite-state_machine">DFA</a>). Lần này ta xây dựng hẳn một DFA để giải quyết bài toán này. Ta thiết kế DFA có ít trạng thái nhất để nhận dạng ngôn ngữ <code>&Sigma;*p</code>.</p>
<p><span id="more-3568"></span></p>
<p>Ví dụ, nếu <code>p = "abaa"</code> thì DFA là</p>
<p><a href="http://www.procul.org/blog/wp-content/uploads/2011/08/abaa.png"><img src="http://www.procul.org/blog/wp-content/uploads/2011/08/abaa.png" alt="" title="abaa" width="500" class="aligncenter size-full wp-image-3572" /></a></p>
<p>Kiểm tra bằng &#8230; mắt, dễ thấy rằng DFA này nhận dạng pattern <code>p = "abaa"</code>. Thế làm thể nào để xây dựng DFA trong trường hợp pattern bất kỳ? Ta có thể xây dựng DFA một cách đệ quy. Để đơn giản, giả sử pattern của ta là <code>abaab</code>, và ta đã xây dựng được DFA cho tiền tố <code>abaa</code> rồi (chính là DFA ở trên). Khi có thêm ký tự <code>b</code>, ta &#8220;bẻ&#8221; mũi tên nhãn <code>b</code> từ trạng thái số 4 trỏ ngang qua trạng thái mới, số 5. Thế các mũi tên từ trạng thái số 5 thì trỏ đi đâu? Nhớ rằng nếu không có trạng thái 5 thì khi gặp ký tự <code>b</code> ta từ trạng thái 4 chuyển sang trạng thái 2. Như vậy, trạng thái số 5 &#8212; với ý nghĩa là đã gặp ký tự <code>b</code> từ trạng thái 4 &#8212; sẽ phải di chuyển y như trạng thái 2. Vì thế, các mũi tên từ trạng thái 5 sẽ trỏ đi y như các mũi tên từ trạng thái 2. Kết quả là</p>
<p><a href="http://www.procul.org/blog/wp-content/uploads/2011/08/abaab.png"><img src="http://www.procul.org/blog/wp-content/uploads/2011/08/abaab.png" alt="" title="abaab" width="550" class="aligncenter size-full wp-image-3573" /></a></p>
<p>Sau khi đã xây dựng được DFA thì phần còn lại của chương trình là rất đơn giản. Ta viết nó bằng Python:</p>
<pre class="brush: python; title: ; notranslate">
def dfa_pm(s, p): # print all occurrences of pattern p in string s
    n = len(s); m = len(p)
    atoz  = map(chr, range(97, 123)) # list from 'a' to 'z'
    dfa = [dict.fromkeys(atoz, 0)]   # the string matching automaton

    # build the automaton
    for i in range(m):
        q = dfa[i][p[i]]
        dfa[i][p[i]] = i+1
        dfa.append({})
        dfa[i+1].update(dfa[q])

    # scan the text string
    q = 0
    for j in range(n):
        q = dfa[q][s[j]]
        if (q == m): print &quot;Match at position &quot;, j-m+1

&gt;&gt;&gt; dfa_pm(&quot;abacabcabcabbc&quot;, &quot;cabc&quot;)
Match at position  3
Match at position  6
</pre>
<p>Với cách viết như trên thì thuật toán này cần thời gian O(m|&Sigma;|) để xây dựng DFA, không gian O(m|&Sigma;|) để chứa DFA, và thời gian O(n) để quét chuỗi nhập. Với &Sigma; nhỏ thì không có vấn đề gì, nhưng với &Sigma; lớn thì cả thời gian tiền xử lý và không gian cần cho DFA hơi bị phí. Nhớ rằng, trong thuật toán Knuth-Morris-Pratt thì thời gian và không gian tiền xử lý chỉ có O(m) thôi. </p>
<p><strong>2. Xây dựng DFA &#8220;nén&#8221;</strong></p>
<p>Để giảm thời gian và không gian tiền xử lý, ta phân tích kỹ hơn cấu trúc của cái DFA nọ. Trạng thái số <code>0</code> có ý nghĩa là ta chưa gặp ký tự nào trong pattern. Nếu ta đang ở trạng thái thứ <code>i, 1 &le; i &le; m</code> thì có nghĩa là ta đã &#8220;match&#8221; được <code>i</code> ký tự của <code>p</code>. Cụ thể hơn, giả sử ta đã quét chuỗi nhập <code>s</code> đến vị trí <code>s[j]</code>, cái hậu tố <code>w</code> dài nhất của <code>s[0..j]</code> mà cũng đồng thời là tiền tố của <code>p</code> có chiều dài đúng bằng <code>|w| = i</code>. Bạn đọc nên đối chiếu lại với <a href="http://www.procul.org/blog/2011/04/02/pm-1-thu%E1%BA%ADt-toan-knuth-morris-pratt/">thuật toán Morris-Pratt</a> để thấy rằng Morris-Pratt (và KMP) thật sự là mô phỏng một DFA. </p>
<p>Cái DFA có hai loại cạnh, <em>cạnh tiến</em> là các cạnh loại <code>(i, i+1)</code>, và <em>cạnh lùi</em> là các cạnh loại <code>(k, i)</code> với <code>i &le; k</code>. Chức năng của cạnh tiến thì đã rõ. Xét một cạnh lùi <code>(k, i, a)</code>, nghĩa là cạnh lùi <code>(k, i)</code> với nhãn là ký tự <code>a</code>. Ta gọi đại lượng <code>k-i</code> là <em>chiều dài</em> của cạnh lùi này. Lý do ta phải đi lùi là vì <code>p[k] != a</code>, không tiến được nữa. Nếu <code>i == 0</code> thì có nghĩa là không có tiền tố nào của <code>p[0..k-1]</code> mà cũng đồng thời là hậu tố của <code>p[0..k-1]a</code>. Khi <code>i &gt; 0</code> thì <code>p[0..i-1]</code> là hậu tố của <code>p[0..k-1]a</code>. Đặc biệt ta suy ra rằng <code>p[i-1] == a != p[k]</code>.</p>
<p>Gọi các cạnh lùi <code>(k, i)</code> với <code>i &gt; 0</code> là các <em>cạnh lùi dương</em>. Xét hai cạnh lùi dương <code>(k, i, a)</code> và <code>(k', i', a')</code> có chiều dài bằng nhau. Ta sẽ chứng minh rằng <code>i == i'</code>, từ đó dễ thấy rằng <code>k == k'</code> và <code>a == a'</code>. Không mất tính tổng quát, giả sử <code>i &lt; i'</code>. Như trên đã phân tích, ta có <code>p[0..i'-1]</code> là một hậu tố của <code>p[0..k'-1]a'</code>. Do đó, <code>p[i-1..i'-1]</code> là một hậu tố của <code>p[0..k'-1]a'</code>. Từng ký tự một thì ta phải có <code>p[i'-1] == a'</code>, <code>p[i'-2] == p[k'-1]</code>, vân vân, đến <code>p[i-1] == p[(i-1)+(k'-i'+1)]</code> Nhưng mà <code>k'-i' == k-i</code>. Do đó, <code>p[i] == p[k]</code>, vô lý.</p>
<p>Do có tối đa <code>m</code> chiều dài khác nhau cho các cạnh lùi dương, mà mỗi chiều dài chỉ có <strong>một</strong> cạnh lùi dương (bất kể bộ ký tự lớn nhỏ!), tổng số cạnh lùi dương tối đa là <code>m</code>. Như vậy, thay vì dùng một bảng kích thước <code>m|&Sigma;|</code> để lưu trữ cái DFA, ta chỉ cần lưu trữ <code>m</code> cạnh lùi dương. Tất cả các cạnh lùi khác là lùi về <code>0</code> và không cần phải lưu trữ chúng. Với mỗi một trạng thái <code>i</code>, ta lưu trữ một danh sách các cạnh lùi dương từ nó. Cấu trúc dữ liệu thích hợp là một <a href="http://en.wikipedia.org/wiki/Associative_array">associative array</a> (trong Python gọi là <a href="http://docs.python.org/tutorial/datastructures.html#dictionaries">dictionary</a>), tại vì ta cần chứa và tìm kiếm các cặp <code>(key, value)</code> với <code>key</code> là một ký tự và <code>value</code> là một trạng thái dương. Các associative arrays thường được hiện thực bằng một dạng cây cân bằng (như <a href="http://en.wikipedia.org/wiki/Red-black_tree">cây đỏ đen</a>), hoặc <a href="http://en.wikipedia.org/wiki/Hash_table">bảng băm</a>. Cây đỏ đen với <code>m</code> đỉnh chẳng hạn, thì các phép tìm kiếm và insert mất O(log m) thời gian. Với bảng băm thì tìm kiếm chỉ mất O(1) thời gian, và mất khoảng O(m)-space để xây dựng. Nhìn chung, bằng cách &#8220;nén&#8221; các cạnh về trạng thái <code>0</code>, xây dựng DFA chỉ còn mất O(m) không/thời gian. </p>
<p>Mã Python dùng dictionary có thể viết như sau:</p>
<pre class="brush: python; title: ; notranslate">
def compact_dfa(s, p): # print all occurrences of pattern p in string s
    n = len(s); m = len(p); dfa = [{}]

    # build the automaton
    for i in range(m):
        if (dfa[i].has_key(p[i])): q = dfa[i][p[i]]
        else: q = 0
        dfa[i][p[i]] = i+1
        dfa.append({})
        for key in dfa[q].keys(): dfa[i+1][key] = dfa[q][key]

    # scan the text string
    q = 0
    for j in range(n):
        if (dfa[q].has_key(s[j])): q = dfa[q][s[j]]
        else: q = 0
        if (q == m): print &quot;Match at position &quot;, j-m+1
</pre>
]]></content:encoded>
			<wfw:commentRss>http://www.procul.org/blog/2011/08/26/pm-2-pattern-matching-b%e1%ba%b1ng-automaton-d%c6%a1n-d%e1%bb%8bnh/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Một lớp ngắn ở Bách Khoa về thử nhóm</title>
		<link>http://www.procul.org/blog/2011/05/08/m%e1%bb%99t-l%e1%bb%9bp-ng%e1%ba%afn-%e1%bb%9f-bach-khoa-v%e1%bb%81-th%e1%bb%ad-nhom/</link>
		<comments>http://www.procul.org/blog/2011/05/08/m%e1%bb%99t-l%e1%bb%9bp-ng%e1%ba%afn-%e1%bb%9f-bach-khoa-v%e1%bb%81-th%e1%bb%ad-nhom/#comments</comments>
		<pubDate>Mon, 09 May 2011 01:12:17 +0000</pubDate>
		<dc:creator>NQH</dc:creator>
				<category><![CDATA[Giáo dục]]></category>
		<category><![CDATA[Lý thuyết thông tin]]></category>
		<category><![CDATA[Thuật Toán]]></category>

		<guid isPermaLink="false">http://www.procul.org/blog/?p=3348</guid>
		<description><![CDATA[Tôi sẽ đứng lớp một lớp ngắn hạn ở đại học Bách Khoa về đề tài thử nhóm. Homepage của lớp với lecture notes ở đây. Lớp 3 tuần, 2 buổi mỗi buổi 2 tiếng rưỡi, thứ ba + thứ sáu 9-11:30 sáng. Học phí 300 nghìn. Học phí cho sinh viên đại học là [...]]]></description>
			<content:encoded><![CDATA[<p>Tôi sẽ đứng lớp một lớp ngắn hạn ở đại học Bách Khoa về đề tài thử nhóm. Homepage của lớp với lecture notes <a href="http://www.cse.buffalo.edu/~hungngo/classes/GT-Seminar/schedule.html">ở đây</a>. Lớp 3 tuần, 2 buổi mỗi buổi 2 tiếng rưỡi, thứ ba + thứ sáu 9-11:30 sáng. Học phí 300 nghìn. Học phí cho sinh viên đại học là 100 nghìn. Các bác trong ban tổ chức hy vọng rằng đặt học phí khiêm tốn để lọc ra các bạn muốn học nghiêm túc. Học phí bao gồm nước uống. </p>
<p>Chân thành cảm ơn các anh Nguyễn Văn Minh Mẫn, Trần Văn Hoài, Trần Nam Dũng, Nguyễn Đình Thúc, và các anh chị khác đã tổ chức lớp học và cho tôi cơ hội này. </p>
<p>Mục tiêu chính của lớp học là giới thiệu các kỹ thuật thiết kế thuật toán như phương pháp xác suất, thuật toán ngẫu nhiên hóa, lý thuyết mã hóa, vân vân. Cái nền Thử Nhóm cũng quan trọng vì nó có ứng dụng khắp nơi. </p>
<p><strong>Cập nhật</strong>: phòng seminar số 2, A3. Lớp học thì ai đăng ký cũng được, không nhất thiết là sv Bách Khoa. Thông tin cụ thể các bạn có thể liên hệ với anh Mẫn qua <code>samgroup@cse.hcmut.edu.vn</code> Tốt nhất là sáng mai, thứ ba, các bạn cứ ghé qua căn tin A3 Bách Khoa lúc 8h sáng để biết thêm chi tiết. </p>
]]></content:encoded>
			<wfw:commentRss>http://www.procul.org/blog/2011/05/08/m%e1%bb%99t-l%e1%bb%9bp-ng%e1%ba%afn-%e1%bb%9f-bach-khoa-v%e1%bb%81-th%e1%bb%ad-nhom/feed/</wfw:commentRss>
		<slash:comments>16</slash:comments>
		</item>
		<item>
		<title>PM 1: Thuật toán Knuth-Morris-Pratt</title>
		<link>http://www.procul.org/blog/2011/04/02/pm-1-thu%e1%ba%adt-toan-knuth-morris-pratt/</link>
		<comments>http://www.procul.org/blog/2011/04/02/pm-1-thu%e1%ba%adt-toan-knuth-morris-pratt/#comments</comments>
		<pubDate>Sat, 02 Apr 2011 22:51:29 +0000</pubDate>
		<dc:creator>NQH</dc:creator>
				<category><![CDATA[Lập trình]]></category>
		<category><![CDATA[Thuật Toán]]></category>
		<category><![CDATA[Golden ratio]]></category>
		<category><![CDATA[Knuth-Morris-Pratt]]></category>
		<category><![CDATA[Pattern matching]]></category>
		<category><![CDATA[Quy hoạch động]]></category>
		<category><![CDATA[String matching]]></category>
		<category><![CDATA[thuật toán]]></category>

		<guid isPermaLink="false">http://www.procul.org/blog/?p=3176</guid>
		<description><![CDATA[PM 2: Pattern matching bằng automaton đơn định Pattern matching là một trong những bài toán cơ bản và quan trọng nhất của ngành máy tính. Tìm patterns trong các chuỗi-DNA là bài toán cơ bản của sinh tin học. Các phần mềm quét virus hiện đại có mấy chục triệu &#8220;patterns&#8221; là các &#8220;chữ [...]]]></description>
			<content:encoded><![CDATA[<ul>
<li><a href="http://www.procul.org/blog/2011/08/26/pm-2-pattern-matching-b%E1%BA%B1ng-automaton-d%C6%A1n-d%E1%BB%8Bnh/">PM 2: Pattern matching bằng automaton đơn định</a>
</ul>
<p><a href="http://en.wikipedia.org/wiki/Pattern_matching">Pattern matching</a> là một trong những bài toán cơ bản và quan trọng nhất của ngành máy tính. </p>
<p>Tìm patterns trong các chuỗi-DNA là <a href="http://www.cs.cmu.edu/~guyb/realworld/matching.html">bài toán cơ bản</a> của sinh tin học. Các phần mềm quét virus hiện đại có mấy chục triệu &#8220;patterns&#8221; là các &#8220;chữ ký&#8221; (<a href="http://en.wikipedia.org/wiki/Virus_signature#Self-modification">virus signature</a>) của các con virus máy tính đã biết. Khi quét máy thì phần mềm phải tìm các patterns này trong các files hay bộ nhớ của máy. Mỗi pattern thường là một chuỗi bytes nhất định. Lệnh <code>grep</code> chúng ta thường dùng trong Unix cũng làm tác vụ tương tự, trong đó các patterns có thể được biểu diễn bằng các biểu thức chính quy (<a href="http://en.wikipedia.org/wiki/Regular_expression">regular expressions</a>). </p>
<p><span id="more-3176"></span></p>
<p>Nếu chỉ tính các thuật toán tìm các xuất hiện của một chuỗi đơn (một chuỗi các ký tự cho sẵn) bên trong một chuỗi khác thôi thì ta đã có đến <a href="http://arxiv.org/pdf/1012.2547v1">cả trăm thuật toán</a> khác nhau. <a href="http://en.wikipedia.org/wiki/Knuth%E2%80%93Morris%E2%80%93Pratt_algorithm">Knuth-Morris-Pratt</a> (KMP) là một trong những thuật toán đó. KMP không phải là thuật toán nhanh nhất hay tốn ít bộ nhớ nhất trên thực tế. Trên thực tế các biến thể của thuật toán Boyer-Moore hay Rabin-Karp với hàm băm và bộ lọc Bloom thường được dùng. Ta sẽ nói về chúng sau. Nhưng KMP thật sự rất thanh lịch và nếu tác vụ tìm kiếm không ghê gớm quá thì KMP không kém Boyer-Moore là mấy. </p>
<p>Ý tưởng của KMP rất đơn giản. Nếu bạn đọc nó trong quyển CLRS (<a href="http://www.amazon.com/gp/product/0262033844/ref=as_li_tf_tl?ie=UTF8&#038;tag=bk06d-20&#038;linkCode=as2&#038;camp=1789&#038;creative=9325&#038;creativeASIN=0262033844">Introduction to Algorithms</a><img src="http://www.assoc-amazon.com/e/ir?t=bk06d-20&#038;l=as2&#038;o=1&#038;a=0262033844" width="1" height="1" border="0" alt="" style="border:none !important; margin:0px !important;" />) thì bạn sẽ bị lạc trong sa mạc. Thật sự là CLRS làm cho mọi thứ phức tạp hơn cần thiết. Quá nhiều ký hiệu và quá ít trực quan. Ta sẽ thảo luận KMP dùng 2 bước. Bước 1 là thuật toán Morris-Pratt (1970, A linear pattern-matching algorithm, Technical Report 40, UC Berkeley). Bước 2 là một cải tiến của thuật toán MP, có thêm Knuth vào (1977, Fast pattern matching in strings, SIAM Journal on Computing 6(1):323-350). </p>
<p>Mặc dù hai bài báo cách nhau 7 năm, thật ra là thuật toán này đã được khám phá từ khoảng 1969 với một lịch sử thú vị. Phần cuối <a href="http://www.procul.org/blog/wp-content/uploads/2011/04/KMP77.pdf">bài báo</a> của KMP mô tả chi tiết lịch sử này. Cả phần kỹ thuật lẫn lịch sử trong bài báo đều rất chi tiết, kiểu Knuth, đọc rất thích. </p>
<p><strong>1. Thuật toán Morris-Pratt</strong> </p>
<p>MP là thuật toán thời gian O(n) đầu tiên cho bài này. Ý tưởng của thuật toán MP là như sau. Giả sử ta muốn tìm pattern <code>p[0..m-1]</code> trong chuỗi <code>s[0..n-1]</code>. Đến một lúc nào đó thì ta có mis-match: <code>s[j] != p[i]</code> như trong hình sau: </p>
<p><img src="http://www.procul.org/blog/wp-content/uploads/2011/04/KMP1.jpg" alt="" title="KMP" width="500" height="206" class="aligncenter size-full wp-image-3180" /></p>
<p>Nếu dùng thuật toán cơ bắp (điều mà bạn nên làm khi đi phỏng vấn người ta hỏi viết <code>strstr()</code> lên bảng) thì ta dịch <code>p</code> <strong>một</strong> vị trí và làm lại từ đầu. Thời gian chạy sẽ là O(mn). Tồi! Nhưng mà làm lại từ đầu thì rất phí công chúng ta đã so sánh đến <code>s[j]</code>. Ta tìm cách dịch <code>p</code> đi xa hơn. Càng xa càng tốt miễn là không bị lố qua một xuất hiện của pattern trong chuỗi <code>s</code>. Dễ thấy rằng, để giữ vị trí của <code>j</code> không đổi thì ta phải dịch <code>p</code> đi một đoạn sao cho một <em>tiền tố</em> (prefix) của <code>p[0..i-1]</code> được xếp trùng bằng một hậu tố (suffix) của <code>p[0..i-1]</code>, tại vì đoạn hậu tố này đã được so trùng với đoạn hậu tố cùng chiều dài của <code>s[0..j-1]</code>. Khi đó, ta chỉ cần tiếp tục so sánh <code>s[j]</code> với <code>p[map[i]]</code> mà không cần làm lại từ đầu. Trong đó, <code>map[i] &lt; i</code> chỉ chỗ cho ta biết xê dịch <code>p</code> như thế nào.</p>
<p>Chiều dài của sự xê dịch (bằng với đại lượng <code>i - map[i]</code>) được gọi là một <em>chu kỳ</em> (period) của chuỗi <code>p[0..i-1]</code>. Phần tiền tố và hậu tố trùng khớp với nhau được gọi là <em>biên</em> (border) của chuỗi <code>p[0..i-1]</code>. Chiều dài của biên bằng <code>i</code> trừ đi chu kỳ. Để cho sự xê dịch có nghĩa, chu kỳ phải lớn hơn 0 và vì thế biên của <code>p[0..i-1]</code> luôn có chiều dài nhỏ hơn <code>i</code>. Để tránh trường hợp bị bỏ sót một xuất hiện của <code>p</code> trong <code>s</code> thì ta phải chọn biên dài nhất của <code>p[0..i-1]</code>, ký hiệu là <code>border(p[0..i-1])</code>. </p>
<p>Trong thuật toán MP ta tính trước dãy <code>map</code>. Sau đó dùng dãy <code>map</code> này để so sánh hai chuỗi. Chặt chẽ hơn, với một chuỗi <code>x</code> bất kỳ, định nghĩa <code>w = border(x)</code> là chuỗi <code>w</code> dài nhất thỏa mãn <code>0 &le; |w| &lt; |x|</code> sao cho <code>w</code> vừa là tiền tố vừa là hậu tố của <code>x</code>, nghĩa là <code>x = wy = zw</code>, trong đó <code>y, z</code> là các chuỗi nào đó. Lưu ý rằng <code>0 < |y| = |z| = period(x)</code>. </p>
<p>Bây giờ giả sử ta đã có bảng <code>map[0...m]</code>, trong đó <code>map[0] = -1</code> và <code>map[i] = |border(p[0..i-1])|</code> với <code>1 &le; i &le; m</code>. Thuật toán Morris-Pratt có thể được viết như sau:</p>
<pre class="brush: python; title: ; notranslate">
def MP(s, p): # print all occurrences of pattern p in string s
    map = compute_MP_map(p)
    i = 0
    n = len(s); m = len(p)
    for j in range(n):
        while ( (i &gt;= 0) and (s[j] != p[i]) ):
            i = map[i]
        i = i+1
        if (i == m):
            print &quot;Match at position &quot;, j-m+1
            i = map[i]
</pre>
<p>Ta phân tích thời gian chạy của MP dùng phương pháp phân tích khấu hao (<a href="http://en.wikipedia.org/wiki/Amortized_analysis">Amortized Analysis</a>). Ta cho mỗi chú <code>s[j]</code> 2 đồng xu để dùng. Và tưởng tượng bọn <code>p[i]</code> là <code>m</code> cái rọ. Lúc đầu bỏ vào rọ <code>p[0]</code> một đồng xu làm vốn. Mỗi lần phép so sánh ký tự ở dòng 6 được chạy thì ta dùng đồng xu có sẵn trong rọ <code>p[i]</code> để trả nợ cho phép so sánh này. (Lưu ý rằng ta giả sử nếu <code>i=-1</code> thì không có phép so sánh. Nếu bạn cẩn thận bạn có thể bẻ đôi điều kiện của <code>while</code> ra để tránh truy cập <code>p[-1]</code>. Đoạn Python trên tôi đã chạy, không có vấn đề gì.) Xong rồi đến cuối, ngay trước khi thực hiện phép gán <code>i = i+1</code> ở dòng 8 ta lấy 2 đồng xu của <code>s[j]</code> bỏ vào <code>p[i]</code> và <code>p[i+1]</code>. Như vậy thời gian chạy của MP là O(n), và nó chỉ dùng nhiều nhất 2n phép so sánh. </p>
<p>Vấn đề tiếp theo là làm sao tính <code>map</code>. Đây là một bài toán quy hoạch động hay. Lưu ý rằng biên của biên của một chuỗi <code>x</code> cũng là biên của <code>x</code>.  Giả sử ta muốn tính <code>map[i+1] = |border(p[0..i])|</code>. Dễ thấy rằng, nếu <code>p[i] = p[map[i]]</code> thì <code>map[i+1] = map[i] + 1</code>. Nếu <code>p[i] != p[map[i]]</code> thì ta so <code>p[i]</code> với <code>p[map[map[i]]]</code>, vân vân. Từ đó ta có đoạn mã sau:</p>
<pre class="brush: python; title: ; notranslate">
def compute_MP_map(p):
    m = len(p)        # p is the input pattern
    map = [-1]*(m+1)  # map[0..m], the Morris-Pratt border map
    i = 0; j = map[i]
    while (i &lt; m):
        while ( (j &gt;= 0) and (p[i] != p[j]) ):
            j = map[j]
        j = j+1; i = i+1
        map[i] = j
    return map
</pre>
<p><strong>Bài tập</strong>: dùng phương pháp phân tích khấu hao tương tự như trên, chứng minh rằng thời gian chạy của <code>compute_MP_map()</code> là O(m). </p>
<p>Chạy thử</p>
<pre class="brush: python; title: ; notranslate">
&gt;&gt;&gt; MP(&quot;abcabcabcabc&quot;, &quot;cabc&quot;)
Match at position  2
Match at position  5
Match at position  8
</pre>
<p><strong>2. Thuật toán Knuth-Morris-Pratt</strong></p>
<p>KMP cải tiến <code>map</code> một chút. Trong hình ở trên, nếu <code>p[map[i]] = b</code> thì ta lại có mis-match. Do đó, ta áp đặt một cú "dòm trước" (look ahead) khi tính <code>map</code>. Gọi <code>MPmap</code> là dãy <code>map</code> của thuật toán MP. Cái map cải tiến của thuật toán KMP được định nghĩa như sau. <code>KMPmap[0] = -1</code>. Với <code>1 &le; i &le; m-1</code> thì gọi <code>j = MPmap[i]</code>. Khi đó, nếu <code>p[i] != p[j]</code> thì <code>KMPmap[i] = j</code>. Nếu <code>p[i] == p[j]</code> thì <code>KMPmap[i] = KMPmap[j]</code>. Và cuối cùng, <code>KMPmap[m] = MPmap[m]</code>. Bạn nên nghĩ cẩn thận xem tại sao định nghĩa <code>KMPmap</code> như vậy là hữu lý.</p>
<p>Dĩ nhiên, nếu đã tính <code>MPmap</code> rồi thì tính <code>KMPmap</code> theo công thức trên dễ dàng thôi. Nhưng ta có thể viết hàm tính <code>KMPmap</code> trực tiếp. Đây là một bài tập lập trình rất hay! Trong Python có thể viết như sau</p>
<pre class="brush: python; title: ; notranslate">
def compute_KMP_map(p):
    m = len(p)        # p is the input pattern
    map = [-1]*(m+1)  # map[0..m], the Knuth-Morris-Pratt border map
    i = 1; map[i] = 0; j = map[i]
    while (i &lt; m):
        # at this point, j is MP_map[i], which is not necessarily KMP_map[i]
        if (p[i] == p[j]):
            map[i] = map[j]
        else:
            map[i] = j
            while ( (j &gt;= 0) and (p[i] != p[j]) ):
                j = map[j]
        j = j+1; i = i+1
    map[m] = j
    return map

def KMP(s, p): # print all occurrences of pattern p in string s
    map = compute_KMP_map(p)
    i = 0; j = 0
    n = len(s); m = len(p)
    for j in range(n):
        while ( (i &gt;= 0) and (s[j] != p[i]) ):
            i = map[i]
        i = i+1
        if (i == m):
            print &quot;Match at position &quot;, j-m+1
            i = map[i]

&gt;&gt;&gt; KMP(&quot;abcabcabcabc&quot;, &quot;cabc&quot;)
Match at position  2
Match at position  5
Match at position  8
</pre>
<p>Điểm thú vị cuối cùng là, mỗi ký tự của chuỗi <code>s</code> được so sánh với nhiều nhất là <img src='http://s.wordpress.com/latex.php?latex=%5Clog_%7B%5CPhi%7D%28m%2B1%29&#038;bg=ffffff&#038;fg=000000&#038;s=0' alt='\log_{\Phi}(m+1)' title='\log_{\Phi}(m+1)' class='latex' /> (đại lượng này gọi là <em>delay</em> của thuật toán), trong đó <img src='http://s.wordpress.com/latex.php?latex=%5CPhi&#038;bg=ffffff&#038;fg=000000&#038;s=0' alt='\Phi' title='\Phi' class='latex' /> là <a href="http://en.wikipedia.org/wiki/Golden_ratio">tỉ lệ vàng</a>! </p>
]]></content:encoded>
			<wfw:commentRss>http://www.procul.org/blog/2011/04/02/pm-1-thu%e1%ba%adt-toan-knuth-morris-pratt/feed/</wfw:commentRss>
		<slash:comments>11</slash:comments>
		</item>
		<item>
		<title>Phần thuật toán của bài lính bắn laser</title>
		<link>http://www.procul.org/blog/2011/03/19/ph%e1%ba%a7n-thu%e1%ba%adt-toan-c%e1%bb%a7a-bai-linh-b%e1%ba%afn-laser/</link>
		<comments>http://www.procul.org/blog/2011/03/19/ph%e1%ba%a7n-thu%e1%ba%adt-toan-c%e1%bb%a7a-bai-linh-b%e1%ba%afn-laser/#comments</comments>
		<pubDate>Sat, 19 Mar 2011 12:45:49 +0000</pubDate>
		<dc:creator>NQH</dc:creator>
				<category><![CDATA[Combinatorics]]></category>
		<category><![CDATA[Thuật Toán]]></category>
		<category><![CDATA[Loomis-Whitney]]></category>
		<category><![CDATA[thuật toán]]></category>
		<category><![CDATA[Đố]]></category>

		<guid isPermaLink="false">http://www.procul.org/blog/?p=3133</guid>
		<description><![CDATA[Ta tiếp tục với bài lính bắn laser. Nhờ định lý Loomis-Whitney, ta đã biết điều sau đây. Cho ba tập điểm thì không tồn tại nhiều hơn điểm sao scho , , và . Gọi các điểm này là các điểm ba màu. Với inputs là , để in ra các điểm ba màu [...]]]></description>
			<content:encoded><![CDATA[<p>Ta tiếp tục với bài <a href="http://www.procul.org/blog/2011/03/01/tia/">lính bắn laser</a>. Nhờ <a href="http://www.procul.org/blog/2011/03/04/ch%e1%bb%a9ng-minh-d%e1%bb%8bnh-ly-loomis-whitney-dung-entropy/">định lý Loomis-Whitney</a>, ta đã biết điều sau đây. Cho ba tập điểm</p>
<img src='http://s.wordpress.com/latex.php?latex=A%20%3D%20%5C%7B%28x%5Ea_1%2C%20y%5Ea_1%29%2C%20%5Cdots%2C%20%28x%5Ea_k%2C%20y%5Ea_k%29%5C%7D&#038;bg=ffffff&#038;fg=000000&#038;s=0' alt='A = \{(x^a_1, y^a_1), \dots, (x^a_k, y^a_k)\}' title='A = \{(x^a_1, y^a_1), \dots, (x^a_k, y^a_k)\}' class='latex' />
<img src='http://s.wordpress.com/latex.php?latex=B%20%3D%20%5C%7B%28y%5Eb_1%2C%20z%5Eb_1%29%2C%20%5Cdots%2C%20%28y%5Eb_k%2C%20z%5Eb_k%29%5C%7D&#038;bg=ffffff&#038;fg=000000&#038;s=0' alt='B = \{(y^b_1, z^b_1), \dots, (y^b_k, z^b_k)\}' title='B = \{(y^b_1, z^b_1), \dots, (y^b_k, z^b_k)\}' class='latex' />
<img src='http://s.wordpress.com/latex.php?latex=C%20%3D%20%5C%7B%28z%5Ec_1%2C%20x%5Ec_1%29%2C%20%5Cdots%2C%20%28z%5Ec_k%2C%20x%5Ec_k%29%5C%7D&#038;bg=ffffff&#038;fg=000000&#038;s=0' alt='C = \{(z^c_1, x^c_1), \dots, (z^c_k, x^c_k)\}' title='C = \{(z^c_1, x^c_1), \dots, (z^c_k, x^c_k)\}' class='latex' />
<p>thì không tồn tại nhiều hơn <img src='http://s.wordpress.com/latex.php?latex=k%5E%7B3%2F2%7D&#038;bg=ffffff&#038;fg=000000&#038;s=0' alt='k^{3/2}' title='k^{3/2}' class='latex' /> điểm <img src='http://s.wordpress.com/latex.php?latex=%28x%2Cy%2Cz%29&#038;bg=ffffff&#038;fg=000000&#038;s=0' alt='(x,y,z)' title='(x,y,z)' class='latex' /> sao scho <img src='http://s.wordpress.com/latex.php?latex=%28x%2Cy%29%20%5Cin%20A&#038;bg=ffffff&#038;fg=000000&#038;s=0' alt='(x,y) \in A' title='(x,y) \in A' class='latex' />, <img src='http://s.wordpress.com/latex.php?latex=%28y%2C%20z%29%20%5Cin%20B&#038;bg=ffffff&#038;fg=000000&#038;s=0' alt='(y, z) \in B' title='(y, z) \in B' class='latex' />, và <img src='http://s.wordpress.com/latex.php?latex=%28z%2Cx%29%20%5Cin%20C&#038;bg=ffffff&#038;fg=000000&#038;s=0' alt='(z,x) \in C' title='(z,x) \in C' class='latex' />. Gọi các điểm này là các <em>điểm ba màu</em>.</p>
<p>Với inputs là <img src='http://s.wordpress.com/latex.php?latex=A%2C%20B%2C%20C&#038;bg=ffffff&#038;fg=000000&#038;s=0' alt='A, B, C' title='A, B, C' class='latex' />, để in ra các điểm ba màu thì thời gian chạy <img src='http://s.wordpress.com/latex.php?latex=O%28k%5E3%29&#038;bg=ffffff&#038;fg=000000&#038;s=0' alt='O(k^3)' title='O(k^3)' class='latex' /> là tầm thường. Và, ta biết trong trường hợp tệ nhất thì cần <img src='http://s.wordpress.com/latex.php?latex=%5COmega%28k%5E%7B3%2F2%7D%29&#038;bg=ffffff&#038;fg=000000&#038;s=0' alt='\Omega(k^{3/2})' title='\Omega(k^{3/2})' class='latex' />. </p>
<p><strong>Câu hỏi:</strong> thiết kế thuật toán với thời gian chạy càng nhanh càng tốt để in ra toàn bộ các điểm ba màu. Có thể đạt tới <img src='http://s.wordpress.com/latex.php?latex=O%28k%5E%7B3%2F2%7D%29&#038;bg=ffffff&#038;fg=000000&#038;s=0' alt='O(k^{3/2})' title='O(k^{3/2})' class='latex' /> được không? </p>
]]></content:encoded>
			<wfw:commentRss>http://www.procul.org/blog/2011/03/19/ph%e1%ba%a7n-thu%e1%ba%adt-toan-c%e1%bb%a7a-bai-linh-b%e1%ba%afn-laser/feed/</wfw:commentRss>
		<slash:comments>22</slash:comments>
		</item>
		<item>
		<title>Bài toán Santa Claus</title>
		<link>http://www.procul.org/blog/2010/12/26/bai-toan-santa-claus/</link>
		<comments>http://www.procul.org/blog/2010/12/26/bai-toan-santa-claus/#comments</comments>
		<pubDate>Mon, 27 Dec 2010 03:57:17 +0000</pubDate>
		<dc:creator>NQH</dc:creator>
				<category><![CDATA[Thuật Toán]]></category>
		<category><![CDATA[Bài toán Santa Claus]]></category>
		<category><![CDATA[Thuật toán xấp xỉ]]></category>

		<guid isPermaLink="false">http://www.procul.org/blog/?p=2839</guid>
		<description><![CDATA[Trong tinh thần lễ hội, và cũng rất tình cờ là một bài toán về truyền video trên mạng một anh học trò hỏi tuần trước lại dẫn đến bài toán Santa Claus, do Nikhil Bansal và Maxim Srividenko giới thiệu ở STOC 2006. Thật ra thì cách định hình bài này rất tự nhiên, [...]]]></description>
			<content:encoded><![CDATA[<p>Trong tinh thần lễ hội, và cũng rất tình cờ là một bài toán về truyền video trên mạng một anh học trò hỏi tuần trước lại dẫn đến <a href="http://domino.research.ibm.com/comm/research_people.nsf/pages/nikhil.pubs.html/$FILE/santajourn.ps">bài toán Santa Claus</a>, do Nikhil Bansal và Maxim Srividenko giới thiệu ở STOC 2006. Thật ra thì cách định hình bài này rất tự nhiên, cho nên người ta đã nghiên cứu về bài toán này từ lâu rồi, ở ngữ cảnh khác. Trong scheduling thì nó gọi là <em>machine covering problem</em>. Xem <a href="http://www.sciencedirect.com/science?_ob=ArticleURL&amp;_udi=B6V8M-3VV051G-P&amp;_user=10&amp;_coverDate=05/31/1997&amp;_rdoc=1&amp;_fmt=high&amp;_orig=search&amp;_origin=search&amp;_sort=d&amp;_docanchor=&amp;view=c&amp;_searchStrId=1589238146&amp;_rerunOrigin=google&amp;_acct=C000050221&amp;_version=1&amp;_urlVersion=0&amp;_userid=10&amp;md5=5c1550811fde0cad5c7bf3ca9b28f28a&amp;searchtype=a">một bài</a> từ hồi 1997 chẳng hạn. Tuy nhiên, Bansal-Srividenko là bài đầu tiên đặt tên Santa Claus cho nó từ ngữ cảnh bài toán phân chia tài nguyên để tối đa sự công bằng.</p>
<p>Santa Claus có <img src='http://s.wordpress.com/latex.php?latex=n&#038;bg=ffffff&#038;fg=000000&#038;s=0' alt='n' title='n' class='latex' /> món quà phải chia cho <img src='http://s.wordpress.com/latex.php?latex=m&#038;bg=ffffff&#038;fg=000000&#038;s=0' alt='m' title='m' class='latex' /> em bé. Món quà thứ <img src='http://s.wordpress.com/latex.php?latex=j&#038;bg=ffffff&#038;fg=000000&#038;s=0' alt='j' title='j' class='latex' /> có <em>giá trị hạnh phúc</em> <img src='http://s.wordpress.com/latex.php?latex=p_%7Bij%7D%20%5Cin%20%5Cmathbb%7BZ%7D%5E%2B&#038;bg=ffffff&#038;fg=000000&#038;s=0' alt='p_{ij} \in \mathbb{Z}^+' title='p_{ij} \in \mathbb{Z}^+' class='latex' /> đối với em bé <img src='http://s.wordpress.com/latex.php?latex=i&#038;bg=ffffff&#038;fg=000000&#038;s=0' alt='i' title='i' class='latex' />. Gọi <img src='http://s.wordpress.com/latex.php?latex=f%3A%20%5Bn%5D%20%5Cto%20%5Bm%5D&#038;bg=ffffff&#038;fg=000000&#038;s=0' alt='f: [n] \to [m]' title='f: [n] \to [m]' class='latex' /> là hàm chia quà. Tổng hạnh phúc mà em bé <img src='http://s.wordpress.com/latex.php?latex=i&#038;bg=ffffff&#038;fg=000000&#038;s=0' alt='i' title='i' class='latex' /> nhận được là</p>
<p><center><img src='http://s.wordpress.com/latex.php?latex=%5Cdisplaystyle%7Bh_f%28i%29%20%3D%20%5Csum_%7Bj%5C%20%3A%20%5C%20f%28j%29%20%3D%20i%7D%20p_%7Bij%7D%7D&#038;bg=ffffff&#038;fg=000000&#038;s=0' alt='\displaystyle{h_f(i) = \sum_{j\ : \ f(j) = i} p_{ij}}' title='\displaystyle{h_f(i) = \sum_{j\ : \ f(j) = i} p_{ij}}' class='latex' />.</center></p>
<p>Santa cần tìm hàm chia quà để tối đa hóa sự công bằng Tư Bản Chủ Nghĩa, ở đây định nghĩa là <em>tối đa hóa hạnh phúc nhỏ nhất</em> (maximin). Cụ thể hơn, chúng ta cần giải bài toán</p>
<p><center><img src='http://s.wordpress.com/latex.php?latex=%5Cmax_f%20%5Cmin_i%20h_f%28i%29&#038;bg=ffffff&#038;fg=000000&#038;s=0' alt='\max_f \min_i h_f(i)' title='\max_f \min_i h_f(i)' class='latex' />.</center></p>
<p>Dễ thấy rằng Santa Claus phải giải một bài toán NP-khó, chỉ với <img src='http://s.wordpress.com/latex.php?latex=m%3D2&#038;bg=ffffff&#038;fg=000000&#038;s=0' alt='m=2' title='m=2' class='latex' /> thì nó đã khó hơn <a href="http://en.wikipedia.org/wiki/Partition_problem">bài toán phân hoạch</a> rồi (cho một tập số nguyên, có tồn tại một cách chia tập thành hai phần sao cho tổng số nguyên trong các phần bằng nhau). </p>
<p>À, còn nếu ta định nghĩa sự công bằng theo kiểu Xã Hội Chủ Nghĩa, nghĩa là <em>tối thiểu hóa hạnh phúc lớn nhất</em> (minimax), thì đây chính là bài toán scheduling unrelated parallel machines. <a href="http://portal.acm.org/citation.cfm?id=81018.81019">Bài báo kinh điển</a> của Lenstra-Shmoys-Tardos cho ta một thuật toán xấp xỉ với tỉ lệ xấp xỉ bằng 2, và họ cũng chứng minh rằng xấp xỉ đến 1.5 là NP-khó. Cho đến nay chưa có ai giảm 2 hoặc tăng 1.5 được. Đã hơn 20 năm rồi. </p>
<p><strong>Bài tập</strong>: tìm thuật toán giải bài toán Santa Claus trong thời gian đa thức cho trường hợp <img src='http://s.wordpress.com/latex.php?latex=m%3Dn&#038;bg=ffffff&#038;fg=000000&#038;s=0' alt='m=n' title='m=n' class='latex' />.</p>
<p>Bansal-Srividenko thiết kế một thuật toán xấp xỉ với tỉ lệ <img src='http://s.wordpress.com/latex.php?latex=O%28%5Clog%20%5Clog%20m%2F%5Clog%20%5Clog%20log%20m%29&#038;bg=ffffff&#038;fg=000000&#038;s=0' alt='O(\log \log m/\log \log log m)' title='O(\log \log m/\log \log log m)' class='latex' /> cho trường hợp <img src='http://s.wordpress.com/latex.php?latex=p_%7Bij%7D%20%5Cin%20%5C%7Bp_j%2C%200%5C%7D%2C%20%5Cforall%20i&#038;bg=ffffff&#038;fg=000000&#038;s=0' alt='p_{ij} \in \{p_j, 0\}, \forall i' title='p_{ij} \in \{p_j, 0\}, \forall i' class='latex' />, nghĩa là mỗi cậu bé hoặc là không thấy hạnh phúc gì khi nhận quà <img src='http://s.wordpress.com/latex.php?latex=j&#038;bg=ffffff&#038;fg=000000&#038;s=0' alt='j' title='j' class='latex' />, hoặc là có thêm hạnh phúc bằng <img src='http://s.wordpress.com/latex.php?latex=p_j&#038;bg=ffffff&#038;fg=000000&#038;s=0' alt='p_j' title='p_j' class='latex' />. <a href="http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.95.4975&amp;rep=rep1&amp;type=pdf">Bezáková-Dani</a> đã chứng mỉnh rằng riêng trường hợp đặc biệt này, xấp xỉ đến tỉ lệ 2 đã là NP-khó. Có <a href="http://portal.acm.org/citation.cfm?id=1347114">một</a> <a href="http://portal.acm.org/citation.cfm?id=1536488&amp;CFID=3519737&amp;CFTOKEN=79513415">vài</a> <a href="http://portal.acm.org/citation.cfm?id=1429794&amp;CFID=3519737&amp;CFTOKEN=79513415">phát</a> triển khác gần đây hơn cũng thú vị. Xem thêm cả <a href="http://www.cs.princeton.edu/courses/archive/spr10/cos598B/lec2.pdf">bài giảng</a> này.</p>
<p>Tóm lại, chặn dưới tốt nhất đã biết cho bài toán tổng quát là <img src='http://s.wordpress.com/latex.php?latex=2&#038;bg=ffffff&#038;fg=000000&#038;s=0' alt='2' title='2' class='latex' />, và chặn trên là <img src='http://s.wordpress.com/latex.php?latex=%5Ctilde%20O%28%5Csqrt%20m%29&#038;bg=ffffff&#038;fg=000000&#038;s=0' alt='\tilde O(\sqrt m)' title='\tilde O(\sqrt m)' class='latex' />. (Cái chặn trên này dùng LP-formulation của Bansal-Srividenko, cộng với rounding, do <a href="http://www.stanford.edu/~asadpour/papers/maxmin.pdf">Asadpour-Saberi</a> thiết kế, bài đăng ở STOC 2007.)</p>
]]></content:encoded>
			<wfw:commentRss>http://www.procul.org/blog/2010/12/26/bai-toan-santa-claus/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>Sách thuật toán xấp xỉ miễn phí</title>
		<link>http://www.procul.org/blog/2010/11/07/sach-thu%e1%ba%adt-toan-x%e1%ba%a5p-x%e1%bb%89-mi%e1%bb%85n-phi/</link>
		<comments>http://www.procul.org/blog/2010/11/07/sach-thu%e1%ba%adt-toan-x%e1%ba%a5p-x%e1%bb%89-mi%e1%bb%85n-phi/#comments</comments>
		<pubDate>Sun, 07 Nov 2010 18:09:49 +0000</pubDate>
		<dc:creator>NQH</dc:creator>
				<category><![CDATA[Giới thiệu sách]]></category>
		<category><![CDATA[Thuật Toán]]></category>
		<category><![CDATA[Sách miễn phí]]></category>
		<category><![CDATA[thuật toán]]></category>
		<category><![CDATA[xấp xỉ]]></category>

		<guid isPermaLink="false">http://www.procul.org/blog/?p=2517</guid>
		<description><![CDATA[Miễn phí online: Sách của hai chuyên gia hàng đầu về các thuật toán xấp xỉ, nhất là mấy TTXX dựa trên quy hoạch tuyến tính và SDP (semi-definite programming, tiếng Việt?).]]></description>
			<content:encoded><![CDATA[<p><a href="http://www.designofapproxalgs.com/download.php">Miễn phí online</a>: Sách của hai chuyên gia hàng đầu về các thuật toán xấp xỉ, nhất là mấy TTXX dựa trên quy hoạch tuyến tính và SDP (semi-definite programming, tiếng Việt?).</p>
]]></content:encoded>
			<wfw:commentRss>http://www.procul.org/blog/2010/11/07/sach-thu%e1%ba%adt-toan-x%e1%ba%a5p-x%e1%bb%89-mi%e1%bb%85n-phi/feed/</wfw:commentRss>
		<slash:comments>6</slash:comments>
		</item>
	</channel>
</rss>

