﻿<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/"><channel><title>BlogJava-weidagang2046的专栏-文章分类-Algorithm</title><link>http://www.blogjava.net/weidagang2046/category/1268.html</link><description>物格而后知致</description><language>zh-cn</language><lastBuildDate>Fri, 02 Mar 2007 06:29:04 GMT</lastBuildDate><pubDate>Fri, 02 Mar 2007 06:29:04 GMT</pubDate><ttl>60</ttl><item><title>Map Reduce - the Free Lunch is not over?</title><link>http://www.blogjava.net/weidagang2046/articles/92459.html</link><dc:creator>weidagang2046</dc:creator><author>weidagang2046</author><pubDate>Mon, 08 Jan 2007 12:48:00 GMT</pubDate><guid>http://www.blogjava.net/weidagang2046/articles/92459.html</guid><wfw:comment>http://www.blogjava.net/weidagang2046/comments/92459.html</wfw:comment><comments>http://www.blogjava.net/weidagang2046/articles/92459.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/weidagang2046/comments/commentRss/92459.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/weidagang2046/services/trackbacks/92459.html</trackback:ping><description><![CDATA[
		<p>微软著名的C++大师<a href="http://www.gotw.ca/">Herb Sutter</a>在2005年初的时候曾经写过一篇重量级的文章：”<a href="http://www.gotw.ca/publications/concurrency-ddj.htm">The Free Lunch Is Over: A Fundamental Turn Toward Concurrency in Software</a>“，预言OO之后软件开发将要面临的又一次重大变革-并行计算。</p>
		<p>摩尔定律统制下的软件开发时代有一个非常有意思的现象：”Andy giveth, and Bill taketh away.”。不管CPU的主频有多快，我们始终有办法来利用它，而我们也陶醉在机器升级带来的程序性能提高中。</p>
		<p>我记着我大二的时候曾经做过一个五子棋的程序，当时的算法就是预先设计一些棋型（有优先级），然后扫描棋盘，对形势进行分析，看看当前走哪部对自己最重要。当然下棋还要堵别人，这就需要互换双方的棋型再计算。如果只算一步，很可能被狡猾的对手欺骗，所以为了多想几步，还需要递归和回朔。在当时的机器上，算3步就基本上需要3秒左右的时间了。后来大学毕业收拾东西的时候找到这个程序，试了一下，发现算10步需要的时间也基本上感觉不出来了。</p>
		<p>不知道你是否有同样的经历，我们不知不觉的一直在享受着这样的免费午餐。可是，随着摩尔定律的提前终结，免费的午餐终究要还回去。虽然硬件设计师还在努力：Hyper Threading CPU（多出一套寄存器，相当于一个逻辑CPU）使得Pipeline尽可能满负荷，使多个Thread的操作有可能并行，使得多线程程序的性能有5%-15%的提升；增加Cache容量也使得包括Single-Thread和Multi-Thread程序都能受益。也许这些还能帮助你一段时间，但问题是，我们必须做出改变，面对这个即将到来的变革，你准备好了么？</p>
		<p>Concurrency Programming != Multi-Thread Programming。很多人都会说MultiThreading谁不会，问题是，你是为什么使用/如何使用多线程的？我从前做过一个类似AcdSee一样的图像查看/处理程序，我通常用它来处理我的数码照片。我在里面用了大量的多线程，不过主要目的是在图像处理的时候不要Block住UI，所以将CPU Intensive的计算部分用后台线程进行处理。而并没有把对图像矩阵的运算并行分开。</p>
		<p>我觉得Concurrency Programming真正的挑战在于Programming Model的改变，在程序员的脑子里面要对自己的程序怎样并行化有很清楚的<strong>认识</strong>，更重要的是，如何去<strong>实现</strong>（包括架构、容错、实时监控等等）这种并行化，如何去<strong>调试</strong>，如何去<strong>测试</strong>。</p>
		<p>在Google，每天有海量的数据需要在有限的时间内进行处理（其实每个互联网公司都会碰到这样的问题），每个程序员都需要进行分布式的程序开发，这其中包括如何分布、调度、监控以及容错等等。Google的<a href="http://labs.google.com/papers/mapreduce.html">MapReduce</a>正是把分布式的业务逻辑从这些复杂的细节中抽象出来，使得没有或者很少并行开发经验的程序员也能进行并行应用程序的开发。</p>
		<p>MapReduce中最重要的两个词就是Map（映射）和Reduce（规约）。初看Map/Reduce这两个词，熟悉Function Language的人一定感觉很熟悉。FP把这样的函数称为”higher order function”（”High order function”被成为Function Programming的利器之一哦），也就是说，这些函数是编写来被与其它函数相结合（或者说被其它函数调用的）。如果说硬要比的化，可以把它想象成C里面的CallBack函数，或者STL里面的Functor。比如你要对一个STL的容器进行查找，需要制定每两个元素相比较的Functor（Comparator），这个Comparator在遍历容器的时候就会被调用。</p>
		<p>拿前面说过图像处理程序来举例，其实大多数的图像处理操作都是对图像矩阵进行某种运算。这里的运算通常有两种，一种是映射，一种是规约。拿两种效果来说，”老照片”效果通常是强化照片的G/B值，然后对每个象素加一些随机的偏移，这些操作在二维矩阵上的每一个元素都是独立的，是Map操作。而”雕刻”效果需要提取图像边缘，就需要元素之间的运算了，是一种Reduce操作。再举个简单的例子，一个一维矩阵（数组）[0,1,2,3,4]可以映射为[0,2,3,6,8]（乘2），也可以映射为[1,2,3,4,5]（加1）。它可以规约为0（元素求积）也可以规约为10（元素求和）。</p>
		<p>面对复杂问题，古人教导我们要“<strong>分</strong>而<strong>治</strong>之”，英文中对应的词是”<strong>Divide</strong> and <strong>Conquer</strong>“。Map/Reduce其实就是Divide/Conquer的过程，通过把问题Divide，使这些Divide后的Map运算高度并行，再将Map后的结果Reduce（根据某一个Key），得到最终的结果。</p>
		<p>Googler发现这是问题的核心，其它都是共性问题。因此，他们把MapReduce抽象分离出来。这样，Google的程序员可以只关心应用逻辑，关心根据哪些Key把问题进行分解，哪些操作是Map操作，哪些操作是Reduce操作。其它并行计算中的复杂问题诸如分布、工作调度、容错、机器间通信都交给Map/Reduce Framework去做，很大程度上简化了整个编程模型。</p>
		<p>MapReduce的另一个特点是，Map和Reduce的<strong>输入和输出都是中间临时文件</strong>（MapReduce利用Google文件系统来管理和访问这些文件），而不是不同进程间或者不同机器间的其它通信方式。我觉得，这是Google一贯的风格，化繁为简，返璞归真。</p>
		<p>接下来就放下其它，研究一下Map/Reduce操作。（其它比如容错、备份任务也有很经典的经验和实现，论文里面都有详述）</p>
		<p>Map的定义：</p>
		<blockquote>
				<p>Map, written by the user, takes an input pair and produces a set of intermediate key/value pairs. The MapReduce library groups together all intermediate values associated with the same intermediate key I and passes them to the Reduce function.</p>
		</blockquote>
		<p>Reduce的定义：</p>
		<blockquote>
				<p>The Reduce function, also written by the user, accepts an intermediate key I and a set of values for that key. It merges together these values to form a possibly smaller set of values. Typically just zero or one output value is produced per Reduce invocation. The intermediate values are supplied to the user’s reduce function via an iterator. This allows us to handle lists of values that are too large to fit in memory.</p>
		</blockquote>
		<p>MapReduce论文中给出了这样一个例子：在一个文档集合中统计每个单词出现的次数。</p>
		<p>Map操作的输入是每一篇文档，将输入文档中每一个单词的出现输出到中间文件中去。</p>
		<blockquote>
				<p>map(String key, String value):<br />    // key: document name<br />    // value: document contents<br />    for each word w in value:<br />        EmitIntermediate(w, “1″);</p>
		</blockquote>
		<p>比如我们有两篇文档，内容分别是</p>
		<p>A － “I love programming”</p>
		<p>B － “I am a blogger, you are also a blogger”。</p>
		<p>B文档经过Map运算后输出的中间文件将会是：</p>
		<pre>	I,1
	am,1
	a,1
	blogger,1
	you,1
	are,1
	a,1
	blogger,1
</pre>
		<p>Reduce操作的输入是单词和出现次数的序列。用上面的例子来说，就是 (”I”, [1, 1]), (”love”, [1]), (”programming”, [1]), (”am”, [1]), (”a”, [1,1]) 等。然后根据每个单词，算出总的出现次数。</p>
		<blockquote>
				<p>reduce(String key, Iterator values):<br />    // key: a word<br />    // values: a list of counts<br />    int result = 0;<br />    for each v in values:<br />        result += ParseInt(v);<br />    Emit(AsString(result));</p>
		</blockquote>
		<p>最后输出的最终结果就会是：(”I”, 2″), (”a”, 2″)……</p>
		<p>实际的执行顺序是：</p>
		<ol>
				<li>MapReduce Library将Input分成M份。这里的Input Splitter也可以是多台机器<strong>并行Split</strong>。 
</li>
				<li>Master将M份Job分给Idle状态的M个worker来处理； 
</li>
				<li>对于输入中的每一个&lt;key, value&gt; pair 进行Map操作，将中间结果Buffer在Memory里； 
</li>
				<li>定期的（或者根据内存状态），将Buffer中的中间信息Dump到<strong>本地</strong>磁盘上，并且把文件信息传回给Master（Master需要把这些信息发送给Reduce worker）。这里最重要的一点是，<strong>在写磁盘的时候，需要将中间文件做Partition（比如R个）</strong>。拿上面的例子来举例，如果把所有的信息存到一个文件，Reduce worker又会变成瓶颈。我们只需要保证<strong>相同Key能出现在同一个Partition</strong>里面就可以把这个问题分解。 
</li>
				<li>R个Reduce worker开始工作，从不同的Map worker的Partition那里拿到数据（<strong>read the buffered data from the local disks of the map workers</strong>），用key进行排序（如果内存中放不下需要用到外部排序 - external sort）。很显然，排序（或者说Group）是Reduce函数之前必须做的一步。 这里面很关键的是，每个Reduce worker会去从很多Map worker那里拿到X(0&lt;X&lt;R) Partition的中间结果，这样，所有属于这个Key的信息已经都在这个worker上了。 
</li>
				<li>Reduce worker遍历中间数据，对每一个唯一Key，执行Reduce函数（参数是这个key以及相对应的一系列Value）。 
</li>
				<li>执行完毕后，唤醒用户程序，返回结果（最后应该有R份Output，每个Reduce Worker一个）。 </li>
		</ol>
		<p>可见，这里的分（Divide）体现在两步，分别是将输入分成M份，以及将Map的中间结果分成R份。将输入分开通常很简单，Map的中间结果通常用”hash(key) mod R”这个结果作为标准，保证相同的Key出现在同一个Partition里面。当然，使用者也可以指定自己的Partition Function，比如，对于Url Key，如果希望同一个Host的URL出现在同一个Partition，可以用”hash(Hostname(urlkey)) mod R”作为Partition Function。</p>
		<p>对于上面的例子来说，每个文档中都可能会出现成千上万的 (”the”, 1)这样的中间结果，琐碎的中间文件必然导致传输上的损失。因此，MapReduce还支持用户提供Combiner Function。这个函数通常与Reduce Function有相同的实现，不同点在于Reduce函数的输出是最终结果，而Combiner函数的输出是Reduce函数的某一个输入的中间文件。</p>
		<p>Tom White给出了Nutch[2]中另一个很直观的例子，<a href="http://weblogs.java.net/blog/tomwhite/archive/2005/09/mapreduce.html">分布式Grep</a>。我一直觉得，Pipe中的很多操作，比如More、Grep、Cat都类似于一种Map操作，而Sort、Uniq、wc等都相当于某种Reduce操作。</p>
		<p>加上前两天Google刚刚发布的<a href="http://labs.google.com/papers/bigtable.html">BigTable</a>论文，现在Google有了自己的集群 - <a href="http://labs.google.com/papers/googlecluster.html">Googel Cluster</a>，分布式文件系统 - <a href="http://labs.google.com/papers/gfs.html">GFS</a>，分布式计算环境 - <a href="http://labs.google.com/papers/mapreduce.html">MapReduce</a>，分布式结构化存储 - <a href="http://labs.google.com/papers/bigtable.html">BigTable</a>，再加上<a href="http://labs.google.com/papers/chubby.html">Lock Service</a>。我真的能感觉的到Google著名的免费晚餐之外的对于程序员的另一种免费的晚餐，那个由大量的commodity PC组成的large clusters。我觉得这些才真正是Google的核心价值所在。</p>
		<p>呵呵，就像微软老兵<a href="http://www.joelonsoftware.com/">Joel Spolsky</a>（你应该看过他的”Joel on Software”吧？）曾经说过，对于微软来说最可怕的是[1]，微软还在苦苦追赶Google来完善Search功能的时候，Google已经在部署下一代的超级计算机了。</p>
		<blockquote>
				<p>The very fact that Google invented MapReduce, and Microsoft didn’t, says something about why <strong>Microsoft is still playing catch up trying to get basic search features to work, while Google has moved on to the next problem: building Skynet^H^H^H^H^H^H the world’s largest massively parallel supercomputer</strong>. I don’t think Microsoft completely understands just how far behind they are on that wave.</p>
		</blockquote>
		<p>注1：其实，微软也有自己的方案 - <a href="http://research.microsoft.com/research/sv/dryad/">DryAd</a>。问题是，大公司里，要想重新部署这样一个底层的InfraStructure，无论是技术的原因，还是政治的原因，将是如何的难。</p>
		<p>注2：<a href="http://lucene.apache.org/">Lucene</a>之父Doug Cutting的又一力作，Project <a href="http://lucene.apache.org/hadoop/">Hadoop</a> - 由Hadoop分布式文件系统和一个Map/Reduce的实现组成，Lucene/Nutch的成产线也够齐全的了。<br /><br />from: <a href="http://xerdoc.com/blog/archives/246.html">http://xerdoc.com/blog/archives/246.html</a></p>
<img src ="http://www.blogjava.net/weidagang2046/aggbug/92459.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/weidagang2046/" target="_blank">weidagang2046</a> 2007-01-08 20:48 <a href="http://www.blogjava.net/weidagang2046/articles/92459.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Can Your Programming Language Do This?</title><link>http://www.blogjava.net/weidagang2046/articles/92458.html</link><dc:creator>weidagang2046</dc:creator><author>weidagang2046</author><pubDate>Mon, 08 Jan 2007 12:22:00 GMT</pubDate><guid>http://www.blogjava.net/weidagang2046/articles/92458.html</guid><wfw:comment>http://www.blogjava.net/weidagang2046/comments/92458.html</wfw:comment><comments>http://www.blogjava.net/weidagang2046/articles/92458.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/weidagang2046/comments/commentRss/92458.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/weidagang2046/services/trackbacks/92458.html</trackback:ping><description><![CDATA[
		<h2>This item ran on the Joel on Software homepage on Tuesday, August 01, 2006</h2>
		<p>One day, you're browsing through your code, and you notice two big blocks that look almost exactly the same. In fact, they're exactly the same, except that one block refers to "Spaghetti" and one block refers to "Chocolate Moose."</p>
		<pre>    // A trivial example:
    
    alert("I'd like some Spaghetti!");
    alert("I'd like some Chocolate Moose!");
</pre>
		<p>These examples happen to be in JavaScript, but even if you don't know JavaScript, you should be able to follow along.</p>
		<p>The repeated code looks wrong, of course, so you create a function:</p>
		<pre>    function SwedishChef( food )
    {
        alert("I'd like some " + food + "!");
    }
	
    SwedishChef("Spaghetti");
    SwedishChef("Chocolate Moose");
</pre>
		<p>OK, it's a trivial example, but you can imagine a more substantial example. This is better code for many reasons, all of which you've heard a million times. Maintainability, Readability, Abstraction = Good!</p>
		<p>Now you notice two other blocks of code which look almost the same, except that one of them keeps calling this function called BoomBoom and the other one keeps calling this function called PutInPot. Other than that, the code is pretty much the same.</p>
		<img src="http://www.blogjava.net/images/blogjava_net/emu/1359/o_01BorkBorkBork.png" />
		<br clear="all" />
		<pre>    alert("get the lobster");
    PutInPot("lobster");
    PutInPot("water");

    alert("get the chicken");
    BoomBoom("chicken");
    BoomBoom("coconut");
</pre>
		<p>Now you need a way to pass an argument to the function which itself is a function. This is an important capability, because it increases the chances that you'll be able to find common code that can be stashed away in a function.</p>
		<pre>    function Cook( i1, i2, f )
    {
        alert("get the " + i1);
        f(i1);
        f(i2);
    }

    Cook( "lobster", "water", PutInPot );
    Cook( "chicken", "coconut", BoomBoom );
</pre>
		<p>Look! We're passing in a function as an argument. </p>
		<p>Can your language do this?</p>
		<p>Wait... suppose you haven't already defined the functions PutInPot or BoomBoom. Wouldn't it be nice if you could just write them inline instead of declaring them elsewhere?</p>
		<pre>    Cook( "lobster", 
          "water", 
          function(x) { alert("pot " + x); }  );
    Cook( "chicken", 
          "coconut", 
          function(x) { alert("boom " + x); } );
</pre>
		<p>Jeez, that is handy. Notice that I'm creating a function there on the fly, not even bothering to name it, just picking it up by its ears and tossing it into a function.</p>
		<p>As soon as you start thinking in terms of anonymous functions as arguments, you might notice code all over the place that, say, does something to every element of an array.</p>
		<pre>    var a = [1,2,3];
	
    for (i=0; i&lt;a.length; i++)
    {
        a[i] = a[i] * 2;
    }
	
    for (i=0; i&lt;a.length; i++)
    {
        alert(a[i]);
    }
</pre>
		<p>Doing something to every element of an array is pretty common, and you can write a function that does it for you:</p>
		<pre>    function map(fn, a)
    {
        for (i = 0; i &lt; a.length; i++)
        {
            a[i] = fn(a[i]);
        }
    }
</pre>
		<p>Now you can rewrite the code above as:</p>
		<pre>    map( function(x){return x*2;}, a );
    map( alert, a );
</pre>
		<p>Another common thing with arrays is to combine all the values of the array in some way. </p>
		<pre>    function sum(a)
    {
        var s = 0;
        for (i = 0; i &lt; a.length; i++)
            s += a[i];
        return s;
    }
    
    function join(a)
    {
        var s = "";
        for (i = 0; i &lt; a.length; i++)
            s += a[i];
        return s;
    }
    
    alert(sum([1,2,3]));
    alert(join(["a","b","c"]));
</pre>
		<p>
				<strong>sum</strong> and <strong>join</strong> look so similar, you might want to abstract out their essence into a generic function that combines elements of an array into a single value:</p>
		<pre>    function reduce(fn, a, init)
    {
        var s = init;
        for (i = 0; i &lt; a.length; i++)
            s = fn( s, a[i] );
        return s;
    }
    
    function sum(a)
    {
        return reduce( function(a, b){ return a + b; }, 
                       a, 0 );
    }
    
    function join(a)
    {
        return reduce( function(a, b){ return a + b; }, 
                       a, "" );
    }

</pre>
		<p>Many older languages simply had no way to do this kind of stuff. Other languages let you do it, but it's hard (for example, C has function pointers, but you have to declare and define the function somewhere else). Object-oriented programming languages aren't completely convinced that you should be allowed to do anything with functions. </p>
		<p>Java required you to create a whole object with a single method called a functor if you wanted to treat a function like a first class object. Combine that with the fact that many OO languages want you to create a whole file for each class, and it gets really klunky fast. If your programming language requires you to use functors, you're not getting all the benefits of a modern programming environment. See if you can get some of your money back.</p>
		<p>How much benefit do you really get out of writting itty bitty functions that do nothing more than iterate through an array doing something to each element?</p>
		<p>Well, let's go back to that <strong>map</strong> function. When you need to do something to every element in an array in turn, the truth is, it probably doesn't matter what order you do them in. You can run through the array forward or backwards and get the same result, right? In fact, if you have two CPUs handy, maybe you could write some code to have each CPU do half of the elements, and suddenly <strong>map</strong> is twice as fast.</p>
		<p>Or maybe, just hypothetically, you have hundreds of thousands of servers in several data centers around the world, and you have a really big array, containing, let's say, again, just hypothetically, the entire contents of the internet. Now you can run <strong>map</strong> on thousands of computers, each of which will attack a tiny part of the problem.</p>
		<p>So now, for example, writing some really fast code to search the entire contents of the internet is as simple as calling the <strong>map</strong> function with a basic string searcher as an argument. </p>
		<p>The really interesting thing I want you to notice, here, is that as soon as you think of <strong>map</strong> and <strong>reduce</strong> as functions that everybody can use, and they use them, you only have to get one supergenius to write the hard code to run <strong>map</strong> and <strong>reduce</strong> on a global massively parallel array of computers, and all the old code that used to work fine when you just ran a loop still works only it's a zillion times faster which means it can be used to tackle huge problems in an instant.</p>
		<p>Lemme repeat that. By abstracting away the very concept of looping, you can implement looping any way you want, including implementing it in a way that scales nicely with extra hardware. </p>
		<p>And now you understand something I wrote a while ago where I <a href="http://www.joelonsoftware.com/articles/ThePerilsofJavaSchools.html">complained about CS students who are never taught anything but Java</a>:</p>
		<blockquote dir="ltr" style="MARGIN-RIGHT: 0px">
				<p>Without understanding functional programming, you can't invent <a href="http://labs.google.com/papers/mapreduce.html">MapReduce</a>, the algorithm that makes Google so massively scalable. The terms Map and Reduce come from Lisp and functional programming. MapReduce is, in retrospect, obvious to anyone who remembers from their 6.001-equivalent programming class that purely functional programs have no side effects and are thus trivially parallelizable. The very fact that Google invented MapReduce, and Microsoft didn't, says something about why Microsoft is still playing catch up trying to get basic search features to work, while Google has moved on to the next problem: building Skynet^H^H^H^H^H^H the world's largest massively parallel supercomputer. I don't think Microsoft completely understands just how far behind they are on that wave.</p>
		</blockquote>
		<p>Ok. I hope you're convinced, by now, that programming languages with first-class functions let you find more opportunities for abstraction, which means your code is smaller, tighter, more reusable, and more scalable. Lots of Google applications use MapReduce and they all benefit whenever someone optimizes it or fixes bugs.</p>
		<p>And now I'm going to get a little bit mushy, and argue that the most productive programming environments are the ones that let you work at <em>different levels of abstraction</em>. Crappy old FORTRAN really didn't even let you write functions. C had function pointers, but they were ugleeeeee and not anonymous and had to be implemented somewhere else than where you were using them. Java made you use functors, which is even uglier. As Steve Yegge points out, Java is the <a href="http://steve-yegge.blogspot.com/2006/03/execution-in-kingdom-of-nouns.html">Kingdom of Nouns</a>.</p>
		<p>
				<strong>
						<font size="2">Correction: The last time I used FORTRAN was 27 years ago. Apparently it got functions. I must have been thinking about GW-BASIC.</font>
				</strong>
		</p>
		<br />
		<br />
		<br />
		<br />
		<b>About the Author:</b> I'm your host, Joel Spolsky, a software developer in New York City. Since 2000, I've been writing about software development, management, business, and the Internet on this site. For my day job, I run <a href="http://www.fogcreek.com/">Fog Creek Software</a>, makers of <a href="http://www.fogcreek.com/FogBugz">FogBugz</a> - the smart bug tracking software with the stupid name, and <a href="https://www.copilot.com/">Fog Creek Copilot</a> - the easiest way to provide remote tech support over the Internet, with nothing to install or configure. <br /><br />from: <a href="http://www.joelonsoftware.com/items/2006/08/01.html"><font size="4">http://www.joelonsoftware.com/items/2006/08/01.html</font></a><img src ="http://www.blogjava.net/weidagang2046/aggbug/92458.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/weidagang2046/" target="_blank">weidagang2046</a> 2007-01-08 20:22 <a href="http://www.blogjava.net/weidagang2046/articles/92458.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Partial Evaluation - An Overview</title><link>http://www.blogjava.net/weidagang2046/articles/87942.html</link><dc:creator>weidagang2046</dc:creator><author>weidagang2046</author><pubDate>Fri, 15 Dec 2006 06:40:00 GMT</pubDate><guid>http://www.blogjava.net/weidagang2046/articles/87942.html</guid><wfw:comment>http://www.blogjava.net/weidagang2046/comments/87942.html</wfw:comment><comments>http://www.blogjava.net/weidagang2046/articles/87942.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/weidagang2046/comments/commentRss/87942.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/weidagang2046/services/trackbacks/87942.html</trackback:ping><description><![CDATA[
		<ul>
				<li>Program specialization 
<ul><li><a href="http://compose.labri.fr/documentation/pe/pe_overview.php3#spec">General Idea</a></li><li><a href="http://compose.labri.fr/documentation/pe/pe_overview.php3#examples">Examples</a> (in <a href="http://compose.labri.fr/documentation/pe/pe_overview.php3#examples">Scheme</a> or in <a href="http://compose.labri.fr/documentation/pe/pe_overview.php3#bta">C</a>) 
</li><li><a href="http://compose.labri.fr/documentation/pe/pe_overview.php3#interest">Interest</a></li></ul></li>
				<li>Partial evaluation 
