爱因万江斯坦 用文字记录思想

排序研究二

五.归并排序
归并排序比前面讲到的排序方法要有效的多.冒泡排序,插入排序和选择排序要用O(N^2)时间,而归并只要O(N*logN).如果N是10000,那么N^2就是100000000,而N*logN只是40000,如果为这么多数据项排序用并归排序的话需要40秒,那么用插入排序则需要将近28个小时.
但归并排序有个最大的缺点,就是它需要在内存能装下一个大小等于被排序的数据项数目的数组.

在研究归并排序前,先要说说什么是归并.
假设有两个有序数组,数组A有4个数据,数组B有6个数据,它们要归并到数组C中,那么我们就要为C初始10个空的存储空间.

然后,对A的第一个数据,和B的第一个数据进行比较,把较小的数据项被复制到数组C中.再把未复制的数据与另一个数组的第二个数据进行比较,再把比较小的数据复制到C中,依次类推.

                                  // 把数组A和B归并到数组C
     public static void merge( int[] arrayA, int sizeA,
                               int[] arrayB, int sizeB,
                               int[] arrayC )
      {
      int aDex=0, bDex=0, cDex=0;

      while(aDex < sizeA && bDex < sizeB)  // 两个数组均不为空时
         if( arrayA[aDex] < arrayB[bDex] )
            arrayC[cDex++] = arrayA[aDex++];
         else
            arrayC[cDex++] = arrayB[bDex++];

      while(aDex < sizeA)                  // 数组B空了,
         arrayC[cDex++] = arrayA[aDex++];  // 数组A还没空

      while(bDex < sizeB)                  // 数组A空了,
         arrayC[cDex++] = arrayB[bDex++];  // 数组B还没空
      }  // end merge()

现在来看看归并排序的思想,是把一个数组分成两半,排序每一半,然后用上面的归并思想把数组的两半归并成一个有序的数组.但如何对每一部分排序呢,就要用到递归了,把每一半都分成两个1/4,对每个1/4部分排序,然后把它们归并成一个有序的一半,再以此类推不停的分解问题,直到得到了基值条件:只有一个数据项的数组是有序的.

public void mergeSort()          // 归并排序组程序,theArray是待排序数组,nElems是其长度
{                              // 建一个存放有序结果的初始数组
      long[] workSpace = new long[nElems];
      recMergeSort(workSpace, 0, nElems-1);
      }

 private void recMergeSort(long[] workSpace, int lowerBound,
                                               int upperBound)
      {
      if(lowerBound == upperBound)            // 如果就一个了,没
         return;                              // 必要排序了
      else
         {                                    // 找到中间点
         int mid = (lowerBound+upperBound) / 2;
                                              // 对左边的一半排序
         recMergeSort(workSpace, lowerBound, mid);
                                              // 对右边的一半排序
         recMergeSort(workSpace, mid+1, upperBound);
                                             // 合并两个排好序的一半
         merge(workSpace, lowerBound, mid+1, upperBound);
         }
      }  

 //-----------------------------------------------------------
   private void merge(long[] workSpace, int lowPtr,     //lowPtr:前半部分的子数组的开始;
                           int highPtr, int upperBound) //highPtr:后半部分子数组的开始位置;
      {                                                 //upperBound:后半部分子数组的上界
      int j = 0;
      int lowerBound = lowPtr;
      int mid = highPtr-1;
      int n = upperBound-lowerBound+1; 

      while(lowPtr <= mid && highPtr <= upperBound) //把theArray数组中前半部分和后半部分
         if( theArray[lowPtr] < theArray[highPtr] ) //中小的数据复制到workSpace中
            workSpace[j++] = theArray[lowPtr++];
         else
            workSpace[j++] = theArray[highPtr++];

      while(lowPtr <= mid)
         workSpace[j++] = theArray[lowPtr++];

      while(highPtr <= upperBound)
         workSpace[j++] = theArray[highPtr++];

      for(j=0; j<n; j++)
         theArray[lowerBound+j] = workSpace[j];
      }  // end merge()
   //-----------------------------------------------------------

workspace数组的创建在mergeSort()中实现,是因为如果在recMergeSort()中创建数组会在每一次递归调用中都创建新数组,这是没有效率的.

但是一个算法作为一个递归的方法通常从概念上很容易理解,但是,在实际的运用中证明递归算法的效率不太高,有时可以用一个基于栈的方法来替代它,有兴趣的朋友可以继续研究.

六,希尔排序
希尔排序不像快速排序和其他时间复杂度为O(N*logN)的排序算法那么快,因此对非常大的文件排序,它不是最优的.但是希尔排序比选择排序和插入排序这种时间复杂度为O(N^2)的排序算法还是要快得多.另外 ,希尔排序算法的代码既短又简单.
我们先看看插入排序带来的问题.假设一个很小的数据项在很靠近右端的位置上,这里本来应该是值比较大的数据项所在的位置.把这个小数据项移动到在左边的正确位置上,所有的中间数据都必须向右移一位.这个步骤对每一个数据项都执行了将近N次的复制.

希尔排序是通过加大插入排序中元素之间的间隔,并在这些有间隔的元素中进行插入排序,从而使数据项能大跨度地移动.
进行希尔排序时数据项之间的间隔被称为增量,并且习惯上用字母h来表示.
如对包含10个数据项的数组进行排序,我们设h=4,我们则先对0,4,8位置上的元素排序,然后再对1,5,9号数据项进行排序.这个排序过程持续进行,直到所有的数据项都已经完成了4增量排序,也就是说所有间隔为4的数据项之间都已经有序.
然后我们就可以进行普通的插入排序,即1-增量排序.
在完成以h为增量的希尔排序后,所有元素离它在最终有序序列中的位置都比较近,这就是数据"基本有序"的含义.这也正是希尔排序的奥秘所在.
接着上面的例子4-增量排序和1-增量排序结合起来应用,比前面只用普通的插入排序要快得多.
那对于希尔排序中的h间隔如何取呢,一般我们选用Knuth序列:h初值为1,然后应用h=h*3+1生成序列:1,4,13,40,121,364,…当间隔大于数组大小时,这个过程停止.

 public void shellSort(int []a)
      {
      int inner, outer;
      long temp;

      int h = 1;                     // 间隔h的初始值是1
      while(h <= a.length/3)
         h = h*3 + 1;                // (1, 4, 13, 40, 121, 等等)

      while(h>0)                     // h递减,直到h=1
         {
                                     // h-增量排序
         for(outer=h; outer<a.length; outer++)  //即插入排序
            {
            temp = a[outer];
            inner = outer;
            while(inner > h-1 && a[inner-h] >=  temp)
               {
               a[inner] = a[inner-h];
               inner -= h;
               }
            a[inner] = temp;
            }  // end for
         h = (h-1) / 3;              // h递减
         }  // end while(h>0)
      }  // end shellSort()

迄今为止,除了在一些特殊情况下,还没有人能够从理论上分析希尔排序的效率,有各种各样基于试验的评估,估计它的时间级从O(N^3/2)到O(N^7/6).

七,快速排序
讲快速排序,就要先说一下划分,因为划分是快速排序的根本机制.
划分就是把数组分为两组,使所有关键字大于特定值的数据项在一组,使所有关键字小于特定值的数据项在另一组.
完成划分,数据还不能称为有序,也只是比没有划分前更接近有序了.同时划分也是不稳定的,因为它往往会颠倒数组中一些数据的顺序.

 public int partitionIt(int left, int right, long pivot)
       {
       int leftPtr = left - 1;           // 左边
       int rightPtr = right + 1;         // 右边
       while(true)
          {
          while(leftPtr < right &&       // 找出左边比pivot大的元素时停止
                theArray[++leftPtr] < pivot)
             ;  // (nop)

          while(rightPtr > left &&       // 找出右边比pivot小的元素时停止
                theArray[--rightPtr] > pivot)
             ;  // (nop)
          if(leftPtr >= rightPtr)        // 左右相逢
             break;                      // 划分结束
          else                           // 左右未逢
             swap(leftPtr, rightPtr);    // 交换元素
          }  // end while(true)
	  swap(leftPtr, rightPtr);        //将pivot放到正确点
       return leftPtr;                   //返回相逢点
       }

划分算法主要是两个指针,分别指向数组的两头.在左边的指针,leftPtr向右移动,而在右边的rightPtr则向左移动.实际上,leftPtr初始化时是在第一数据的左边一位,rightPtr是在最后一个数据项的右边一位,这是因为它们在执行前都要分别的加一和减一.
当leftPtr遇到比待比较数据小的数据项时,它继续右移,但遇到比待比较数大时,它就停止.类似的,当rightPtr遇到大于待比较数的数据项时,它继续左移,但是当遇到比待比较数小时,也停止.两个内层while循环,第一个用于leftPtr,第二个用于rightPtr,控制左右两部分的扫描过程.
当这两个循环都退出之后,也就是leftPtr与rightPtr都指向左右两边位置不对的数据项上,所以交换这两个数据.交换结束之后,继续移动两个指针,再停止,再交换.这一切都包含在一个外部循环中.

快速排序是最流行的算法,在大多数情况下,快速排序都是最快的,执行时间为O(N*logN)级.这也只是对内部排序而言,对于在磁盘文件中的数据进行排序,其他的排序算法可能更好.
快速排序算法本质上是通过把一个数组划分为两个子数组,然后递归地调用自身为每一个子数组进行快速排序.

 public void recQuickSort(int left, int right)
      {
      if(right-left <= 0)              // if size <= 1,
          return;                      //    already sorted
      else                             // size is 2 or larger
         {
         long pivot = theArray[right];      // 选择最右端的元素为待比较数
                                            // 划分
         int partition = partitionIt(left, right, pivot);
         recQuickSort(left, partition-1);   // 排序左边
         recQuickSort(partition+1, right);  // 排序右边
         }
      }  // end recQuickSort()

web网站的发展方向