<ul><li><a href="http://compose.labri.fr/documentation/pe/pe_overview.php3#pe">General Idea</a></li><li><a href="http://compose.labri.fr/documentation/pe/pe_overview.php3#appli">Applications</a></li><li><a href="http://compose.labri.fr/documentation/pe/pe_overview.php3#offvson">Off-line vs. on-line </a><ul><li><a href="http://compose.labri.fr/documentation/pe/pe_overview.php3#bta">Binding-time analysis</a></li><li><a href="http://compose.labri.fr/documentation/pe/pe_overview.php3#ctspec">At compile time</a></li><li><a href="http://compose.labri.fr/documentation/pe/pe_overview.php3#rtspec">At run time</a></li></ul></li><li><a href="http://compose.labri.fr/documentation/pe/pe_overview.php3#references">References</a></li></ul></li>
		</ul>
		<a name="spec">
				<!--====================================================================-->
				<hr size="4" />
				<h2>Program Specialization</h2>
		</a>
		<p>Let us consider a program P, taking two arguments S and D, and producing a result R:</p>
		<center>run P(S,D) = R</center>
		<p>A <em>specialization</em> of P with respect to S is a program P<sub>S</sub> such that, for all input D,</p>
		<center>run P<sub>S</sub>(D) = run P(S,D) = R</center>
		<p>Input S is called <em>static</em>, it is known (i.e., available) at specialization time. Input D is <em>dynamic</em>, it is unknown (i.e., unavailable) until run time.</p>
		<a name="examples">
				<!--====================================================================-->
				<hr size="4" />
				<h2>Specialization Examples</h2>
		</a>
		<p>Program specialization makes sense in any programming language. Consider for example the following Scheme program. (See below for more examples, in C.)</p>
		<dl>
				<dd>
						<!--indent-->
						<pre>(define (append list1 list2)
   (if (null? list1)
       list2
       (cons (car list1) (append (cdr list1) list2))))
</pre>
				</dd>
		</dl>
		<!--indent-->
		<p>A possible specialization of <code>append</code> with respect to a static argument <code>list1</code> = <code>(4 2)</code> is function <code>append42</code> below.</p>
		<dl>
				<dd>
						<!--indent-->
						<pre>(define (append42 list2)
   (cons 4 (cons 2 list2)))