互联网的发展,带来了很多的新生事物,一堆一堆的新名词和新技术在不断的充实这张连接全世界的大网,也许N年以后,这张网除了联起地球,还会成为联起外太空的智慧生物的信息大网。想想在地球上访问来自另一个行星上的某位外星人的网站,会是什么感觉,先科幻一下了,呵。
自从HTML这种基于WWW的文件格式在互联网上传播开来,web网站也就诞生了。
咱不说远了,就说说自2000年到现在,web网站的类型和它的发展趋向吧
2000年那会,应该正是网络泡沫经济的时候吧。尽管是泡沫,但它是web网站一次空前的繁荣,那个时候的网站都类型比较集中,都是门户型网站。主要是提供信息,新闻,和邮箱等服务。门户型网站的特点是内容依赖于网站本身所提供,按现在的话来说,就是和访问者几乎没有互动,访问者要自己去网站上不停的点击,不停的通过超链去找到自己的感兴趣的信息,一旦访问者发现有的网站信息更新速度更快,就会很快的喜新厌旧,转向其它网站。
不过,出于新奇,确实能带来一时的人气和流量。但这种类型的网站,太讲究大而全,要运作起来需要相当高的成本,钱总有烧完的一天,当时我访问过的很多网站,现在都已经不存在了(欢迎大家列举一些大家以前经常去,而现在已经不存在的网站了),这种类型的网站最终只剩下sina,sohu,163这样的实力强劲的门户网站了,它们代表了第一代互联网web网站,这种大而全的门户网站,流量很高,但主要是提供信息给用户,就是web网站是主动方,而用户是被动或少量主动的接受方,所以门户网站也一直在不断探索新的发展方向。
那时IM软件也还刚刚起步,大家在互联网中交流还主要是电邮和BBS,但随着成本不断增加,赢利模式仍然混乱,这种提供单一功能的网站也相继死去或被并购。能活下来的,都已经不在风光。
所以,我们看一下,第一代的web网站是特点:提供静态的信息,单一的电邮和BBS互动功能,这是和当时的网络环境,和互联的伊始相呼应的,那一代是门户网站的天下。
随着互联网硬件的发展,以ADSL为主流的半宽带入户(本人觉得宽带接入小于1M就应算是半宽带,不能称之为宽带),互联网的普及,更多针对性的功能web网站也相继出现。
就在这个时候,一个承上启下的web巨人出现了:Google。它把第一代的门户网站做了一个简洁的总结,告诉大家,一个网站的首页其实只要一个输入框和一个提交按钮就可以了。
Google本身不提供内容,它通过后台的复杂运算给出一堆可供选择的信息给用户。我们仔细看看,用户在Google上获取信息的过程:是用户自己提交想要的信息关键词,点击搜索,然后,Google会在众多的门户网站和其它你所想不的到的地方都为你都找出来。Google在全世界的迅速普及让当时的门户网站们看傻了眼,互联网从不缺少奇迹。
这个时候,用户和web网站之间的互动开始了。web2.0的概念开始初倪端。
由于网络速度的提高,一些功能性的网站得以出现,,如视频网站,音乐在线网站。同时由于网络的普及,电子商务网站也出现了,无论是B2B,还是B2C,C2C,唉,概念真麻烦。
这个时候的网站,已经不再是只提供文字图片信息,开始有视频和音乐,但总的来说,他们与第一代的web网站特点大致相同,都是单纯提供内容,而很少与用户直接互动。
这个时候,web网站的发展是处在一个发展期,因为赢利模式依然不清晰,大家不知何时才能在互联网赚到money,那时连Google都有过要卖掉自己的念头,可惜yahoo当时因为已经有了自己的搜索引擎的开发计划拒绝了Google。
我打算把这个时期称为过渡期,是web网站理性思考和平稳发展的时期,差不多从2003年到现在。
在2005年的时候,随着Google在04年的高价上市成功,互联网开始了新一轮的复苏,这个时候是理性的复苏,但其间还是有少许泡沫。
这个时候,web网站的发展方向是:聚合网站内容;充分互动;让用户参与网站内容的提供。所以除了google的兴起,blog开始步入主流的web网站,我本人是从04年初开始写blog,那时整个互联网上blog还远不如现在那么普及。现在,blog地址都开始印在名片上了。
在这个理性的发展期,web网站更加精细化和针对性,不再追求大而全,各种有针对性的社区:旅游,电影,音乐,技术,交友,聚会,人脉社区相继出现,也出现了好几次惊天动地的大收购。
目前是过渡期,所以,新的,旧的,超前思想都会在这个时期出现。
web2.0,说起来也只是个概念,但总的来说,就是web网站开始以人为主了,而不是单纯的做一个信息的发布源了。
如果大家也有自己个搞个网站的想法,那就赶紧想想自己的针对人群,怎样以人为主,这样的网站才有在这个时期发展起来的可能性。
web网站在发展,基于的web的技术也在发展。
在技术方面,RIA开始渐渐流行。我大概是从04年开始接触RIA,当时研究了Flex,Laszlo,结果没想到Ajax大红了起来,呵,自己失策了一把。尽管听到有人说Ajax是过渡技术,但我觉得只要HTML还存在,js还存在,Ajax就不会是过渡技术,Ajax是RIA领域里的一个非常优秀的补充,Flex和Laszlo等的成熟还有待时日。RoR和敏捷开发的突起,也是让我这种老牌的JAVA人看到了解决问题的银弹。
其实说到这里,还只是谈了web网站到目前的发展,由于时间已晚,困的不行,上面写的有点凌乱,停笔先,明天再补充,也欢迎大家在web网站的发展方向和技术发展方向上做点补充。

Spry框架初步入门

Adobe的Ajax框架spry的正式版还没放出,所以文档是少之又少,在这里接合自己使用的情况总结20个spry的知识点给大家,相信会对大家有一定帮助,至少大家也会对spry有个初步的认识了,这个轻量型的框架就一个字:“易用”。
1,使用spry框架,必须引用的两个核心js文件

<script type="text/javascript" src="../../includes/xpath.js"></script>
<script type="text/javascript" src="../../includes/SpryData.js"></script>

2,创建一个数据器dataset

var dsPhotos = new Spry.Data.XMLDataSet("/photos.php", "/gallery/photos/photo", { method: "POST", postData: "galleryid=2000&offset=20&limit=10", headers: { "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8" } });
var dsPhotos2 = new Spry.Data.XMLDataSet("/photos.php?galleryid=2000", "/gallery/photos/photo");

method:为请求发送方式,POST / GET ;默认为GET
postData:为请求参数,可省略,可直接把”/photos.php”换成/photos.php?galleryid=2000&offset=20&limit=10
Content-Type:为头信息

3,不使用缓存

Var dsPhotos = new  Spry.Data.XMLDataSet("/photos.php?galleryid=2000", "/gallery/photos/photo", { useCache:  false })

这里要重点说明一下,我在应用时,曾经发现设置不用缓存时,页面仍然不是最新数据,我是用一个servlet生成xml,然后对生成的数据进行删除和添加,但页面显示的数据不会自动更新。
后来在adobe的官方论坛上找到了答案,在IE中,使用userCache:false是不够的,还要在生成xml的jsp,php等中设置头不使用cache,如在生成xml的jsp/servlet中是要加上:
response.addHeader(”Cache-Control”,”no-cache”);
以上问题只存在IE中,在firefox和opera中不存在。注,spry对opera9版本支持很好,使用版本8的朋友要注意了。
另外,也可以在构造完数据器后,再设置缓存:

dsData.useCache = false;
dsData.loadData();

4,获取数据形式
假如我们上面的请求,返回的xml如下:

<gallery id="12345">
    <photographer id="4532">John Doe</photographer>
    <email>john@doe.com</email>  

    <photos id="2000">
        <photo path="sun.jpg" width="16" height="16" />
        <photo path="tree.jpg" width="16" height="16" />
        <photo path="surf.jpg" width="16" height="16" />  

    </photos>
</gallery>

那么“gallery/photos/photo”返回的数据是下面的数组:
[
{ “@path”: “sun.jpg”, “@width”: 16, “@height”: 16 },
{ “@path”: “tree.jpg”, “@width”: 16, “@height”: 16 },
{ “@path”: “surf.jpg”, “@width”: 16, “@height”: 16 }
]

var rows = dsPhotos.getData(); // 获取所有行.
var path = rows[0]["@path"];   // 获取第一行中"@path"的值   

dsPhotos.setCurrentRowNumber(2); // 将第3行做为当前处理行,下标以0开始   

var id = dsPhotos.getData()[2]["ds_RowID"]; // 获取第3行的ID.   

dsPhotos.setCurrentRow(id); // 通过第3行的id,将第3行设为当前处理行.

5,排序

dsPhotos.sort("@path"); //以"@path"列的值为关健字对行排序   

dsPhotos.sort("@path", "toggle");  //"ascending", "descending"和"toggle",默认是 "ascending"。
var dsPhotos = new Spry.Data.XMLDataSet("/photos.php?galleryid=2000", "/gallery/photos/photo", { sortOnLoad: "@path", sortOrderOnLoad: "descending" });//也可在数据构造器中设置初始排序   

dsPhotos.setColumnType("@width", "number");//设置类型
dsPhotos.setColumnType("@height", "number");   

...   

dsPhotos.sort("@width"); // 对 "@width" 列数据进行排序.

6,去除重复

dsPhotos.distinct(); // Remove all duplicate rows.
//distinct()方法是具有破坏性的,多余的行是被删掉的,如果你想再得到所有的包括重复的原始项就得重新载入XML数据。
var dsPhotos = new Spry.Data.XMLDataSet("/photos.php?galleryid=2000", "/gallery/photos/photo", { distinctOnLoad: true });//可在构造时预设

7,设置过滤器

var myFilterFunc = function(dataSet, row, rowNumber)
{
    if (row["@path"].search(/^s/) != -1)    //只返回以s开头的行
        return row;                     // Return the row to keep it in the data set.
    return null;                        // Return null to remove the row from the data set.
}   

dsPhotos.filterData(myFilterFunc); // Filter the rows in the data set.
dsPhotos.filter(myFilterFunc); // 不破坏数据,是建一个新的数组   

dsPhotos.filterData(null); // 取消过滤.

8,自动刷新,以毫秒为单位

var dsPhotos = new  Spry.Data.XMLDataSet("/photos.php?galleryid=2000", "/gallery/photos/photo", { useCache:  false, loadInterval: 10000 });//在构造器设置   

dsPhotos.startLoadInterval(10000); // 设置自动刷新
...   

dsPhotos.stopLoadInterval(); // 停止自动刷新

9,把类注册成观察器

var myObserver = new Object;
myObserver.onDataChanged = function(dataSet, data)      //可支持:onPreLoad / onPostLoad / onLoadError / onDataChanged / onPreSort / onPostSort /  onCurrentRowChanged
//第一个是发送通知的对象,做为数据器观察器,这个值永远都是dataSet对象。第二个参数可以不定义,也可以是一个对象(内置对象)
{
    alert("onDataChanged called!";
};   

dsPhotos.addObserver(myObserver);   

dsPhotos.removeObserver(myObserver);

10,把函数方法注册为观察器

function myObserverFunc(notificationType, dataSet, data)  //notfication,是通知器类型,dataSet是数据器对象,data是要观察的数据
{
    if (notificationType == "onDataChanged")
        alert("onDataChanged called!";
    else if (notificationType == "onPostSort")
        alert("onPostSort called!";
};   

dsPhotos.addObserver(myObserverFunc);

11,动态区域块
所有使用Spry动态区域块的HTML页面都要在它们的<html>标签中加入xmlns:spry=http://ns.adobe.com/spry

<html xmlns="http://www.w3.org/1999/xhtml" xmlns:spry="http://ns.adobe.com/spry">
 .... ....
   .... ....
    <ul spry:region="dsPhotos">
        <li>{dsPhotos::path}</li>
    </ul>

COL, COLGROUP, FRAMESET, HTML, IFRAME, STYLE, TABLE, TBODY, TFOOT, THEAD, TITLE, TR 不能设为动态区域

12,数据引用

{<数据器名>::<数据器列名>}

如果一个动态区域块只和一个数据器相关联,则你甚至可以省略掉数据器的名字
<li>{@path}</li>
ds_RowID – 这是数据器的行id。这个id可以帮我们指定数据器中的一个数据。它和数据是对应的,即使数据执行了排序操作,这个id和数据的对应关系也不会变化。
ds_RowNumber – 这是数据器当前数据的行号。
ds_RowNumberPlus1 – 这个与上面的ds_RowNumber相同,只不过它规定了数据的行号从1开始,而不是从0开始。
ds_RowCount – 它是数据器中数据的行的数量。如果使用了一个非破坏性的过滤器,则它的值是这个过滤器执行后得到的行的数量。
ds_UnfilteredRowCount – 在执行非破坏性过滤器前,数据器中行的数量。
ds_CurrentRowID – 当前行的id。这个值不会改变,除非使用了一个循环的构造。
ds_CurrentRowNumber – 当前行的行号。这个值不会改变,除非使用了一个循环的构造。
ds_SortColumn – 上一次排序所依赖的列名。如果这个数据器的数据还未进行过排序,则返回一个空字符串。
ds_SortOrder – 数据器中数据排序的参数,将返回三种字符串”ascending”, “descending”, 或空字符串.。
ds_EvenOddRow – 它返回的是”even”或”odd”,告诉我们ds_RowNumber的值是奇数还是偶数。

13,循环

//方式一:
<li spry:repeat="dsPhotos">{@path}</li>      

//方式二:
<ul spry:repeatchildren="dsPhotos">
  <li>{@path}</li>
</ul>     

//只输出以s开头的 spry:test属性的值可以是任何等于0或非0值的JavaScript表达示。如果这个表达示返回非0值,这个内容将会被输出,相当于if <>0就输出后面元素。
<li spry:repeat="dsPhotos" spry:test="'{@path}'.search(/^s/) != -1;">{@path}</li>

14,if 条件

<li spry:if="'{@path}'.search(/^s/) != -1;">{@path}</li>   

//if/else的形式,要使用"spry:choose"属性
            <div spry:choose="spry:choose">
                 <div spry:when="'{@path}' == 'surf.gif'">{@path}</div>
                 <div spry:when="'{@path}' == 'undefined'">Path was not defined.</div>
                 <div spry:default="spry:default">Unexpected value for path!</div>
          </div>

15,状态

<div spry:region="dsEmployees">
    <div spry:state="loading">正在载入数据 ...</div>
    <div spry:state="error">数据载入失败!</div>
    <ul spry:state="ready">
      <li spry:repeat="dsEmployees">{firstname} {lastname}</li>
    </ul>
</div>

16,通过对象将区域注册成观察者

myObserver = new Object;
myObserver.onPostUpdate = function(notifier, data)
{
     alert("onPostUpdate called for " + data.regionID);
};
...
// 调用addObserver() 将类注册为观察者.
Spry.Data.Region.addObserver("employeeListRegion", myObserver);
...
//注销
Spry.Data.Region.removeObserver("employeeListRegion", myObserver);
...   

<ul id="employeeListRegion" spry:region="dsEmployees">
...
</ul>

17,以函数将区域注册成观察者

function myRegionCallback(notificationState, notifier, data)
{
      if (notificationType == "onPreUpdate") //onLoadingData / onPreUpdate / onPostUpdate / onError
         alert(regionID + " is starting an update!");
      else if (notificationType == "onPostUpdate")
         alert(regionID + " is done updating!");
}   

...   

// 注册
Spry.Data.Region.addObserver("employeeListRegion", MyRegionCallback);
...   

// 注销
Spry.Data.Region.removeObserver("employeeListRegion", MyRegionCallback);
...   

<ul id="employeeListRegion" spry:region="dsEmployees">
...
</ul>

18,主细节模式,同一数据器

<span spry:region="dsEmployees">
<select spry:repeatchildren="dsEmployees" onchange="dsEmployees.setCurrentRow(this.value)">
    <option spry:if="{ds_RowNumber} == 0" value="{ds_RowID}" selected="selected">{username}</option>   

    <option spry:if="{ds_RowNumber} != 0" value="{ds_RowID}">{username}</option>
</select>
</span>
<span spry:detailregion="dsEmployees">{@id} - {firstname} {lastname} - {phone} </span>
//spry:detailregion"会在接收到"CurrentRowChanged"通知后改变自己的展示形式。

19,主细节模式,多个数据器

var dsStates = new Spry.Data.XMLDataSet("../../data/states/states.xml", "states/state");
var dsCities = new Spry.Data.XMLDataSet("../../data/states/{dsStates::url}", "state/cities/city");
//两个数据器有依赖关系,也可以用:"/webapp/cities.php?stateid={dsStates::@id}".   

<form name="selectForm">
//State:
    <span spry:region="dsStates" id="stateSelector">
        <select spry:repeatchildren="dsStates" name="stateSelect" onchange="document.forms[0].citySelect.disabled = true; dsStates.setCurrentRow(this.value);">   

            <option spry:if="{ds_RowNumber} == 0" value="{ds_RowID}" selected="selected">{name}</option>
            <option spry:if="{ds_RowNumber} != 0" value="{ds_RowID}">{name}</option>   

        </select>
    </span>   

//City:
    <span spry:region="dsCities" id="citySelector">
        <select spry:repeatchildren="dsCities" name="citySelect">   

            <option spry:if="{ds_RowNumber} == 0" value="{name}" selected="selected">{name}</option>
            <option spry:if="{ds_RowNumber} != 0" value="{name}">{name}</option>   

        </select>
    </span>
</form>

20,改变数据源

<select onchange="dsEmployees.setURL(this.value); dsEmployees.loadData();">   

    <option value="../../data/employees-01.xml" selected>Set 1</option>
    <option value="../../data/employees-02.xml">Set 2</option>
</select>   

<th scope="col" onclick="dsEmployees.sort('@id');">Employee ID </th>

Adobe的AJAX框架–Spry

最近看完了Adobe的AJAX框架Spry的所有文档和Demo,觉得这东西挺有意思的,在这里介绍给大家。
Spry框架的开发人员是来自于DreamWeaver开发组,他们把Spry框架做为DreamWeaver的一个完美补充为设计者和开发者提供对AJAX技术的支持。Spry框架是一个轻量级的AJAX框架,它的代码和标签十分的简洁和优雅,以保证让用户能便捷的使用,并不会为过繁杂的标签所惑。

Spry框架的官方网址:
http://labs.adobe.com/technologies/spry
在这里你能找到最新的文档和下载最新的Spry版本,目前版本是预览版1.3_08-11。
大家可以先在下面的看到Spry的示例和Demo:
http://labs.adobe.com/technologies/spry/samples/
http://labs.adobe.com/technologies/spry/demos/

 

Spry框架其实就是一个客户端的JavaScript类库,包含了一组JavaScript文件,CSS,图片文件,通过官方的框架结构图,我们能看出Spry框架的核心是四部分:XML数据器(XML Data Sets),动态区域(Dynamic Regions),装饰器库(Widgets)和变化效果库(Transition Effects)。

我们可以看出,Spry框架接收的数据格式只是XML数据格式。

一,XML数据器(XML Data Sets)
XML数据器是一个提供了从XML文档中载入和管理数据的JavaScript对象。它是Spry框架中处理XML格式数据的一个JavaScript功能实现。通过它,我们可以从XML中直接得到转换成表格数据格式的行和列的值,其实就是数组。它封装了获取XMLhttpRequest的方法,和发送并接收数据等一系列获取数据的方法。
要创建一个XML数据器,你必须在你的HTML文件中加入两行引入JavaScript文件的代码:

<script type=”text/javascript” src=”../../includes/xpath.js”></script>
<script type=”text/javascript” src=”../../includes/SpryData.js”></script>

上面引入的是Spry框架的核心js文件之一。”xpath.js”是Google基于XPath 1.0标准的JavaScript功能实现。你如果想获得更多的关于它的信息,可以访问Google的开源项目 google-ajaxslt project page .
”SpryData.js”则包含了定义XML数据器和动态区域的代码。
构造XML数据器就好像新建一个类一样,用”new”关键字即可:
<script type=”text/javascript”>
var dsPhotos = new Spry.Data.XMLDataSet(”/photos.php?galleryid=2000″, “/gallery/photos/photo”);
</script>
Spry框架为XML数据器提供了一些有特色的功能:如数据排序,数据过滤,按指定时间间隔自动更新,并引入了观察者通知模式以支持事件的触发。
关于XML数据器更多资料,大家可以参考http://labs.adobe.com/technologies/spry/articles/data_set_overview/,这一部分笔者已经翻译完成了,但英文原文其实写的通俗易懂,如果对一些名词翻译的不准确反倒会误导了大家,所以还是不贴出来了,大家可以参考官方英文文档。

二,动态区域(Dynamic Regions)
一旦你建立了XML数据器,你就可以在动态区域中去显示这个数据器的数据了。
创建动态区域块很简单,只要在html标签的相应位置加上”spry:region”属性就可以了,Spry框架就会知道这一块是被标识成动态区域了。
在动态区域,你可以有条件的选择要输出的数据,也可用循环输出。
动态区域另一个特点就是,它分为master和detail两个区域类型。
如下面的Demo截图:

master区域的数据改变会使detail区域的数据相应发生改变

两者都注册成XML数据器的观察者

当master区域的数据变化时,触发detail区域的响应事件,从面达到更新相应数据。
大家可参考http://labs.adobe.com/technologies/spry/samples/DataSetMasterDetailSample.html所示的功能。
这只是动态区域简单的示例,复杂的情况可能会有多个XML数据器与多个动态区域相互关联触发。

三,装饰器库(Widgets)
一个装饰器是由一组HTML,CSS,JavaScript封装成的高级UI。最常见的装饰器有可折叠的菜单,树型菜单和选项table面板等。这些对象都比较难于创建,需要一些更高级的编程经验。Spry的开发组在创建装饰器这一概念就是希望开发者们能相互协作,共享各自的设计,把这些高级界面元素用在自己的页面上。
Spry框架下的装饰器是易于编辑的。这种模型非常适合于设计者和编辑人员:要改变外观,只要改变CSS就可以了,要增加一个可折叠菜单只要copy和paste一个代码块就够了。
如,你能看懂的这段代码是创建了一个可折叠菜单吗?
<div id=”Acc1″ >
<div>
<div>Panel Header  1</div>
<div>Panel  1 Content </div>
</div>
<div>
<div>Panel Header  2</div>
<div>Panel  2 Content</div>
</div>
<div>
<div>Panel Header  3</div>
<div>Panel  3 Content</div>
</div>
</div>
<script>
var acc1 = new  Hanzo.Widget.Accordion(”Acc1″);
</script>
这段代码是非常简洁清晰的,没有什么繁杂的标签,这样设计是为了易于阅读。
关于这个例子,可以参考http://labs.adobe.com/technologies/spry/samples/accordion/AccordionSample.html

四,变化效果库(Transition Effects)
Spry框架的变化效果库都存于SpryEffects.js文件中,是基于JavaScript的一些动态变化效果,如,淡出,改变形状等。
Spry框架在设计时,曾考虑直接用第三方的效果库,如Script.aculo.us,但后来开发小组觉得要保证框架代码和标签的一致性,还是选择了自已开发,但是也基本上是以Script.aculo.us为原型进行设计,因为Script.aculo.us 本身就是一个非常优秀的变化效果库框架。
由于Spry框架现在只是预览版,所以目前只支持七种变换:
Appear/Fade Makes an element appear or fade away
Highlight Flashes a color as the background of an element
BlindUp/BlindDown Simulates a window blind, up or down, where the contents of the affected elements stay in place
SlideUp/SlideDown  Simulates a window blind, where the contents of the affected element scroll accordingly
Grow/Shrink Increases/reduces the size of the element
Shake Moves the element slightly to the left, then to the right, repeatedly
Squish Reduces the element to its top-left corner and disappears

可能到这大家对Spry框架已经有了大致的了解,其实这个东西已经足够我们的大多数应用的开发了,笔者也十分期待着正式版能早日放出,并打算在自己现在的项目中试一试了。

排序研究一

排序是数据结构中重要的一个部分,也是在实际开发中最易遇到的问题之一,当然了,你也可以不考虑这些排序的算法,直接把要排序的数据insert到数据库中,用数据库的order by 再select一下,也能产生排序结果,不过,开发一个好的系统,性能同样很重要。

在一堆数据中,是比较的执行耗时多,还是复制交换的执行耗时比较多,大量数据比较时,是否会有内存限制等等,在综合这些因素后,我们选择适当的排序算法,常常会让系统性能提升数倍,当然了,如果你的系统中没有任何需要数据排序的,那就不考虑了。

所有的排序算法,都是在大数据量时才会显示出其运行差别,所以,在下面的讨论中,大家了解各特性,按需选用。(如果你觉得往数据库里插入数据,再带着order by 去select执行耗时最小,那就使用我提的这个方法吧)

注:以下的排序,均以int或long型来比较,其实,比较的元素可以是除这以外的任何对象,要对象实现比较功能,可参考jdk的compareable接口,这个后面再讨论。

所有的问题,都会有一个简单的解决方法,但它往往并不是最佳的方法。

一,冒泡排序

public void bubbleSort(int[] array) {
  int temp;
  for(int i=0;i<array.length-1;i++){
   for(int j=i+1;j<array.length;j++){
    if (array[i]>array[j]){
     temp=array[i];
     array[i]=array[j];
     array[j]=temp;
    }
   }
  }

这个是最简单,最易理解的排序算法。从队列的最左边开始,比较0号位置和1号位置的元素,如果左边的元素(0号)大,就让两个元素交换;如果右边的元素大,就什么也不做。然后右移一个位置比较1号位置和2号位置;沿着这个队列照这样比较下去,一直比较到队列的最右端,虽然没有把所有元素排好充,但是最大的那个元素已经在最右边了,也就像是在水底下冒泡一样,冒上来了。然后再从左边的1号开始,再循环前面的操作。。。。

可以看出,冒泡排序运行需要O(N^2)时间级别,其速度是很慢的,比较次数:N*(N-1)/2,交换次数最坏的情况也是:N*(N-1)/2。

2,选择排序

选择排序与冒泡排序有点相似,但是,选择排序对冒泡排序做了些许优化:减少元素交换的次数。
从下面的代码中,我们可以看出,通过每一次循环,标识出当前最小的元素,再做交换。

选择排序和冒泡排序执行了相同的比较次数:N*(N-1)/2,对于100个数据项就是要4950次比较,但选择排序只进行了不到100次交换。当N值很大时,比较的次数是主要的。所以,当N比较小,特别是如果交换的性能消耗比比较的性能消耗大得多时,用选择排序是相当快的。

public void selectionSort(int[] array)
      {
      int out, in, min,nElems=array.length;

      for(out=0; out<nElems-1; out++)   // 外层循环
         {
         min = out;                     // 最小值
         for(in=out+1; in<nElems; in++) // 内层循环
            if(a[in] < a[min] )         // 如果有比最小值还小的
                min = in;               // 得到新最小值
         swap(out, min);                // 交换
         }  // end for(out)
      }  // end selectionSort()

 private void swap(int one, int two)
      {
      long temp = a[one];
      a[one] = a[two];
      a[two] = temp;
      }

三,插入排序

插入排序的特点是局部有序,通过循环,在(局部)有序组中的适当位置插入元素进行排序。然而要做到这一点,就需要把部分已排序的队员右移以让出空间。当把最后一个要比较的元素移位之后,这个移动的过程就结束了。

public void insertionSort(int a[]){
      int in, out,nElems=a.length;

      for(out=1; out<nElems; out++) {    // 外层循环
          long temp = a[out];            // 先把要插入有序队列中的元素取出
         in = out;                      // 要从这个元素的左边开始依次比较了
         while(in>0 && a[in-1] >= temp){ // 比较条件,
            a[in] = a[in-1];            // 比temp大的元素,就要右移了
            --in;                       // 再比较左边的
            }
         a[in] = temp;                  // 找到合适的位置了
         }  // end for
      }  // end insertionSort()

在外层的for循环中,out变量从1开始,向右移动。它标记了未排序部分的最左端的数据。而在内层的while循环中,in变量从out变量开始,向左移动,直到temp变量小于in所指的数组数据项,或者它已经不能再往左移动了。while循环的每一趟都向右移动了一个已排序的数据项。

这个算法需要多少次比较和复制呢?在第一趟排序中,它最多比较一次,在第二趟中最多比较两次,依此类推了,最后一趟最多,N-1次.所以:

1+2+3+……+N-1=N*(N-1)/2

然而,在每一趟排序发现插入点之前,平均只有全体数据项的一半真的进行了比较,所以实际上大约是N*(N-1)/4 次 (这个值不是精确值,只是一个概率的估算值,学过数理统计的朋友就不要太过计较了)

复制的次数大致等于比较的次数,由于一次复制与一次交换的时间耗费不同,所以相对于随机数据,这个算法比冒泡排序快一倍,比选择排序略快。

如果数据基本有序,插入排序几乎只需要O(N)的时间;然后对于逆序排列的数据,每次比较和移动都会执行,所以这时插入排序并不比冒泡快。

大家在选择算法时,要注意了,在事先估算待排序数据的状况下,再选择相应的算法。

四,有序链表排序

这里存储数据的方式就不是前面讨论的用数组来存储了,而是用链表这一个数据结构。

有序链表排序是一种高效的排序机制。所设有一个无序数组,如果从这个数组中取出数据,然后一个一个地插入有序链表,它们自动地按序排列,把它们从链表中删除重新放入数组,那么数组就会排好序了。

建立链表类Link.java:

class Link
   {
   public long dData;                  

   public Link next;
   public Link(long dd)
      { dData = dd; }
   }

建立有序链表SortedList.java

class SortedList
   {
   private Link first;
   public SortedList()
      { first = null; }
   public SortedList(Link[] linkArr)  

      {                               

      first = null;
      for(int j=0; j<linkArr.length; j++)
         insert( linkArr[j] );             

      }
   public void insert(Link k)     

      {
      Link previous = null;
      Link current = first;

      while(current != null && k.dData > current.dData)
         {                             

         previous = current;
         current = current.next;
         }
      if(previous==null)
         first = k;                    

      else
         previous.next = k;            

      k.next = current;
      }  // end insert()
   public Link remove()           

      {
      Link temp = first;             

      first = first.next;             

      return temp;                     

      }
   }

测试类ListInsertionSortApp.java:

class ListInsertionSortApp
   {
   public static void main(String[] args)
      {
      int size = 10;
                                 // 建立一个随机数组
      Link[] linkArray = new Link[size];

      for(int j=0; j<size; j++)  

         {
         int n = (int)(java.lang.Math.random()*99);
         Link newLink = new Link(n);  // 建立链表

         linkArray[j] = newLink;      

         }

      System.out.print("Unsorted array: ");
      for(int j=0; j<size; j++)
         System.out.print( linkArray[j].dData + " " );
      System.out.println("");

      SortedList theSortedList = new SortedList(linkArray);

      for(int j=0; j<size; j++)
         linkArray[j] = theSortedList.remove();

      System.out.print("Sorted Array:   ");
      for(int j=0; j<size; j++)
         System.out.print(linkArray[j].dData + " ");
      System.out.println("");
      } 

   }

这种排序方式总体上比在数组中用常用的插入排序效率更高一些,因为这种方式进行的复制次数少一些,但它仍然是一个时间级为O(N^2)的过程,因为在有序链表中每插入一个新的链结点,平均要与一半已存在数据进行比较。如果插入N个新数据,就进行了N*N/4次比较。每一个链结点只进行两次复制:一次从数组到链表,一次从链表到数组。在数组中进行插入排序需要N*N次移动,相比之下,2*N次移动更好。

注:以上提供的算法出自《Data Structures & Algorithms in Java》by Robert Lafore Sams © 1998

《黑客帝国》的故事(转自央视第10放映室)

什么是Matrix(矩阵)? 
Matrix的本意是子宫、母体、孕育生命的地方,同时,在数学名词中,矩阵用来表示统计数据等方面的各种有关联的数据。这个定义很好地解释了Matrix代码制造世界的数学逻辑基础。在电影中,Matrix不仅是一个虚拟程序,也是一个实际存在的地方。在这里,人类的身体被放在一个盛满营养液的器皿中,身上插满了各种插头以接受电脑系统的感官刺激信号。人类就依靠这些信号,生活在一个完全虚拟的电脑幻景中。机器用这样的方式占领了人类的思维空间,用人类的身体作为电池以维持自己的运行。

在电影中,Matrix是一套复杂的模拟系统程序,它是由具有人工智能的机器建立的,模拟了人类以前的世界,用以控制人类。在Matrix中出现的人物,都可以看做是具有人类意识特征的程序。这些程序根据所附着的载体不同有三类:一类是附着在生物载体上的,就是在矩阵中生活的普通人;一类是附着在电脑芯片上的,就是具有人工智能的机器;这些载体通过硬件与Matrix连接。而另一类则是自由程序,它没有载体,诸如再特工、先知、建筑师、梅罗文加、火车人等。

Matrix是一个巨大的网络,连接着无数人的意识,系统分配给他们不同的角色,就象电脑游戏中的角色扮演游戏一样,只是他们没有选择角色的权利和意识。人类通过这种联网的虚拟生活来维持自身的生存需要,但Matrix中的智能程序,也就是先知的角色,发现在系统中有1%的人由于自主意识过强,不能兼容系统分配的角色,如果对他们不进行控制就会导致系统的不稳定,进而导致系统崩溃。因此编写Matrix的智能程序,也就是建筑师就制造了“救世主”,让他有部分自主意识,并成为觉醒人类的领袖,带领他们建造了锡安。

什么是Zion(锡安)?

“Zion(锡安)”一词在《圣经》中,是所罗门王建造圣殿所坐落的山,位于圣城耶路撒冷。而在犹太教中,“锡安”代表着上帝的荣耀,是神的救赎来临的标志。当大地被毁灭后,人类将在锡安接受最后的审判。

在电影中,“锡安”是指那些从Matrix中被解放的人类所栖居的家园,位于地球深处,依靠地热作为能源,成为人类对抗Matrix和机器之城的最后基地。电影用这个名字来命名人类的最后家园,象征着这里是正义得到彰显的地方,是对抗机器的圣地。

锡安的议会结构很象古罗马的元老院,是兼有立法和管理权的国家机构,制定一切法律和制度,通过执行官进行管理。

锡安是由占据Matrix 人口总数的1%的觉醒者构成的,其中主要是以有色人种为主,尤其是议会里的议员和战舰的船长等高层人员,都是黑人。而电影中之所以这样设置锡安的人口,主要是为了体现多民族的融合与宽容,因为这是一个讲述人类对抗共同敌人的故事,人类自己首先要团结,要实现大同的理想。而从另一个角度讲,在西方主流科幻电影中,破败的未来以及非白人的世界,一直是最重要的两个视觉元素。沃卓斯基兄弟作为科幻片导演,自然会在电影中加入这两个西方电影观众耳熟能详的视觉元素。

Matrix中的救世主

Matrix是一个建立在数学基础上的严整系统,一切都是有规律的,包括特工们和尼奥的超能力在内,都是包含在这个系统中的。而尼奥这个“救世主”的产生,则和数学中的哥德尔命题有关。奥地利数学家哥德尔在1931年发表了题为《论<数学原理>及有关系统的形式不可判定命题》的论文,其中提出这样一个观点,在任何数学系统中,只要其能包含整数的算术,这个系统的相容性就不可能通过几个基础学派所采用的逻辑原理建立。简单地说,就是在任何系统中,总有些真理是游离于逻辑之外的,这些真理就叫做歌德尔命题。

在Matrix中,尼奥就是在Matrix这个严整系统中不能被数学推得的歌德尔命题,不符合系统的规律。(建筑师对尼奥的谈话中涉及部分)当尼奥重生后,他就担负起系统所有的扰动,所有的规则在他面前都变得透明,因此他能够看到系统中别人所看不到的东西。先知叫尼奥回到源头去终止灾难,在数学逻辑中就是将歌德尔命题变成整个系统的一部分,当作系统的一个变量,从而消除整个系统的不确定性。如果尼奥当初选择了毁灭锡安的门,他所携带的代码将反馈给系统,将系统的稳定性提高到一个新阶段。而这个选择的前提则是系统中没有斯密斯这个狂人。但从数学的角度上来说,这样的稳定也是暂时的,不是对系统的彻底修正,新的系统还是会产生自己的歌德尔命题,从而继续这个轮回。这就是为什么在尼奥之前会有六任救世主的原因。

按照建筑师最初编写救世主时的任务,救世主的使命就是在锡安运行一段时间后,将锡安的代码带回到Matrix的源程序进行重装,同时机器摧毁锡安,完成Matrix系统的升级。之后救世主将按照初始设置,带领16女7男返回真实世界,再开始重建锡安,等待下一代的救世主。而尼奥与前任们不同的是,建筑师在他的意识中编写了关于爱的编码,这本来是系统处于不断升级的需要,也是考察人类反应的新实验。但这个关于爱的编码,不但导致了尼奥在第二集中做出违背程序设置的选择,而且在第三集中将“爱情”升华为“博爱”,从而最后终结了战争,终止了矩阵和锡安之间的循环。

特工史密斯 
电影中的特工史密斯实际上就是矩阵这个程序世界中的杀毒程序,他们在矩阵中是没有身体的,由于他们是杀毒程序,所以他们被矩阵赋予了超越常人的能力。在矩阵中他们具有改写人类角色程序的能力,所以可以不断借用他人身体。

尼奥最后可以战胜特工,实际上是因为他复活后具有了识别矩阵代码的能力,并可以轻松改写这些代码,所以特工就不能再利用超能力战胜他了。

特工史密斯被尼奥消灭后,因为在他被尼奥消灭前明明是他先杀死了尼奥,所以这就导致了一个逻辑错误。因为这种程序上的逻辑运算错误,导致了特工史密斯不但拒绝被系统删除,而且由杀毒程序变成了病毒,最后危害到了整个矩阵世界。

因为这个逻辑错误是由尼奥导致的,所以特工史密斯就变成了和尼奥相对的负极。最后尼奥选择了让史密斯感染自己,在复制过程中矩阵掌握了史密斯的代码,最后才得以将他们两个同时删除,使矩阵回到了平衡。

尼奥(Neo)/托马斯·安德森(Thomas Anderson)

在希伯来语中,托马斯的意思是双生。这象征着尼奥平时的双重身份:一个是程序员托马斯·安德森,一个是黑客尼奥。而安德森在希伯来语中的含义是“人之子”,这正是耶稣的身份。

组成Neo(尼奥)的这三个字母掉转顺序后就可以组成“one”,表示他就是那个拯救人类的救世主“The One”。而“基督”一词在希伯来语中的本意就是“被指定的那个人”——The One。

墨菲斯(Morpheus)

在希腊神话中,墨菲斯是梦神,拥有改变梦境的能力。在电影中,墨菲斯是把人们从梦境般的虚幻世界中唤醒的指路人。

墨菲斯指挥的飞船是“尼布甲尼撒”号,这是用巴比伦的智慧之神的名字命名的。而在《圣经》中,尼布甲尼撒是巴比伦的国王,曾找人解梦。而在电影中,墨菲斯等人乘坐“尼布甲尼撒”号飞船去找先知诠释什么是真实。

崔尼蒂(Trinity)

Trinity的意思是“三位一体”,在基督教中,“三位一体”指得是圣父、圣子、圣灵。而在现代心理学的奠基之作《梦的解析》一书中,“三位一体”指代了女性意识,她能够进入神秘的领地和完美的境界。

先知(Oracle)

Oracle的希腊语本意是解惑、传递解释神的预言,可以是人、地方,也可以是物品。这些预言通常是模糊的,是现实的一种扭曲,所以能解释的人一定要很有智慧,但即使是他们也不一定能保证预言正确。先知的目的是用自己看到的模糊景象指导信徒,但不能帮他们做决定,决定本身完全取决于人们主观的意愿。

史密斯(Smith)

英文中的Smith意思就是铁匠,而他的车牌号是IS 5416,这都暗含着宗教含义。在《圣经·以塞亚书》第54章16节里说到:吹嘘炭火,打造合用的器械的铁匠是我所造;残害人、行毁灭的也是我所造。这正暗指特工史密斯在矩阵系统中的作用——消灭一切危害矩阵运行的异常程序。

梅罗纹加(Merovingian)

梅罗纹加是法国封建社会中六个王朝的第一个,欧洲中世纪的黑暗历史正是从梅罗纹加王朝开始的,经历六朝,正符合电影中矩阵曾经有六代版本的故事。在电影中,梅罗纹加是一个曾经很有力量的人,而且他喜欢说法语,居住在法国式的城堡中。

法国的梅罗纹加王朝也是欧洲浪漫神话的发源时期,而这些神话的核心人物则是“堕落天使”,他们因为背叛上帝被赶出天堂,撒旦正是这些堕落天使的首领。这也正符合电影中梅罗纹加在矩阵中的身份——他是所有背叛矩阵的程序人的首领,利用自己的能力来对抗矩阵。

塞拉夫(Seraph)

塞拉夫是先知的守卫者,这个名字在欧洲中世纪神话中是天使9个等级里级别最高的六翼天使。当尼奥在矩阵中第一次见到他的时候,他的代码呈现了与众不同的金色。塞拉夫在矩阵中的作用相当于保护先知不受侵害的防火墙,非常有力量,曾经打败过史密斯。

卡玛拉(Kamala)

卡玛拉在梵语中的意思是“莲华”,代表的是清净。在佛教中有句真言就叫做“卡玛拉”。在影片中,卡玛拉是一个由程序自行产生出的新程序,是矩阵世界中第一个由人工智能培养出来的智能程序。在影片结尾暗示了她具有改变矩阵世界代码的能力。

希波克拉底誓言

希波克拉底誓言,是在医神阿波罗以及埃斯克雷彼斯等诸神面前宣读的誓言。
在医学院中,这是立志成为医生的学生所必须背诵一章。甚至连我学中医出身的同事也说他们曾背过关于这很长的一篇。
我不是医生,尽管我曾梦想自己是个救死扶伤的医生,但大家可以把下面的医生做为”职业”,患者做为”顾客”来看:
1,请允许我行医,我要终生奉行人道主义。
2,向恩师表达尊敬与感谢之意。
3,在行医过程中严守良心与尊严。
4,以患者的健康与生命为第一位。
5,严格为患者保守秘密。
6,保持医学界的名誉与宝贵的传统。
7,把同事视为兄弟;不因患者的人种,宗教,国籍和社会地位的不同而区别对待。
8,从受孕之始,即把人的生命作为至高无上之物来尊重。
9,无论承受怎样的压力,在运用自己的知识时也不会违背人道主义

走路有风

其实北京也有雨季,虽然很短。
早上的细雨还是让人有点feel,这种小雨,打伞就太可惜了,北方这种雨太少,这次不淋一次,下一次不知又要等到哪一年了。
不过,淋雨无助于提高智力,也不会预防老年痴呆,所以小朋友们就不要模仿了,呵,还是打伞吧

奉献的艺术–李嘉诚2004年汕头大学演讲

  硬盘里一直存着这一段2004年李嘉诚先生在家乡汕头大学面向长江商学院300名EMBA所作的报告现场的视频,但正如李嘉诚本人所说“我的普通话真的很普通”,视频里的很多词句我都听不太清,潮洲口音的普通话,让我听起来还是有些费力,还好,网上有这一次演讲的文字版,在此贴出,与众享之,共聆大师之言。

文/李嘉诚

多谢大家常称赞我是一个成功的企业家,对于这些支持、鼓励,我内心是感激的。

很多时传媒访问我,都会问及如何可以做一个成功的商人,其实我很害怕被人这样定位。我首先是一个人,再而是一个商人。

每个人一生中都要扮演很多不同的角色;也许,最关键的成功方法就是寻找到导航人生的坐标。没有原则的人,会飘流不定,有正确的坐标,我们做什么角色都可以保持真我,挥洒自如,有不同程度的成就,活得更快乐更精彩。

不知道什么时候开始,“士农工商”社会等级的概念,深深扎根在中国人传统思想内。几千年来,从政治家到学者,在评价“商”的同时,几乎都异口同声带着贬意。他们负面看待商人的经济推动力,在制度上,各种有欠公允的法令,历代层出不穷,把司马迁“货殖列传”所形容,商人“各任其能,竭其力,以得所欲”、资源互通有无、理性客观的风险意识、资本运作技巧、生生不息的创意贡献等等正面的评价,曲解为唯利是图的表征,贬为“无商不奸”,或是“熙熙攘攘,都是为利而来,为利而往”的唯利主义者。

当然,在商人的行列里,也有满脑袋只知道赚钱,不惜在道德上有所亏欠,干出恶劣行为的人。他们伤害到企业本身及整个行业的形象。也有一些企业钻营于道德标准和法律尺度中的灰色地带。今天商业社会的进步,不仅要靠个人勇气、勤奋和坚持,更重要的是建立社群所需要的诚实、慷慨,从而创造出一个更公平、更公正的社会。

从小我就很喜欢听故事,从别人的生活,得到启发。当然,不单是名人或历史人物,四周的各人、各事,言行举止,都是如此。在商言商,有些时候,更会带来巨利的机会。洛克菲勒(Rockefeller)与擦鞋童的故事,大家都听过:1929年,华尔街股灾前,一个擦鞋童也想给Rockefelle炒卖股票的秘密消息,Rockefeller听后,马上领悟到股票市场过热,是离场的时候,他立刻将股票兑现,躲过股灾。

范蠡一句“飞鸟尽,良弓藏;狡兔死,走狗烹”,说尽了当时社会制度的缺憾,大家都忘不了他这句话。范蠡是《史记.货殖列传》中所记的第一人,他曾拜计然为师,研习治国方略,博学多才,是春秋时代著名的政治家。

他有谋略,有渊博及系统化的经济思维,他的经济智慧为他赢得巨大的财富。

现代经济学很多供求机制的理论,我国历史早有记载。

范蠡的“积著之理”研究商品过或短缺的情况,说出物价涨跌的道理。怎样抓住时机,货物和现金流的周转,要如同流水那样生生不息。

范蠡的“计然之术”,还试图从物质世界出发,探索经济活动水平起落波动的根据;其“待乏”原则则阐明了如何预计需求变化并作出反应。他主张平价出售粮食,并平抑调整其他物价,使关卡税收和市场供应都不缺乏,才是治国之道,更提出了国家积极调控经济的方略。

“旱时,要备船以待涝;涝时,要备车以待旱”。强调人们不仅要尊重客观规律,而且要运用和把握客观规律,应用在变化万千的经济现象之中。

我觉得范蠡一生可算无憾,有文种这样知心相重的朋友;有共渡艰难,共渡辰光的西施为伴侣,最重要的是,有智慧守候他的终生。我相信他是快乐的,因为他清楚知道在不同时候,自己要担当什么角色,而且都这样出色,这么诚恳有节。勾践败国,范蠡侍于身后,不被夫差力邀招揽所动。

范蠡助勾贱复国后,又看透时局,离越赴齐,变名更姓为夷子皮。他与儿子们耕作于海边,由于经营有方,没有多久,产业竟然达数十万钱。

齐国的人,见范蠡贤明,欲委以大任。范蠡却相信“久受尊名,终不是什么好事”,他散其家财,分给亲友乡邻,然后怀带少数财物,离开齐到了陶,再次变易姓名,自称为陶朱公。

他继续从商,每日买贱卖贵,没过多久,又积聚资财巨万,成了富翁。

范蠡老死于陶。一生三次迁徙,皆有英名。

书中没有记载范蠡终归是否无憾。我们的中国心有很多包袱,自我概念未能完善发展。范蠡没有日记,没有回忆录;只有他行动的记录,故无法分析他的心态。他历尽艰辛协助勾践复国,又看透勾践不仁不义的性格,他建立制度,却又害怕制度;他雄才伟略,但又厌倦社会的争辩和无理;他成就伟大,却欲深刻体会到世间上最强最有杀伤力的情绪是嫉妒,范蠡为什么会有如此消极的抗拒(不参与本身就是一种抗拒)?

说完我国著名历史人物范蠡,我想谈一谈一个美国的伟人。

来自另一个世界的本杰明.富兰克林(Benjamin Franklin),他墓碑上只简单刻上“富兰克林,印刷工人”的字。他是个哲学家、政治家、外交家、作家、科学家、商家,发明家和音乐家,闻名于世,像他这样在各方面都展现卓越才能的人是少见的。

富兰克林,1706年生于波士顿,家境清贫,没有受过正规教育,他一直努力弥补这一遗憾,完全是靠自学获得了广泛的知识。他12岁当印刷学徒,1730年接办宾州公报,他著作的《可怜李察的日记》一纸风行,成为除圣经外最畅销的书,他为政府印刷纸币,实业上获得了很大成功。

富兰克林不单有超越年龄的智慧,更对别人关心,有健全的思维,他对公共事业的热心和能力,更赢得了当地居民的信任。富兰克林曾经立下志愿,凡是对公众有益的事情,不管多困难,他都要努力承担。自1748年始,他开展了不同的公共项目,包括建立图书馆、学校、医院等。

做好事、做好人是驱动富兰克林终生的核心思想,他极希望自己做的每一件事,均有益于社会,有用于社会,身体力行为后人谋取幸福。

他名成利就后,从未忘记帮助年轻人找到自己增值的方法,在“给一个年轻商人的忠告”的文章内,他的名句“Time is money, credit is money”,将时间和诚信作为钱能生钱可量化的投资;在“财富之路”一文内,富兰克林清楚简单地说明,勤奋、小心、俭朴、稳健是致富之核心态度。

勤奋为他带来财富,俭朴让他保存产业。

富兰克林十三个人生信条他都写得简明扼要:“节制、缄默、秩序、决心、节俭、勤勉、真诚、正义、中庸、清洁、平静、贞节、谦逊”都是年轻人的座佑铭。

他更是一位杰出的政治家,在美国独立战争期间,他曾出使法国,赢得法国对美国的同情与支持。独立后,制宪会议一开始,富兰克林更表现出一个政治家的博大胸怀。虽然他是众望所归,但却提名华盛顿将军当总统。

富兰克林坚持留给制宪会议的绝非是名誉高位,而是胸襟、智慧和爱国精神。

1790年,这位为教育、科学和公务献出了自己一生的人,平静地与世长辞。他获得了很高的荣誉,美国人民称他为“伟大的公民”,历代世人都给予他很高的评价。

人类历史碑上上永远会铭刻富兰克林的名字。

范蠡和富兰克林,两个不同的人,不同时代,不同文化背景,放在一起说好像互不相干,然而,他们的故事是值得大家深思的。

范蠡改变自己迁就社会,而富兰克林推动社会的变迁。

他们在人生某个阶段都扮演过相同的角色,但他们设定人生的坐标完全不同,范蠡只想过他自己的日子,富兰克林利用他的智慧、能力和奉献精神建立未来的社会。就如他们从商所得,虽然一样毫不吝啬馈赠别人,但方法成果有天渊之别;范蠡赠给邻居,富兰克林用于建造社会能力(Capacity buiding),推动人们更有远见、能力、动力和冲劲。有能力的人可以为社会服务,有奉献心的人才可以带动社会进步。

今天的中国人是幸运的,我们经历中国历史前所未见的制度工程,努力建设持续开放及法治的社会,拥抱经济动力和健康自我概念的发展,尽管未尽完善,亦不必像范蠡一样受制于当时社会价值观,只能以“无我”为外衣,追求“自我”,今日我们可以像富兰克林建立自我,追求无我。

在今天,停滞的思想模式已变得不合时宜 ,这不是弃旧立新,采取二元对立、非黑即白的思维,而是要鼓励传统的更生力,使中国文化更适用于层次多元的世界。

在全球化的今天,我们要懂得比较历史,观察现在和梦想未来。

从商的人,应更积极、更努力、更自律,建立公平公正、有道德感、自重和守法精神的社会,才可以为稳定、自由的原则赋予真正的意义。

虽然没有人要求我们,我们自己要愿意发挥我们的智慧和勇气,为自己、企业和社会创造财富和机会,大家可以各适其适。

最近我看到一段故事《三等车票》:在印度,一位善心的富孀,临终遗愿要将她的金钱留给同村的贫因小孩分批搭乘三等火车,让他们有机会见识自己的国家,增长知识之余,更可体会世界的转变和希望。

“栽种思想,成就行为;栽种行为,成就习惯;栽种习惯,成就性格;栽种性格,成就命运”。这不知道是谁说的话,但我觉得适用于个人和国家。

我最近常常对人说,我有了第三个儿子,朋友们听说后都一脸不好意思的恭喜我。我是很高兴,我不仅爱他,我的儿子也将爱他,我的孙儿也将爱他。我的基金会就是我第三个儿子。

过去六十多年的工作,沧海桑田,但我始终坚持最重要的核心价值:公平、正直、真诚、同情心,凭仗努力和蒙上天的眷顾,循正途争取到一定的成就,我相信,我已创立的一定能继续发扬;我希望,财富的能力可有系统地发挥。我们要同心协力,积极、真心、决心,在这个世上散播最好的种子,并肩建立一个较平等及富有同情心的社会,亦为经济、教育及医疗作出贡献;希望大家抱慷慨宽容的胸怀,打造奉献的文化,实现我们人生最有意义的目标,为我们心爱的民族和人类创造繁荣和幸福。

谢谢大家。

霍金北京《宇宙的起源》演讲全文

6月19日上午,世界科学大师斯蒂芬·霍金教授参加在人民大会堂举行的2006年国际弦理论大会开幕式,并作“宇宙的起源”主题讲座。据大会主办方之一中国科学院理论物理研究所透露,在北京期间,霍金教授将向科学界和公众作两场“宇宙的起源”主题讲座。

以下为霍金今天上午的演讲全文:

根据中非Boshongo人的传说,世界太初只有黑暗、水和伟大的Bumba上帝。一天,Bumba胃痛发作,呕吐出太阳。太阳灼干了一些水,留下土地。他仍然胃痛不止,又吐出了月亮和星辰,然后吐出一些动物,豹、鳄鱼、乌龟、最后是人。

这个创世纪的神话,和其它许多神话一样,试图回答我们大家都想诘问的问题:为何我们在此?我们从何而来?一般的答案是,人类的起源是发生在比较近期的事。人类正在知识上和技术上不断地取得进步。这样,它不可能存在那么久,否则的话,它应该取得更大的进步。这一点甚至在更早的时候就应该很清楚了。

例如,按照Usher主教《创世纪》把世界的创生定于公元前4004年10月23日上午9时。另一方面,诸如山岳和河流的自然环境,在人的生命周期里改变甚微。所以人们通常把它们当作不变的背景。要么作为空洞的风景已经存在了无限久,要么是和人类在相同的时刻被创生出来。

但是并非所有人都喜欢宇宙有个开端的思想。例如,希腊最著名的哲学家亚里士多德,相信宇宙已经存在了无限久的时间。某种永恒的东西比某种创生的东西更完美。他提出我们之所以看到发展处于这个情形,那是因为洪水或者其它自然灾害,不断重复地让文明回复到萌芽阶段。信仰永恒宇宙的动机是想避免求助于神意的干涉,以创生宇宙并启始运行。相反地,那些相信宇宙具有开端的人,将开端当作上帝存在的论据,把上帝当作宇宙的第一原因或者原动力。

如果人们相信宇宙有一个开端,那么很明显的问题是,在开端之前发生了甚么?上帝在创造宇宙之前,他在做甚么?他是在为那些诘问这类问题的人准备地狱吗?德国哲学家伊曼努尔.康德十分关心宇宙有无开端的问题。他觉得,不管宇宙有无开端,都会引起逻辑矛盾或者二律背反。如果宇宙有一个开端,为何在它起始之前要等待无限久。他将此称为正题。另一方面,如果宇宙已经存在无限久,为甚么它要花费无限长的时间才达到现在这个阶段。他把此称为反题。无论正题还是反题,都是基于康德的假设,几乎所有人也是这么办的,那就是,时间是绝对的,也就是说,时间从无限的过去向无限的将来流逝。时间独立于宇宙,在这个背景中,宇宙可以存在,也可以不存在。

直至今天,在许多科学家的心中,仍然保持这样的图景。然而,1915年爱因斯坦提出他的革命性的广义相对论。在该理论中,空间和时间不再是绝对的,不再是事件的固定背景。相反地,它们是动力量,宇宙中的物质和能量确定其形状。它们只有在宇宙之中才能够定义。这样谈论宇宙开端之前的时间是毫无意义的。这有点儿像去寻找比南极还南的一点没有意义一样。它是没有定义的。

如果宇宙随时间本质上不变,正如20世纪20年代之前一般认为的那样,就没有理由阻止在过去任意早的时刻定义时间。人们总可以将历史往更早的时刻延展,在这个意义上,任何所谓的宇宙开端都是人为的。于是,情形可以是这样,这个宇宙是去年创生的,但是所有记忆和物理证据都显得它要古老得多。这就产生了有关存在意义的高深哲学问题。我将采用所谓的实证主义方法来对付这些问题。在这个方法中,其思想是,我们按照我们构造世界的模型来解释自己感官的输入。人们不能询问这个模型是否代表实在,只能问它能否行得通。首先,如果按照一个简单而优雅的模型可以解释大量的观测;其次,如果这个模型作出可能被观察检验,也可能被证伪的明确预言,这个模型即是一个好模型。

根据实证主义方法,人们可以比较宇宙的两个模型。第一个模型,宇宙是去年创生的,而另一个是宇宙已经存在了远为长久的时间。一对孪生子在比一年前更早的时刻诞生,已经存在了久于一年的宇宙的模型能够解释像孪生子这样的事物。

另一方面,宇宙去年创生的模型不能解释这类事件,因此第二个模型更好。人们不能诘问宇宙是否在一年前确实存在过,或者仅仅显得是那样。在实证主义的方法中,它们没有区别。

在一个不变的宇宙中,不存在一个自然的起始之点。然而,20世纪20年代当埃德温.哈勃在威尔逊山上开始利用100英寸的望远镜进行观测时,情形发生了根本的改变。哈勃发现,恒星并非均匀地分布于整个空间,而是大量地聚集在称为星系的集团之中。

哈勃测量来自星系的光,进而能够确定它们的速度。他预料向我们飞来的星系和离我们飞去的星系一样多。这是在一个随时间不变的宇宙中应有的。但是令哈勃惊讶的是,他发现几乎所有的星系都飞离我们而去。此外,星系离开我们越远,则飞离得越快。宇宙不随时间不变,不像原先所有人以为的那样。它正在膨胀。星系之间的距离随时间而增大。

宇宙膨胀是20世纪或者任何世纪最重要的智力发现之一。它转变了宇宙是否有一个开端的争论。如果星系现在正分开运动,那么,它们在过去一定更加靠近。如果它们过去的速度一直不变,则大约150亿年之前,所有星系应该一个落在另一个上。这个时刻是宇宙的开端吗?

许多科学家仍然不喜欢宇宙具有开端。因为这似乎意味着物理学崩溃了。人们就不得不去求助于外界的作用,为方便起见,可以把它称作上帝,去确定宇宙如何起始。因此他们提出一些理论。在这些理论中,宇宙此刻正在膨胀,但是没有开端。其中之一便是邦迪、高尔德和霍伊尔于1948年提出的稳恒态理论。

在稳恒态理论中,其思想是,随着星系离开,由假设中的在整个空间连续创生的物质形成新的星系。宇宙会永远存在,而且在所有时间中都显得一样。这最后的性质从实证主义的观点来看,作为一个可以用观测来检验的明确预言,具有巨大的优点。在马丁.莱尔领导下的剑桥射电观测天文小组,在20世纪60年代早期对弱射电源进行了调查。这些源在天空分布得相当均匀,表明大部分源位于银河系之外。平均而言,较弱的源离得较远。

稳恒态理论预言了源的数目对应于源强度的图的形状。但是观测表明,微弱的源比预言的更多,这表明在过去源的密度较高。这就和稳恒态理论的任何东西在时间中都是不变的基本假设相冲突。由于这个,也由于其它原因,稳恒态理论被抛弃了。

还有另一种避免宇宙有一开端的企图是,建议存在一个早先的收缩相,但是由于旋转和局部的无规性,物质不会落到同一点。相反,物质的不同部分会相互错开,宇宙会重新膨胀,这时密度保持有限。两位俄国人利弗席兹和哈拉尼科夫实际上声称,他们证明了,没有严格对称的一般收缩总会引起反弹,而密度保持有限。这个结果对于马克思主义列宁主义的唯物辩证法十分便利,因为它避免了有关宇宙创生的难以应付的问题。因此,这对于苏联科学家而言成为一篇信仰的文章。

当利弗席兹和哈拉尼科夫发表其断言时,我是一名21岁的研究生,为了完成博士论文,我正在寻找一个问题。我不相信他们所谓的证明,于是就着手和罗杰.彭罗斯一起发展新的数学方法去研究这个问题。我们证明了宇宙不能反弹。如果爱因斯坦的广义相对论是正确的,就存在一个奇点,这是具有无限密度和无限时空曲率的点,时间在那里有一个开端。

在我得到第一个奇点结果数月之后,即1965年10月,人们得到了确认宇宙有一个非常密集开端的思想的观察证据,那是发现了贯穿整个空间的微弱的微波背景。这些微波和你使用的微波炉的微波是一样的,但是比它微弱多了。它们只能将匹萨加热到摄氏负270.4度,甚至无法将匹萨化冻,更不用说烤熟它。实际上你自己就可以观察到这些微波。把你的电视调到一个空的频道去,在荧幕上看到的雪花的百分之几就归因于这个微波背景。早期非常热和密集状态遗留下的辐射是对这个背景的仅有的合理解释。随着宇宙膨胀,辐射一直冷却下来,直至我们今天观察到它的微弱的残余。

虽然彭罗斯和我自己的奇性定理预言,宇宙有一个开端,这些定理并没有告诉宇宙如何起始。广义相对论方程在奇点处崩溃了。这样,爱因斯坦理论不能预言宇宙如何起始,它只能预言一旦起始后如何演化。人们对彭罗斯和我的结果可有两种态度。一种是上帝由于我们不能理解的原因,选择宇宙的启始方式。这是约翰.保罗教的观点。在梵蒂冈的一次宇宙论会议上,这位教皇告诉代表们,在宇宙起始之后,研究它是可以的。但是他们不应该探究起始的本身,因为这是创生的时刻,这是上帝的事体。我暗自庆幸,他没有意识到,我在会议上发表了一篇论文,刚好提出宇宙如何起始。我可不想象伽利略那样被递交给宗教裁判厅。

对我们结果的另外解释,这也是得到大多数科学家赞同的解释。这个结果显示,在早期宇宙中的非常强大的引力场中,广义相对论崩溃了,必须用一个更完备的理论来取代它。因为广义相对论没有注意到物质小尺度结构,而后者是由量子理论制约的,所以人们预料总要进行这种取代。在通常情况下,因为宇宙的尺度和量子理论的微观尺度相比较极为巨大,所以是否取代无所谓。但是当宇宙处于普朗克尺度,也就是1千亿亿亿亿分之一米时,这两个尺度变成相同,必须考虑量子理论。

为了理解宇宙的起源,我们必须把广义相对论和量子理论相结合。里查德.费恩曼对历史求和的思想似乎是实现这个目标的最佳方法。里查德.费恩曼是一位多姿多彩的人物。他在帕沙迪那的脱衣舞酒吧里敲小鼓,又是加州理工学院卓越的物理学家。他提议一个系统从状态A到状态B经过所有可能的路径或历史。  每个路径或者历史都有一定的振幅和强度。而系统从A到B的概率是将每个路径的振幅加起来。存在一个由兰干酪制成月亮的历史。但是其振幅很低。这对于老鼠来说不是一个好消息。

宇宙现在状态的概率可将结局为这个状态的所有历史迭加得到。但是这些历史如何起始的呢?这是一个改头换面的起源问题。是否需要一个造物主下达命令,宇宙如此这般起始呢?还是由科学定律来确定宇宙的初始条件呢?

事实上,即便宇宙的历史回到无限的过去,这个问题仍然存在。但是如果宇宙只在150亿年前起始,这个问题就更加急切。询问在时间的开端会发生甚么,有点像当人们认为世界是平坦的,询问在世界的边缘会发生甚么一样。世界是一块平板吗?海洋从它边缘上倾泻下去吗?我已经用实验对此验证过。我环球旅行过,我并没有掉下去。

正如大家知道的,当人们意识到世界不是一块平板,而是一个弯曲的面时,在宇宙的边缘发生甚么的问题就被解决了。然而,时间似乎不同。它显得和空间相分离。像是一个铁轨模型。如果它有一个开端,就必须有人去启动火车运行。

爱因斯坦的广义相对论将时间和空间统一成时空。但是时间仍然和空间不同,它正像一个通道,要么有开端和终结,要么无限地伸展出去。然而,詹姆.哈特尔和我意识到,当广义相对论和量子论相结合时,在极端情形下,时间可以像空间中另一方向那样行为。这意味着,和我们摆脱世界边缘的方法类似,可以摆脱时间具有开端的问题。

假定宇宙的开端正如地球的南极,其纬度取时间的角色。宇宙就在南极作为一个起始点。随着往北运动,代表宇宙尺度的常纬度的圆就膨胀。诘问在宇宙开端之前发生了甚么是没有意义的问题。因为在南极的南边没有任何东西。

时间,用纬度来测量,在南极处有一个开端。但是南极和其它的点非常相像。至少我听别人这么讲的。我去过南极洲,没有去过南极。

同样的自然定律正如在其它地方一样,在南极成立。长期以来,人们说宇宙的开端是正常定律失效之处,所以宇宙不应该有开端。而现在,宇宙的开端由科学定律来制约,所以反对宇宙有开端的论证不再成立。   詹姆.哈特尔和我发展宇宙自发创生的图景有一点像泡泡在沸腾的水中形成。

其思想是,宇宙最可能的历史像是泡泡的表面。许多小泡泡出现,然后再消失。这些对应于微小的宇宙,它们膨胀,但在仍然处于微观尺度时再次坍缩。它们是另外可能的宇宙,由于不能维持足够长的时间,来不及发展星系和恒星,更不用说智慧生命了,所以我们对它们没有多大兴趣。然而,这些小泡泡中的一些会膨胀到一定的尺度,到那时可以安全地逃避坍缩。它们会继续以不断增大的速率膨胀,形成我们看到的泡泡。它们对应于开始以不断增加的速率膨胀的宇宙。这就是所谓的暴胀,正如每年的价格上涨一样。

通货膨胀的世界纪录应归一战以后的德国。在18月期间价格增大了一千万倍。但是,它和早期宇宙中的暴胀相比实在微不足道。宇宙在比一秒还微小得多的时间里膨胀了十的30次方倍。和通货膨胀不同,早期宇宙的暴胀是非常好的事情。它产生了一个非常巨大的均匀的宇宙,正如我们观察到的。然而,它不是完全均匀的。在对历史求和中,稍微具有无规性的历史和完全均匀和规则历史的概率几乎相同。因此,理论预言早期宇宙很可能是稍微不均匀的。这些无规性在从不同方向来的微波背景强度上引起小的变化。利用MAP(微波各向异性)卫星已经观察到微波背景,发现了和预言完全一致的变化。这样,我们知道自己正在正确的道路上前进。

早期宇宙中的无规性,意味着在有些区域的密度,比其它地方的稍高。这些额外密度的引力吸引使这个区域的膨胀减缓,而且最终能够使这些区域坍缩形成星系和恒星。请仔细看这张微波天图。它是宇宙中一切结构的蓝图。我们是极早期宇宙的量子起伏的产物。上帝的确在掷骰子。

在过去的百年间,我们在宇宙学中取得了惊人的进步。广义相对论和宇宙膨胀的发现,粉碎了永远存在并将永远继续存在的宇宙的古老图像。取而代之,广义相对论预言,宇宙和时间本身都在大爆炸处起始。它还预言时间在黑洞里终结。宇宙微波背景的发现,以及黑洞的观测,支持这些结论。这是我们的宇宙图像和实在本身的一个深刻的改变。

虽然广义相对论预言了,宇宙来自于过去一个高曲率的时期,但它不能预言宇宙如何从大爆炸形成。这样,广义相对论自身不能回答宇宙学的核心问题,为何宇宙如此这般。然而,如果广义相对论和量子论相合并,就可能预言宇宙是如何起始的。它开始以不断增大的速率膨胀。这两个理论的结合预言,在这个称作暴胀的时期,微小的起伏会发展,导致星系、恒星以及宇宙中所有其它结构的形成。对宇宙微波背景中的小的非均匀性的观测,完全证实了预言的性质。这样,我们似乎正朝着理解宇宙起源的正确方向前进,尽管还有许多工作要做。当我们通过精密测量空间航空器之间距离,进而能够检测到引力波,就会打开极早期宇宙的新窗口。引力波从最早的时刻自由地向我们传播,所有介入的物质都无法阻碍它。与此相比较,自由电子多次地散射光。这种散射一直进行到30万年后电子被凝结之前。

尽管我们已经取得了一些伟大成功,并非一切都已解决。我们观察到,宇宙的膨胀在长期的变缓之后,再次加速。对此理论还不能理解清楚。缺乏这种理解,对宇宙的未来还无法确定。它会继续地无限地膨胀下去吗?暴胀是一个自然定律吗?或者宇宙最终会再次坍缩吗?新的观测结果,理论的进步正迅速涌来。宇宙学是一个非常激动人心和活跃的学科。我们正接近回答这古老的问题:我们为何在此?我们从何而来?

谢谢各位。