</pre>
				</dd>
		</dl>
		<!--indent-->
		<p>Function <code>append42</code> preserves the semantics of <code>append</code>, or more precisely, it has the same semantics as the <em>trivial specialization</em> function <code>triv_append42</code>, defined as</p>
		<dl>
				<dd>
						<!--indent-->
						<pre>(define (triv-append42 list2)
   (append '(4 2) list2))
</pre>
				</dd>
		</dl>
		<!--indent-->
		<p>Depending on the context, S is called a <em>specialization value</em> or an <em>invariant</em>. In the general case, a specialization may exploit several invariants, whether input values or constants already present in the code of P.</p>
		<a name="interest">
				<!--====================================================================-->
				<hr size="4" />
				<h2>Interest of Specialization</h2>
		</a>
		<p>The interest of function <code>append42</code> above, as opposed to <code>triv-append42</code>, is that computations depending only on the static input <code>list1</code> = <code>(4 2)</code> have already been performed. More generally, specialization impacts on speed and size of programs, thus offering applications to program optimization.</p>
		<dl>
				<dt>
						<strong>Speed</strong>
				</dt>
				<dd>Specialization factors out computations from the specialized program. As a result, a specialized program runs faster than the original program. (Although, in some rare cases, cache effects may yield a slight slowdown.) For example, function <code>append42</code> above runs faster than <code>append</code> (or more precisely, <code>triv-append42</code>) because the traversal of argument <code>list1</code> as already been performed. 
<p></p></dd>
				<dt>
						<strong>Size</strong>
				</dt>
				<dd>A specialized program is sometimes smaller than the original program (e.g., when a static input corresponds to an option that dispatches on different functionalities of the program). It is sometimes bigger (e.g., when a loop or a recursive call is unfolded). 
<p></p></dd>
		</dl>
		<p>Note that all program arguments do not have the same impact on specialization. For example, specializing <code>append</code> with respect to <code>list2</code> = <code>(4 2)</code> leads to the quite unexciting function below.</p>
		<dl>
				<dd>
						<!--indent-->
						<pre>(define (dull-append42 list1)
   (if (null? list1)
       '(4 2)
       (cons (car list1) (dull-append42 (cdr list1)))))
</pre>
				</dd>
		</dl>
		<!--indent-->
		<p>Specialization is used in particular (sometimes unknowingly) to optimize critical sections of code. It is often handwritten.</p>
		<a name="pe">
				<!--====================================================================-->
				<hr size="4" />
				<h2>Partial Evaluation</h2>
		</a>
		<p>
				<em>Partial evaluation</em> (PE) is the process that automates <a href="http://compose.labri.fr/documentation/pe/pe_overview.php3#spec">program specialization</a> [<a href="http://compose.labri.fr/documentation/papers/pe-tutorial.ps.gz">CD93</a>, <a href="http://www.diku.dk/users/glueck/PE96-book.html">DRT96</a>, <a href="http://www.dina.kvl.dk/~sestoft/pebook/">JGS93</a>]. A <em>partial evaluator</em> (or <em>specializer</em>) is a program M that takes two arguments, the source of a program P and a static (known) subset of the input S, and produces a specialized program P<sub>S</sub>:</p>
		<center>run M(P,S) = P<sub>S</sub></center>
		<p>Roughly speaking, partial evaluation can be thought of as a combination of aggressive constant folding, inlining, loop unrolling and <em>inter-procedural</em> constant propagation applied to <em>all</em> data types (including pointers, structures and arrays) instead of just scalars.</p>
		<a name="appli">
				<!--====================================================================-->
				<hr size="4" />
				<h2>Applications of Partial Evaluation</h2>
		</a>
		<p>Handwritten specialization is tedious, error-prone and does not scale to large programs. Because it is automatic, specialization via partial evaluation does not have all those drawbacks; it is even predictable (see below). As a result, specialization becomes an issue in engineering software: it is possible to rapidly write generic programs, which are maintainable but slow, and automatically produce fast specialized instances. Because the programmer focuses less on optimization hacks, and more on reusability, partial evaluation greatly improve productivity and program safety.</p>
		<p>Partial evaluation has been successfully applied as an optimizer in various domains such as operating systems and networking, computer graphics, numerical computation, circuit simulation, software architectures, compiling and compiler generation.</p>
		<p>It has also been used for program understanding and reengineering: given various running options, partial evaluation may split large programs into smaller ones.</p>
		<a name="offvson">
				<!--====================================================================-->
				<hr size="4" />
				<h2>Off-line vs. On-line Partial Evaluation</h2>
		</a>
		<p>An <em>on-line</em> partial evaluator takes as arguments the source of a program P and a static subset of the input S, performs symbolic computations on available data, and directly yields the source of a specialized program P<sub>S</sub>.</p>
		<p>In an <em>off-line</em> partial evaluator, the <a href="http://compose.labri.fr/documentation/pe/pe_overview.php3#spec">specialization</a> is divided into two steps. First, an program <em><a href="http://compose.labri.fr/documentation/pe/pe_overview.php3#bta">binding-time analysis</a></em> propagate abstract information about static and dynamic values throughout the code. It prepares the second phase that, given actual specialization values, produce <a href="http://compose.labri.fr/documentation/pe/pe_overview.php3#ctspec">specialized code</a>. 
</p>
		<p>On-line partial evaluator are theoretically more powerful: specialization relies on actual values, not on the fact that values are known. On the other hand, off-line partial evaluator are faster because value propagation is "pre-compiled". Moreover, they are predictable in the sense that it is possible to assess the degree of specialization.</p>
		<p>Some partial evaluators, like <a href="http://compose.labri.fr/prototypes/tempo/">Tempo</a>, can specialize programs not only at <a href="http://compose.labri.fr/documentation/pe/pe_overview.php3#ctspec">compile time</a> (i.e., source-to-source transformation) but also <a href="http://compose.labri.fr/documentation/pe/pe_overview.php3#rtspec">run time</a> (i.e., run-time code generation). Only off-line partial evaluation lends itself to run-time specialization.</p>
		<a name="bta">
				<!--====================================================================-->
				<hr size="4" />
				<h2>Binding-Time Analysis</h2>
		</a>As a first step, the user provides a program and specifies initial binding times, that is, which arguments (including global variables) are static (i.e., known) and which are dynamic (i.e., yet unknown). For example, the user provides the following code for a <code>miniprintf</code> function, and specifies that the first argument is static whereas the second is dynamic: <code>miniprintf(S,D)</code>. 
<dl><dd><!--indent--><pre>miniprintf(char fmt[], int val[])
{
  int i = 0;
  while( *fmt != '\0' ) {
    if( *fmt != '%' )
       putchar(*fmt);
    else
      switch(*++fmt) {
        case 'd' : putint(val[i++]); break;             
        case '%' : putchar('%');     break;
        default  : prterror(*fmt);   break;
      }
    fmt++;
  }
}
</pre></dd></dl><!--indent--><p>Binding-time analysis (BTA) propagates the static/dynamic information throughout the program and annotates each statement and expression with a binding time. These annotations can be visualized using colors (or font effects). </p><dl><dd><!--indent--><pre><font color="maroon">/*  LEGEND:  <font color="blue"><b>STATIC</b></font><font color="red">DYNAMIC</font></font>
 */
<font color="red">miniprintf(</font><font color="blue"><b>char fmt[],</b></font><font color="red">int val[]</font><font color="red">)</font><font color="red">{</font><font color="blue"><b>int i = 0;</b></font><font color="blue"><b>while( *fmt != '\0' ) {</b></font><font color="blue"><b>if( *fmt != '%' )</b></font><font color="red">putchar(</font><font color="blue"><b>*fmt</b></font><font color="red">);</font><font color="blue"><b>else</b></font><font color="blue"><b>switch(*++fmt) {</b></font><font color="blue"><b>case 'd' :</b></font><font color="red">putint(val[</font><font color="blue"><b>i++</b></font><font color="red">]);</font><font color="blue"><b>break;</b></font><font color="blue"><b>case '%' :</b></font><font color="red">putchar(<font color="blue"><b>'%'</b></font>);</font><font color="blue"><b>break;</b></font><font color="blue"><b>default  :</b></font><font color="red">prterror(</font><font color="blue"><b>*fmt</b></font><font color="red">);</font><font color="blue"><b>break;</b></font><font color="blue"><b>}</b></font><font color="blue"><b>fmt++;</b></font><font color="blue"><b>}</b></font><font color="red">}</font></pre></dd></dl><p>The blue color (bold face for black and white display) represent static constructions, i.e. values that can be computed at specialization time. The red color (standard font for black and white display) is for dynamic expressions, whose value cannot be precomputed knowing only the static arguments. Basically, everything in blue (bold) will disappear after specialization; only red (standard font) parts will remain. Visualizing of the analysis is very important for the user to assess the amount of specialization in the code.</p><p>Note that in the case of languages like C, the binding-time analysis must takes into account pointer aliases and side-effects.</p><a name="ctspec"><!--====================================================================--><hr size="4" /><h2>Compile-Time Specialization</h2></a>When the user is satisfied with the analysis (i.e., what the user expects to specialize is indeed considered as static by the <a href="http://compose.labri.fr/documentation/pe/pe_overview.php3#bta">BTA</a>), actual specialization values must be provided. For example, giving <code>"&lt;%d|%d&gt;"</code> as the actual specialization value for the <code>fmt</code> argument of the <code>miniprintf()</code> function yields the following specialized code. 
<dl><dd><!--indent--><pre>miniprintf_1(int val[])
{
  putchar( '&lt;'    );
  putint ( val[0] );
  putchar( '|'    );
  putint ( val[1] );
  putchar( '&gt;'    );
}
</pre></dd></dl>Many specializations can be performed, sharing the same analysis. Only different specialization values have to be provided. 
<p></p><a name="rtspec"><!--====================================================================--><hr size="4" /><h2>Run-Time Specialization</h2></a>Partial evaluators like <a href="http://compose.labri.fr/prototypes/tempo/">Tempo</a> [<a href="http://compose.labri.fr/documentation/papers/acm-pe98-tempo.ps.gz">CHL+98</a>,<a href="http://compose.labri.fr/documentation/papers/tempo-tr96.ps.gz">CHN+96</a>] can also perform run-time specialization [<a href="http://compose.labri.fr/documentation/papers/rt-spec.ps.gz">CN96</a>], using optimized binary code templates [<a href="http://compose.labri.fr/documentation/papers/rt_bench.ps.gz">NHCL97</a>]. A dedicated run-time specializer is generated from the results of the program analysis. In the case of the <code>miniprintf</code> function, a runtime specializer <code>rts_miniprintf()</code> is generated, which can be used as in the following example. 
<dl><dd><!--indent--><pre><i>/*</i><i> * Some dynamic execution context setting variable 'format'</i><i> */</i>
spec_printf = rts_miniprintf(format);  <i>// specialize</i>
...
(*spec_printf)(val1);  <i>// &lt;=&gt; miniprintf(format,val1)</i>
(*spec_printf)(val2);  <i>// &lt;=&gt; miniprintf(format,val2)</i></pre></dd></dl><p>The function <code>rts_miniprintf()</code> is a dedicated runtime specializer. It returns a pointer to the specialized function. Several specialized versions can also be generated and used at the same time.</p><a name="references"><!--====================================================================--><hr size="4" /><h2>References</h2></a><p>Various resources concerning partial evaluation, including <a href="http://compose.labri.fr/documentation/pe/pe_resources.php3#existing">existing specializers</a>, <a href="http://compose.labri.fr/documentation/pe/pe_resources.php3#events">PE-related events</a> and <a href="http://compose.labri.fr/documentation/pe/pe_resources.php3#references">basic references</a> are accessible from <a href="http://compose.labri.fr/documentation/pe/pe_resources.php3">pe_resources.php3</a>.</p><dl><dt>[<a href="http://compose.labri.fr/documentation/papers/pe-tutorial.ps.gz">CD93</a>] 
</dt><dd>Tutorial Notes on Partial Evaluation. C. Consel and O. Danvy. In ACM Symposium on Principles of Programming Languages, pages 493-501, 1993. 
</dd><dt>[<a href="http://compose.labri.fr/documentation/papers/acm-pe98-tempo.ps.gz">CHL+98</a>] 
</dt><dd>C. Consel, L. Hornof, J. Lawall, R. Marlet, G. Muller, J. Noyé, S. Thibault, and N. Volanschi. Tempo: Specializing systems applications and beyond. <em>ACM Computing Surveys, Symposium on Partial Evaluation</em>, 1998. To appear. 
</dd><dt>[<a href="http://compose.labri.fr/documentation/papers/tempo-tr96.ps.gz">CHN+96</a>] 
</dt><dd>C. Consel, L. Hornof, F. Noël, J. Noyé, and E.N. Volanschi. A uniform approach for compile-time and run-time specialization. In O. Danvy, R. Glück, and P. Thiemann, editors, <em>Partial Evaluation, International Seminar, Dagstuhl Castle</em>, number 1110 in Lecture Notes in Computer Science, pages 54-72, February 1996. 
</dd><dt>[<a href="http://compose.labri.fr/documentation/papers/rt-spec.ps.gz">CN96</a>] 
</dt><dd>C. Consel and F. Noël. A general approach for run-time specialization and its application to C. In <em>Conference Record of the 23rd Annual ACM SIGPLAN-SIGACT Symposium on Principles Of Programming Languages</em>, pages 145-156, St. Petersburg Beach, FL, USA, January 1996. ACM Press. 
</dd><dt>[<a href="http://www.diku.dk/users/glueck/PE96-book.html">DRT96</a>] 
</dt><dd>Partial Evaluation. O. Danvy, R. Glück and P. Thiemann (Eds.). Lecture Notes in Computer Science, Vol. 1110. 
</dd><dt>[<a href="http://www.dina.kvl.dk/~sestoft/pebook/">JGS93</a>] 
</dt><dd>Partial evaluation and automatic program generation. N.D. Jones, C. Gomard and P. Sestoft. Prentice Hall international series in computer science, 1993. 
</dd><dt>[<a href="http://compose.labri.fr/documentation/papers/rt_bench.ps.gz">NHCL97</a>] 
</dt><dd>F. Noël, L. Hornof, C. Consel, and J. Lawall. Automatic, template-based run-time specialization : Implementation and experimental study. In <em>International Conference on Computer Languages</em>, Chicago, IL, May 1998. IEEE Computer Society Press. Also available as IRISA report PI-1065. </dd></dl><hr size="4" /><!--====================================================================--><center><i>Something missing? Send suggestions for additions and improvement! </i></center><!--====================================================================--><hr size="4" /><font size="-1">Last modified: 2003-09-25. - <a href="mailto:Jocelyn.Frechot@labri.fr">Jocelyn.Frechot@labri.fr</a> - <a href="http://compose.labri.fr/">http://compose.labri.fr</a><br /><br />from: <a href="http://compose.labri.fr/documentation/pe/pe_overview.php3">http://compose.labri.fr/documentation/pe/pe_overview.php3</a></font><img src ="http://www.blogjava.net/weidagang2046/aggbug/87942.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/weidagang2046/" target="_blank">weidagang2046</a> 2006-12-15 14:40 <a href="http://www.blogjava.net/weidagang2046/articles/87942.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>遗传算法小生境技术简介</title><link>http://www.blogjava.net/weidagang2046/articles/84798.html</link><dc:creator>weidagang2046</dc:creator><author>weidagang2046</author><pubDate>Fri, 01 Dec 2006 04:08:00 GMT</pubDate><guid>http://www.blogjava.net/weidagang2046/articles/84798.html</guid><wfw:comment>http://www.blogjava.net/weidagang2046/comments/84798.html</wfw:comment><comments>http://www.blogjava.net/weidagang2046/articles/84798.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/weidagang2046/comments/commentRss/84798.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/weidagang2046/services/trackbacks/84798.html</trackback:ping><description><![CDATA[生物学上，小生境是指特定环境下的一种组织结构。在自然界中，往往特征，形状相似的物种相聚在一起，并在同类中交配繁衍后代。在SGA 中，交配完全是随机的，在进化的后期，大量的个体集中于某一极值点上，在用遗传算法求解多峰值问题时，经常只能找到个别的几个最优值，甚至往往得到是局部最优解。利用小生境我们可以找到全部最优解。<br />小生境技术就是将每一代个体划分为若干类，每个类中选出若干适应度较大的个体作为一个类的优秀代表组成一个群，再在种群中，以及不同种群中之间，杂交，变异产生新一代个体群。同时采用预选择机制和排挤机制或分享机制完成任务。基于这种小生境的遗传算法（Niched Genetic Algorithms，NGA），可以更好的保持解的多样性，同时具有很高的全局寻优能力和收敛速度，特别适合于复杂多峰函数的优化问题。<br />模拟小生境技术主要建立在常规选择操作的改进之上。Cavichio 在1970年提出了基于预选择机制的选择策略，其基本做法是：当新产生的子代个体的适应度超过其父代个体的适应度时，所产生的子代才能代替其父代而遗传到下一代群体中去，否则父代个体仍保留在下一代群体中。由于子代个体和父代个体之间编码结构的相似性，所以替换掉的只是一些编码结构相似的个体，故它能够有效的维持群体的多样性，并造就小生境的进化环境。De Jong在1975年提出基于排挤机制的选择策略，其基本思想源于在一个有限的生存环境中，各种不同的生物为了能够延续生存，他们之间必须相互竞争各种有限的生存资源。因此，在算法中设置一个排挤因子CF（一般取CF=2或3），由群体中随机选取的1/CF 个个体组成排挤成员，然后依据新产生的的个体与排挤成员的相似性来排挤一些与预排挤成员相类似的个体，个体之间的相似性可用个体编码之间的海明距离来度量。随着排挤过程的进行，群体中的个体逐渐被分类，从而形成一个个小的生成环境，并维持群体的多样性。<br />  Goldberg等在1987年提出了基于共享机制（Sharing）的小生境实现方法。这种实现方法的基本思想是：通过反映个体之间的相似程度的共享函数来调节群体中各个个体的适应度，从而在这以后的群体进化过程中，算法能够依据这个调整后的新适应度来进行选择运算，以维持群体的多样性，创造出小生境的进化环境。<br />共享函数（Sharing Function）是表示群体中两个个体之间密切关系程度的一个函数，可记为S（d ）其中表示个体i和j之间的关系。例如，个体基因型之间的海明距离就可以为一种共享函数。这里，个体之间的密切程度主要体现为个体基因型的相似性或个体表现型的相似性上。当个体之间比较相似时，其共享函数值就比较大；反之，当个体之间不太相似时，其共享函数值比较小。<br />共享度是某个个体在群体中共享程度的一中度量，它定义为该个体与群体内其它各个个体之间的共享函数值之和，用S 表示：<br />S = （i=1， ，M）<br />在计算出了群体中各个个体的共享度之后，依据下式来调整各个个体的适应度：<br />F （X）=F （X）/S （i=1， ，M）<br />由于每个个体的遗传概率是由其适应度大小来控制的，所以这种调整适应度的方法就能够限制群体中个别个体的大量增加，从而维护了群体的多样性，并造就了一种小生境的进化环境。<br />下面介绍一个基于小生境概念的遗传算法。这个算法的基本思想是：首先两两比较群体中各个个体之间的距离，若这个距离在预先的距离L 之内的话，在比较两者之间的适应度大小，并对其中适应值较低的个体施加一个较强的罚函数，极大地降低其适应度，这样，对于在预先指定的某一距离L之内的两个个体，其中较差的个体经处理后其适应度变得更差，他在后面的进化过程被淘汰的概率就极大。也就是说，在距离L 内将只存在一个优良个体，从而既维护了群体的多样性，又使得各个个体之间保持一定的距离，并使得个体能够在整个约束的空间中分散开来，这样就实现了一种小生境遗传算法。<br />这个小生境算法的描述如下：<br />算法 NicheGA （1）设置进化代数计数器；随机生成M个初始群体P（t），并求出各个个体的适应度F （i=1，2，M）。（2） 依据各个个体的适应度对其进行降序排列，记忆前N个个体（N&lt;M）.(3) 选择算法。对群体P（t）进行比例选择运算，得到P （t）。（4）交叉选择。对选择的个体集合P （t） 作单点交叉运算，得到P （t）。（5）变异运算。对P （t）作均匀变异运算，得到P （t）。（6）小生境淘汰运算。将第（5）步得到的M个个体和第（2）步所记忆的N个个体合并在一起，得到一个含有M+N 个个体的新群体；对着M+N个个体，按照下式得到两个个体x 和x 之间的海明距离：|| x - x ||= ( )当|| x - x ||&lt;L时，比较个体x 和个体x 的适应度大小，并对其中适应度较低的个体处以罚函数： Fmin(x ，x )=Penalty（7）依据这M+N个个体的新适应度对各个个体进行降序排列，记忆前N个个体。（8）终止条件判断。若不满足终止条件，则：更新进化代数记忆器t t+1， 并将第（7）步排列中的前M个个体作为新的下一代群体P(t),然后转到第（3）步：若满足终止条件，则：输出计算结果，算法结束。<br />[例] Shubert 函数的全局最优化计算。<br />min f(x , x )={ } { }<br />s.t. -10 x 10（i=1，2）<br />上述函数共有760个局部最优点，其中有18个是全局最优点，全局最优点处的目标函数值是f （x ， x ）=-186.731。<br />用上述小生境遗传算法求解该例题时，可用下式进行目标函数值到个体适应度的变换处理：<br />F（x ， x ）= <br />L=202（二进制编码串长度，其中每个变量用10位二进制编码来表示）<br />M=50<br />T=500<br />p =0.1<br />p =0.1<br />L=0.5(小生境之间的距离参数)<br />Penlty=10 （罚函数）<br />使用上述参数进行了50次，试算，每次都可得到许多全局最优解下表为其中一次运算所得到的最好的18个个体。从该表可以看出，从小生境的角度来数，该算法得到了一个较好的结果。上述算法的特点保证了在一个函数峰内只存在一个较优的个体，这样每一个函数峰就是一个小生境。<br />基于小生境遗传算法的Shubert函数优化算法计算结果<br />个体标号  x   x   f（x ， x ）<br />1  5.4828  4.8581  -186.731<br />2  5.4830  -7.7083   -186.731<br />3  4.8581  5.4831   -186.731<br />4  4.8581  -7.0838  -186.731<br />5  -4.4252  -7.4983  -186.731<br />6  -7.0832  -7.0838  -186.731<br />7  5.4827  -1.4249  -186.731<br />8  0.8580  5.4831  -186.731<br />9  4.8580  -0.8009  -186.730<br />10  -0.8009  -7.7084  -186.730<br />11  -0.8009  4.8581  -186.730<br />12  -7.7088  -0.7999  -186.730<br />13  -7.7088  -7.0831  -186.730<br />14  -1.4256  -0.8009  -186.730<br />15  -0.8011  -1.4252  -186.730<br />16  -7.7075  5.4834  -186.730<br />17  -7.7088  4.8579  -186.730<br />18  -7.0825  -1.4249  -186.730<br />下面再介绍一种隔离小生境技术的遗传算法<br />　 隔离小生境技术的基本概念及进化策略依照自然界的地理隔离技术,将遗传算法的初始群体分为几个子群体,子群体之间独立进化,各个子群体的进化快慢及规模取决于各个子群体的平均适应水平.由于隔离后的子群体彼此独立,界限分明,可以对各个子群体的进化过程灵活控制。生物界中,竞争不仅存在于个体之间,种群作为整体同样存在着竞争,适者生存的法则在种群这一层次上同样适用.在基于隔离的小生境技术中,是通过将种群的规模同种群个体平均适应值相联系来实现优胜劣汰、适者生存这一机制的.子群体平均适应值高,则其群体规模就大,反之,群体规模就小.生物界在进化过程中,适应环境的物种能得到更多的繁殖机会,其后代不断地增多,但这种增加不是无限制的,否则就会引起生态环境的失衡.在遗传算法中,群体的总体规模是一定的,为了保证群体中物种的多样性,就必须限制某些子群体的规模,称子群体中所允许的最大规模为子群体最大允许规模(maximum allowed scale),记为S .生物界中同样会出现某些物种因不适应环境数量逐渐减少,直至灭绝的现象.在隔离小生境机制中,为了保持群体的多样性,有时需要有意识地保护某些子群体,使之不会过早地被淘汰,并保持一定的进化能力.子群体的进化能力是和子群体的规模相联系的,要保证子群体的进化能力,必须规定每一子群体生存的最小规模,称为子群体最小生存规模(minimum live scale),记为S .在群体进化过程中,如果某一子群体在规定的代数内,持续表现最差,应该使这个子群体灭绝,代之以搜索空间的新解,这一最劣子群体灭绝的机制,定义为劣种不活(the worst die).子群体在进化过程中,如果出现两个子群体相似或相同的现象,则去掉其中的一个,代之以搜索空间的新解,这种策略称为同种互斥或种内竞争(intraspecific competition).解群中出现的新的子群体,在进化的初期往往无法同已经得到进化的其它子群体相竞争,如果不对此施加保护,这些新解往往在进化的初期就被淘汰掉,这显然是我们所不希望的.为了解决这个问题,必须对新产生的解加以保护,这种保护新解的策略叫幼弱保护(immature protection).子群体在进化过程中,如果收敛到或接近局部最优解,会出现进化停滞的现象,此时应当以某种概率将该子群体去掉,代之以搜索空间的新解,此种策略称为新老更替(the new superseding the old).在进化过程中,表现最优的个体进化为最优解的概率最大,应当使它充分进化,故新老更替策略不能用于最优子群体,这种做法称为优种保留(the best live).优种保留可以作用于最好的一个子群体,也可以作用于最好的几个子群体. <br />基于隔离小生境技术的遗传算法步骤<br />1)编码:针对具体问题,选择合适的编码方案,完成问题解空间向遗传算法解空间的转化. <br />2)产生初始群体:随机产生N个初始个体.<br />3)初始群体隔离:将N个初始个体均分给K个子群体,每个子群体含有的个体数均为N/K.<br />4)计算适应值:计算群体中所有个体的适应值.并保存适应值最高的个体.<br />5)确定子群体规模:子群体的规模同子群体的平均适应值相关,子群体的平均适应值越大,其在下一代中拥有的个体就越多;反之,在下一代中拥有的个体就少.但数目必须满足最大允许规模和最小保护规模的限制,即第t+1代第k个子群体的规模n (t+1)满足S ≤n (t+1) ≤S .<br />确定子群体规模的具体方法如下,首先给每个子群体都预分配S 个个体,剩下的个体根据子群体的平均适应值利用赌轮法选择,直到总的群体数量达到N为止.子群体的平均适应值一般可简单取为f (t)= (1)<br />式中f (t)为t代第k个子群体的平均适应值;f (t)为t代第k个子群体中第i个个体的适应值; n (t+1)为t代第k个子群体的规模.子群体k第t+1代的规模n (t+1)为：　　<br />n (t+1)=N . f (t)/ （2）<br />子群体规模的确定也可以根据其平均适应水平用赌轮法确定.<br />6)保护解除判定:对群体中施加保护的群体,进行保护解除判定,对满足保护解除条件的,撤除保护.<br />7)劣种不活判定:对解群中没有保护而连续几代表现又最差的群体,予以剔除并产生等规模的新子群体.<br />8)同种互斥判定:随机挑选出两个子群体,依据某种原则判定其相似程度,对满足相似条件的两个子群体,去掉其中的一个,产生同等规模的新解.<br />9)新老更替判定:判定解群中是否存在已经进化停滞的子群体,如果有,进行新老更替,产生同等规模的新解,但对包含最优个体的子群体要保留(最优保留机制).<br />10)重新计算适应值:对新产生的子群体计算适应性值,并施加幼弱保护措施.<br />11)子群体进化:由于子群体的规模同其在群体中的平均表现水平相联系,故子群体的规模是不断变化的.<br />根据公式(2)确定的规模,选择出子群体的繁殖个体,利用交叉和变异算子产生下一代解群.<br />12)收敛性判定,如果满足收敛性条件,或已经进化了规定的代数,则结束进化过程;否则返回第4步。<br />除了上面的还有下面几种常用的的小生境算法：<br />1 确定性拥挤算法<br />确定性拥挤（Deterministic crowding, DC）算法由Mahfoud 提出。该算法属于拥挤算法范畴，采用子个体与父个体直接进行竞争的模式，竞争的内容包括适应值和个体之间的距离。算法的过程如下：<br />确定性拥挤算法（重复G 代）<br />重复下列步骤N/2次：<br />（1）用放回的方式随机选择两个父个体p 和p 。<br />（2）对其进行杂交和变异，产生两个个体c 和c 。<br />（3）如果[d(p ，c )+d(p ，c )] [d(p ，c )+d(p ，c )]，则<br />如果f（c ）&gt;f（p ）,则用c 代替p ，否则保留p 。<br />如果f（c ）&gt;f（p ），则用c 替换p ，否则保留p 。<br />如果f（c ）&gt;f（p ），则用c 替换p ，否则保留p 。<br />如果f（c ）&gt;f（p ），则用c 替换p ，否则保留p 。<br />其中，N 是种群规模，的d（i，j）是个体i 和个体j 之间的距离。<br />2 限制锦标赛算法<br />限制锦标赛选择（Restricted tournament selection RTS）算法由Harik 提出。该算法属于拥挤算法范畴，采用了个体与种群中其它个体进行竞争的模式，竞争的内容包括适应值和个体之间的距离。该算法的过程如下：<br />限制锦标赛算法（重复G代）<br />重复下列步骤N/2次：<br />（1）  用有放回的方式随机选择两个父个体p 和p 。<br />（2）  对其进行杂夹和变异，产生两个子个体c 和才c 。<br />（3）  分别为c 和c从当前的种群中随机的选择出w个个体。<br />（4） 不失一般性，设d 和d 分别是w个个体的中与c 和c 距离最近的两个个体。<br />（5） 如果f（c ）&gt;f（d ），则用c 替d 换，否则保留d 。<br />如果f（c ）&gt;f（d ），则用c 替换d ，否则保留d 。<br />3多小生境拥挤算法<br />多小生境拥挤算法（Multi-niche crowding，MNC）由Cedeno提出。该算法属拥挤算法的范畴，采用种群中的若干个体相互竞争的模式，竞争的内容包括适应值和个体之间的距离。竞争选择出的老个体被新个体产生的子个体替换。算法的过程如下：<br />多小生境拥挤算法（重复G 代）<br />重复下列步骤N/2次：<br />（1）  用有放回的方式随机选父个体p 。<br />（2）  从种群中随机选择C 个体作为p 的交配候选集，从中选出与p 最接近的个体p 。<br />（3）  对p 和p 进行杂交和变异，产生两个个体c 和c 。<br />（4）  分别为c 和c 从中当前种群中各随机选择出C 群个体，每群个体包含w个个体。<br />（5）  每一群个体都选出一个与对应字个体距离最近的个体。这样就为每个个体产生了C 个替换候选个体。<br />（6）  不失一般性，设d 和d 是两个替换候选集中适应值最低的个体。<br />（7）  用c 替换d ，用c 替换d 。<br />Cedeno 还给出了C ，w和C 的最优参数值。C 应该在区间[2，4]内，C 和w至少应该两倍于用户希望找到的全局峰个数。该算法的步骤2提出了一中基于试探性的方法的限制交配策略。<br />4 标准适应值共享算法<br />标准适应值共享算法（Standard fitness sharing SH）由Goldberg 和Richardson 提出。该算法属于适应值共享算法范畴，事先需要给出解空间中小生境的半径，并假设解空间中峰半径均相同。算法的过程如下：<br />标准的适应值共享算法（重复G 代）<br />（1）  计算种群中个体之间的共享函数值sh（d ）<br />sh（d ）= <br />其中， 是事先给出的峰半径，d 是个体i和个体j之间的距离， 是控制共享函数形状的参数，一般取 =1（线形共享函数）。两个个体之间共享函数值越大，则两个个体越接近。<br />（2）  计算种群中个体的小生境数m <br />m = <br />其中，N 是种群规模。个体的小生境数越大，则该个体周围绕着越多其它个体。<br />（3）  计算种群中个体共享后的适应值f <br />f =f / m <br />（4）  用个体共享后的适应值进行选择，杂交和变异出新的个体，生成新一代种群。<br />Deb和Goldberg 在假设解空间中峰均匀分布并且峰半径相同的前提下，提出计算峰半径的计算公式。此外它们还提供了一种基于峰半径的限制交配策略，从而保证所有的杂交均在同一物种进行，确保了后代和父母的均属于同一小生境。标准适应值共享算法计算距离的时间复杂度为O（N ）。<br />5 清除算法<br />清除（Clearing）算法由Petrowski 提出。该算法属于适应值共性算法范畴，事先需要给出解空间的小生境半径 （重要参数）和小生境的容量 （次要参数），并假设解空间中峰值半径均相同。算法的过程如下：<br />清除算法（G）<br />（1）  按照适应值对个体进行降序排列。<br />（2）  将第一个体指定为第一个小生境中心。<br />（3）  从第二个个体开始顺序执行下列步骤到最后一个个体：<br />（3.1）如果当前个体到所有已指定小生境中心的距离均大于，则该个体被指定为一个新的小生境中心。该个成为优胜者。<br />（3.2）如果当前个体到某个已指定的小生境中心的距离小于，并且该小生境个数小于，则该个体加入到该小生境中去，该小生境的个体总数增加1。该个体成为优胜者。<br />（3.3）其它个体均为失败者。<br />（3.4）维持所有优胜者的适应度不变，将所有失败者的适应值置为0。<br />（4）用个体修改后的适应值进行选择，杂交和变异出新个体，生成新一代种群。<br />清除算法计算距离的时间复杂度为O（kN），其中k是该算法维持的小生境数量。如果将优胜者的小生境数看为一，而将失败者的小生境看作无穷大，则清除算法也可看作标准适应值共享算法的改进。<br />6 结合适应值共享的自适应k均值聚类算法<br />结合适应值共享的自适应算法k均值聚类算法（Adaptive k-mean clustering with fitness sharing）算法由Yin 和German提出。该算法属于适应值共性算法范畴，事先需要给出解空间中小生境中新建的最小距离 和小生境中的个体到该小生境中心之间的最大距离 。解空间中峰半径可能不相同。算法的过程如下：结合适应值共享的自适应k均值均类算法（重复G代）<br />（1）  按照适应值对个体进行降序排列。<br />（2）   产生在[1，N]之间的随机整数k（初始小生境个数）。<br />（3）  将前k个个体分别放入不同的小生境中并成为小生境中心。确保所有 小生境中心间距离大于 ，如果不能满足这一条件，则合并小生境，新的小生境中心就是该小生境中所有个体的中心。<br />（4）  对于其它N-k个个体中的每一个，计算其与当前所有想生境中心之间 的距离。如果距离大于 ，则生成新的小生境，该个体成为新小生境的中心。否则将该个体安排到距离最近的小生境中去。据需要确保所有小生境中心间的距离均大于 ，如果不能满足这一条件，则需要合并小生境。<br />（5）  所有个体均被安置完毕后，固定小生境的中心，将所有个体按照最小 <br />距离原则安排到最近的小生境中去。<br />（6）  计算计算种群个体的小生境数m <br />m =n - n （d /2 ） 若x C <br />其中，n 是第c个小生境中包含个个体总数，d 是个体i与它归属的小生境中心之间的距离，x 是第i个个体，C 第c 个小生境的个体基， 是控制函数形状的参数，通常 =1。<br />（7）  用公式计算个体共享后的适应值。<br />（8）  用个体共享后的适应值进行选择，杂交和变异出新的个体，生成新一 代个体种群。<br />结合适应值共享的自适应性k均值聚类算法计算距离的时间复杂度为O（Kn）。<br />7 动态小生境共享算法<br />动态小生境共享算方法（Dynamic niche sharing）是由Miller和Shaw 提出。该算法属于适应值共享算法范畴，事先需要给出解空间中小生境的半径 和小生境的数量k。算法的过程如下；<br />动态小生境共享算法（重复G代）<br />（1）  按照适应值对个体进行降序排列。<br />（2）  将第一个个体指定为第一个小生境中心。<br />（3）  从第二个个体开始顺序执行下列步骤到最后一个个体：<br />（3.1）如果当前个体与所有已指定的小生境中心之间的距离大于 ，而且已指定的小生境数量小于k，则形成一个新的小生境，该个体成为新小生境的中心。<br />（3.2）如果当前个体与所有小生境中心之间的距离均大于 ，而且已指定的小生境数量不小于k，则该个体成为独立个体。<br />（4） 对于那些属于某个小生境的个体，其小生境数就是它所属的小生境中个体的数量。对于那些独立个体，采用公式计算小生境数。<br />（5） 用公式计算个体共享后的适应值。<br />（6） 用共享后的适应值进行选择，杂交和变异出新的个体，生成新一代种群。动态小生境共享算法计算距离的时间复杂度为O（Kn）。<br />8 自适应小生境算法<br />自适应小生境算法（Adaptive nicking）由Goldberg 和 Wang 提出。该算法属于适应值共享算法范畴，事先需要给出解空间中小生境的半径 和小生境的数量k。算法包含两个分别被称为顾客和商家的个体群，利用这两个个体群的共同演化实现多峰优化的目的。顾客群类似于其它适应值共享算法中的种群，而商家群则代表搜索空间中峰的集合。商家群的个体数量k略大于其它适应值共享算法中的小生境树立功能。顾客群中的个体的适应值与其它适应值共享算法中个体的适应值相同，而商家群中的个体的适应值是属于该商家所有顾客的适应值之和。<br />算法需要首先在搜索空间中随机放置商家群的个体，其余的过程如下；<br />自适应小生境算法（重复G 代）<br />（1）  将每一个顾客群中的个体都安排到最近的商家中去。<br />（2）  计算所有顾客的小生境数（其归属的商家所拥有的顾客数量）。<br />（3）  用公式计算顾客群的个体共享后的适应值。<br />（4）  用顾客群中个体共享后的适应值尽心选择，杂交和变异出新的个体，生成新一代顾客群。<br />（5）  顺序选择每一个商家群中的个体并对其进行变异操作以产生新的商家。如果新商家的适应值比老商家的适应高，而且与其它商家之间的距离均小于，则新商家代替老商家。否则进行另外一次变异操作，直到产生可以替换的新商家或变异操作的次数超过指定的最大变异为止。<br />自适应小生境算法计算距离的时间的复杂度为O(Kn).<br /><br />from: <a href="http://qbwh.com/viewthread_123913.html">http://qbwh.com/viewthread_123913.html</a><img src ="http://www.blogjava.net/weidagang2046/aggbug/84798.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/weidagang2046/" target="_blank">weidagang2046</a> 2006-12-01 12:08 <a href="http://www.blogjava.net/weidagang2046/articles/84798.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Lisp之根源</title><link>http://www.blogjava.net/weidagang2046/articles/84653.html</link><dc:creator>weidagang2046</dc:creator><author>weidagang2046</author><pubDate>Thu, 30 Nov 2006 13:05:00 GMT</pubDate><guid>http://www.blogjava.net/weidagang2046/articles/84653.html</guid><wfw:comment>http://www.blogjava.net/weidagang2046/comments/84653.html</wfw:comment><comments>http://www.blogjava.net/weidagang2046/articles/84653.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/weidagang2046/comments/commentRss/84653.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/weidagang2046/services/trackbacks/84653.html</trackback:ping><description><![CDATA[
		<p>
				<strong>                                                                     保罗格雷厄姆<br /></strong>
				<br />约翰麦卡锡于1960年发表了一篇非凡的论文,他在这篇论文中对编程的贡献有如欧几里德对几何的贡献.<a href="http://daiyuwen.freeshell.org/gb/rol/roots_of_lisp.html#foot222" name="tex2html1"><sup>1</sup></a> 他向我们展示了,在只给定几个简单的操作符和一个表示函数的记号的基础上, 如何构造出一个完整的编程语言. 麦卡锡称这种语言为Lisp, 意为List Processing, 因为他的主要思想之一是用一种简单的数据结构表(list)来代表代码和数据. 
</p>
		<p>值得注意的是,麦卡锡所作的发现,不仅是计算机史上划时代的大事, 而且是一种在我们这个时代编程越来越趋向的模式.我认为目前为止只有两种真正干净利落, 始终如一的编程模式:C语言模式和Lisp语言模式.此二者就象两座高地, 在它们中间是尤如沼泽的低地.随着计算机变得越来越强大,新开发的语言一直在坚定地趋向于Lisp模式. 二十年来,开发新编程语言的一个流行的秘决是,取C语言的计算模式,逐渐地往上加Lisp模式的特性,例如运行时类型和无用单元收集. 
</p>
		<p>在这篇文章中我尽可能用最简单的术语来解释约翰麦卡锡所做的发现. 关键是我们不仅要学习某个人四十年前得出的有趣理论结果, 而且展示编程语言的发展方向. Lisp的不同寻常之处--也就是它优质的定义--是它能够自己来编写自己. 为了理解约翰麦卡锡所表述的这个特点,我们将追溯他的步伐,并将他的数学标记转换成能够运行的Common Lisp代码. 
</p>
		<p>
		</p>
		<h1>
				<a name="SECTION00010000000000000000">七个原始操作符</a>
		</h1>
		<p>开始我们先定义<em>表达式</em>.表达式或是一个<em>原子</em>(atom),它是一个字母序列(如 foo),或是一个由零个或多个表达式组成的<em>表</em>(list), 表达式之间用空格分开, 放入一对括号中. 以下是一些表达式: 
</p>
		<p>
		</p>
		<pre>foo
()
(foo)
(foo bar)
(a b (c) d)
</pre>最后一个表达式是由四个元素组成的表, 第三个元素本身是由一个元素组成的表. 
<p>在算术中表达式 1 + 1 得出值2. 正确的Lisp表达式也有值. 如果表达式<i>e</i>得出值<i>v</i>,我们说<i>e</i><em>返回</em><i>v</i>. 下一步我们将定义几种表达式以及它们的返回值. 
</p><p>如果一个表达式是表,我们称第一个元素为<em>操作符</em>,其余的元素为<em>自变量</em>.我们将定义七个原始(从公理的意义上说)操作符: quote,atom,eq,car,cdr,cons,和 cond. 
</p><p></p><ol><li>(quote <i>x</i>) 返回<i>x</i>.为了可读性我们把(quote <i>x</i>)简记 为'<i>x</i>. 
<p></p><pre>&gt; (quote a)
a
&gt; 'a
a
&gt; (quote (a b c))
(a b c)
</pre><p></p></li><li>(atom <i>x</i>)返回原子t如果<i>x</i>的值是一个原子或是空表,否则返回(). 在Lisp中我们按惯例用原子t表示真, 而用空表表示假. 
<p></p><pre>&gt; (atom 'a)
t
&gt; (atom '(a b c))
()
&gt; (atom '())
t
</pre><p>既然有了一个自变量需要求值的操作符, 我们可以看一下quote的作用. 通过引用(quote)一个表,我们避免它被求值. 一个未被引用的表作为自变量传给象 atom这样的操作符将被视为代码: 
</p><p></p><pre>&gt; (atom (atom 'a))
t
</pre><p>反之一个被引用的表仅被视为表, 在此例中就是有两个元素的表: 
</p><p></p><pre>&gt; (atom '(atom 'a))
()
</pre><p>这与我们在英语中使用引号的方式一致. Cambridge(剑桥)是一个位于麻萨诸塞州有90000人口的城镇. 而``Cambridge''是一个由9个字母组成的单词. 
</p><p>引用看上去可能有点奇怪因为极少有其它语言有类似的概念. 它和Lisp最与众不同的特征紧密联系:代码和数据由相同的数据结构构成, 而我们用quote操作符来区分它们. 
</p><p></p></li><li>(eq <i>x</i><i>y</i>)返回t如果<i>x</i>和<i>y</i>的值是同一个原子或都是空表, 否则返回(). 
<p></p><pre>&gt; (eq 'a 'a)
t
&gt; (eq 'a 'b)
()
&gt; (eq '() '())
t
</pre><p></p></li><li>(car <i>x</i>)期望<i>x</i>的值是一个表并且返回<i>x</i>的第一个元素. 
<p></p><pre>&gt; (car '(a b c))
a
</pre><p></p></li><li>(cdr <i>x</i>)期望<i>x</i>的值是一个表并且返回<i>x</i>的第一个元素之后的所有元素. <pre>&gt; (cdr '(a b c))
(b c)
</pre><p></p></li><li>(cons <i>x</i><i>y</i>)期望<i>y</i>的值是一个表并且返回一个新表,它的第一个元素是<i>x</i>的值, 后面跟着<i>y</i>的值的各个元素. 
<p></p><pre>&gt; (cons 'a '(b c))
(a b c)
&gt; (cons 'a (cons 'b (cons 'c '())))
(a b c)
&gt; (car (cons 'a '(b c)))
a
&gt; (cdr (cons 'a '(b c)))
(b c)
</pre></li><li>(cond (<img height="28" alt="$p_{1}$" src="http://daiyuwen.freeshell.org/gb/rol/img1.png" width="19" align="middle" border="0" />...<img height="28" alt="$e_{1}$" src="http://daiyuwen.freeshell.org/gb/rol/img2.png" width="19" align="middle" border="0" />) ...(<img height="28" alt="$p_{n}$" src="http://daiyuwen.freeshell.org/gb/rol/img3.png" width="21" align="middle" border="0" />...<img height="28" alt="$e_{n}$" src="http://daiyuwen.freeshell.org/gb/rol/img4.png" width="20" align="middle" border="0" />)) 的求值规则如下. <i>p</i>表达式依次求值直到有一个返回t. 如果能找到这样的<i>p</i>表达式,相应的<i>e</i>表达式的值作为整个cond表达式的返回值. 
<p></p><pre>&gt; (cond ((eq 'a 'b) 'first)
        ((atom 'a)  'second))
second
</pre><p>当表达式以七个原始操作符中的五个开头时,它的自变量总是要求值的.<a href="http://daiyuwen.freeshell.org/gb/rol/roots_of_lisp.html#foot84" name="tex2html2"><sup>2</sup></a> 我们称这样 的操作符为<em>函数</em>. </p></li></ol><p></p><h1><a name="SECTION00020000000000000000">函数的表示</a></h1>接着我们定义一个记号来描述函数.函数表示为(lambda (<img height="28" alt="$p_{1}$" src="http://daiyuwen.freeshell.org/gb/rol/img1.png" width="19" align="middle" border="0" />...<img height="28" alt="$p_{n}$" src="http://daiyuwen.freeshell.org/gb/rol/img3.png" width="21" align="middle" border="0" />) <i>e</i>),其中 <img height="28" alt="$p_{1}$" src="http://daiyuwen.freeshell.org/gb/rol/img1.png" width="19" align="middle" border="0" />...<img height="28" alt="$p_{n}$" src="http://daiyuwen.freeshell.org/gb/rol/img3.png" width="21" align="middle" border="0" />是原子(叫做<em>参数</em>),<i>e</i>是表达式. 如果表达式的第一个元素形式如上 
<p><tt>((lambda (<img height="28" alt="$p_{1}$" src="http://daiyuwen.freeshell.org/gb/rol/img1.png" width="19" align="middle" border="0" />...<img height="28" alt="$p_{n}$" src="http://daiyuwen.freeshell.org/gb/rol/img3.png" width="21" align="middle" border="0" />) <i>e</i>) <img height="28" alt="$a_{1}$" src="http://daiyuwen.freeshell.org/gb/rol/img5.png" width="20" align="middle" border="0" />...<img height="28" alt="$a_{n}$" src="http://daiyuwen.freeshell.org/gb/rol/img6.png" width="21" align="middle" border="0" />) </tt></p><p>则称为<em>函数调用</em>.它的值计算如下.每一个表达式<img height="28" alt="$a_{i}$" src="http://daiyuwen.freeshell.org/gb/rol/img7.png" width="18" align="middle" border="0" />先求值,然后<i>e</i>再求值.在<i>e</i>的求值过程中,每个出现在<i>e</i>中的<img height="28" alt="$p_{i}$" src="http://daiyuwen.freeshell.org/gb/rol/img8.png" width="17" align="middle" border="0" />的值是相应的<img height="28" alt="$a_{i}$" src="http://daiyuwen.freeshell.org/gb/rol/img7.png" width="18" align="middle" border="0" />在最近一次的函数调用中的值. 
</p><p></p><pre>&gt; ((lambda (x) (cons x '(b))) 'a)
(a b)
&gt; ((lambda (x y) (cons x (cdr y)))
   'z
   '(a b c))
(z b c)
</pre>如果一个表达式的第一个元素<i>f</i>是原子且<i>f</i>不是原始操作符 
<p><tt>(f <img height="28" alt="$a_{1}$" src="http://daiyuwen.freeshell.org/gb/rol/img5.png" width="20" align="middle" border="0" />...<img height="28" alt="$a_{n}$" src="http://daiyuwen.freeshell.org/gb/rol/img6.png" width="21" align="middle" border="0" />) </tt></p><p>并且<i>f</i>的值是一个函数(lambda (<img height="28" alt="$p_{1}$" src="http://daiyuwen.freeshell.org/gb/rol/img1.png" width="19" align="middle" border="0" />...<img height="28" alt="$p_{n}$" src="http://daiyuwen.freeshell.org/gb/rol/img3.png" width="21" align="middle" border="0" />)),则以上表达式的值就是 
</p><p><tt>((lambda (<img height="28" alt="$p_{1}$" src="http://daiyuwen.freeshell.org/gb/rol/img1.png" width="19" align="middle" border="0" />...<img height="28" alt="$p_{n}$" src="http://daiyuwen.freeshell.org/gb/rol/img3.png" width="21" align="middle" border="0" />) <i>e</i>) <img height="28" alt="$a_{1}$" src="http://daiyuwen.freeshell.org/gb/rol/img5.png" width="20" align="middle" border="0" />...<img height="28" alt="$a_{n}$" src="http://daiyuwen.freeshell.org/gb/rol/img6.png" width="21" align="middle" border="0" />) </tt></p><p>的值. 换句话说,参数在表达式中不但可以作为自变量也可以作为操作符使用: 
</p><p></p><pre>&gt; ((lambda (f) (f '(b c)))
   '(lambda (x) (cons 'a x)))
(a b c)
</pre><p>有另外一个函数记号使得函数能提及它本身,这样我们就能方便地定义递归函数.<a href="http://daiyuwen.freeshell.org/gb/rol/roots_of_lisp.html#foot108" name="tex2html3"><sup>3</sup></a> 记号 
</p><p><tt>(label f (lambda (<img height="28" alt="$p_{1}$" src="http://daiyuwen.freeshell.org/gb/rol/img1.png" width="19" align="middle" border="0" />...<img height="28" alt="$p_{n}$" src="http://daiyuwen.freeshell.org/gb/rol/img3.png" width="21" align="middle" border="0" />) <i>e</i>)) </tt></p><p>表示一个象(lambda (<img height="28" alt="$p_{1}$" src="http://daiyuwen.freeshell.org/gb/rol/img1.png" width="19" align="middle" border="0" />...<img height="28" alt="$p_{n}$" src="http://daiyuwen.freeshell.org/gb/rol/img3.png" width="21" align="middle" border="0" />) <i>e</i>)那样的函数,加上这样的特性: 任何出现在<i>e</i>中的<i>f</i>将求值为此label表达式, 就好象<i>f</i>是此函数的参数. 
</p><p>假设我们要定义函数(subst <i>x y z</i>), 它取表达式<i>x</i>,原子<i>y</i>和表<i>z</i>做参数,返回一个象<i>z</i>那样的表, 不过<i>z</i>中出现的<i>y</i>(在任何嵌套层次上)被<i>x</i>代替. </p><pre>&gt; (subst 'm 'b '(a b (a b c) d))
(a m (a m c) d)
</pre>我们可以这样表示此函数 <pre>(label subst (lambda (x y z)
               (cond ((atom z)
                      (cond ((eq z y) x)
                            ('t z)))
                     ('t (cons (subst x y (car z))
                               (subst x y (cdr z)))))))
</pre>我们简记<i>f</i>=(label <i>f</i> (lambda (<img height="28" alt="$p_{1}$" src="http://daiyuwen.freeshell.org/gb/rol/img1.png" width="19" align="middle" border="0" />...<img height="28" alt="$p_{n}$" src="http://daiyuwen.freeshell.org/gb/rol/img3.png" width="21" align="middle" border="0" />) <i>e</i>))为 
<p><tt>(defun <i>f</i> (<img height="28" alt="$p_{1}$" src="http://daiyuwen.freeshell.org/gb/rol/img1.png" width="19" align="middle" border="0" />...<img height="28" alt="$p_{n}$" src="http://daiyuwen.freeshell.org/gb/rol/img3.png" width="21" align="middle" border="0" />) <i>e</i>) </tt></p><p>于是 </p><pre>(defun subst (x y z)
  (cond ((atom z)
         (cond ((eq z y) x)
               ('t z)))
        ('t (cons (subst x y (car z))
                  (subst x y (cdr z))))))
</pre>偶然地我们在这儿看到如何写cond表达式的缺省子句. 第一个元素是't的子句总是会成功的. 于是 
<p><tt>(cond (<i>x y</i>) ('t <i>z</i>)) </tt></p><p>等同于我们在某些语言中写的 
</p><p><tt>if <i>x</i> then <i>y</i> else <i>z</i></tt></p><p></p><h1><a name="SECTION00030000000000000000">一些函数</a></h1>既然我们有了表示函数的方法,我们根据七个原始操作符来定义一些新的函数. 为了方便我们引进一些常见模式的简记法. 我们用c<i>x</i>r,其中<i>x</i>是a或d的序列,来简记相应的car和cdr的组合. 比如(cadr <i>e</i>)是(car (cdr <i>e</i>))的简记,它返回<i>e</i>的第二个元素. 
<p></p><pre>&gt; (cadr '((a b) (c d) e))
(c d)
&gt; (caddr '((a b) (c d) e))
e
&gt; (cdar '((a b) (c d) e))
(b)
</pre>我们还用(list <img height="28" alt="$e_{1}$" src="http://daiyuwen.freeshell.org/gb/rol/img2.png" width="19" align="middle" border="0" />...<img height="28" alt="$e_{n}$" src="http://daiyuwen.freeshell.org/gb/rol/img4.png" width="20" align="middle" border="0" />)表示(cons <img height="28" alt="$e_{1}$" src="http://daiyuwen.freeshell.org/gb/rol/img2.png" width="19" align="middle" border="0" />...(cons <img height="28" alt="$e_{n}$" src="http://daiyuwen.freeshell.org/gb/rol/img4.png" width="20" align="middle" border="0" />'()) ...). <pre>&gt; (cons 'a (cons 'b (cons 'c '())))
(a b c)
&gt; (list 'a 'b 'c)
(a b c)
</pre><p>现在我们定义一些新函数. 我在函数名后面加了点,以区别函数和定义它们的原始函数,也避免与现存的common Lisp的函数冲突. 
</p><p></p><ol><li>(null. <i>x</i>)测试它的自变量是否是空表. 
<p></p><pre>(defun null. (x)
  (eq x '()))

&gt; (null. 'a)
()
&gt; (null. '())
t
</pre><p></p></li><li>(and. <i>x y</i>)返回t如果它的两个自变量都是t, 否则返回(). 
<p></p><pre>(defun and. (x y)
  (cond (x (cond (y 't) ('t '())))
        ('t '())))

&gt; (and. (atom 'a) (eq 'a 'a))
t
&gt; (and. (atom 'a) (eq 'a 'b))
()
</pre><p></p></li><li>(not. <i>x</i>)返回t如果它的自变量返回(),返回()如果它的自变量返回t. 
<p></p><pre>(defun not. (x)
  (cond (x '())
        ('t 't)))

&gt; (not. (eq 'a 'a))
()
&gt; (not. (eq 'a 'b))
t
</pre><p></p></li><li>(append. x y)取两个表并返回它们的连结. 
<p></p><pre>(defun append. (x y)
   (cond ((null. x) y)
         ('t (cons (car x) (append. (cdr x) y)))))

&gt; (append. '(a b) '(c d))
(a b c d)
&gt; (append. '() '(c d))
(c d)
</pre><p></p></li><li>(pair. <i>x y</i>)取两个相同长度的表,返回一个由双元素表构成的表,双元素表是相应位置的x,y的元素对. 
<p></p><pre>(defun pair. (x y)
  (cond ((and. (null. x) (null. y)) '())
        ((and. (not. (atom x)) (not. (atom y)))
         (cons (list (car x) (car y))
               (pair. (cdr) (cdr y))))))

&gt; (pair. '(x y z) '(a b c))
((x a) (y b) (z c))
</pre><p></p></li><li>(assoc. <i>x y</i>)取原子<i>x</i>和形如pair.函数所返回的表<i>y</i>,返回<i>y</i>中第一个符合如下条件的表的第二个元素:它的第一个元素是<i>x</i>. 
<p></p><pre>(defun assoc. (x y)
  (cond ((eq (caar y) x) (cadar y))
        ('t (assoc. x (cdr y)))))

&gt; (assoc. 'x '((x a) (y b)))
a
&gt; (assoc. 'x '((x new) (x a) (y b)))
new
</pre></li></ol><p></p><h1><a name="SECTION00040000000000000000">一个惊喜</a></h1>因此我们能够定义函数来连接表,替换表达式等等.也许算是一个优美的表示法, 那下一步呢? 现在惊喜来了. 我们可以写一个函数作为我们语言的解释器:此函数取任意Lisp表达式作自变量并返回它的值. 如下所示: 
<p></p><pre>(defun eval. (e a)
  (cond 
    ((atom e) (assoc. e a))
    ((atom (car e))
     (cond 
       ((eq (car e) 'quote) (cadr e))
       ((eq (car e) 'atom)  (atom   (eval. (cadr e) a)))
       ((eq (car e) 'eq)    (eq     (eval. (cadr e) a)
                                    (eval. (caddr e) a)))
       ((eq (car e) 'car)   (car    (eval. (cadr e) a)))
       ((eq (car e) 'cdr)   (cdr    (eval. (cadr e) a)))
       ((eq (car e) 'cons)  (cons   (eval. (cadr e) a)
                                    (eval. (caddr e) a)))
       ((eq (car e) 'cond)  (evcon. (cdr e) a))
       ('t (eval. (cons (assoc. (car e) a)
                        (cdr e))
                  a))))
    ((eq (caar e) 'label)
     (eval. (cons (caddar e) (cdr e))
            (cons (list (cadar e) (car e)) a)))
    ((eq (caar e) 'lambda)
     (eval. (caddar e)
            (append. (pair. (cadar e) (evlis. (cdr  e) a))
                     a)))))

(defun evcon. (c a)
  (cond ((eval. (caar c) a)
         (eval. (cadar c) a))
        ('t (evcon. (cdr c) a))))

(defun evlis. (m a)
  (cond ((null. m) '())
        ('t (cons (eval.  (car m) a)
                  (evlis. (cdr m) a)))))
</pre>eval.的定义比我们以前看到的都要长. 让我们考虑它的每一部分是如何工作的. 
<p>eval.有两个自变量: e是要求值的表达式, a是由一些赋给原子的值构成的表,这些值有点象函数调用中的参数. 这个形如pair.的返回值的表叫做<em>环境</em>. 正是为了构造和搜索这种表我们才写了pair.和assoc.. 
</p><p>eval.的骨架是一个有四个子句的cond表达式. 如何对表达式求值取决于它的类型. 第一个子句处理原子. 如果e是原子, 我们在环境中寻找它的值: 
</p><p></p><pre>&gt; (eval. 'x '((x a) (y b)))
a
</pre><p>第二个子句是另一个cond, 它处理形如(<i>a</i> ...)的表达式, 其中<i>a</i>是原子. 这包括所有的原始操作符, 每个对应一条子句. 
</p><p></p><pre>&gt; (eval. '(eq 'a 'a) '())
t
&gt; (eval. '(cons x '(b c))
         '((x a) (y b)))
(a b c)
</pre>这几个子句(除了quote)都调用eval.来寻找自变量的值. 
<p>最后两个子句更复杂些. 为了求cond表达式的值我们调用了一个叫 evcon.的辅助函数. 它递归地对cond子句进行求值,寻找第一个元素返回t的子句. 如果找到了这样的子句, 它返回此子句的第二个元素. 
</p><p></p><pre>&gt; (eval. '(cond ((atom x) 'atom)
                ('t 'list))
         '((x '(a b))))
list
</pre><p>第二个子句的最后部分处理函数调用. 它把原子替换为它的值(应该是lambda 或label表达式)然后对所得结果表达式求值. 于是 
</p><p></p><pre>(eval. '(f '(b c))
       '((f (lambda (x) (cons 'a x)))))
</pre>变为 <pre>(eval. '((lambda (x) (cons 'a x)) '(b c))
       '((f (lambda (x) (cons 'a x)))))
</pre>它返回(a b c). 
<p>eval.的最后cond两个子句处理第一个元素是lambda或label的函数调用.为了对label 表达式求值, 先把函数名和函数本身压入环境, 然后调用eval.对一个内部有 lambda的表达式求值. 即: 
</p><p></p><pre>(eval. '((label firstatom (lambda (x)
                            (cond ((atom x) x)
                                  ('t (firstatom (car x))))))
         y)
       '((y ((a b) (c d)))))
</pre>变为 <pre>(eval. '((lambda (x)
           (cond ((atom x) x)
                 ('t (firstatom (car x)))))
         y)
        '((firstatom
           (label firstatom (lambda (x)
                            (cond ((atom x) x)
                                  ('t (firstatom (car x)))))))
          (y ((a b) (c d)))))
</pre>最终返回a. 
<p>最后,对形如((lambda (<img height="28" alt="$p_{1}$" src="http://daiyuwen.freeshell.org/gb/rol/img1.png" width="19" align="middle" border="0" />...<img height="28" alt="$p_{n}$" src="http://daiyuwen.freeshell.org/gb/rol/img3.png" width="21" align="middle" border="0" />) <i>e</i>) <img height="28" alt="$a_{1}$" src="http://daiyuwen.freeshell.org/gb/rol/img5.png" width="20" align="middle" border="0" />...<img height="28" alt="$a_{n}$" src="http://daiyuwen.freeshell.org/gb/rol/img6.png" width="21" align="middle" border="0" />)的表达式求值,先调用evlis.来求得自变量(<img height="28" alt="$a_{1}$" src="http://daiyuwen.freeshell.org/gb/rol/img5.png" width="20" align="middle" border="0" />...<img height="28" alt="$a_{n}$" src="http://daiyuwen.freeshell.org/gb/rol/img6.png" width="21" align="middle" border="0" />)对应的值(<img height="28" alt="$v_{1}$" src="http://daiyuwen.freeshell.org/gb/rol/img9.png" width="19" align="middle" border="0" />...<img height="28" alt="$v_{n}$" src="http://daiyuwen.freeshell.org/gb/rol/img10.png" width="21" align="middle" border="0" />),把(<img height="28" alt="$p_{1}$" src="http://daiyuwen.freeshell.org/gb/rol/img1.png" width="19" align="middle" border="0" /><img height="28" alt="$v_{1}$" src="http://daiyuwen.freeshell.org/gb/rol/img9.png" width="19" align="middle" border="0" />)...(<img height="28" alt="$p_{n}$" src="http://daiyuwen.freeshell.org/gb/rol/img3.png" width="21" align="middle" border="0" /><img height="28" alt="$v_{n}$" src="http://daiyuwen.freeshell.org/gb/rol/img10.png" width="21" align="middle" border="0" />)添加到环境里, 然后对<i>e</i>求值. 于是 
</p><p></p><pre>(eval. '((lambda (x y) (cons x (cdr y)))
         'a
         '(b c d))
       '())
</pre>变为 <pre>(eval. '(cons x (cdr y))
       '((x a) (y (b c d))))
</pre>最终返回(a c d). 
<p></p><h1><a name="SECTION00050000000000000000">后果</a></h1><p>既然理解了eval是如何工作的, 让我们回过头考虑一下这意味着什么. 我们在这儿得到了一个非常优美的计算模型. 仅用quote,atom,eq,car,cdr,cons,和cond, 我们定义了函数eval.,它事实上实现了我们的语言,用它可以定义任何我们想要的额外的函数. 
</p><p>当然早已有了各种计算模型--最著名的是图灵机. 但是图灵机程序难以读懂. 如果你要一种描述算法的语言, 你可能需要更抽象的, 而这就是约翰麦卡锡定义 Lisp的目标之一. 
</p><p>约翰麦卡锡于1960年定义的语言还缺不少东西. 它没有副作用, 没有连续执行 (它得和副作用在一起才有用), 没有实际可用的数,<a href="http://daiyuwen.freeshell.org/gb/rol/roots_of_lisp.html#foot199" name="tex2html4"><sup>4</sup></a> 没有动态可视域. 但这些限制可以令人惊讶地用极少的额外代码来补救. Steele和Sussman在一篇叫做``解释器的艺术''的著名论文中描述了如何做到这点.<a href="http://daiyuwen.freeshell.org/gb/rol/roots_of_lisp.html#foot200" name="tex2html5"><sup>5</sup></a></p><p>如果你理解了约翰麦卡锡的eval, 那你就不仅仅是理解了程序语言历史中的一个阶段. 这些思想至今仍是Lisp的语义核心. 所以从某种意义上, 学习约翰麦卡锡的原著向我们展示了Lisp究竟是什么. 与其说Lisp是麦卡锡的设计,不如说是他的发现. 它不是生来就是一门用于人工智能, 快速原型开发或同等层次任务的语言. 它是你试图公理化计算的结果(之一). 
</p><p>随着时间的推移, 中级语言, 即被中间层程序员使用的语言, 正一致地向Lisp靠近. 因此通过理解eval你正在明白将来的主流计算模式会是什么样. 
</p><p></p><h1><a name="SECTION00060000000000000000">注释</a></h1>把约翰麦卡锡的记号翻译为代码的过程中我尽可能地少做改动. 我有过让代码更容易阅读的念头, 但是我还是想保持原汁原味. 
<p>在约翰麦卡锡的论文中,假用f来表示, 而不是空表. 我用空表表示假以使例子能在Common Lisp中运行. (fixme) 
</p><p>我略过了构造dotted pairs, 因为你不需要它来理解eval. 我也没有提apply, 虽然是apply(它的早期形式, 主要作用是引用自变量), 被约翰麦卡锡在1960年称为普遍函数, eval只是不过是被apply调用的子程序来完成所有的工作. 
</p><p>我定义了list和c<i>x</i>r等作为简记法因为麦卡锡就是这么做的. 实际上 c<i>x</i>r等可以被定义为普通的函数. List也可以这样, 如果我们修改eval, 这很容易做到, 让函数可以接受任意数目的自变量. 
</p><p>麦卡锡的论文中只有五个原始操作符. 他使用了cond和quote,但可能把它们作为他的元语言的一部分. 同样他也没有定义逻辑操作符and和not, 这不是个问题, 因为它们可以被定义成合适的函数. 
</p><p>在eval.的定义中我们调用了其它函数如pair.和assoc.,但任何我们用原始操作符定义的函数调用都可以用eval.来代替. 即 </p><pre>(assoc. (car e) a)
</pre>能写成 
<p></p><pre>(eval. '((label assoc.
                (lambda (x y)
                  (cond ((eq (caar y) x) (cadar y))
                        ('t (assoc. x (cdr y))))))
         (car e)
         a)
        (cons (list 'e e) (cons (list 'a a) a)))
</pre><p>麦卡锡的eval有一个错误. 第16行是(相当于)(evlis. (cdr e) a)而不是(cdr e), 这使得自变量在一个有名函数的调用中被求值两次. 这显示当论文发表的时候, eval的这种描述还没有用IBM 704机器语言实现. 它还证明了如果不去运行程序, 要保证不管多短的程序的正确性是多么困难. 
</p><p>我还在麦卡锡的论文中碰到一个问题. 在定义了eval之后, 他继续给出了一些更高级的函数--接受其它函数作为自变量的函数. 他定义了maplist: 
</p><p></p><pre>(label maplist
       (lambda (x f)
         (cond ((null x) '())
               ('t (cons (f x) (maplist (cdr x) f))))))
</pre>然后用它写了一个做微分的简单函数diff. 但是diff传给maplist一个用<i>x</i>做参数的函数, 对它的引用被maplist中的参数x所捕获.<a href="http://daiyuwen.freeshell.org/gb/rol/roots_of_lisp.html#foot211" name="tex2html6"><sup>6</sup></a><p>这是关于动态可视域危险性的雄辩证据, 即使是最早的更高级函数的例子也因为它而出错. 可能麦卡锡在1960年还没有充分意识到动态可视域的含意. 动态可视域令人惊异地在Lisp实现中存在了相当长的时间--直到Sussman和Steele于 1975年开发了Scheme. 词法可视域没使eval的定义复杂多少, 却使编译器更难写了. 
</p><p></p><h1><a name="SECTION00070000000000000000">About this document ...</a></h1><strong>Lisp之根源</strong><p>This document was generated using the <a href="http://www-texdev.mpce.mq.edu.au/l2h/docs/manual/"><strong>LaTeX</strong>2<tt>HTML</tt></a> translator Version 2K.1beta (1.48) 
</p><p>Copyright © 1993, 1994, 1995, 1996, <a href="http://cbl.leeds.ac.uk/nikos/personal.html">Nikos Drakos</a>, Computer Based Learning Unit, University of Leeds. <br />Copyright © 1997, 1998, 1999, <a href="http://www.maths.mq.edu.au/~ross/">Ross Moore</a>, Mathematics Department, Macquarie University, Sydney. 
</p><p>The command line arguments were: <br /><strong>latex2html</strong><tt>-split=0 roots_of_lisp.tex</tt></p><p>The translation was initiated by Dai Yuwen on 2003-10-24 <br /></p><hr /><h4>Footnotes</h4><dl><dt><a name="foot222">... 欧几里德对几何的贡献.</a><a href="http://daiyuwen.freeshell.org/gb/rol/roots_of_lisp.html#tex2html1" name="foot222"><sup>1</sup></a></dt><dd>``Recursive Functions of Symbolic Expressions and Their Computation by Machine, Part1.'' <i>Communication of the ACM</i> 3:4, April 1960, pp. 184-195. 
</dd><dt><a name="foot84">...当表达式以七个原始操作符中的五个开头时,它的自变量总是要求值的.</a><a href="http://daiyuwen.freeshell.org/gb/rol/roots_of_lisp.html#tex2html2" name="foot84"><sup>2</sup></a></dt><dd>以另外两个操作符quote和cond开头的表达式以不同的方式求值. 当 quote表达式求值时, 它的自变量不被求值,而是作为整个表达式的值返回. 在 一个正确的cond表达式中, 只有L形路径上的子表达式会被求值. 
</dd><dt><a name="foot108">... 数.</a><a href="http://daiyuwen.freeshell.org/gb/rol/roots_of_lisp.html#tex2html3" name="foot108"><sup>3</sup></a></dt><dd>逻辑上我们不需要为了这定义一个新的记号. 在现有的记号中用 一个叫做Y组合器的函数上的函数, 我们可以定义递归函数. 可能麦卡锡在写 这篇论文的时候还不知道Y组合器; 无论如何, label可读性更强. 
</dd><dt><a name="foot199">... 没有实际可用的数,</a><a href="http://daiyuwen.freeshell.org/gb/rol/roots_of_lisp.html#tex2html4" name="foot199"><sup>4</sup></a></dt><dd>在麦卡锡的1960 年的Lisp中, 做算术是可能的, 比如用一个有n个原子的表表示数n. 
</dd><dt><a name="foot200">... 的艺术''的著名论文中描述了如何做到这点.</a><a href="http://daiyuwen.freeshell.org/gb/rol/roots_of_lisp.html#tex2html5" name="foot200"><sup>5</sup></a></dt><dd>Guy Lewis Steele, Jr. and Gerald Jay Sussman, ``The Art of the Interpreter, or the Modularity Complex(Parts Zero,One,and Two),'' MIT AL Lab Memo 453, May 1978. 
</dd><dt><a name="foot211">... 对它的引用被maplist中的参数x所捕获.</a><a href="http://daiyuwen.freeshell.org/gb/rol/roots_of_lisp.html#tex2html6" name="foot211"><sup>6</sup></a></dt><dd>当代的Lisp程序 员在这儿会用mapcar代替maplist. 这个例子解开了一个谜团: maplist为什 么会在Common Lisp中. 它是最早的映射函数, mapcar是后来增加的. <br /><br />from: <a href="http://daiyuwen.freeshell.org/gb/rol/roots_of_lisp.html">http://daiyuwen.freeshell.org/gb/rol/roots_of_lisp.html</a></dd></dl><img src ="http://www.blogjava.net/weidagang2046/aggbug/84653.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/weidagang2046/" target="_blank">weidagang2046</a> 2006-11-30 21:05 <a href="http://www.blogjava.net/weidagang2046/articles/84653.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>双核CPU上的快速排序效率</title><link>http://www.blogjava.net/weidagang2046/articles/83970.html</link><dc:creator>weidagang2046</dc:creator><author>weidagang2046</author><pubDate>Tue, 28 Nov 2006 01:58:00 GMT</pubDate><guid>http://www.blogjava.net/weidagang2046/articles/83970.html</guid><wfw:comment>http://www.blogjava.net/weidagang2046/comments/83970.html</wfw:comment><comments>http://www.blogjava.net/weidagang2046/articles/83970.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/weidagang2046/comments/commentRss/83970.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/weidagang2046/services/trackbacks/83970.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: 为了试验一下多核CPU上排序算法的效率，得比较单任务情况下和多任务并行排序算法的差距，因此选用快速排序算法来进行比较。		测试环境：双核CPU 2.66GHZ						          单核CPU 2.4GHZ		 		以下是一个快速排序算法的源代码：												UINT														Split									...&nbsp;&nbsp;<a href='http://www.blogjava.net/weidagang2046/articles/83970.html'>阅读全文</a><img src ="http://www.blogjava.net/weidagang2046/aggbug/83970.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/weidagang2046/" target="_blank">weidagang2046</a> 2006-11-28 09:58 <a href="http://www.blogjava.net/weidagang2046/articles/83970.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>李开复：算法的力量</title><link>http://www.blogjava.net/weidagang2046/articles/83968.html</link><dc:creator>weidagang2046</dc:creator><author>weidagang2046</author><pubDate>Tue, 28 Nov 2006 01:56:00 GMT</pubDate><guid>http://www.blogjava.net/weidagang2046/articles/83968.html</guid><wfw:comment>http://www.blogjava.net/weidagang2046/comments/83968.html</wfw:comment><comments>http://www.blogjava.net/weidagang2046/articles/83968.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/weidagang2046/comments/commentRss/83968.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/weidagang2046/services/trackbacks/83968.html</trackback:ping><description><![CDATA[
		<div class="content" id="fontzoom">
				<p>算法是计算机科学领域最重要的基石之一，但却受到了国内一些程序员的冷落。许多学生看到一些公司在招聘时要求的编程语言五花八门就产生了一种误解，认为学计算机就是学各种编程语言，或者认为，学习最新的语言、技术、标准就是最好的铺路方法。其实大家都被这些公司误导了。编程语言虽然该学，但是学习计算机算法和理论更重要，因为计算机算法和理论更重要，因为计算机语言和开发平台日新月异，但万变不离其宗的是那些算法和理论，例如数据结构、算法、编译原理、计算机体系结构、关系型数据库原理等等。在“开复学生网”上，有位同学生动地把这些基础课程比拟为“内功”，把新的语言、技术、标准比拟为“外功”。整天赶时髦的人最后只懂得招式，没有功力，是不可能成为高手的。 
</p>
				<p>
				</p>
				<p>
						<br />
						<b>算法与我</b>
				</p>
				<p>当我在1980年转入计算机科学系时，还没有多少人的专业方向是计算机科学。有许多其他系的人嘲笑我们说：“知道为什么只有你们系要加一个‘科学 ’，而没有‘物理科学系’或‘化学科学系’吗？因为人家是真的科学，不需要画蛇添足，而你们自己心虚，生怕不‘科学’，才这样欲盖弥彰。”其实，这点他们彻底弄错了。真正学懂计算机的人（不只是“编程匠”）都对数学有相当的造诣，既能用科学家的严谨思维来求证，也能用工程师的务实手段来解决问题——而这种思维和手段的最佳演绎就是“算法”。</p>
				<p>记得我读博时写的Othello对弈软件获得了世界冠军。当时，得第二名的人认为我是靠侥幸才打赢他，不服气地问我的程序平均每秒能搜索多少步棋，当他发现我的软件在搜索效率上比他快60多倍时，才彻底服输。为什么在同样的机器上，我可以多做60倍的工作呢？这是因为我用了一个最新的算法，能够把一个指数函数转换成四个近似的表，只要用常数时间就可得到近似的答案。在这个例子中，是否用对算法才是能否赢得世界冠军的关键。</p>
				<p>还记得1988年贝尔实验室副总裁亲自来访问我的学校，目的就是为了想了解为什么他们的语音识别系统比我开发的慢几十倍，而且，在扩大至大词汇系统后，速度差异更有几百倍之多。他们虽然买了几台超级计算机，勉强让系统跑了起来，但这么贵的计算资源让他们的产品部门很反感，因为“昂贵”的技术是没有应用前景的。在与他们探讨的过程中，我惊讶地发现一个O(n*m)的动态规划(dynamic programming)居然被他们做成了O (n*n*m)。更惊讶的是，他们还为此发表了不少文章，甚至为自己的算法起了一个很特别的名字，并将算法提名到一个科学会议里，希望能得到大奖。当时，贝尔实验室的研究员当然绝顶聪明，但他们全都是学数学、物理或电机出身，从未学过计算机科学或算法，才犯了这么基本的错误。我想那些人以后再也不会嘲笑学计算机科学的人了吧！</p>
				<p>
						<br />
						<b>网络时代的算法</b>
				</p>
				<p>有人也许会说：“今天计算机这么快，算法还重要吗？”其实永远不会有太快的计算机，因为我们总会想出新的应用。虽然在摩尔定律的作用下，计算机的计算能力每年都在飞快增长，价格也在不断下降。可我们不要忘记，需要处理的信息量更是呈指数级的增长。现在每人每天都会创造出大量数据（照片，视频，语音，文本等等）。日益先进的纪录和存储手段使我们每个人的信息量都在爆炸式的增长。互联网的信息流量和日志容量也在飞快增长。在科学研究方面，随着研究手段的进步，数据量更是达到了前所未有的程度。无论是三维图形、海量数据处理、机器学习、语音识别，都需要极大的计算量。在网络时代，越来越多的挑战需要靠卓越的算法来解决。</p>
				<p>再举另一个网络时代的例子。在互联网和手机搜索，如果要找附近的咖啡店，那么搜索引擎该怎么处理这个请求呢？最简单的办法就是把整个城市的咖啡馆都找出来，然后计算出它们的所在位置与你之间的距离，再进行排序，然后返回最近的结果。但该如何计算距离呢？图论里有不少算法可以解决这个问题。</p>
				<p>这么做也许是最直观的，但绝对不是最迅速的。如果一个城市只有为数不多的咖啡馆，那么这么做应该没什么问题，反正计算量不大。但如果一个城市里有很多咖啡馆，又有很多用户都需要类似的搜索，那么服务器所承受的压力就大多了。在这种情况下，我们该怎样优化算法呢？</p>
				<p>首先，我们可以把整个城市的咖啡馆做一次“预处理”。比如，把一个城市分成若干个“格子(grid)”，然后根据用户所在的位置把他放到某一个格子里，只对格子里的咖啡馆进行距离排序。</p>
				<p>问题又来了，如果格子大小一样，那么绝大多数结果都可能出现在市中心的一个格子里，而郊区的格子里只有极少的结果。在这种情况下，我们应该把市中心多分出几个格子。更进一步，格子应该是一个“树结构”，最顶层是一个大格——整个城市，然后逐层下降，格子越来越小，这样有利于用户进行精确搜索——如果在最底层的格子里搜索结果不多，用户可以逐级上升，放大搜索范围。</p>
				<p>上述算法对咖啡馆的例子很实用，但是它具有通用性吗？答案是否定的。把咖啡馆抽象一下，它是一个“点”，如果要搜索一个“面”该怎么办呢？比如，用户想去一个水库玩，而一个水库有好几个入口，那么哪一个离用户最近呢？这个时候，上述“树结构”就要改成“r-tree”，因为树中间的每一个节点都是一个范围，一个有边界的范围（参考:<a class="contentlink" href="http://www.cs.umd.edu/%7Ehjs/rtrees/index.html" target="_blank"><font color="#4455aa"><u>http://www.cs.umd.edu/~hjs/rtrees/index.html</u></font></a>）。</p>
				<p>通过这个小例子，我们看到，应用程序的要求千变万化，很多时候需要把一个复杂的问题分解成若干简单的小问题，然后再选用合适的算法和数据结构。</p>
				<p>
						<br />
						<b>并行算法：Google的核心优势</b>
				</p>
				<p>上面的例子在Google里就要算是小case了！每天Google的网站要处理十亿个以上的搜索，GMail要储存几千万用户的2G邮箱， Google Earth要让数十万用户同时在整个地球上遨游，并将合适的图片经过互联网提交给每个用户。如果没有好的算法，这些应用都无法成为现实。</p>
				<p>在这些的应用中，哪怕是最基本的问题都会给传统的计算带来很大的挑战。例如，每天都有十亿以上的用户访问Google的网站，使用Google的服务，也产生很多很多的日志(Log)。因为Log每份每秒都在飞速增加，我们必须有聪明的办法来进行处理。我曾经在面试中问过关于如何对Log进行一些分析处理的问题，有很多面试者的回答虽然在逻辑上正确，但是实际应用中是几乎不可行的。按照它们的算法，即便用上几万台机器，我们的处理速度都根不上数据产生的速度。</p>
				<p>那么Google是如何解决这些问题的？</p>
				<p>首先，在网络时代，就算有最好的算法，也要能在并行计算的环境下执行。在Google的数据中心，我们使用的是超大的并行计算机。但传统的并行算法运行时，效率会在增加机器数量后迅速降低，也就是说，十台机器如果有五倍的效果，增加到一千台时也许就只有几十倍的效果。这种事半功倍的代价是没有哪家公司可以负担得起的。而且，在许多并行算法中，只要一个结点犯错误，所有计算都会前功尽弃。</p>
				<p>那么Google是如何开发出既有效率又能容错的并行计算的呢？</p>
				<p>Google最资深的计算机科学家Jeff Dean认识到，Google所需的绝大部分数据处理都可以归结为一个简单的并行算法：Map and Reduce（<a class="contentlink" href="http://labs.google.com/papers/mapreduce.html" target="_blank"><font color="#4455aa"><u>http://labs.google.com/papers/mapreduce.html</u></font></a>）。这个算法能够在很多种计算中达到相当高的效率，而且是可扩展的（也就是说，一千台机器就算不能达到一千倍的效果，至少也可以达到几百倍的效果）。 Map and Reduce的另外一大特色是它可以利用大批廉价的机器组成功能强大的server farm。最后，它的容错性能异常出色，就算一个 server farm宕掉一半，整个fram依然能够运行。正是因为这个天才的认识，才有了Map and Reduce算法。借助该算法， Google几乎能无限地增加计算量，与日新月异的互联网应用一同成长。</p>
				<p>
						<br />
						<b>算法并不局限于计算机和网络</b>
				</p>
				<p>举一个计算机领域外的例子：在高能物理研究方面，很多实验每秒钟都能几个TB的数据量。但因为处理能力和存储能力的不足，科学家不得不把绝大部分未经处理的数据丢弃掉。可大家要知道，新元素的信息很有可能就藏在我们来不及处理的数据里面。同样的，在其他任何领域里，算法可以改变人类的生活。例如人类基因的研究，就可能因为算法而发明新的医疗方式。在国家安全领域，有效的算法可能避免下一个911的发生。在气象方面，算法可以更好地预测未来天灾的发生，以拯救生命。</p>
				<p>所以，如果你把计算机的发展放到应用和数据飞速增长的大环境下，你一定会发现；算法的重要性不是在日益减小，而是在日益加强。</p>
				<p>from: <a href="http://www.yuanma.org/data/2006/0824/article_1397.htm">http://www.yuanma.org/data/2006/0824/article_1397.htm</a></p>
		</div>
<img src ="http://www.blogjava.net/weidagang2046/aggbug/83968.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/weidagang2046/" target="_blank">weidagang2046</a> 2006-11-28 09:56 <a href="http://www.blogjava.net/weidagang2046/articles/83968.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>理论计算机初步：从hash函数到王小云的MD5破解</title><link>http://www.blogjava.net/weidagang2046/articles/82012.html</link><dc:creator>weidagang2046</dc:creator><author>weidagang2046</author><pubDate>Sun, 19 Nov 2006 03:24:00 GMT</pubDate><guid>http://www.blogjava.net/weidagang2046/articles/82012.html</guid><wfw:comment>http://www.blogjava.net/weidagang2046/comments/82012.html</wfw:comment><comments>http://www.blogjava.net/weidagang2046/articles/82012.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/weidagang2046/comments/commentRss/82012.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/weidagang2046/services/trackbacks/82012.html</trackback:ping><description><![CDATA[
		<div class="postentry">
				<p>密码学是理论计算机的一个很大的方向。之前准备先写密码学概论再提在hash函数破解上做出重大贡献的王小云教授的工作，不过前两天<a href="http://www.stdaily.com/gb/economy/2006-09/17/content_572778.htm" target="_blank">王小云获得求是杰出科学家奖以及100万奖金</a>，在媒体上又掀起了一轮宣传狂潮，但是有些报道极端弱智，错误百出，所以我趁机纠正一下，并介绍密码学的一个组成部分——hash函数，以及王小云在这上面的工作。</p>
				<p>王小云的主要工作是关于hash函数的破解工作。她在2005一个密码学会议上宣布破解了SHA-1，震惊了全世界。所以要介绍和理解她的工作，先看一下hash函数具体是怎么回事。</p>
				<p>简单的说，<strong>hash函数</strong>就是<font color="#ff0000">把任意长的输入字符串变化成固定长的输出字符串</font>的一种函数。通俗得说，hash函数用来生成信息的摘要。输出字符串的长度称为hash函数的<strong>位数</strong>。</p>
				<p>目前应用最为广泛的hash函数是<strong><a href="http://en.wikipedia.org/wiki/SHA_hash_functions">SHA-1</a></strong>和<strong><a href="http://en.wikipedia.org/wiki/Md5" target="_blank">MD5</a></strong>，大多是128位和更长。</p>
				<p>hash函数在现实生活中应用十分广泛。很多下载网站都提供下载文件的MD5码校验，可以用来判别文件是否完整。另外，比如在WordPress的数据库，所有密码都是保存的MD5码，这样即使数据库的管理员也无法知道用户的原始密码，避免隐私泄露（很多人在不同地方都是用的同一个密码）。</p>
				<p>如果两个输入串的hash函数的值一样，则称这两个串是一个<strong>碰撞</strong>(<strong>Collision</strong>)。既然是把任意长度的字符串变成固定长度的字符串，所以，必有一个输出串对应无穷多个输入串，碰撞是必然存在的。</p>
				<p>一个“优良”的hash函数 <em>f </em>应当满足以下三个条件：</p>
				<ul>
						<li>任意y，找x，使得f(x)=y，非常困难。 
</li>
						<li>给定x1，找x2，使得f(x1)=f(x2)，非常困难。 
</li>
						<li>找x1，x2，使得f(x1)=f(x2)，非常困难。 </li>
				</ul>
				<p>上面的“非常困难”的意思是除了枚举外不可能有别的更快的方法。比如第3条，根据<a href="http://en.wikipedia.org/wiki/Birthday_paradox" target="_blank">生日定理</a>，要想找到这样的x1，x2，理论上需要大约2^(n/2)的枚举次数。</p>
				<p>几乎所有的hash函数的破解，都是指的破坏上面的第三条性质，即找到一个碰撞（前两条都能被破坏的hash函数也太弱了点，早就被人抛弃了）。在密码学上还有一个概念是<strong>理论破解</strong>，指的是提出一个算法，使得可以用低于理论值得枚举次数找到碰撞。</p>
				<p>王小云的主要工作是给出了MD5，<a href="http://en.wikipedia.org/wiki/SHA_hash_functions" target="_blank">SHA-0</a>的碰撞，以及SHA-1的理论破解，她证明了160位SHA-1，只需要大约2^69次计算就能找出来，而理论值是2^80次。她的寻找MD5碰撞的方法是极端高效的。传说王小云当时在会议上把碰撞写出来，结果被下面的人验证发现不对，原来她把MD5算法的一个步骤弄错了。但是她立马联系她的当时留在中国的学生，修正算法，并找到一个新的碰撞。这一个是对的。</p>
				<p>看到这里，那些认为中国国安局应该将这些结果封存作为秘密武器甚至幻想用这些成果来袭击美国之徒可以停住你们的YY了。这种形式上的破解，在大多数情况下没有实际性的作用。更别提MD5早就被美国人抛弃了。</p>
				<p>但是，说这种破解一点实际意义都没有，那就侮辱了广大密码学家的智商，密码学家不会无缘无故的弄出碰撞这么一个概念来。下面简单的介绍一下在特定情况下，怎么利用给定的碰撞来做坏事(翻译自<a href="http://www.cits.rub.de/MD5Collisions/" target="_blank">Attacking Hash Functions</a>)：</p>
				<p>Caesar给实习生Alice叫写了一封推荐信(letter)。同一天，Alice叫Caesar在推荐信上数字签名，并提供了一份推荐信的电子板。Caesar打开文件，发现和原件一模一样。所以他在文件上签了名。</p>
				<p>几个月后，Caesar发现他的秘密文件被非法察看。这到底是怎么回事呢？</p>
				<p align="center">
						<a href="http://www.cits.rub.de/imperia/md/content/magnus/letter_of_rec.ps">
								<img alt="letter" src="http://www.cits.rub.de/imperia/md/content/magnus/letter.png" align="top" border="0" />
						</a>
						<a href="http://www.cits.rub.de/imperia/md/content/magnus/order.ps">
								<img alt="order" src="http://www.cits.rub.de/imperia/md/content/magnus/order.png" align="top" border="0" />
						</a>
						<br />
						<img alt="(apply MD5 to both documents)" src="http://www.cits.rub.de/imperia/md/content/magnus/samehash.png" align="top" border="0" />
						<br />a25f7f0b 29ee0b39 68c86073 8533a4b9</p>
				<p>事实上，Alice要求Caesar签名的文件<a href="http://www.cits.rub.de/imperia/md/content/magnus/letter_of_rec.ps" target="_blank">letter</a>已经被Alice做了手脚，准确地说，Alice还准备了另外一个文件<a href="http://www.cits.rub.de/imperia/md/content/magnus/order.ps" target="_blank">order</a>，它们的MD5码完全一致。而Caesar的数字签名还依赖于MD5算法，所以Alice用order文件替换Letter文件之后，Caesar的数字签名依然有效。那封order给Alice提供了察看秘密文件的权限。</p>
				<p>具体的实现方法可见<a href="http://www.cits.rub.de/imperia/md/content/magnus/rump_ec05.pdf" target="_blank">Hash Functions and the Blind Passenger Attack</a>。我在这里简单的解释一下(只是大致思路，具体实现方式，需要对文件结构信息有所了解)：</p>
				<p>letter文件的内容是：</p>
				<blockquote>
						<p align="left">if(x1==x1) show "letter" else show "order"</p>
				</blockquote>
				<p>order文件的内容是：</p>
				<blockquote>
						<p align="left">if(x2==x1) show "letter" else show "order"</p>
				</blockquote>
				<p>其中字符串"letter"和"order"代表两封信实际显示的内容。x1，x2是一个MD5的碰撞。</p>
				<p>上面的方法，只供参考和学术用途，实际使用所引起的后果概不负责。</p>
				<p>参考：</p>
				<ul>
						<li>
								<a href="http://www.cits.rub.de/MD5Collisions/" target="_blank">Attacking Hash Functions by Poisoned Messages "The Story of Alice and her Boss"</a>
						</li>
						<li>
								<a href="http://en.wikipedia.org/wiki/Hash_function" target="_blank">Hash function</a>, wikipedia 
</li>
						<li>
								<a href="http://en.wikipedia.org/wiki/SHA_hash_functions" target="_blank">SHA</a>, wikipedia 
</li>
						<li>
								<a href="http://news.zdnet.com/2100-1009_22-5598536.html">Interview with Yiqun Lisa Yin concerning the attack on SHA-1</a>
						</li>
				</ul>
				<p>PS：我跟王小云老师的接触很少，上过俩次她的讨论班而已，亦感觉到王小云老师的严谨和耐心。在去年一个Turing奖获得者的演讲上，王小云提问的时候竟口而出“I ask who”的中式英语，在引起哄笑的同时，我也极端佩服她的勇气。也许只有这样才能做出非常好的工作吧。</p>
				<p>PS2: wikipedia在国内可以通过<a href="http://mathzqy.googlepages.com/fd62.exe" target="_blank">free_door</a>浏览。 </p>
		</div>
		<p>
				<a title="Permanent link to 理论计算机初步：从hash函数到王小云的MD5破解" href="http://zhiqiang.org/blog/446.html" rel="bookmark">http://zhiqiang.org/blog/446.html</a>
				<br />参阅: <a href="http://zhiqiang.org/blog/search.php?q=理论计算机">理论计算机</a>, <a href="http://zhiqiang.org/blog/search.php?q=MD5">MD5</a>, <a href="http://zhiqiang.org/blog/search.php?q=SHA-1">SHA-1</a>, <a href="http://zhiqiang.org/blog/search.php?q=hash函数">hash函数</a>, <a href="http://zhiqiang.org/blog/search.php?q=王小云">王小云</a>, <a href="http://zhiqiang.org/blog/search.php?q=密码学">密码学</a>. <br /><br />from: <a href="http://zhiqiang.org/blog/446.html">http://zhiqiang.org/blog/446.html</a></p>
<img src ="http://www.blogjava.net/weidagang2046/aggbug/82012.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/weidagang2046/" target="_blank">weidagang2046</a> 2006-11-19 11:24 <a href="http://www.blogjava.net/weidagang2046/articles/82012.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Similarity Flooding </title><link>http://www.blogjava.net/weidagang2046/articles/81825.html</link><dc:creator>weidagang2046</dc:creator><author>weidagang2046</author><pubDate>Fri, 17 Nov 2006 10:25:00 GMT</pubDate><guid>http://www.blogjava.net/weidagang2046/articles/81825.html</guid><wfw:comment>http://www.blogjava.net/weidagang2046/comments/81825.html</wfw:comment><comments>http://www.blogjava.net/weidagang2046/articles/81825.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/weidagang2046/comments/commentRss/81825.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/weidagang2046/services/trackbacks/81825.html</trackback:ping><description><![CDATA[
		<p>算法大致思路：<br />        把要匹配的模型转换为带标记的有向图（directed labeled graphs。由节点和弧组成的图，允许对象用自身的属性及其和其他对象的关系来定义，类似于ER图）。这些图要用来做迭代的不动点计算，计算结果将告诉我们一张图里的哪些节点和第二张图的节点相似。<br />        为了计算相似度，我们利用了这样一个直觉：两个不同的节点是相似的，当它们邻接元素是相似的。换句话说，两个元素相似性的一部分传播给了它们各自的邻居，这种传播方式类似于IP广播，这也是SF这个名字的由来。我们把算法的结果叫做一个 mapping，然后根据匹配目标，选择特定的过滤器来过滤出一个原始结果的子集。我们希望能够人工对结果进行修正，需要修正的成员数目就反映了算法的准确性。<br /><br />概述：<br /><br />        假设有2个schema，S1和S2。我们要为S1里每一个元素在S2中找到匹配的元素。<br />      过程如下：<br />      1. G1 = SQL2Graph(S1); G2 = SQL2Graph(S2);  把schema变成图，图采用了Open Information Model (OIM)规格，图中node采用矩形和卵形，矩形是文字描述，卵形是标识符<br /><br />      2. initialMap = StringMatch(G1, G2);      用字符串匹配做为初始匹配，主要是比较通常的前缀和后缀，这样的结果通常是不准确的<br /><br />      3. product = SFJoin(G1, G2, initialMap);      用SF算法生成结果。<font color="#0000ff">假设两个不同的节点是相似的，则它们邻接元素的相似度增加。经过一系列的迭代，这种相似度会传遍整个图<br /></font><br />      4. result = SelectThreshold(product);   结果筛选<br /><br /><br />SF算法<br /><br />      图中的每条边，用一个三元组表示（s，p，o），分别是 源点，边名，目的点。<br /><br /><img height="271" alt="δ2.JPG" src="http://www.cnblogs.com/images/cnblogs_com/anf/δ2.JPG" width="762" border="0" /><br />     <font color="#0000ff"> 相似度传播图：首先定义pairwise connectivity graph(PCG) ： ((x; y); p; (x'; y')) 属于 PCG(A;B)&lt;==&gt;(x; p; x') € A and (y; p; y') € B。 <font color="#ff0000">关键是p要相同，也就是边的名字一样。</font>式子从右向左推导，就可以A、B从两个模型建立起它们的PCG。</font>图中的每个节点，都是A和B中的元素构成的2元组，叫做map pairs。<br />      induced propagation graph。从PCG推导而来，加上了反向的边，边上注明了[传播系数]，值为 1/n，n为相应的边的数目。<br />      <font color="#0000ff">不动点计算：</font><br />            设ó(x; y) &gt; 0 代表了节点x € A 和 y € B 的相似度，是在整个 A X B的范围上定义的。我们把 ó 叫做 mapping。相似度的计算就是基于ó-values的迭代计算。设 ó<sup>i</sup> 代表了第 i 次迭代后的结果，ó<sup>0</sup> 是初始相似度（可以用字符串相似度的办法的得出，在我们的例子里，没有 ó<sup>0</sup> ，即让 ó<sup>0</sup> =1）。<br />            每次迭代中，ó-values 都会根据其邻居paris的 <font color="#008000">ó-values  乘以[传播系数]</font> 来增加。例如，在第一次迭代 ó<sup>1</sup>(a1; b1) = ó<sup>0</sup>(a1; b1) + ó<sup>0</sup>(a; b) * 0.5 = 1.5。类似的，ó<sup>1</sup>(a, b) = ó<sup>0</sup>(a, b) + ó<sup>0</sup>(a1; b1) * 1.0 + ó<sup>0</sup>(a2, b1) *1.0 = 3.0。接下来，所有 ó 值进行正规化，比如除以当前迭代的 ó<sup></sup>的最大值，保证所有 ó 都不大于1。所以在正规化以后，ó<sup>1</sup>(a; b) = 1.0, ó<sup>1</sup>(a1, b1) = 1.5/3.0 = 0.5。一般情况下，迭代如下进行：</p>
		<p>
				<img height="127" alt="未命名4.JPG" src="http://www.cnblogs.com/images/cnblogs_com/anf/未命名4.JPG" width="466" border="0" />
				<br />
				<br />
				<img height="1" src="http://www.cnblogs.com/WebResource.axd?d=pLXXeGbWF7eXU8SMs2-GFZvUWY2JNH05dFx5YzJhGUYAYJAFEaTEq36NAhTPy7_KekvzDFwt8wvQWdByvJIGWdEq6x2KpKD80&amp;t=632785713320000000" width="1" />
				<img height="152" alt="δ3.JPG" src="http://www.cnblogs.com/images/cnblogs_com/anf/δ3.JPG" width="434" border="0" />
				<br />      上面的计算进行迭代，直到 ó<sup>n </sup>和 ó<sup>n-1</sup>之间的差别小于一个阈值，如果计算没有聚合，我们就在迭代超过一定次数后停止。上图3的第三副图，就是5次迭代后的结果。表3时一些计算方法，后面的实验表明，C比较好。A叫做 sparce，B叫做 excepted，C叫做verbose<br /><br /><br /><strong>过滤<br /><br /></strong>      迭代出的结果是一种[多匹配]，可能包含有用的匹配子集。<br />      三个步骤：<br />            1。用程序定义的[限制条件]进行过滤。<br />            2。用双向图中的匹配上下文技术进行过滤<br />            3。比较各种技术的有效性（满足用户需求的能力）<br />      限制：主要有两种，一个是[类型]限制，比如只考虑[列]的匹配（匹配双方都是列）。第二个是 cardinality 限制，即模式S1中的所有元素都要在S2中有一个映射。<br /><br />stable marriage问题：n女和n男配对，不存在这样的两对 (x; y)和(x0; y0)，其中x喜欢 y0 胜过 y，而且 y0 喜欢 x 胜过 x0。具有stable marriage的匹配结果的total satisfaction可能会比不具有stable marriage的匹配结果还低！<br /><br /><strong>匹配质量的评估<br /></strong><br />   基本的评估思想，就是  用户对匹配结果做的修改越少，匹配质量就越高（修改结果包括去掉错误的pair，加上正确的pair）<br /><img height="88" alt="未命名1.JPG" src="http://www.cnblogs.com/images/cnblogs_com/anf/未命名1.JPG" width="372" border="0" />  n是找到的匹配数，m是理想的匹配数，c是用户作出修正的数目。<br /><br />from: <a href="http://www.cnblogs.com/anf/archive/2006/08/15/477700.html">http://www.cnblogs.com/anf/archive/2006/08/15/477700.html</a></p>
<img src ="http://www.blogjava.net/weidagang2046/aggbug/81825.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/weidagang2046/" target="_blank">weidagang2046</a> 2006-11-17 18:25 <a href="http://www.blogjava.net/weidagang2046/articles/81825.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Building Classification Models: ID3 and C4.5</title><link>http://www.blogjava.net/weidagang2046/articles/48836.html</link><dc:creator>weidagang2046</dc:creator><author>weidagang2046</author><pubDate>Mon, 29 May 2006 13:45:00 GMT</pubDate><guid>http://www.blogjava.net/weidagang2046/articles/48836.html</guid><wfw:comment>http://www.blogjava.net/weidagang2046/comments/48836.html</wfw:comment><comments>http://www.blogjava.net/weidagang2046/articles/48836.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/weidagang2046/comments/commentRss/48836.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/weidagang2046/services/trackbacks/48836.html</trackback:ping><description><![CDATA[
		<h2>Introduction</h2>ID3 and C4.5 are algorithms introduced by Quinlan for inducing <em>Classification Models</em>, also called <em>Decision Trees</em>, from data.<br />We are given a set of records. Each record has the same structure, consisting of a number of attribute/value pairs. One of these attributes represents the <em>category</em> of the record. The problem is to determine a decision tree that on the basis of answers to questions about the non-category attributes predicts correctly the value of the category attribute. Usually the category attribute takes only the values {<i>true, false</i>}, or {<i>success, failure</i>}, or something equivalent. In any case, one of its values will mean failure. 
<p>For example, we may have the results of measurements taken by experts on some widgets. For each widget we know what is the value for each measurement and what was decided, if to pass, scrap, or repair it. That is, we have a record with as non categorical attributes the measurements, and as categorical attribute the disposition for the widget. 
</p><p>Here is a more detailed example. We are dealing with records reporting on weather conditions for playing golf. The categorical attribute specifies whether or not to Play. The non-categorical attributes are: 
</p><p></p><pre>	ATTRIBUTE   |	POSSIBLE VALUES
	============+=======================
	outlook	    | sunny, overcast, rain
	------------+-----------------------
	temperature | continuous
	------------+-----------------------
	humidity    | continuous
	------------+-----------------------
	windy       | true, false
	============+=======================
</pre><p>and the training data is: 
</p><p></p><pre>	OUTLOOK | TEMPERATURE | HUMIDITY | WINDY | PLAY
	=====================================================
	sunny   |      85     |    85    | false | Don't Play
	sunny   |      80     |    90    | true  | Don't Play
	overcast|      83     |    78    | false | Play
	rain    |      70     |    96    | false | Play
	rain    |      68     |    80    | false | Play
	rain    |      65     |    70    | true  | Don't Play
	overcast|      64     |    65    | true  | Play
	sunny   |      72     |    95    | false | Don't Play
	sunny   |      69     |    70    | false | Play
	rain    |      75     |    80    | false | Play
	sunny   |      75     |    70    | true  | Play
	overcast|      72     |    90    | true  | Play
	overcast|      81     |    75    | false | Play
	rain    |      71     |    80    | true  | Don't Play

</pre>Notice that in this example two of the attributes have continuous ranges, Temperature and Humidity. ID3 does not directly deal with such cases, though <a href="http://www.cis.temple.edu/~ingargio/cis587/readings/id3-c45.html#5">below</a> we examine how it can be extended to do so. A decision tree is important not because it summarizes what we know, i.e. the <em>training set</em>, but because we hope it will <strong>classify correctly</strong> new cases. Thus when building classification models one should have both <em>training data</em> to build the model and <em>test data</em> to verify how well it actually works. 
<p>A simpler example from the stock market involving only discrete ranges has Profit as categorical attribute, with values {up, down}. Its non categorical attributes are: 
</p><p></p><pre>	ATTRIBUTE   |	POSSIBLE VALUES
	============+=======================
	age	    | old, midlife, new
	------------+-----------------------
	competition | no, yes
	------------+-----------------------
	type        | software, hardware
	------------+-----------------------

   and the training data is:

	AGE	| COMPETITION | TYPE	| PROFIT
	=========================================
	old	| yes	      | swr	| down
	--------+-------------+---------+--------
	old	| no	      | swr 	| down
	--------+-------------+---------+--------
	old	| no	      | hwr	| down
	--------+-------------+---------+--------
	mid	| yes	      | swr	| down
	--------+-------------+---------+--------
	mid	| yes	      | hwr	| down
	--------+-------------+---------+--------
	mid	| no	      | hwr	| up
	--------+-------------+---------+--------
	mid	| no	      | swr	| up
	--------+-------------+---------+--------
	new	| yes	      | swr	| up
	--------+-------------+---------+--------
	new	| no	      | hwr	| up
	--------+-------------+---------+--------
	new	| no	      | swr	| up
	--------+-------------+---------+--------
	
</pre>For a more complex example, here are files that provide records for a series of votes in Congress. The first file describes the <a href="http://www.cis.temple.edu/~ingargio/cis587/readings/vote/vote.names"><b>structure</b></a> of the records. The second file provides the <a href="http://www.cis.temple.edu/~ingargio/cis587/readings/vote/vote.data"><b>Training Set</b></a>, and the third the <a href="http://www.cis.temple.edu/~ingargio/cis587/readings/vote/vote.test"><b>Test Set</b></a>. 
<p>The basic ideas behind ID3 are that: 
</p><p></p><ul><li>In the decision tree each node corresponds to a non-categorical attribute and each arc to a possible value of that attribute. A leaf of the tree specifies the expected value of the categorical attribute for the records described by the path from the root to that leaf. [This defines what is a Decision Tree.] 
<p></p></li><li>In the decision tree at each node should be associated the non-categorical attribute which is <em>most informative</em> among the attributes not yet considered in the path from the root. [This establishes what is a "Good" decision tree.] 
<p></p></li><li><em>Entropy</em> is used to measure how informative is a node. [This defines what we mean by "Good". By the way, this notion was introduced by Claude Shannon in Information Theory.] 
<p></p></li></ul><a href="http://www.mkp.com/books_catalog/1-55860-240-2.asp">C4.5</a> is an extension of ID3 that accounts for unavailable values, continuous attribute value ranges, pruning of decision trees, rule derivation, and so on. 
<p><a name="2"><h2>Definitions</h2></a>If there are n equally probable possible messages, then the probability p of each is 1/n and the information conveyed by a message is -log(p) = log(n). <em>[In what follows all logarithms are in base 2.]</em> That is, if there are 16 messages, then log(16) = 4 and we need 4 bits to identify each message. 
</p><p>In general, if we are given a probability distribution P = (p1, p2, .., pn) then the <em>Information conveyed by this distribution</em>, also called <em>the Entropy of P</em>, is: </p><pre>	I(P) = -(p1*log(p1) + p2*log(p2) + .. + pn*log(pn))
</pre>For example, if P is (0.5, 0.5) then I(P) is 1, if P is (0.67, 0.33) then I(P) is 0.92, if P is (1, 0) then I(P) is 0. [Note that the more uniform is the probability distribution, the greater is its information.] 
<p>If a set T of records is partitioned into disjoint exhaustive classes C1, C2, .., Ck on the basis of the value of the categorical attribute, then the information needed to identify the class of an element of T is <strong>Info(T)</strong> = I(P), where P is the probability distribution of the partition (C1, C2, .., Ck): </p><pre>	P = (|C1|/|T|, |C2|/|T|, ..., |Ck|/|T|)
</pre><p>In our golfing example, we have Info(T) = I(9/14, 5/14) = 0.94,<br />and in our stock market example we have Info(T) = I(5/10,5/10) = 1.0. 
</p><p>If we first partition T on the basis of the value of a non-categorical attribute X into sets T1, T2, .., Tn then the information needed to identify the class of an element of T becomes the weighted average of the information needed to identify the class of an element of Ti, i.e. the weighted average of Info(Ti): </p><pre>					      |Ti|
	Info(X,T) = Sum for i from 1 to n of  ---- * Info(Ti)
					      |T|
</pre><p>In the case of our golfing example, for the attribute Outlook we have </p><pre>	Info(Outlook,T) = 5/14*I(2/5,3/5) + 4/14*I(4/4,0) + 5/14*I(3/5,2/5)
			= 0.694
</pre><p>Consider the quantity Gain(X,T) defined as 
</p><p></p><pre>	Gain(X,T) = Info(T) - Info(X,T)
</pre><p>This represents the difference between the <em>information needed to identify an element of T</em> and the <em>information needed to identify an element of T after the value of attribute X has been obtained</em>, that is, this is <em>the gain in information due to attribute X</em>. 
</p><p>In our golfing example, for the Outlook attribute the gain is: 
</p><p></p><pre>	Gain(Outlook,T) = Info(T) - Info(Outlook,T) = 0.94 - 0.694 = 0.246.
</pre><p>If we instead consider the attribute <em>Windy</em>, we find that Info(Windy,T) is 0.892 and Gain(Windy,T) is 0.048. Thus Outlook offers a greater informational gain than Windy. 
</p><p>We can use this notion of <strong>gain</strong> to rank attributes and to build decision trees where at each node is located the attribute with greatest gain among the attributes not yet considered in the path from the root. 
</p><p>The intent of this ordering are twofold: 
</p><p></p><ul><li>To create small decision trees so that records can be identified after only a few questions. 
<p></p></li><li>To match a hoped for minimality of the process represented by the records being considered(Occam's Razor). </li></ul><p><a name="3"><h2>The ID3 Algorithm</h2></a>The ID3 algorithm is used to build a decision tree, given a set of non-categorical attributes C1, C2, .., Cn, the categorical attribute C, and a training set T of records. 
</p><p></p><pre>   function ID3 (R: a set of non-categorical attributes,
		 C: the categorical attribute,
		 S: a training set) returns a decision tree;
   begin
	If S is empty, return a single node with value Failure;
	If S consists of records all with the same value for 
	   the categorical attribute, 
	   return a single node with that value;
	If R is empty, then return a single node with as value
	   the most frequent of the values of the categorical attribute
	   that are found in records of S; [note that then there
	   will be errors, that is, records that will be improperly
	   classified];
	Let D be the attribute with largest Gain(D,S) 
	   among attributes in R;
	Let {dj| j=1,2, .., m} be the values of attribute D;
	Let {Sj| j=1,2, .., m} be the subsets of S consisting 
	   respectively of records with value dj for attribute D;
	Return a tree with root labeled D and arcs labeled 
	   d1, d2, .., dm going respectively to the trees 

	     ID3(R-{D}, C, S1), ID3(R-{D}, C, S2), .., ID3(R-{D}, C, Sm);
   end ID3;
</pre><p>In the Golfing example we obtain the following decision tree: </p><pre>
			Outlook
		       / |     \
		      /  |      \
            overcast /   |sunny  \rain
                    /    |        \
	         Play   Humidity   Windy
		       /   |         |  \
                      /    |         |   \
		&lt;=75 /  &gt;75|     true|    \false
		    /      |         |     \
                 Play   Don'tPlay Don'tPlay Play


   In the stock market case the decision tree is:


			 Age
		       / |    \
		      /  |     \
		  new/   |mid   \old
		    /    |       \
		  Up  Competition Down
                       /      \
		      /        \
		   no/          \yes
		    /            \
		  Up             Down
</pre><p>Here is the decision tree, just as produced by c4.5, for the <a href="http://www.cis.temple.edu/~ingargio/cis587/readings/vote/vote.example">voting example</a> introduced earlier. 
</p><p><a name="4"><h2>Using Gain Ratios</h2><a>The notion of Gain introduced earlier tends to favor attributes that have a large number of values. For example, if we have an attribute D that has a distinct value for each record, then Info(D,T) is 0, thus Gain(D,T) is maximal. To compensate for this Quinlan suggests using the following ratio instead of Gain: 
<p></p><pre>			 Gain(D,T)
	GainRatio(D,T) = ----------
			 SplitInfo(D,T)

   where SplitInfo(D,T) is the information due to the split of T on the basis
   of the value of the categorical attribute D. Thus SplitInfo(D,T) is

		 I(|T1|/|T|, |T2|/|T|, .., |Tm|/|T|)

   where {T1, T2, .. Tm} is the partition of T induced by the value of D.

   In the case of our golfing example SplitInfo(Outlook,T) is 

	-5/14*log(5/14) - 4/14*log(4/14) - 5/14*log(5/14) = 1.577

   thus the GainRatio of Outlook is 0.246/1.577 = 0.156. And 
   SplitInfo(Windy,T) is 

	-6/14*log(6/14) - 8/14*log(8/14) = 6/14*0.1.222 + 8/14*0.807 
					 = 0.985

   thus the GainRatio of Windy is 0.048/0.985 = 0.049
</pre><p></p><h4>You can run <a href="http://yoda.cis.temple.edu:8080/cgi-bin/pail/pail.html">PAIL </a>to see how ID3 generates the decision tree [you need to have an X-server and to allow access (xhost) from yoda.cis.temple.edu]. </h4><p><a name="5"><h2>C4.5 Extensions</h2></a><a href="http://www.mkp.com/books_catalog/1-55860-240-2.asp">C4.5</a> introduces a number of extensions of the original ID3 algorithm. 
</p><p><b>In building a decision tree</b> we can deal with training sets that have records with unknown attribute values by evaluating the gain, or the gain ratio, for an attribute by considering only the records where that attribute is defined. 
</p><p><b>In using a decision tree</b>, we can classify records that have unknown attribute values by estimating the probability of the various possible results. In our golfing example, if we are given a new record for which the outlook is sunny and the humidity is unknown, we proceed as follows: 
</p><p></p><pre>   We move from the Outlook root node to the Humidity node following
   the arc labeled 'sunny'. At that point since we do not know
   the value of Humidity we observe that if the humidity is at most 75
   there are two records where one plays, and if the humidity is over
   75 there are three records where one does not play. Thus one
   can give as answer for the record the probabilities
   (0.4, 0.6) to play or not to play.
</pre><p>We can deal with the case of attributes with <b>continuous ranges</b> as follows. Say that attribute Ci has a continuous range. We examine the values for this attribute in the training set. Say they are, in increasing order, A1, A2, .., Am. Then for each value Aj, j=1,2,..m, we partition the records into those that have Ci values up to and including Aj, and those that have values greater than Aj. For each of these partitions we compute the gain, or gain ratio, and choose the partition that maximizes the gain.<br />In our Golfing example, for humidity, if T is the training set, we determine the information for each partition and find the best partition at 75. Then the range for this attribute becomes {&lt;=75, &gt;75}. Notice that this method involves a substantial number of computations. 
</p><p><a name="6"><h2>Pruning Decision Trees and Deriving Rule Sets</h2></a>The decision tree built using the training set, because of the way it was built, deals correctly with most of the records in the training set. In fact, in order to do so, it may become quite complex, with long and very uneven paths. 
</p><p><em>Pruning</em> of the decision tree is done by replacing a whole subtree by a leaf node. The replacement takes place if a decision rule establishes that the expected error rate in the subtree is greater than in the single leaf. For example, if the simple decision tree 
</p><p></p><pre>			Color
		       /     \
		   red/       \blue
		     /         \
		  Success     Failure
</pre><p>is obtained with one training red success record and two training blue Failures, and then in the Test set we find three red failures and one blue success, we might consider replacing this subtree by a single Failure node. After replacement we will have only two errors instead of five failures. 
</p><p>Winston shows how to use <em>Fisher's exact test</em> to determine if the category attribute is truly dependent on a non-categorical attribute. If it is not, then the non-categorical attribute need not appear in the current path of the decision tree. 
</p><p>Quinlan and Breiman suggest more sophisticated pruning heuristics. 
</p><p>It is easy to <em>derive a rule set from a decision tree</em>: write a rule for each path in the decision tree from the root to a leaf. In that rule the left-hand side is easily built from the label of the nodes and the labels of the arcs. 
</p><p>The resulting rules set can be simplified: 
</p><p>Let LHS be the left hand side of a rule. Let LHS' be obtained from LHS by eliminating some of its conditions. We can certainly replace LHS by LHS' in this rule if the subsets of the training set that satisfy respectively LHS and LHS' are equal. 
</p><p>A rule may be eliminated by using metaconditions such as <em>"if no other rule applies"</em>. 
</p><p></p><h4>You can run the <a href="http://yoda.cis.temple.edu:8080/cgi-bin/c45/nph-c45">C45 </a>program here [you need to have an X-server and to allow access (xhost) from yoda.cis.temple.edu]. </h4><a name="7"><h2>Classification Models in the Undergraduate AI Course</h2></a>It is easy to find implementations of ID3. For example, a Prolog program by <a href="http://yoda.cis.temple.edu:8080/books/shoham/chapter9/section9.2">Shoham</a> and a nice <a href="http://www.idsia.ch/pail.html">Pail</a><a href="http://yoda.cis.temple.edu:8080/pail-2.4.3/id3/">module</a>. 
<p>The software for <a href="http://www.mkp.com/books_catalog/1-55860-240-2.asp">C4.5</a> can be obtained with Quinlan's book. A wide variety of training and test data is available, some provided by Quinlan, some at specialized sites such as the <a href="ftp://ftp.ics.uci.edu/pub/machine-learning-databases">University of California at Irvine</a>. 
</p><p>Student projects may involve the implementation of these algorithms. More interesting is for students to collect or find a significant data set, partition it into training and test sets, determine a decision tree, simplify it, determine the corresponding rule set, and simplify the rule set. 
</p><p>The study of methods to evaluate the error performance of a decision tree is probably too advanced for most undergraduate courses. 
</p><p><a name="8"><h2>References</h2></a></p><pre>   Breiman,Friedman,Olshen,Stone: Classification and Decision Trees
	Wadsworth, 1984

   A decision science perspective on decision trees.

   Quinlan,J.R.: C4.5: Programs for Machine Learning
	Morgan Kauffman, 1993

   Quinlan is a very readable, thorough book, with actual usable programs 
   that are available on the internet. Also available are a number of 
   interesting data sets.

   Quinlan,J.R.: Simplifying decision trees
	International Journal of Man-Machine Studies, 27, 221-234, 1987

   Winston,P.H.: Artificial Intelligence, Third Edition
	Addison-Wesley, 1992

   Excellent introduction to ID3 and its use in building decision trees and,
   from them, rule sets.
</pre><hr /><p><i>ingargiola@cis.temple.edu</i><br /><br />from: <a href="http://www.cis.temple.edu/~ingargio/cis587/readings/id3-c45.html">http://www.cis.temple.edu/~ingargio/cis587/readings/id3-c45.html</a></p></a></a></p><img src ="http://www.blogjava.net/weidagang2046/aggbug/48836.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/weidagang2046/" target="_blank">weidagang2046</a> 2006-05-29 21:45 <a href="http://www.blogjava.net/weidagang2046/articles/48836.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>称球问题的一般解法</title><link>http://www.blogjava.net/weidagang2046/articles/38833.html</link><dc:creator>weidagang2046</dc:creator><author>weidagang2046</author><pubDate>Mon, 03 Apr 2006 02:11:00 GMT</pubDate><guid>http://www.blogjava.net/weidagang2046/articles/38833.html</guid><wfw:comment>http://www.blogjava.net/weidagang2046/comments/38833.html</wfw:comment><comments>http://www.blogjava.net/weidagang2046/articles/38833.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/weidagang2046/comments/commentRss/38833.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/weidagang2046/services/trackbacks/38833.html</trackback:ping><description><![CDATA[
		<font face="宋体"> 称球问题相信大家已经很熟悉了，并且已经知道从12个球中找出坏球并判断其轻重最多只需要3次称量。但如果把球数改变一下，比如说13个球，答案又是几次呢？本文将对这一问题进行“深入”分析。为了后面叙述方便，先在这里把一般化后的问题重复一下：</font>
		<p>
				<font face="宋体">    有m（m≥3）个球，记为q<sub>1</sub>、q<sub>2</sub>、…、q<sub>m</sub>，其中有且仅有一个坏球，其重量与其他的不同，现使用无砝码的天平进行称量，令n为称量次数，问：能确保找到坏球并指出它与好球的轻重关系的n的最小值是多少？</font>
		</p>
		<p>
				<font face="宋体">    先来看理论上要多少次。每次称量有左边轻、平衡和右边轻共3种可能的情况，而坏球的可能结果有q<sub>1</sub>轻、q<sub>1</sub>重、q<sub>2</sub>轻、q<sub>2</sub>重、…、q<sub>m</sub>轻、q<sub>m</sub>重等共2m种。因此，根据商农的信息论，此问题的熵就是需要的称量次数，又因为n是整数，所以有：<img alt="" hspace="0" src="http://blog.vckbase.com/images/vckbase_com/localvar/701/o_ball-01.gif" border="0" /></font>
		</p>
		<p>
				<font face="宋体">    不过理论终归是理论，直接拿到现实生活中往往行不通。一个很简单的情况：4个球，上面的公式说2次称量就够了。但你可以想想办法，反正我是没找到两次解决问题的方案。 </font>
		</p>
		<p>
				<font face="宋体">    那，是理论错了吗？唔，我可不敢怀疑商农，我只敢怀疑我自己。来看看我们错在哪了吧。对4个球的情况，第一次称量只有两个可选的方案：方案1：q<sub>1</sub>放左盘，q<sub>2</sub>放右盘。若不平衡（由于对称性，只分析左边轻的情况，下同），则可能的结果还剩q<sub>1</sub>轻和q<sub>2</sub>重，再称一次就能找到坏球；若平衡，则可能的结果还剩q<sub>3</sub>轻、q<sub>3</sub>重、q<sub>4</sub>轻和q<sub>4</sub>重4个，再套用一下商农的定理，此时还要称<img alt="" hspace="0" src="http://blog.vckbase.com/images/vckbase_com/localvar/701/o_ball-02.gif" border="0" />次。所以方案1被否决。方案2：q<sub>1</sub>、q<sub>2</sub>放左盘，q<sub>3</sub>、q<sub>4</sub>放右盘。此时天平肯定不会平衡，称量后，可能的结果有q<sub>1</sub>轻、q<sub>2</sub>轻、q<sub>3</sub>重和q<sub>4</sub>重4个。同样的道理，方案2也难逃被否决的命运。</font>
		</p>
		<p>
				<font face="宋体">    在4个球这么简单的情况下就撞得满头是包，未免让人难以接受，总结一下经验教训吧，把上面的分析归纳一下并推广到一般情况，就是：整个称量过程中，要达到目的，倒数第k次称量前的可能结果数h，必须满足条件h≤3<sup>k</sup>。</font>
		</p>
		<p>
				<font face="宋体">    上面的得出的结论虽然不能让我们找到问题的答案，但却有助于我们确定每次称量的方案，特别是第一次如何做。假设我们计划的称量次数是n，第一次在左右两盘中各放x个球，则保证下面两个不等式同时成立是解决问题的必要条件：</font>
		</p>
		<p>
				<font face="宋体">    2(m-2x)≤3<sup>n-1</sup>  （平衡时）</font>
		</p>
		<p>
				<font face="宋体">    2x≤3<sup>n-1</sup> （不平衡时）</font>
		</p>
		<p>
				<font face="宋体">把这两个不等式稍加变换，就成了下面的样子：</font>
		</p>
		<p>
				<font face="宋体">
				</font>
		</p>
		<p>
				<font face="宋体">
						<img alt="" hspace="0" src="http://blog.vckbase.com/images/vckbase_com/localvar/701/o_ball-03.gif" border="0" />
						<br />注意到x是整数，3n-1是奇数，2m是偶数，所以上面的不等式等价于：</font>
		</p>
		<p>
				<font face="宋体">
				</font>
		</p>
		<p>
				<font face="宋体">
						<img alt="" hspace="0" src="http://blog.vckbase.com/images/vckbase_com/localvar/701/o_ball-04.gif" border="0" />
						<br />显然，在n一定的情况下，m越大，x的取值范围越小，而当x只能取值<img alt="" hspace="0" src="http://blog.vckbase.com/images/vckbase_com/localvar/701/o_ball-05.gif" border="0" />时，m继续增大，就会导致n次称量找到坏球的计划破产。籍此，可以得出在n一定的情况下m的取值范围：<img alt="" hspace="0" src="http://blog.vckbase.com/images/vckbase_com/localvar/701/o_ball-06.gif" border="0" />。发现了吗？现在m的最大值正好比我们最初的结果少了1。同时此结果也与前面提到的4个球的实际情况相符。</font>
		</p>
		<p>
				<font face="宋体">    但分析了半天，我们只证明了m不在取值范围内时，n次称量不能确保找到坏球。那m在取值范围内的时候，肯定能找到吗？答案是肯定的，不过马上证明它有点难，先来看两个简单一点的命题。</font>
		</p>
		<p>
				<font face="宋体">    命题1：有A、B两组球，球的个数分别为a、b，且0≤b-a≤1，已知这些球中有且仅有一个坏球，若它在A组中，则比正常球轻，在B组中则比正常球重。另有一个好球。先使用无砝码的天平称量，令<img alt="" hspace="0" src="http://blog.vckbase.com/images/vckbase_com/localvar/701/o_ball-07.gif" border="0" />，则可以找到一个称量方案，使得最多经过n次称量，就可以找到坏球（此时肯定能指出它与好球的重量关系）。</font>
		</p>
		<p>
				<font face="宋体">    使用数学归纳法证明如下：</font>
		</p>
		<p>
				<font face="宋体">    ①当n=1时，a、b的取值可能有{0，1}、{1，1}、{1，2}三组，由于还有一个已知的好球，所以不难验证此时命题成立。<br />    ②假设当n=k时命题也成立。<br />    ③当n=k+1时。我们将A、B两组球分别尽量平均得分为三组，记为A1、A2、A3、B1、B2和B3。不影响一般性，假设这六组球按球数从少到多的排列次序也与前面的顺序一致，且A1有球a1个。则第一次称量时的称量方案与每组球个数的对应关系如下，其中需要注意的是：在带蓝色的两种情况下，必有<img alt="" hspace="0" src="http://blog.vckbase.com/images/vckbase_com/localvar/701/o_ball-08.gif" border="0" />，否则就与命题的前提不符了。</font>
		</p>
		<p>
		</p>
		<table id="Table1" cellspacing="1" cellpadding="1" width="100%" border="1">
				<tbody>
						<tr>
								<td>
										<font face="宋体">A1</font>
								</td>
								<td>
										<font face="宋体">A2</font>
								</td>
								<td>
										<font face="宋体">A3</font>
								</td>
								<td>
										<font face="宋体">B1</font>
								</td>
								<td>
										<font face="宋体">B2</font>
								</td>
								<td>
										<font face="宋体">B3</font>
								</td>
								<td>
										<font face="宋体">称量方案</font>
								</td>
						</tr>
						<tr>
								<td>
										<font face="宋体">a1</font>
								</td>
								<td>
										<font face="宋体">a1</font>
								</td>
								<td>
										<font face="宋体">a1</font>
								</td>
								<td>
										<font face="宋体">a1</font>
								</td>
								<td>
										<font face="宋体">a1</font>
								</td>
								<td>
										<font face="宋体">a1</font>
								</td>
								<td>
										<font face="宋体">A1、B1放左盘；A2、B2放右盘 </font>
								</td>
						</tr>
						<tr>
								<td>
										<font face="宋体">a1</font>
								</td>
								<td>
										<font face="宋体">a1</font>
								</td>
								<td>
										<font face="宋体">a1</font>
								</td>
								<td>
										<font face="宋体">a1</font>
								</td>
								<td>
										<font face="宋体">a1</font>
								</td>
								<td>
										<font face="宋体">a1+1</font>
								</td>
								<td>
										<font face="宋体">A1、B1放左盘；A2、B2放右盘 </font>
								</td>
						</tr>
						<tr>
								<td>
										<font face="宋体">a1</font>
								</td>
								<td>
										<font face="宋体">a1</font>
								</td>
								<td>
										<font face="宋体">a1+1</font>
								</td>
								<td>
										<font face="宋体">a1</font>
								</td>
								<td>
										<font face="宋体">a1</font>
								</td>
								<td>
										<font face="宋体">a1+1</font>
								</td>
								<td>
										<font face="宋体">A1、B3放左盘；A3、B1放右盘 </font>
								</td>
						</tr>
						<tr>
								<td>
										<font face="宋体">a1</font>
								</td>
								<td>
										<font face="宋体">a1</font>
								</td>
								<td>
										<font face="宋体">a1+1</font>
								</td>
								<td>
										<font face="宋体">a1</font>
								</td>
								<td>
										<font face="宋体">a1+1</font>
								</td>
								<td>
										<font face="宋体">a1+1</font>
								</td>
								<td>
										<font face="宋体">A1、B2放左盘；A2、B3放右盘 </font>
								</td>
						</tr>
						<tr>
								<td>
										<font face="宋体" color="#0000ff">a1</font>
								</td>
								<td>
										<font face="宋体" color="#0000ff">a1+1</font>
								</td>
								<td>
										<font face="宋体" color="#0000ff">a1+1</font>
								</td>
								<td>
										<font face="宋体" color="#0000ff">a1</font>
								</td>
								<td>
										<font face="宋体" color="#0000ff">a1+1</font>
								</td>
								<td>
										<font face="宋体" color="#0000ff">a1+1</font>
								</td>
								<td>
										<font face="宋体" color="#0000ff">A2、B2放左盘；A3、B3放右盘 </font>
								</td>
						</tr>
						<tr>
								<td>
										<font face="宋体" color="#0000ff">a1</font>
								</td>
								<td>
										<font face="宋体" color="#0000ff">a1+1</font>
								</td>
								<td>
										<font face="宋体" color="#0000ff">a1+1</font>
								</td>
								<td>
										<font face="宋体" color="#0000ff">a1+1</font>
								</td>
								<td>
										<font face="宋体" color="#0000ff">a1+1</font>
								</td>
								<td>
										<font face="宋体" color="#0000ff">a1+1</font>
								</td>
								<td>
										<font face="宋体" color="#0000ff">A2、B2放左盘；A3、B3放右盘</font>
								</td>
						</tr>
				</tbody>
		</table>
		<p>
				<font face="宋体">很明显，不管结果是什么，第一次称量之后，问题都能转化为n=k时的情形。所以，命题1是真命题。</font>
		</p>
		<p>
				<font face="宋体">    前面已经证明<img alt="" hspace="0" src="http://blog.vckbase.com/images/vckbase_com/localvar/701/o_ball-09.gif" border="0" />时，n次称量无法确保找到坏球并指出其轻重关系。但如果此时也有一个已知的好球的话，答案就不一样了，这时n次称量就已经足够（命题2）。仍使用数学归纳法。</font>
		</p>
		<p>
				<font face="宋体">    ①当n=2时，m=4，验证一下可知命题成立。 <br />    ②假设当n=k时命题也成立。 <br />    ③当n=k+1时。我们把这些球尽量平均的分成三组，则每组球的个数分别为：<img alt="" hspace="0" src="http://blog.vckbase.com/images/vckbase_com/localvar/701/o_ball-10.gif" border="0" />、<img alt="" hspace="0" src="http://blog.vckbase.com/images/vckbase_com/localvar/701/o_ball-10.gif" border="0" />、<img alt="" hspace="0" src="http://blog.vckbase.com/images/vckbase_com/localvar/701/o_ball-11.gif" border="0" />。第一次称量时，第一组和那个好球放左盘，第三组放右盘。若平衡，问题转化为n=k时的情形，不平衡，问题转化为命题1的情形。命题成立。 </font>
		</p>
		<p>
				<font face="宋体">    有了前面两个证明作基础，最初的问题就很简单了，再次祭出数据学归纳法。由于m&lt;5时的情况有些特殊(考虑只有一个球或两个球的情况)，不能作为递推得依据，所以我们从n=3，也就是m=5开始。</font>
		</p>
		<p>
				<font face="宋体">    ①当n=3时，m在5和12之间（13的情况已经被排除在外），通过一一验证可知命题成立。 <br />    ②假设当n=k时命题也成立。 <br />    ③当n=k+1时，找到一个满足不等式<img alt="" hspace="0" src="http://blog.vckbase.com/images/vckbase_com/localvar/701/o_ball-04.gif" border="0" />的x，在天平左右两盘中各放x个球。如果天平平衡，问题转化为n=k时的情形或命题2中的情形；不平衡，则转化为命题1的情形。命题成立。</font>
		</p>
		<p>
				<font face="宋体">    综上所述，称球问题的完整答案是：当球数<img alt="" hspace="0" src="http://blog.vckbase.com/images/vckbase_com/localvar/701/o_ball-06.gif" border="0" />时，n次称量时就能确保找到坏球，并指出它与好球的轻重关系；当球数<img alt="" hspace="0" src="http://blog.vckbase.com/images/vckbase_com/localvar/701/o_ball-09.gif" border="0" />时，n次称量只能确保找到坏球，而无法指出它与好球的轻重关系。要想指出轻重关系，就可能需要多进行一次称量。但如果此时再有一个好球，就又可以把这次称量省掉了。<br /><br />from: <a href="http://blog.vckbase.com/localvar/archive/2005/07/17/9717.aspx">http://blog.vckbase.com/localvar/archive/2005/07/17/9717.aspx</a></font>
		</p>
<img src ="http://www.blogjava.net/weidagang2046/aggbug/38833.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/weidagang2046/" target="_blank">weidagang2046</a> 2006-04-03 10:11 <a href="http://www.blogjava.net/weidagang2046/articles/38833.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>信号量</title><link>http://www.blogjava.net/weidagang2046/articles/17420.html</link><dc:creator>weidagang2046</dc:creator><author>weidagang2046</author><pubDate>Sun, 30 Oct 2005 04:44:00 GMT</pubDate><guid>http://www.blogjava.net/weidagang2046/articles/17420.html</guid><wfw:comment>http://www.blogjava.net/weidagang2046/comments/17420.html</wfw:comment><comments>http://www.blogjava.net/weidagang2046/articles/17420.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/weidagang2046/comments/commentRss/17420.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/weidagang2046/services/trackbacks/17420.html</trackback:ping><description><![CDATA[<TABLE cellSpacing=0 cellPadding=5 width=700 border=0>
<TBODY>
<TR>
<TD>
<P align=right><A href="http://www.huihoo.com/os/process/main.htm" tppabs="http://home.sei.pku.edu.cn/~panying/os/main.htm"><BR><FONT face=楷体_GB2312 color=rgb(128,0,255)>返回本讲概述</FONT><BR></A></P></TD></TR>
<TR>
<TD align=left width="100%" bgColor=#50a0a0></A><A name=0></A><FONT face=楷体_GB2312 color=#ffff0f size=6><STRONG>信号量 </STRONG></FONT></TD></TR>
<TR>
<TD align=left width="100%" bgColor=#50a0a0><A name=0></A><FONT face=楷体_GB2312 color=#ffffff size=4><STRONG>信号量是最早出现的用来解决进程同步与互斥问题的机制，<BR>包括一个称为信号量的变量及对它进行的两个原语操作。<BR></STRONG></FONT></TD></TR>
<TR>
<TD align=left width="100%" bgColor=#50a0a0><A name=0></A><FONT face=楷体_GB2312 color=#ffffff size=4><STRONG>本节将从以下几个方面进行介绍-- </STRONG></FONT>
<TABLE width="50%" border=0>
<TBODY>
<TR>
<TD width="100%" colSpan=2>
<HR color=#662299 SIZE=1>
</TD></TR>
<TR>
<TD language=JavaScript onmouseup=turnit(Content2,Img2,Aux2); style="CURSOR: hand" width=2>
<P align=right><IMG id=Img2 height=9 src="http://www.huihoo.com/os/process/minus.gif" width=9 tppabs="http://home.sei.pku.edu.cn/~panying/os/plus.gif"></P></TD>
<TD language=JavaScript onmouseup=turnit(Content2,Img2,Aux2); id=item2 style="CURSOR: hand"><STRONG><A href="http://www.huihoo.com/os/process/semaphore.htm#1-1"><SPAN class=90V><FONT class=90v color=#0000a0 size=2>一. 信号量的概念 </FONT></SPAN></A></STRONG></TD></TR>
<TR>
<TD id=Aux2 width=14></TD>
<TD id=Content2>
<TABLE width="100%" border=0>
<TBODY>
<TR>
<TD width="100%"><FONT class=90v size=2><A class=bb href="http://www.huihoo.com/os/process/semaphore.htm#2-1"><SPAN class=90V>1． 信号量的类型定义 </SPAN></A></FONT></TD></TR>
<TR>
<TD width="100%"><FONT class=90v size=2><A class=bb href="http://www.huihoo.com/os/process/semaphore.htm#2-2"><SPAN class=90V>2． PV原语 </SPAN></A></FONT></TD></TR></TBODY></TABLE></TD></TR>
<TR>
<TD width="100%" colSpan=2>
<HR color=#662299 SIZE=1>
</TD></TR>
<TR>
<TD language=JavaScript onmouseup=turnit(Content3,Img3,Aux3); style="CURSOR: hand" width=2>
<P align=right><IMG id=Img3 height=9 src="http://www.huihoo.com/os/process/minus.gif" width=9 tppabs="http://home.sei.pku.edu.cn/~panying/os/plus.gif"></P></TD>
<TD language=JavaScript onmouseup=turnit(Content3,Img3,Aux3); id=item3 style="CURSOR: hand"><STRONG><A href="http://www.huihoo.com/os/process/semaphore.htm#1-2"><SPAN class=90V><FONT class=90v color=#0000a0 size=2>二. 实例 </FONT></SPAN></A></STRONG></TD></TR>
<TR>
<TD id=Aux3 width=14></TD>
<TD id=Content3>
<TABLE width="100%" border=0>
<TBODY>
<TR>
<TD width="100%"><FONT class=90v size=2><A class=bb href="http://www.huihoo.com/os/process/semaphore.htm#3-1"><SPAN class=90V>1． 生产者-消费者问题（有buffer） </SPAN></A></FONT></TD></TR>
<TR>
<TD width="100%"><FONT class=90v size=2><A class=bb href="http://www.huihoo.com/os/process/semaphore.htm#3-2"><SPAN class=90V>2． 第一类读-写者问题 </SPAN></A></FONT></TD></TR>
<TR>
<TD width="100%"><FONT class=90v size=2><A class=bb href="http://www.huihoo.com/os/process/semaphore.htm#3-3"><SPAN class=90V>3． 哲学家问题 </SPAN></A></FONT></TD></TR></TBODY></TABLE></TD></TR>
<TR>
<TD width="100%" colSpan=2>
<HR color=#662299 SIZE=1>
</TD></TR></TBODY></TABLE></TD></TR></TBODY></TABLE>
<TABLE cellSpacing=0 cellPadding=5 width=600 border=0>
<TBODY>
<TR>
<TD width="100%" bgColor=#50a0a0><A name=1-1></A><STRONG><FONT color=#ffffff>一. 信号量的概念 </FONT></STRONG></TD></TR>
<TR>
<TD width="100%"><A name=2-1></A><STRONG><FONT color=#ffffff>1． 信号量的类型定义 </FONT></STRONG></TD></TR>
<TR>
<TD width="100%"><BR>每个信号量至少须记录两个信息：信号量的值和等待该信号量的进程队列。它的类型定义如下：（用类PASCAL语言表述）<BR>&nbsp;&nbsp;&nbsp;&nbsp;semaphore = record <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; value: integer; <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; queue: ^PCB; <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; end; <BR>&nbsp;&nbsp;其中PCB是进程控制块，是操作系统为每个进程建立的数据结构。<BR>s.value&gt;=0时，s.queue为空； <BR>s.value&lt;0时，s.value的绝对值为s.queue中等待进程的个数；<BR></TD></TR>
<TR>
<TD width="100%"><FONT face=楷体_GB2312 color=#8000ff>
<P align=right></FONT><FONT face=楷体_GB2312 color=#8000ff size=3><A style="COLOR: rgb(128,0,255); TEXT-DECORATION: none" href="http://www.huihoo.com/os/process/semaphore.htm#0"><STRONG>返回</STRONG> </A></FONT></P></TD></TR>
<TR>
<TD width="100%">
<HR align=left color=#008080 noShade>
</TD></TR>
<TR>
<TD width="100%"><A name=2-2></A><STRONG><FONT color=#ffffff>2． PV原语 </FONT></STRONG></TD></TR>
<TR>
<TD width="100%"><BR>对一个信号量变量可以进行两种原语操作：p操作和v操作，定义如下： &nbsp;&nbsp;procedure p(var s:samephore); <BR>&nbsp;&nbsp;&nbsp;&nbsp; { <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; s.value=s.value-1;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (s.value&lt;0) asleep(s.queue); <BR>&nbsp;&nbsp;&nbsp;&nbsp; } <BR>&nbsp;&nbsp;procedure v(var s:samephore); <BR>&nbsp;&nbsp;&nbsp;&nbsp; { <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; s.value=s.value+1;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (s.value&lt;=0) wakeup(s.queue); <BR>&nbsp;&nbsp;&nbsp;&nbsp; } <BR><BR>其中用到两个标准过程：<BR>&nbsp;&nbsp;asleep(s.queue);执行此操作的进程的PCB进入s.queue尾部，进程变成等待状态<BR>&nbsp;&nbsp;wakeup(s.queue);将s.queue头进程唤醒插入就绪队列<BR>s.value初值为1时，可以用来实现进程的互斥。<BR>p操作和v操作是不可中断的程序段，称为原语。如果将信号量看作共享变量，则pv操作为其临界区，多个进程不能同时执行，一般用硬件方法保证。一个信号量只能置一次初值，以后只能对之进行p操作或v操作。<BR>由此也可以看到，信号量机制必须有公共内存，不能用于分布式操作系统，这是它最大的弱点。<BR><BR></TD></TR>
<TR>
<TD width="100%"><FONT face=楷体_GB2312 color=#8000ff>
<P align=right></FONT><FONT face=楷体_GB2312 color=#8000ff size=3><A style="COLOR: rgb(128,0,255); TEXT-DECORATION: none" href="http://www.huihoo.com/os/process/semaphore.htm#0"><STRONG>返回</STRONG> </A></FONT></P></TD></TR>
<TR>
<TD width="100%">
<HR align=left color=#008080 noShade>
</TD></TR>
<TR>
<TD width="100%"><A name=1-2></A><STRONG><FONT color=#ffffff>二. 实例 </FONT></STRONG></TD></TR>
<TR>
<TD width="100%"><A name=3-1></A><STRONG><FONT color=#ffffff>1． 生产者-消费者问题（有buffer） </FONT></STRONG></TD></TR>
<TR>
<TD width="100%"><BR>问题描述：<BR>&nbsp;&nbsp;一个仓库可以存放K件物品。生产者每生产一件产品，将产品放入仓库，仓库满了就停止生产。消费者每次从仓库中去一件物品，然后进行消费，仓库空时就停止消费。<BR>解答：<BR>&nbsp;&nbsp;进程：Producer - 生产者进程，Consumer - 消费者进程 <BR>&nbsp;&nbsp;共有的数据结构：<BR>&nbsp;&nbsp;&nbsp;&nbsp; buffer: array [0..k-1] of integer; <BR>&nbsp;&nbsp;&nbsp;&nbsp; in,out: 0..k-1; <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; — in记录第一个空缓冲区，out记录第一个不空的缓冲区 <BR>&nbsp;&nbsp;&nbsp;&nbsp; s1,s2,mutex: semaphore; <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; — s1控制缓冲区不满,s2控制缓冲区不空,mutex保护临界区；<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 初始化s1=k,s2=0,mutex=1<BR>&nbsp;&nbsp;producer（生产者进程）： <BR>&nbsp;&nbsp; Item_Type item; <BR>&nbsp;&nbsp;{ <BR>&nbsp;&nbsp;&nbsp;&nbsp; while (true) <BR>&nbsp;&nbsp;&nbsp;&nbsp; { <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; produce(&amp;item);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; p(s1); <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; p(mutex); <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; buffer[in]:=item; <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; in:=(in+1) mod k; <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; v(mutex); <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; v(s2); <BR>&nbsp;&nbsp;&nbsp;&nbsp; } <BR>&nbsp;&nbsp;} <BR><BR>&nbsp;&nbsp;consumer（消费者进程）： <BR>&nbsp;&nbsp; Item_Type item; <BR>&nbsp;&nbsp;{ <BR>&nbsp;&nbsp;&nbsp;&nbsp; while (true) <BR>&nbsp;&nbsp;&nbsp;&nbsp; { <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; p(s2); <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; p(mutex); <BR>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; item:=buffer[out]; <BR>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; out:=(out+1) mod k; <BR>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; v(mutex); <BR>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; v(s1); <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; consume(&amp;item);<BR>&nbsp;&nbsp;&nbsp;&nbsp; } <BR>&nbsp;&nbsp; } <BR><BR><A style="COLOR: rgb(0,0,255)" href="http://www.huihoo.com/os/process/ps.htm" tppabs="http://home.sei.pku.edu.cn/~panying/os/ps_sema/ps.htm"><FONT face=楷体_GB2312 color=#8000ff size=4>例程演示 </FONT></A><BR></TD></TR>
<TR>
<TD width="100%"><FONT face=楷体_GB2312 color=#8000ff>
<P align=right></FONT><FONT face=楷体_GB2312 color=#8000ff size=3><A style="COLOR: rgb(128,0,255); TEXT-DECORATION: none" href="http://www.huihoo.com/os/process/semaphore.htm#0"><STRONG>返回</STRONG> </A></FONT></P></TD></TR>
<TR>
<TD width="100%">
<HR align=left color=#008080 noShade>
</TD></TR>
<TR>
<TD width="100%"><A name=3-2></A><STRONG><FONT color=#ffffff>2． 第一类读-写者问题 </FONT></STRONG></TD></TR>
<TR>
<TD width="100%"><BR>问题描述：<BR>&nbsp;&nbsp;一些读者和一些写者对同一个黑板进行读写。多个读者可同时读黑板，但一个时刻只能有一个写者，读者写者不能同时使用黑板。对使用黑板优先级的不同规定使读者-写者问题又可分为几类。第一类问题规定读者优先级较高，仅当无读者时允许写者使用黑板。<BR>解答：<BR>&nbsp;&nbsp;进程：writer - 写者进程，reader - 读者进程 <BR>&nbsp;&nbsp;共有的数据结构：<BR>&nbsp;&nbsp;&nbsp;&nbsp; read_account:integer; <BR>&nbsp;&nbsp;&nbsp;&nbsp; r_w,mutex: semaphore; <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; — r_w控制谁使用黑板,mutex保护临界区，初值都为1<BR>&nbsp;&nbsp;reader - (读者进程）： <BR>&nbsp;&nbsp;{ <BR>&nbsp;&nbsp;&nbsp;&nbsp; while (true) <BR>&nbsp;&nbsp;&nbsp;&nbsp; { <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; p(mutex);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; read_account++; <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if(read_account=1) p(r_w); <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; v(mutex); <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; read(); <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; p(mutex); <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; read_account--; <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if(read_account=0) v(r_w);; <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; v(mutex); <BR>&nbsp;&nbsp;&nbsp;&nbsp; } <BR>&nbsp;&nbsp;} <BR><BR>&nbsp;&nbsp;writer - (写者进程）： <BR>&nbsp;&nbsp;{ <BR>&nbsp;&nbsp;&nbsp;&nbsp; while (true) <BR>&nbsp;&nbsp;&nbsp;&nbsp; { <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; p(mutex); <BR>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; write(); <BR>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; v(mutex); <BR>&nbsp;&nbsp;&nbsp;&nbsp; } <BR>&nbsp;&nbsp; } <BR><BR><A style="COLOR: rgb(0,0,255)" href="http://www.huihoo.com/os/process/rw.htm" tppabs="http://home.sei.pku.edu.cn/~panying/os/rw_sema/rw.htm"><FONT face=楷体_GB2312 color=#8000ff size=4>例程演示 </FONT></A><BR></TD></TR>
<TR>
<TD width="100%"><FONT face=楷体_GB2312 color=#8000ff>
<P align=right></FONT><FONT face=楷体_GB2312 color=#8000ff size=3><A style="COLOR: rgb(128,0,255); TEXT-DECORATION: none" href="http://www.huihoo.com/os/process/semaphore.htm#0"><STRONG>返回</STRONG> </A></FONT></P></TD></TR>
<TR>
<TD width="100%">
<HR align=left color=#008080 noShade>
</TD></TR>
<TR>
<TD width="100%"><A name=3-3></A><STRONG><FONT color=#ffffff>3． 哲学家问题 </FONT></STRONG></TD></TR>
<TR>
<TD width="100%"><BR>问题描述：<BR>&nbsp;&nbsp;一个房间内有5个哲学家，他们的生活就是思考和进食。房间里有一张圆桌，中间放着一盘通心粉（假定通心粉无限多）。桌子周围放有五把椅子，分别属于五位哲学家每两位哲学家之间有一把叉子，哲学家进食时必须同时使用左右两把叉子。<BR>解答：<BR>&nbsp;&nbsp;进程：philosopher - 哲学家 <BR>&nbsp;&nbsp;共有的数据结构&amp;过程：<BR>&nbsp;&nbsp;&nbsp;&nbsp; state: array [0..4] of (think,hungry,eat); <BR>&nbsp;&nbsp;&nbsp;&nbsp; ph: array [0..4] of semaphore; <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; — 每个哲学家有一个信号量，初值为0 <BR>&nbsp;&nbsp;&nbsp;&nbsp; mutex: semaphore; <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; — mutex保护临界区，初值=1<BR>&nbsp;&nbsp;&nbsp;&nbsp; procedure test(i:0..4); <BR>&nbsp;&nbsp;&nbsp;&nbsp; { <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if ((state[i]=hungry) and (state[(i+1)mod 5]&lt;&gt;eating)<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; and (state[(i-1)mod 5]&lt;&gt;eating))<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; { state[i]=eating; <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; V(ph[i]); <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<BR>&nbsp;&nbsp;&nbsp;&nbsp; } <BR><BR>&nbsp;&nbsp;philosopher(i:0..4)： <BR>&nbsp;&nbsp;{ <BR>&nbsp;&nbsp;&nbsp;&nbsp; while (true) <BR>&nbsp;&nbsp;&nbsp;&nbsp; { <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; think();<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; p(mutex); <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; state[i]=hungry; <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; test(i); <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; v(mutex); <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; p(ph[i]); <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; eat(); <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; p(mutex); <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; state[i]=think; <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; test((i-1) mod 5); <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; test((i+1) mod 5); <BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; v(mutex); <BR>&nbsp;&nbsp;&nbsp;&nbsp; } <BR>&nbsp;&nbsp;} <BR><BR><A style="COLOR: rgb(0,0,255)" href="http://www.huihoo.com/os/process/dp.htm" tppabs="http://home.sei.pku.edu.cn/~panying/os/dp_sema/dp.htm"><FONT face=楷体_GB2312 color=#8000ff size=4>例程演示 </FONT></A><BR></TD></TR>
<TR>
<TD width="100%"><FONT face=楷体_GB2312 color=#8000ff>
<P align=right></FONT><FONT face=楷体_GB2312 color=#8000ff size=3><A style="COLOR: rgb(128,0,255); TEXT-DECORATION: none" href="http://www.huihoo.com/os/process/semaphore.htm#0"><STRONG>返回</STRONG> </A></FONT></P></TD></TR></TBODY></TABLE><img src ="http://www.blogjava.net/weidagang2046/aggbug/17420.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/weidagang2046/" target="_blank">weidagang2046</a> 2005-10-30 12:44 <a href="http://www.blogjava.net/weidagang2046/articles/17420.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>理解计算</title><link>http://www.blogjava.net/weidagang2046/articles/4186.html</link><dc:creator>weidagang2046</dc:creator><author>weidagang2046</author><pubDate>Wed, 11 May 2005 16:27:00 GMT</pubDate><guid>http://www.blogjava.net/weidagang2046/articles/4186.html</guid><wfw:comment>http://www.blogjava.net/weidagang2046/comments/4186.html</wfw:comment><comments>http://www.blogjava.net/weidagang2046/articles/4186.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/weidagang2046/comments/commentRss/4186.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/weidagang2046/services/trackbacks/4186.html</trackback:ping><description><![CDATA[<FONT size=2>&nbsp;随着计算机日益广泛而深刻的运用，计算这个原本专门的数学概念已经泛化到了人类的整个知识领域，并上升为一种极为普适的科学概念和哲学概念，成为人们认识事物、研究问题的一种新视角、新观念和新方法。</FONT>
<P align=center><STRONG><FONT size=2>什么是计算与计算的类型</FONT></STRONG></P>
<P><FONT size=2>&nbsp;&nbsp;&nbsp;&nbsp;在大众的意识里，计算首先指的就是数的加减乘除，其次则为方程的求解、函数的微分积分等；懂的多一点的人知道，计算在本质上还包括定理的证明推导。可以说，“计算”是一个无人不知元人不晓的数学概念，但是，真正能够回答计算的本质是什么的人恐怕不多。事实上，直到1930年代，由于哥德尔（K.Godel，1906-1978）、丘奇(A.Church，1903-1995)、图灵(A.M.TUI-ing，1912-1954)等数学家的工作，人们才弄清楚什么是计算的本质，以及什么是可计算的、什么是不可计算的等根本性问题。</FONT></P>
<P><FONT size=2>&nbsp;&nbsp;&nbsp;&nbsp;抽象地说，所谓计算，就是从一个符号串f变换成另一个符号串g。比如说,从符号串12+3变换成15就是一个加法计算。如果符号串f是<IMG height=15 src="http://cfc.nankai.edu.cn/readings/image/lijie/1.jpg" width=15>，而符号串g是2x,从f到g的计算就是微分。定理证明也是如此，令f表示一组公理和推导规则，令g是一个定理,那么从f到g的一系列变换就是定理g的证明。从这个角度看，文字翻译也是计算，如f代表一个英文句子，而g为含意相同的中文句子，那么从f到g就是把英文翻译成中文。这些变换间有什么共同点？为什么把它们都叫做计算？因为它们都是从己知符号(串)开始，一步一步地改变符号(串)，经过有限步骤，最后得到一个满足预先规定的符号(串)的变换过程。</FONT></P>
<P><FONT size=2>&nbsp;&nbsp;&nbsp;&nbsp;从类型上讲，计算主要有两大类：数值计算和符号推导。数值计算包括实数和函数的加减乘除、幕运算、开方运算、方程的求解等。符号推导包括代数与各种函数的恒等式、不等式的证明,几何命题的证明等。但无论是数值计算还是符号推导,它们在本质上是等价的、一致的，即二者是密切关联的，可以相互转化，具有共同的计算本质。随着数学的不断发展,还可能出现新的计算类型。</FONT></P>
<P align=center><STRONG><FONT size=2>计算的实质与E奇－图灵论点</FONT></STRONG></P>
<P><FONT size=2>&nbsp;&nbsp;&nbsp;&nbsp;为了回答究竟什么是计算、什么是可计算性等问题，人们采取的是建立计算模型的方法。从20世纪30年代到40年代，数理逻辑学家相继提出了四种模型，它们是一般递归函数、λ可计算函数、图灵机和波斯特(E.L.Post，1897-1954)系统。这种种模型完全从不同的角度探究计算过程或证明过程，表面上看区别很大，但事实上却是等价的，即它们完全具有一样的计算能力D在这一事实基础上，最终形成了如今著名的丘奇-图灵论点：凡是可计算的函数都是一般递归函数(或是图灵机可计算函数等)。这就确立了计算与可计算性的数学含义。下面主要对一般递归函数作一简要介绍。</FONT></P>
<P><FONT size=2>&nbsp;&nbsp;&nbsp;&nbsp;哥德尔首先在1931年提出了原始递归函数的概念。所谓原始递归函数,就是由初始函数出发，经过有限次的使用代人与原始递归式而做出的函数。这里所说的初始函数是指下列三种函数：</FONT></P>
<P><FONT size=2>&nbsp;&nbsp;&nbsp;&nbsp;(1) 零函数0(x)=0(函数值恒为零)；</FONT></P>
<P><FONT size=2>&nbsp;&nbsp;&nbsp;&nbsp;(2) 射影函数<IMG height=15 src="http://cfc.nankai.edu.cn/readings/image/lijie/2.jpg" width=25>(x1,x2,…,xn)=xi(1≤i≤n)(函数的值与第i个自变元的值相同)；</FONT></P>
<P><FONT size=2>&nbsp;&nbsp;&nbsp;&nbsp;后继函数S(x)=x+1(其值为x的直接后继数)。</FONT></P>
<P><FONT size=2>&nbsp;&nbsp;&nbsp;&nbsp;代人与原始递归式是构造新函数的算子。</FONT></P>
<P><FONT size=2>&nbsp;&nbsp;&nbsp;&nbsp;代人(又名叠置、迭置)，它是最简单又最重要的算子,其一般形式是:由一个m元函数f与m个n元函数g1，g2，…，gm造成新函数f(g1(x1,x2,…,xn),g2(x1,x2,…,xn),…,gm(x1,x2,…,xn))。</FONT></P>
<P><FONT size=2>&nbsp;&nbsp;&nbsp;&nbsp;原始递归式，其一般形式为</FONT></P>
<P align=center><FONT size=2><IMG height=80 src="http://cfc.nankai.edu.cn/readings/image/lijie/3.jpg" width=300></FONT></P>
<P><FONT size=2>&nbsp;&nbsp;&nbsp;&nbsp;特殊地为</FONT></P>
<P align=center><FONT size=2><IMG height=50 src="http://cfc.nankai.edu.cn/readings/image/lijie/4.jpg" width=300></FONT></P>
<P><FONT size=2>其特点是，不能由g,h两已知函数直接计算新函数的一般值f(u,x),而只能依次计算f(u,0)，f(u,1)，f(u,2)，…；但只要依次计算，必能把任何一个f(u,x)，对值都算出来。换句话说，只要g,h有定义且可计算，则新函数f也有定义且可计算。</FONT></P>
<P><FONT size=2>&nbsp;&nbsp;&nbsp;&nbsp;根据埃尔布朗(J.Herbrand，1908-1931)一封信的暗示，哥德尔于1934年引进了一般递归函数的概念。后经克林(S.C.Kleene，1909-1994)的改进与阐明，便出现了现在普遍采用的定义。所谓一般递归函数，就是由初始函数出发，经过有限次使用代人、原始递归式和μ算子而做成的有定义的函数。 这里的μ算子就是造逆函数的算子或求根算子。</FONT></P>
<P><FONT size=2><IMG height=400 src="http://cfc.nankai.edu.cn/readings/image/lijie/p34.jpg" width=300 align=right>&nbsp;&nbsp;&nbsp;&nbsp;如此定义的一般递归函数比原始递归函数更广，这是没有任何疑问的。但是，人们还是可以问：这样定义的函数是否已经包括了所有直观上的可计算函数？如果还有更广的可计算函数又该怎样定义？在受到这类问题困惑的同时，丘奇、克林又提出了一类可计算函数，叫做λ可计算函数。但事隔不久，丘奇和克林便分别证明了λ可计算函数正好就是一般递归函数，即这两类可计算函数是等价的、一致的。在这一有力的证据基础上，丘奇于1936年公开发表了他早在两年前就孕育过的一个论点，即著名的丘奇论点：每个能行地可计算的函数都是一般递归函数。</FONT></P>
<P><FONT size=2>&nbsp;&nbsp;&nbsp;&nbsp;与此同时，图灵定义了另一类可计算函数，叫做图灵机可计算性函数,并且提出了著名的图灵论点：能行可计算函数都是用图灵机可计算的函数。图灵机是图灵提出的一种计算模型，或一台理论计算机口它可以说是对人类计算与机器计算的最一般、最高度的抽象。一年后，图灵进一步证明了图灵机可计算函数与λ可定义函数是一致的，当然也就和一般递归函数一致、等价。于是，表面上不同的三类可计算函数在本质上就是一类。这样一来，丘奇论点和图灵论点也就是一回事了，现将它们合称为丘奇-图灵论点，即直观的能行可计算函数等同于一般递归函数、可λ定义函数和图灵机可计算函数。</FONT></P>
<P><FONT size=2>&nbsp;&nbsp;&nbsp;&nbsp;丘奇－图灵论点的提出，标志着人类对可计算函数与计算本质的认识达到了空前的高度，它是数学史上一块夺目的里程碑。</FONT></P>
<P><FONT size=2>&nbsp;&nbsp;&nbsp;&nbsp;一般递归函数比较抽象，为此给出一种较为直观的解释。大家知道，凡能够计算的，即使是“心算”，总可以把其计算过程记录下来，而且是逐个步骤逐个步骤地记录下来。所谓计算过程，是指从初始符号或已知符号开始，一步一步地改变(变换)符号，最后得到一个满足预先规定的条件的符号，并从该符号按照一定方法得到所求结果，即所求函数的值的全过程。可如此计算的函数，一般称为可以在有限步骤内计算的函数。现已证明：凡是可以从某些初始符号开始，而在有限步骤内计算的函数都是递归函数。由此可以看到，“能够记录下来”便符合了可计算性或递归性的本质要求。一般递归函数的实质也由此显得十分直观易懂。</FONT></P>
<P><FONT size=2>&nbsp;&nbsp;&nbsp;&nbsp;丘奇－图灵论点的提出与确认，在数学和计算机科学上具有重大的理论和现实意义。正如我国数理逻辑专家莫绍揆教授所言，有了这个论点以后，就可以断定某些问题是不能能行地解决或不能能行地判定的。对于计算机科学，丘奇-图灵论点的意义在于它明确刻画了计算机的本质或计算机的计算能力，确定了计算机只能计算一般递归函数，对于一般递归函数之外的函数，计算机是无法计算的。</FONT></P>
<P align=center><STRONG><FONT size=2>DNA计算:新型计算方式的出现</FONT></STRONG></P>
<P><FONT size=2>&nbsp;&nbsp;&nbsp;&nbsp;1994年11月，美国计算机科学家阿德勒曼(L.Adleman)在美国《科学》上公布DNA计算机的理论，并成功运用DNA计算机解决了一个有向哈密顿路径问题。 DNA计算机的提出，产生于这样一个发现，即生物与数学的相似性：(1)生物体异常复杂的结构是对由DNA序列表示的初始信息执行简单操作(复制、剪接)的结果；(2)可计算函数f(ω)的结果可以通过在ω上执行一系列基本的简单函数而获得。</FONT></P>
<P><FONT size=2>&nbsp;&nbsp;&nbsp;&nbsp;阿德勒曼不仅意识到这两个过程的相似性，而且意识到可以利用生物过程来模拟数学过程。更确切地说是，DNA串可用于表示信息，酶可用于模拟简单的计算。这是因为：首先，DNA是由称作核昔酸的一些单元组成，这些核昔酸随着附在其上的化学组或基的不同而不同。共有四种基：腺嘌呤、鸟嘌呤、胞嘧啶和胸腺嘧啶，分别用A、G、C、T表示。单链DNA可以看作是由符号A、G、C、T组成的字符串。从数学上讲，这意味着可以用一个含有四个字符的字符集∑ =A、G、C、T来为信息编码(电子计算机仅使用0和1这两个数字)。其次，DNA序列上的一些简单操作需要酶的协助，不同的酶发挥不同的作用。起作用的有四种酶：限制性内切酶，主要功能是切开包含限制性位点的双链DNA；DNA连接酶,它主要是把一个DNA链的端点同另一个链连接在一起；DNA聚合酶,它的功能包括DNA的复制与促进DNA的合成；外切酶，它可以有选择地破坏双链或单链DNA分子。正是基于这四种酶的协作实现了DNA计算。</FONT></P>
<P><FONT size=2>&nbsp;&nbsp;&nbsp;&nbsp;不过，目前DNA计算机能够处理的问题，还仅仅是利用分子技术解决的几个特定问题，属一次性实验。DNA计算机还没有一个固定的程式。由于问题的多样性，导致所采用的分子生物学技术的多样性，具体问题需要设计具体的实验方案口这便引出了两个根本性问题(也是阿德勒曼最早意识到的)：(1)DNA计算机可以解决哪些问题确切地说，DNA计算机是完备的吗？即通过操纵DNA能完成所有的(图灵机)可计算函数吗？(2)是否可设计出可编程序的DNA计算机？即是否存在类似于电子计算机的通用计算模型——图灵机——那样的通用DNA系统(模型)？目前，人们正处在对这两个根本性问题的研究过程之中口在笔者看来，这就类似于在电子计算机诞生之前的20世纪三四十年代理论计算机的研究阶段。如今，已经提出了多种DNA计算模型，但各有千秋，公认的DNA计算机的“图灵机”还没有诞生。相对而言，一种被称为“剪接系统”的DNA计算机模型较为成功。</FONT></P>
<P><FONT size=2>&nbsp;&nbsp;&nbsp;&nbsp;有了“剪接系统”这个DNA计算机的数学模型后，便可以来回答前面提出的DNA计算的完备性与通用性问题。前面讲过，丘奇-图灵论点深刻地刻画了任何实际计算机的计算能力——任何可计算函数都是可由图灵机计算的函数(一般递归函数)。现已证明：剪接系统是计算完备的，即任何可计算函数都可用剪接系统来计算D反之亦然。这就回答了DNA计算机可以解决哪些问题——全部图灵机可计算问题。至于是否存在基于剪接的可编程计算机，也有了肯定的答案：对每个给定的字符集T，都存在一个剪接系统，其公理集和规则集都是有限的，而且对于以T为终结字符集的一类系统是通用的。这就是说，理论上存在一个基于剪接操作的通用可编程的DNA计算机。这些计算机使用的生物操作只有合成、剪接(切割-连接)和抽取。</FONT></P>
<P><FONT size=2>&nbsp;&nbsp;&nbsp;&nbsp;DNA计算机理论的出现意味着计算方式的重大变革。当然，引起计算方式重大变革的远不止DNA计算机，光学计算机、量子计算机、蛋白质计算机等新型计算机模型层出不穷，它们使原有的计算方式发生了前所未有的变化。</FONT></P>
<P align=center><STRONG><FONT size=2>计算方式及其演变</FONT></STRONG></P>
<P><FONT size=2>&nbsp;&nbsp;&nbsp;&nbsp;简单地讲，所谓计算方式就是符号变换的操作方式，尤其指最基本的动作方式。广义地讲，还应包括符号的载体或符号的外在表现形式，亦即信息的表征或表达。比如，中国古代的筹算，就是用一组竹棍表征的计算方式，后来的珠算则是用算盘或算珠表征的计算方式，再后来的笔算又是一种用文字符号表征的计算方式，这一系列计算方式的变化，表现出计算方式的多样性与不断进化的趋势。相对于后来出现的机器计算方式，上述各种计算方式均可归结为“手工计算方式”，其特点是用手工操作符号，实施符号的变换。</FONT></P>
<P><FONT size=2>&nbsp;&nbsp;&nbsp;&nbsp;不过，真正具有革命性的计算方式，还是随着电子计算机的产生才出现的。机器计算的历史可以追溯到1641年，当年18岁的法国数学家帕斯卡从机械时钟得到启示：齿轮也能计数，于是成功地制作了一台齿轮传动的八位加法计算机口这使人类计算方式、计算技术进入了一个新的阶段。后来经过人们数百年的艰辛努力，终于在1945年成功研制出了世界上第一台电子计算机。从此，人类进入了一个全新的计算技术时代。</FONT></P>
<P><FONT size=2>&nbsp;&nbsp;&nbsp;&nbsp;从最早的帕斯卡齿轮机到今天最先进的电子计算机，计算机已经历了四大发展时期。计算技术有了长足的发展。这时计算表现为一种物理性质的机械的操作过程。符号不再是用竹棍、算珠、字母表征，而是用齿轮表征，用电流表征，用电压表征等等。但是，无论是手工计算还是机器计算，其计算方式——操作的基本动作都是一种物理性质的符号变换(具体是由“加”“减”这种基本动作构成)。二者的区别在于：前者是手工的，运算速度比较慢；后者则是自动的，运算速度极快。</FONT></P>
<P><FONT size=2>&nbsp;&nbsp;&nbsp;&nbsp;如今出现的DNA计算无疑有着更大的本质性变化，计算不再是一种物理性质的符号变换，而是一种化学性质的符号变换，即不再是物理性质的“加”“减”操作，而是化学性质的切割和粘贴、插人和删除。这种计算方式将彻底改变计算机硬件的性质，改变计算机基本的运作方式，其意义将是极为深远的。阿德勒曼在提出DNA计算机的时候就相信，DNA计算机所蕴涵的理念可使计算的方式产生进化。</FONT></P>
<P><FONT size=2>&nbsp;&nbsp;&nbsp;&nbsp;量子计算机在理论上的出现，使计算方式的进化又有了新的可能。电子计算机的理论模型是经典的通用图灵机——一种确定型图灵机，量子计算机的理论模型——量子图灵机则是一种概率型图灵机。直观一些说，传统电脑是通过硅芯片上微型晶体管电位的“开”和“关”状态来表达二进位制的0和1，从而进行信息数据的处理和储存。每个电位只能处理一个数据，非0即1，许多个电位依次串连起来，才能共同完成一次复杂的运算。这种线性计算方式遵循普通的物理学原则，具有明显的局限性。而量子计算机的运算方式则建立在原子运动的层面上，突破了分子物理的界限。根据量子论原理，原子具有在同一时刻处于两个不同位置、又同时向上下两个相反方向旋转的特性，称为“量子超态”。而一旦有外力干扰，模糊运动的原子又可以马上归于准确的定位。这种似是而非的混沌状态与人们熟知的常规世界相矛盾，但如果利用其表达信息，却能发挥出其瞬息之间千变万化而又万变不离其宗的神奇功效。因为当许多个量子状态的原子纠缠在一起时，它们又因量子位的“叠加性”，可以同时一起展开“并行计算”，从而使其具备超高速的运算能力。电子线性计算方式如同万只蜗牛排队过独木桥，而量子并行运算好比万只飞鸟同时升上天空。</FONT></P>
<P align=center><STRONG><FONT size=2>计算方式演变的意义</FONT></STRONG></P>
<P><FONT size=2>&nbsp;&nbsp;&nbsp;&nbsp;计算方式的不断进化有着十分重要的理论意义和现实意义，笔者认为至少表明以下两方面。其一，计算方式是一种历史的结果，而非计算本性的逻辑必然。加拿大的卡里(L.Kari)指出：“DNA计算是考察计算问题的一种全新方式。或许这正是大自然做数学的方法：不是用加和减，而是用切割和粘贴、用插入和删除。正如用十进制计数是因为我们有十个手指那样，或许我们目前计算中的基本功能仅因为人类历史使然。正如人们已经采用其他进制计数一样，或许现在是考虑其他的计算方式的时候了。”笔者以为，这一说法是很有启示性的。确实，仔细回顾一下人类计算方式或计算技术的历史,就不难体会到计算方式是一种历史的结果，而非计算本性的逻辑必然。</FONT></P>
<P><FONT size=2>&nbsp;&nbsp;&nbsp;&nbsp;也就是说，计算之所以为计算，在于它具有一种根本的递归性，或在于它是一种可一步一步进行的符号串变换操作。至于这种符号变换的操作方式如何，以及符号的载体或其外在表现形式如何，都不是本质性的东西，它们元不是一种历史的结果，无不处于一种不断变革或进化的过程之中。不同表征下的符号变换有着不同的操作方式，甚至同一种表征下的符号变换都可以有不同的操作方式：既可以是物理性的方式，也可以是化学性的方式；即可以是经典的方式,也可以是量子的方式；既可以是确定性的方式，也可以是概率性的方式。在此，计算本质的统一性与计算方式的多样性得到了深刻的体现。笔者相信，DNA计算机、量子计算机等的出现已经打开了人们畅想未来计算方式的思维视窗，随着科学技术的不断发展，计算方式的多样性还会有新的表现。</FONT></P>
<P><FONT size=2>&nbsp;&nbsp;&nbsp;&nbsp;其二，计算方式的历史性、多样性反观了计算本性的逻辑必然性、统一性。由丘奇-图灵论点所揭示的计算本质是非常普适的，它不仅包括数值计算、定理推导等不同形式的计算，而且包括人脑、电子计算机等不同“计算器”的计算。大家不要忘了，以丘奇-图灵论点为基石的可计算性理论是在电子计算机诞生之前的1930年代提出的，即它并非在对电子计算机进行总结与抽象的基础上提出，但又深刻地刻画了电子计算机的计算本质。如今最先进的电子计算机在本质上就是一台图灵机，或者凡是计算机可计算的函数都是一般递归函数。现在人们又进一步认识到，目前尚在实验室阶段的DNA计算机、量子计算机，在本质上也是一种图灵计算。这说明不同形式的计算、不同“计算器”的计算，在计算本质上是一致的，这就是递归计算或图灵计算。<BR><BR>转自：<A href="http://cfc.nankai.edu.cn/readings/lijie.htm">http://cfc.nankai.edu.cn/readings/lijie.htm</A></FONT></P><img src ="http://www.blogjava.net/weidagang2046/aggbug/4186.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/weidagang2046/" target="_blank">weidagang2046</a> 2005-05-12 00:27 <a href="http://www.blogjava.net/weidagang2046/articles/4186.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>