JAVA中的传递都是值传递吗?有没有引用传递呢?
在回答这两个问题前,让我们首先来看一段代码:
- public class ParamTest {
-
- protected int num = 0;
-
-
- public void change(int i) {
- i = 5;
- }
-
-
- public void change(ParamTest t) {
- ParamTest tmp = new ParamTest();
- tmp.num = 9;
- t = tmp;
- }
-
-
- public void add(int i) {
- i += 10;
- }
-
-
- public void add(ParamTest pt) {
- pt.num += 20;
- }
-
- public static void main(String[] args) {
- ParamTest t = new ParamTest();
-
- System.out.println("参数--基本类型");
- System.out.println("原有的值:" + t.num);
-
- t.change(t.num);
- System.out.println("赋值之后:" + t.num);
-
- t.change(t);
- System.out.println("运算之后:" + t.num);
-
- System.out.println();
-
- t = new ParamTest();
- System.out.println("参数--引用类型");
- System.out.println("原有的值:" + t.num);
-
- t.add(t.num);
- System.out.println("赋引用后:" + t.num);
-
- t.add(t);
- System.out.println("改属性后:" + t.num);
- }
- }
public class ParamTest {
// 初始值为0
protected int num = 0;
// 为方法参数重新赋值
public void change(int i) {
i = 5;
}
// 为方法参数重新赋值
public void change(ParamTest t) {
ParamTest tmp = new ParamTest();
tmp.num = 9;
t = tmp;
}
// 改变方法参数的值
public void add(int i) {
i += 10;
}
// 改变方法参数属性的值
public void add(ParamTest pt) {
pt.num += 20;
}
public static void main(String[] args) {
ParamTest t = new ParamTest();
System.out.println("参数--基本类型");
System.out.println("原有的值:" + t.num);
// 为基本类型参数重新赋值
t.change(t.num);
System.out.println("赋值之后:" + t.num);
// 为引用型参数重新赋值
t.change(t);
System.out.println("运算之后:" + t.num);
System.out.println();
t = new ParamTest();
System.out.println("参数--引用类型");
System.out.println("原有的值:" + t.num);
// 改变基本类型参数的值
t.add(t.num);
System.out.println("赋引用后:" + t.num);
// 改变引用类型参数所指向对象的属性值
t.add(t);
System.out.println("改属性后:" + t.num);
}
}
这段代码的运行结果如下:
- 参数--基本类型
- 原有的值:0
- 赋值之后:0
- 运算之后:0
-
- 参数--引用类型
- 原有的值:0
- 赋引用后:0
- 改属性后:20
从上面这个直观的结果中我们很容易得出如下结论:
- 对于基本类型,在方法体内对方法参数进行重新赋值,并不会改变原有变量的值。
- 对于引用类型,在方法体内对方法参数进行重新赋予引用,并不会改变原有变量所持有的引用。
- 方法体内对参数进行运算,不影响原有变量的值。
- 方法体内对参数所指向对象的属性进行运算,将改变原有变量所指向对象的属性值。
上面总结出来的不过是我们所看到的表面现象。那么,为什么会出现这样的现象呢?这就要说到值传递和引用传递的概念了。这个问题向来是颇有争议的。
大家都知道,在JAVA中变量有以下两种:
- 基本类型变量,包括char、byte、short、int、long、float、double、boolean。
- 引用类型变量,包括类、接口、数组(基本类型数组和对象数组)。
当基本类型的变量被当作参数传递给方法时,JAVA虚拟机所做的工作是把这个值拷贝了一份,然后把拷贝后的值传递到了方法的内部。因此在上面的例子中,我们回头来看看这个方法:
-
- public void change(int i) {
- i = 5;
- }
// 为方法参数重新赋值
public void change(int i) {
i = 5;
}
在这个方法被调用时,变量i和ParamTest型对象t的属性num具有相同的值,却是两个不同变量。变量i是由JAVA虚拟机创建的作用域在
change(int i)方法内的局部变量,在这个方法执行完毕后,它的生命周期就结束了。在JAVA虚拟机中,它们是以类似如下的方式存储的:
很明显,在基本类型被作为参数传递给方式时,是值传递,在整个过程中根本没有牵扯到引用这个概念。这也是大家所公认的。对于布尔型变量当然也是如此,请看下面的例子:
- public class BooleanTest {
-
- boolean bool = true;
-
-
- public void change(boolean b) {
- b = false;
- }
-
-
- public void calculate(boolean b) {
- b = b && false;
-
- System.out.println("b运算后的值:" + b);
- }
-
- public static void main(String[] args) {
- BooleanTest t = new BooleanTest();
-
- System.out.println("参数--布尔型");
- System.out.println("原有的值:" + t.bool);
-
- t.change(t.bool);
- System.out.println("赋值之后:" + t.bool);
-
-
- t.calculate(t.bool);
- System.out.println("运算之后:" + t.bool);
- }
- }
public class BooleanTest {
// 布尔型值
boolean bool = true;
// 为布尔型参数重新赋值
public void change(boolean b) {
b = false;
}
// 对布尔型参数进行运算
public void calculate(boolean b) {
b = b && false;
// 为了方便对比,将运算结果输出
System.out.println("b运算后的值:" + b);
}
public static void main(String[] args) {
BooleanTest t = new BooleanTest();
System.out.println("参数--布尔型");
System.out.println("原有的值:" + t.bool);
// 为布尔型参数重新赋值
t.change(t.bool);
System.out.println("赋值之后:" + t.bool);
// 改变布尔型参数的值
t.calculate(t.bool);
System.out.println("运算之后:" + t.bool);
}
}
输出结果如下:
- 参数--布尔型
- 原有的值:true
- 赋值之后:true
- b运算后的值:false
- 运算之后:true
那么当引用型变量被当作参数传递给方法时JAVA虚拟机又是怎样处理的呢?同样,它会拷贝一份这个变量所持有的引用,然后把它传递给JAVA虚拟机为方法
创建的局部变量,从而这两个变量指向了同一个对象。在篇首所举的示例中,ParamTest类型变量t和局部变量pt在JAVA虚拟机中是以如下的方式存
储的:
有一种说法是当一个对象或引用类型变量被当作参数传递时,也是值传递,这个值就是对象的引用,因此JAVA中只有值传递,没有引用传递。还有一种说法是引
用可以看作是对象的别名,当对象被当作参数传递给方法时,传递的是对象的引用,因此是引用传递。这两种观点各有支持者,但是前一种观点被绝大多数人所接
受,其中有《Core Java》一书的作者,以及JAVA的创造者James Gosling,而《Thinking in
Java》一书的作者Bruce Eckel则站在了中立的立场上。
我个人认为值传递中的值指的是基本类型的数值,即使对于布尔型,虽然它的表现形式为true和false,但是在栈中,它仍然是以数值形式保存的,即0表
示false,其它数值表示true。而引用是我们用来操作对象的工具,它包含了对象在堆中保存地址的信息。即使在被作为参数传递给方法时,实际上传递的
是它的拷贝,但那仍是引用。因此,用引用传递来区别与值传递,概念上更加清晰。
最后我们得出如下的结论:
- 基本类型和基本类型变量被当作参数传递给方法时,是值传递。在方法实体中,无法给原变量重新赋值,也无法改变它的值。
- 对象和引用型变量被当作参数传递给方法时,在方法实体中,无法给原变量重新赋值,但是可以改变它所指向对象的属性。至于到底它是值传递还是引用传递,这并不重要,重要的是我们要清楚当一个引用被作为参数传递给一个方法时,在这个方法体内会发生什么。
什么叫引用?只因为这个变量的值和其它的不一样.
首先理解:都是变量
int i;
ArrayList b;
i和b都是变量.
但i是基本变量,也叫原始变量.
其它的就叫引用变量,因为它的值是一个内存地址值.引用对象的.但记住:它们都是有一个值的!i是一个数字,而b是一个内存地址值(简单的说是一个十六进
制的值).除了基本变量之外的变量都是引用变量.Vector a;这里的a也是一个变量.它也是有值的,它的值是一个十六进制的值.
变量的赋值:
int i=10;
int j=i;
//这里把i的值10给了j,所以j的值也是10
ArrayList b=new ArrayList();
ArrayList c=b;
//首先,b是一个引用变量,它的"值":是一个内存地址值!!! new
ArrayList()要分配一段内存保存它们,怎么样找到这段内存?那就是通过b里的值了.b的值就是new
ArrayList()所占内存的首地址.然后c也是一个引用变量,它的值(地址值)和b是一样的.也就是new
ArrayList()所占内存的首地址.所以当通过b或者c进行操作时,它们都是操作同一个对象的.
在方法调用的时候,方法的参数实际也就是一个变量.如果是基本类型变量的时候,假设有方法method(int aa);
int j=10;
method(j);
这里边,int aa实际也是定义了一个变量,调用的时候把j的值:10也给了aa.所以aa也是10,改变了aa的值并不会改变j的值.
如果是引用变量的时候,假设有方法methodA(ArrayList aa);
ArrayList b = new ArrayList();
methodA(b);
//方法定义了变量aa,调用的时候把b的值(地址值!!!!!)给了aa,所以aa与b有一样的值(地址值!!!!),在方法里通过aa去操作的时候,b所引用的对象也就被改变了,因为它们引用同一个对象.
纸 a = new 银行帐户();//开一个银行帐户,返回一个卡号给你,写在你的纸a里边.
用一张纸(引用变量),把你的银行卡号写在上边,然后调用我的时候,我用另外一张纸(引用变量---方法的形数),把你的号码抄过来.然后我通过这个卡号,去到银行找到你的帐号,给你存点钱.
然后你用你的纸(引用变量)上的卡号 <没变,还是那个卡号>再去查询银行帐号的时候就会发现了多了一些钱了.....
说说我对值传递和引用传递的看法:
首先我认为,大家对Java传递参数的行为是清楚的,这个争论只是一个语义上的争论。
也就是我们是否需要区分值传递和应用传递呢?或者说这样的区分有没有意义?是否合理?
博主认为存在引用传递的关键点在于,传递的对象地址值,本质上它是一个引用,无论它是否被copy过。
认为只有值传递的关键点在于,传递的对象地址值,它是一个值的copy,这个值代表的意义无所谓。
引用是c++里的概念,由于java跟c++是有一定关系的,这里把引用迁移过来,如果合理未尝不可。
c++中关于引用的解释一般喜欢说是看作“别名”,我查了几本书,大部分提到引用并不会分配内存空间,也有一本书提到,某些编译器会分配存储空间来存储被引用对象的地址。
那么还是回到语义上来,c++里的这个引用,语义上是“别名”的意思,我的理解是,一组指向同一个对象的别名应该只存储一份内存地址。当然具体实现可能会
把引用当做一个不可变的指针来处理(每个别名都存储自己的对象地址)。但是请注意,我们应该关注于它的语义,即:它没有任何值的copy,即使是一个地
址,只是另外一个名字而已。
但是java里面没有这样的概念,所有的地址传递其行为是值的传递方式,语义上统一成值传递更为清晰,我们只需要考虑这个值具体是什么,无非两种,要么是基本类型值,要么是个地址。
所以我认为这个“引用”的概念放到java中并不合适。只有值传递的说法更合理。
posted @
2008-09-12 10:25 保尔任 阅读(3403) |
评论 (1) |
编辑 收藏
Linux 发展到今天,可用的软件已经非常多了。这样自然会有一些软件的功能大致上相同。例如,同样是编辑器,就有
nvi、vim、emacs、nano,而且我说的这些还只是一部分。大多数情况下,这样的功能相似的软件都是同时安装在系统里的,可以用它们的名称来执
行。例如,要执行 vim,只要在终端下输入 vim
并按回车就可以了。不过,有些情况下我们需要用一个相对固定的命令调用这些程序中的一个。例如,当我们写一个脚本程序时,只要写下
editor,而不希望要为“编辑器是哪个”而操心。Debian 提供了一种机制来解决这个问题,而 update-alternatives
就是用来实现这种机制的。
在说明 update-alternatives 的详细内容之间,先让我们看看系统中已有的例子。打开终端,执行下面的命令:
herbert@natsu:~$ ls -l /usr/bin/editor
lrwxrwxrwx 1 root root 24 2004-09-26 08:48 /usr/bin/editor -> /etc/alternatives/editor
herbert@natsu:~$ ls -l /etc/alternatives/editor
lrwxrwxrwx 1 root root 12 2004-10-27 16:24 /etc/alternatives/editor -> /usr/bin/vim
herbert@natsu:~$
我
们看到,editor 这个可执行命令实际上是个符号链接,它指向 /etc/alternatives/editor;而
/etc/alternatives/editor 也是个符号链接,它指向 /usr/bin/vim。这样,当我输入 editor
并回车时,将执行 vim。之所以要在 /usr/bin 和 /etc/alternatives
中费心建立这样两个链接,就是要实现上面说到的特性:方便脚本
程序的编写和系统的管理。
下面我们就来看看 update-alternatives 的功能。当然,如果你觉得我说得不详细,可以看看这个命令的 manpage:UPDATE-ALTERNATIVES(8)。
首先要介绍的参数是 --display。它使我们可以看到一个命令的所有可选命令。执行
natsu:/home/herbert# update-alternatives --display editor
editor - status is auto.
link currently points to /usr/bin/vim
/bin/ed - priority -100
slave editor.1.gz: /usr/share/man/man1/ed.1.gz
/usr/bin/nvi - priority 19
slave editor.1.gz: /usr/share/man/man1/nvi.1.gz
/bin/nano - priority 40
slave editor.1.gz: /usr/share/man/man1/nano.1.gz
/usr/bin/vim - priority 120
slave editor.1.gz: /usr/share/man/man1/vim.1.gz
/usr/bin/emacs21 - priority 0
slave editor.1.gz: /usr/share/man/man1/emacs.1emacs21.gz
Current `best' version is /usr/bin/vim.
natsu:/home/herbert#
你可以看到我的机器上的所有可以用来被 editor 链接的命令。
下面说说 --config。这个选项使我们可以选择其中一个命令:
natsu:/home/herbert# update-alternatives --config editor
There are 5 alternatives which provide `editor'.
Selection Alternative
-----------------------------------------------
1 /bin/ed
2 /usr/bin/nvi
3 /bin/nano
*+ 4 /usr/bin/vim
5 /usr/bin/emacs21
Press enter to keep the default[*], or type selection number: 4
Using `/usr/bin/vim' to provide `editor'.
natsu:/home/herbert#
我并没有修改它,因为我还是比较喜欢 vim 的。当然,你可以选择别的程序。
说
到这里我们就要介绍一些概念了。首先,update-alternatives 在一般情况下是由 postinst 和 prerm
这样的安装脚本自动调用的,所以一个 alternative 的状态有两种:自动和手动。每个 alternative
的初始状态都是自动。如果系统发现管理员手动修改了一个
alternative,它的状态就从自动变成了手动,这样安装脚本就不会更新它了。如果你希望将一个 alternative 变回自动,只要执行
update-alternatives --auto editor
就可以了。你注意到了吗?我们说到了“名字”。该怎样写名字呢?这就是我们要介绍的第二个概念:
general name -- 这是指一系列功能相似的程序的“公用”名字(包括绝对路径),比如 /usr/bin/editor。
link -- 这是指一个 alternative 在 /etc/alternative 中的名字,比如 editor。
alternative -- 顾名思义,这是指一个可选的程序所在的路径(包括绝对路径),比如 /usr/bin/vim。
--
auto,--display 和 --config 跟的都是
link。我们要说的第三个概念是优先级。这个比较简单,当然优先级越高的程序越好啦(在大多数情况下,我不想争论)最后一个概念是主和从的
alternative。想想看,你将 /usr/bin/editor 链接到了 vim,可是当你执行 man editor 时看到的却是
emacs 的 manpage,你会做何感想呢?这就引出了主和从 alternative 的概念了:当更新主的 alternative
时,从的 alternative 也会被更新。
说完这四个重要的概念后,我们介绍另外两个选项。至于其他的。。。。我相信你会去看手册页的,对吗?
第一个是 --install。它的格式是:
update-alternatives --install gen link alt pri [--slave sgen slink salt] ...
gen,
link,alt,pri 分别是我们上面说过的。如果需要从的 alternative,你可以用 --slave
加在后面。如果你在向一个已经存在的 alternative 组中添加新的 alternatives,该命令会把这些 alternatives
加入到这个已经存在的 alternative 组的
列表中,并用新的可选命令作为新的命令;否则,将会建立一个新的自动的 alternative 组。
呜呼!我加入了一个错误的 alternative。我不想要这个 alternative 了。在这种情况 下,可以执行下面的命令:
update-alternatives --remove name path
name
是一个在 /etc/alternatives 中的名字,也就是上面的 link,而 path
是希望删除的可选程序名的绝对路径名(放心,这样只是从列表中删除了这个程序,并不会真的从硬盘上删除程序的可执行文件)。如果从一个
alternative 组中删除了一个正在被链接的程序并且这个组仍然没有变成空的,update-alternatives
会自动用一个具有其他优先级的可选程序代替原来的程序。如果这个组变成空的了,那么连这个 alternative
组都会被移除。如果删除的程序没有被链接,则只有有关这个程序的信息会被移除。
说个例子吧。我下载了 Eclipse,并且安装了
gcj 和 gij。可是我发现 GNU 的 java 工具还不足以运行 Eclipse。我只好到 Sun 公司的网页上下载了它的 java
工具 jdk。因为是自己安装的,我将它们安装在 /usr/local 上,以便将来重新安装 Linux
系统时这些程序仍然可以使用。于是我要做的就是用这个 jdk 中的 java 和 javac 来代替系统原来的。执行
natsu:/home/herbert# update-alternatives --display java
java - status is auto.
link currently points to /usr/local/j2sdk1.4.2_06/bin/java
/usr/bin/gij-wrapper-3.3 - priority 33
slave java.1.gz: /usr/share/man/man1/gij-wrapper-3.3.1.gz
/usr/local/j2sdk1.4.2_06/bin/java - priority 100
slave java.1.gz: /usr/local/j2sdk1.4.2_06/man/man1/java.1
Current `best' version is /usr/local/j2sdk1.4.2_06/bin/java.
natsu:/home/herbert# update-alternatives --display javac
javac - status is auto.
link currently points to /usr/local/j2sdk1.4.2_06/bin/javac
/usr/bin/gcj-wrapper-3.3 - priority 33
slave javah: /usr/bin/gcjh-wrapper-3.3
slave javac.1.gz: /usr/share/man/man1/gcj-wrapper-3.3.1.gz
slave javah.1.gz: /usr/share/man/man1/gcjh-wrapper-3.3.1.gz
/usr/bin/gcj-wrapper-3.4 - priority 33
slave javah: /usr/bin/gcjh-wrapper-3.4
slave javac.1.gz: /usr/share/man/man1/gcj-wrapper-3.4.1.gz
slave javah.1.gz: /usr/share/man/man1/gcjh-wrapper-3.4.1.gz
/usr/local/j2sdk1.4.2_06/bin/javac - priority 100
slave javah: /usr/local/j2sdk1.4.2_06/bin/javah
slave javac.1.gz: /usr/local/j2sdk1.4.2_06/man/man1/javac.1
slave javah.1.gz: /usr/local/j2sdk1.4.2_06/man/man1/javah.1
Current `best' version is /usr/local/j2sdk1.4.2_06/bin/javac.
natsu:/home/herbert#
(你看到的是我更新以后的)就可以得到关于要更新哪些 alternatives 的信息。我是这么更新的:
update-alternatives
--install /usr/bin/javac javac /usr/local/j2sdk1.4.2_06/bin/javac 100
--slave /usr/bin/javah javah /usr/local/j2sdk1.4.2_06/bin/javah --slave
/usr/share/man/man1/javac.1.gz javac.1.gz
/usr/local/j2sdk1.4.2_06/man/man1/javac.1 --slave
/usr/share/man/man1/javah.1.gz javah.1.gz
/usr/local/j2sdk1.4.2_06/man/man1/javah.1
update-alternatives
--install /usr/bin/java java /usr/local/j2sdk1.4.2_06/bin/java 100
--slave /usr/share/man/man1/java.1.gz java.1.gz
/usr/local/j2sdk1.4.2_06/man/man1/java.1
posted @
2008-02-13 10:08 保尔任 阅读(2556) |
评论 (0) |
编辑 收藏
1, insert Ubuntu 7.10 CD
a, format disc(primary 10G ext3; extend 59G ext3; swap 1G)
b, install(timezone shanghai; en_US; "prepare disc space" manual, or the system will partition autoly)
c, auto restart, go on install system(remenber cut off the net line except the netwidth is large, or it will cost long time to download from far away)
2, config
a, sources list
sudo vim /etc/apt/sources.list
# add "deb http://debian.exoweb.net/debian.cn99.com/debian etch main" into it
sudo apt-get update
sudo apt-get upgrade
b, vedio card driver
在ubuntu7.10下装nvidia 7 series显卡并配置双屏显示:
一,显卡驱动 + 双显示器
(修改X配置命令:sudo dpkg-reconfigure xserver-xorg)
1,到nvidia网站下载7系列显卡的最新驱动
2,ensure that the linux-restricted-modules or linux-restricted-modules-common packages have been uninstalled. Alternatively, you can edit the /etc/default/linux-restricted-modules or /etc/default/linux-restricted-modules-common configuration file and disable the NVIDIA linux-restricted kernel modules (nvidia, nvidia_legacy) via:
DISABLED_MODULES="nv nvidia_new"
3,
sudo apt-get remove --purge nvidia-glx nvidia-glx-new
sudo rm /etc/init.d/nvidia-glx /etc/init.d/nvidia-kernel /lib/linux-restricted-modules/.nvidia_new_installed
4,然后ctrl+alt+1进入tty1
sudo /etc/init.d/gdm stop
sudo sh NVIDIA-Linux-x86-100.14.23-pkg1.run
(这时会出现错误提示,说少了“libc header file...libc development package”)
sudo apt-get install sudo apt-get install build-essential xorg-dev pkg-config linux-headers-$(uname -r), libc6-dev
sudo sh NVIDIA-Linux-x86-100.14.23-pkg1.run
sudo /etc/init.d/gdm start
用application -> system tools里的nvidia工具去配置双显示器
c, multi-language
System -> Administration -> Language support: install English and Chinese
check "input method"
d, Wen Quan Yi font
browse http://wenq.org/, and download 文泉驿点阵宋体 and 文泉驿矢量正黑, then install them
System -> Preference -> Appearance -> Fonts 前四项选择:点阵宋体(WenQuanYi Bitmap Song), 第五项不改(Monospace)
sudo fc-cache -f -v (刷新字体缓存,每次修改字体都要这样,不然Xorg会很慢)
e, stardict
sudo apt-get install stardict
(http://stardict.sourceforge.net/Dictionaries_zh_CN.php 下载朗道英汉,汉英字典)
tar -xjvf *** --directory /usr/share/stardict/dic/
f, pidgin internet messager
sudo apt-get install gaim-guifications
config: Tools -> Plugins -> (check) Guifications; then, config it to uncheck on "Chat message"
3, install and config Software
sudo apt-get install postgresql-8.1 python2.4 ipython vim-gnome sun-java5-jdk eclipse subversion build-essential ssh build-essential meld kompare
a, postgresql
sudo su - postgres (for user postgres has Null password, so you can't just "su - postgres", or you can sudo "sudo passwd postgres" to set password for postgres, then "su - postgres")
createuser (enter username and password.)
config postgresql as below:
In /etc/postgresql/8.1/main/postgresql.conf, Change listen_addresses to '*' and change datestyle to 'ISO,European' and uncomment them.
In /etc/postgresql/8.1/main/pg_hba.conf, 最后加入一行“host all justin 127.0.0.1/16 trust”
b, eclipse
sudo eclipse, exit, eclipse
c, ssh
When other mathines want to ssh or scp your mathine which is new OS, it should "rm ~/.ssh/known_hosts" to reload the new Cert.
d, kompare
add a file svndiff in src with context
"""
if [ $1 ] ; then
svn up -r $1
svn st -q
svn log -r $1
PRE=`expr $1 - 1`
svn diff --diff-cmd=diff -x "-U 10000" -r$PRE:$1 > /tmp/$1.diff
cat /tmp/$1.diff | kompare -
else
svn up
svn st
svn diff --diff-cmd=diff -x "-U 10000" | kompare -
fi
"""
then, in src, ./svndiff 9827 will show diff about r9827
e, firefox add-ons
firebug, flashblock
3, chroot
a,
sudo apt-get install debootstrap
sudo debootstrap --arch i386 etch /home/etch http://debian.exoweb.net/debian.cn99.com/debian/
(if in 64 bit system, use --arch amd64)
sudo chroot /home/etch
#in etch as root
apt-get install locales
dpkg-reconfigure locales #(choose en_us UTF8 as before)
apt-get install vim vim-gnome xbase-clients less sudo postgresql-client subversion
echo "etch" > /etc/debian-chroot
visudo (add user justin to sudo)
adduser justin (删除的命令是userdel justin)
在ubuntu的/usr/bin/etch加入:
sudo cp /etc/passwd /home/etch/etc/
sudo cp /etc/shadow /home/etch/etc/
sudo cp /etc/group /home/etch/etc/
sudo cp /etc/sudoers /home/etch/etc/
sudo cp /etc/resolv.conf /home/etch/etc/
sudo cp /etc/hosts /home/etch/etc/
在/etc/fstab加入:
/home /home/etch/home none bind 0 0
/tmp /home/etch/tmp none bind 0 0
/dev /home/etch/dev none bind 0 0
/proc /home/etch/proc none bind 0 0
sudo chroot /home/etch/ su - justin
现在就可一享受chroot的双系统了
b, run X in etch 3 steps
b1, (etch)mkdir /tmp/.X11-unix
(ubuntu)sudo echo "/tmp/.X11-unix/x0 /home/justin/etch/tmp/.X11-unix/x0 none bind 0 0" >> /etc/fstab
# another way is write it in to /etc/fstab, or sudo mount --bind /tmp/*** /home/justin/etch/tmp/***
b2, (etch)vim ~/.bashrc # add "export DISPLAY=:0.0"
b3, (ubuntu) cp ~/.Xauthority ~/etch/home/justin/ (其实这步不需要,因为上面已经把/home mount到了/home/etch/home了)
c, install java
#download jdk-1_5_0_14-linux-i586.bin to /opt/, and into etch/opt/
sudo chmod +x jdk-1_5_0_14-linux-i586.bin
sudo ./jdk-1_5_0_14-linux-i586.bin
vim ~/.bashrc
#add below in the end of .bashrc
#export JAVA_HOME=/opt/jdk1.5.0_14
#export CLASSPATH=.:$JAVA_HOME/lib/tools.jar:$JAVA_HOME/lib/dt.jar
#export PATH=$JAVA_HOME/bin:$PATH
java -version
#java version "1.5.0_14"
#Java(TM) 2 Runtime Environment, Standard Edition (build 1.5.0_14-b03)
#Java HotSpot(TM) Client VM (build 1.5.0_14-b03, mixed mode, sharing)
配置默认Java使用哪个 sudo update-alternatives --config java
posted @
2007-12-19 17:29 保尔任 阅读(2759) |
评论 (0) |
编辑 收藏
一,两个数的最大公约数:
1、欧几里德算法
欧几里德算法又称辗转相除法,用于计算两个整数a,b的最大公约数。其计算原理依赖于下面的定理:
定理:gcd(a,b) = gcd(b,a mod b)
证明:a可以表示成a = kb + r,则r = a mod b
假设d是a,b的一个公约数,则有
d|a, d|b,而r = a - kb,因此d|r
因此d是(b,a mod b)的公约数
假设d 是(b,a mod b)的公约数,则
d | b , d |r ,但是a = kb +r
因此d也是(a,b)的公约数
因此(a,b)和(b,a mod b)的公约数是一样的,其最大公约数也必然相等,得证
欧几里德算法就是根据这个原理来做的,其算法用C++语言描述为:
void swap(int & a, int & b){
int c = a;
a = b;
b = c;
}
int gcd(int a,int b){
if(0 == a ){
return b;
}
if( 0 == b){
return a;
}
if(a > b){
swap(a,b);
}
int c;
for(c = a % b ; c > 0 ; c = a % b){
a = b;
b = c;
}
return b;
}
2、Stein算法
欧几里德算法是计算两个数最大公约数的传统算法,它无论从理论还是从效率上都是很好的。但是有一个致命的缺陷,这个缺陷只有在大素数时才会显现出来。
考虑现在的硬件平台,一般整数最多也就是64位,对于这样的整数,计算两个数之间的模是很简单的。对于字长为32位的平台,计算两个不超过32位的整数的
模,只需要一个指令周期,而计算64位以下的整数模,也不过几个周期而已。但是对于更大的素数,这样的计算过程就不得不由用户来设计,为了计算两个超过
64位的整数的模,用户也许不得不采用类似于多位数除法手算过程中的试商法,这个过程不但复杂,而且消耗了很多CPU时间。对于现代密码算法,要求计算
128位以上的素数的情况比比皆是,设计这样的程序迫切希望能够抛弃除法和取模。
Stein算法由J. Stein 1961年提出,这个方法也是计算两个数的最大公约数。和欧几里德算法 算法不同的是,Stein算法只有整数的移位和加减法,这对于程序设计者是一个福音。
为了说明Stein算法的正确性,首先必须注意到以下结论:
gcd(a,a) = a,也就是一个数和它自身的公约数是其自身
gcd(ka,kb) = k gcd(a,b),也就是最大公约数运算和倍乘运算可以交换,特殊的,当k=2时,说明两个偶数的最大公约数必然能被2整除
C++/java 实现
// c++/java stein 算法
int gcd(int a,int b){
if(a<b){//arrange so that a>b
int temp = a;
a = b;
b=temp;
}
if(0==b)//the base case
return a;
if(a%2==0 && b%2 ==0)//a and b are even
return 2*gcd(a/2,b/2);
if ( a%2 == 0)// only a is even
return gcd(a/2,b);
if ( b%2==0 )// only b is even
return gcd(a,b/2);
return gcd((a+b)/2,(a-b)/2);// a and b are odd
}
二,多个数的最大公约数:(python实现:取出数组a中最小的,从2到最小的循环,找出其中最大的能被数组中所有数整除的那个数,就是最大公约数)
def gcd(a):
a.sort()
min = a[0]
result = 1
for i in range(2, min+1):
flag = True
for j in a:
if j % i != 0:
flag = False
if flag == True:
result = i
return result
posted @
2007-12-15 15:40 保尔任 阅读(4662) |
评论 (2) |
编辑 收藏
Catalan数:(for http://acm.pku.edu.cn/JudgeOnline/problem?id=2084)
C_n =
ΣC_i*C_(n-i),其中0≤i<n;
C_n = C(2n,n) / (n+1); 其中C(2n, n) 表示组合数,公式为:C(n, k) = n! / (k!(n-k)!)
C_n=C_(n-1)*(4n-2)/(n+1)。
它的意义有很多,例如:n+1边形用对角线划分成
三角形的方法数;n个+1和n个-1满足所有部分和不小于零的排列数;具有n个节点的二叉树的数量……
(详细说明参考:http://hi.baidu.com/kikoqiu/blog/item/81d792015ab13e01738da51d.html)
posted @
2007-11-16 18:07 保尔任 阅读(1447) |
评论 (0) |
编辑 收藏
指令語法
crontab [ -u user ] file
crontab [ -u user ] { -l | -r | -e }
指令說明
crontab 提供我們在固定的間隔時間執行自訂的程式、系統指令或 shell secrip。時間間隔的單位可以是分鐘、小時、日、週、月及以上的任意組合。允許使用者離線執行,並且可以將執行結果以 email 通知使用者。因此,非常設合對週期性的管理分析或資料備份等工作。
基本上,crontab 的指令格式分為六個部分,前五個為時間間隔,最後則是執行的指令。每一個部分用空格來區隔。
分 -- 0-59
時 -- 0-23
日 -- 1-31
月 -- 1-12 或使用英文名稱
星期 -- 0-7 或使用英文名稱
工作命令 -- 指令,shell script,程式....(建議使用絕對路徑)
以上是 crontab 的基本格式。
選項說明
-u user
以指定的使用者身份,執行 crontab 工作。此選項僅供 root 使用。
-l
顯示使用者現行的 crontab 檔。
-r
移除現行的 crontab 檔。
-e
進入 vi 編輯 crontab 檔(如有設定 VISUAL 或 EDITOR 環境變數,怎使用該環境變數所設定的編輯器來編輯)。在使用者退出編輯器後,會自動將所編輯 crontab 檔,置入 crontab 執行。
相關檔案
/etc/cron.allow
/etc/cron.deny
實例說明
# crontab -l
# DO NOT EDIT THIS FILE - edit the master and reinstall.
# (/tmp/crontab.3672 installed on Thu Jan 1 15:55:18 2004)
# (Cron version -- $Id: crontab.c,v 2.13 1994/01/17 03:20:37 vixie Exp $)
0 0-23/6 * * * /usr/bin/webalizer
30 3 * * * /root/fbin/bak-web
#
先前曾提到,crontab 的格式分成六個部分,前五個是時間參數。在上例中你會發現除了數字與英文名稱,有使用到符號"*",這個符號代表每一單位的意思,譬如 30 3 * * * 既代表 30分 3點 每日 每月 星期的每天。
時間的指定,可以是單一的數字,或幾個數字用逗號來連接。看下例
30 3,12 * * * /root/fbin/bak-web
其中的第二項為 3,12,這代表 3 以及 12 小時的意思。再來看下例
30 */6 * * * /root/fbin/bak-web
我把第二項改成 */6 這代表每 6 小時,也相當於 6,12,18,24 的作用。此外還有一個區段的做法
30 8-18/2 * * * /root/fbin/bak-web
我把第二項改成 8-18/2 這代表在 8 小時到 18 小時之間每 2 小時,也相當於 8,10,12,14,16,18 的作用。
在認知的以上介紹各項時間用法後,你可以視實際的需要自行組合。使用上的彈性是相當自由的。這篇暫時到此。
posted @
2007-11-02 16:56 保尔任 阅读(646) |
评论 (0) |
编辑 收藏
(转自:http://blog.chinaunix.net/u/24474/showart_217098.html
)
diff和patch是一对工具,在数学上来说,diff是对两个集合的差运算,patch是对两个集合的和运算。
diff比较两个文件或文件集合的差异,并记录下来,生成一个diff文件,这也是我们常说的patch文件,即补丁文件。
patch能将diff文件运用于 原来的两个集合之一,从而得到另一个集合。举个例子来说文件A和文件B,经过diff之后生成了补丁文件C,那么着个过程相当于 A -B = C ,那么patch的过程就是B+C = A 或A-C =B。
因此我们只要能得到A, B, C三个文件中的任何两个,就能用diff和patch这对工具生成另外一个文件。
这就是diff和patch的妙处。下面分别介绍一下两个工具的用法:
1. diff的用法
diff后面可以接两个文件名或两个目录名。 如果是一个目录名加一个文件名,那么只作用在那么个目录下的同名文件。
如果是两个目录的话,作用于该目录下的所有文件,不递归。如果我们希望递归执行,需要使用-r参数。
命令diff A B > C ,一般A是原始文件,B是修改后的文件,C称为A的补丁文件。
不加任何参数生成的diff文件格式是一种简单的格式,这种格式只标出了不一样的行数和内容。我们需要一种更详细的格式,可以标识出不同之处的上下文环境,这样更有利于提高patch命令的识别能力。这个时候可以用-c开关。
2. patch的用法
patch用于根据原文件和补丁文件生成目标文件。还是拿上个例子来说
patch A C 就能得到B, 这一步叫做对A打上了B的名字为C的补丁。
之一步之后,你的文件A就变成了文件B。如果你打完补丁之后想恢复到A怎么办呢?
patch -R B C 就可以重新还原到A了。
所以不用担心会失去A的问题。
其实patch在具体使用的时候是不用指定原文件的,因为补丁文件中都已经记载了原文件的路径和名称。patch足够聪明可以认出来。但是有时候会有点小
问题。比如一般对两个目录diff的时候可能已经包含了原目录的名字,但是我们打补丁的时候会进入到目录中再使用patch,着个时候就需要你告诉
patch命令怎么处理补丁文件中的路径。可以利用-pn开关,告诉patch命令忽略的路径分隔符的个数。举例如下:
A文件在 DIR_A下,修改后的B文件在DIR_B下,一般DIR_A和DIR_B在同一级目录。我们为了对整个目录下的所有文件一次性diff,我们一般会到DIR_A和DIR_B的父目录下执行以下命令
diff -rc DIR_A DIR_B > C
这个时候补丁文件C中会记录了原始文件的路径为 DIR_A/A
现在另一个用户得到了A文件和C文件,其中A文件所在的目录也是DIR_A。 一般,他会比较喜欢在DIR_A目录下面进行patch操作,它会执行
patch < C
但是这个时候patch分析C文件中的记录,认为原始文件是./DIR_A/A,但实际上是./A,此时patch会找不到原始文件。为了避免这种情况我们可以使用-p1参数如下
patch -p1 < C
此时,patch会忽略掉第1个”/”之前的内容,认为原始文件是 ./A,这样就正确了。
使用patch
patch附带有一个很好的帮助,其中罗列了很多选项,但是99%的时间只要两个选项就能满足我们的需要:
patch -p1 < [patchfile]
patch -R < [patchfile] (used to undo a patch)
-p1选项代表patchfile中 文件名左边目录的层数,顶层目录在不同的机器上有所不同。要使用这个选项,就要把你的patch放在要被打补丁的目录下,然后在这个目录中运行path -p1 < [patchfile]。
posted @
2007-10-25 10:22 保尔任 阅读(1353) |
评论 (0) |
编辑 收藏
断言概述
编写代码时,我们总是会做出一些假设,断言就是用于在代码中捕捉这些假设
可以将断言看作是异常处理的一种高级形式
断言表示为一些布尔表达式,程序员相信在程序中的某个特定点该表达式值为真
可以在任何时候启用和禁用断言验证,因此可以在测试时启用断言而在部署时禁用断言。同样,程序投入运行后,最终用户在遇到问题时可以重新起用断言。
使用断言可以创建更稳定,品质更好且易于除错的代码
当需要在一个值为FALSE时中断当前操作的话,可以使用断言
单元测试必须使用断言(Junit/JunitX)
除了类型检查和单元测试外,断言还提供了一种确定个种特性是否在程序中得到维护的极好的方法
使用断言使我们向按契约式设计更近了一步
常见的断言特性
前置条件断言:代码执行之前必须具备的特性
后置条件断言:代码执行之后必须具备的特性
前后不变断言:代码执行前后不能变化的特性
断言使用方式
断言可以有两种形式
1.assert Expression1
2.assert Expression1:Expression2
其中Expression1应该总是一个布尔值,Expression2是断言失败时输出的失败消息的字符串。如果Expression1为假,则抛出一个 AssertionError,这是一个错误,而不是一个异常,也就是说是一个不可控制异常(unchecked Exception),AssertionError由于是错误,所以可以不捕获,但不推荐这样做,因为那样会使你的系统进入不稳定状态。
起用断言
断言在默认情况下是关闭的,要在编译时启用断言,需要使用source1.4标记既javac source1.4 Test.java ,在运行时启用断言需要使用 -ea参数。要在系统类中启用和禁用断言可以使用 -esa 和 -dsa参数。
例如:
public > public AssertExampleOne(){}
public static void main(String args[]){
int x=10;
System.out.println("Testing Assertion that x==100");
assert x=100;"Out assertion failed!";
System.out.println("Test passed!");
}
}
如果编译时未加 -source1.4,则编译通不过
在执行时未加 -ea 时输出为
Testing Assertion that x==100
Test passed
jre忽略了断言的就代码,而使用了该参数就会输出为
Testing Assertion that x==100
Exception in thread "main" java.lang.AssertionError: Out assertion failed!
at AssertExampleOne.main(AssertExampleOne.java:6)
断言的副作用
由于程序员的问题,断言的使用可能会带来副作用,例如:
boolean isEnable=false;
//...
assert isEnable=true;
这个断言的副作用是因为它修改了程序中变量的值并且未抛出错误,这样的错误如果不细心的检查是很难发现的。但是同时我们可以根据以上的副作用得到一个有用的特性,根据它来测试断言是否打开。
public >
public static void main(String args[]){
boolean isEnable=false;
//...
assert isEnable=true;
if(isEnable==false){
throw new RuntimeException("Assertion shoule be enable!");
}
}
}
何时需要使用断言
1.可以在预计正常情况下程序不会到达的地方放置断言:assert false
2.断言可以用于检查传递给私有方法的参数。(对于公有方法,因为是提供给外部的接口,所以必须在方法中有相应的参数检验才能保证代码的健壮性)
3.使用断言测试方法执行的前置条件和后置条件
4.使用断言检查类的不变状态,确保任何情况下,某个变量的状态必须满足。(如age属性应大于0小于某个合适值)
什么地方不要使用断言
断言语句不是永远会执行,可以屏蔽也可以启用
因此:
1.不要使用断言作为公共方法的参数检查,公共方法的参数永远都要执行
2.断言语句不可以有任何边界效应,不要使用断言语句去修改变量和改变方法的返回值
下边是介绍断言的用法:
assert是在J2SE1.4中引入的新特性,assertion就是在代码中包括的布尔型状态,程序员认为这个状态是true。一般来说assert在开发的时候是检查程序的安全性的,在发布的时候通常都不使用assert。在1.4中添加了assert关键字和java.lang.AssertError类的支持。
首先,我们有必要从一个例子说起assert
public > public static void main(String[] args) {
AssertTest at = new AssertTest();
at.assertMe(true);
at.assertMe(false);
}
private void assertMe(boolean boo) {
assert boo?true:false;
System.out.println("true condition");
}
}
程序中包含了assert的话,你要用javac -source 1.4 xxx.java来编译,否则编译器会报错的。要想让assert得部分运行的话,要使用java -ea xxx来运行,否则包含assert得行会被忽略。下面我们运行
javac -source 1.4 AssertTest.java
java -ea AssertTest
看看结果的输出是:
true condition
Exception in thread "main" java.lang.AssertionError
at AssertTest.assertMe(AssertTest.java:13)
at AssertTest.main(AssertTest.java:7)
当我们运行at.assertMe(true)得时候,由于assert boo?true:false相当于 assert true;因此没有任何问题,程序往下执行打印出true condition,但是执行at.assertMe(false)的时候相当于assert false,这个时候解释器就会抛出AssertionError了,程序就终止了。大家必须清楚AssertionError是继承自Error得,因此你可以不再程序中catch它的,当然你也可以在程序中catch它然后程序可以继续执行。例如:
public > public static void main(String[] args) {
AssertTest at = new AssertTest();
try {
at.assertMe(true);
at.assertMe(false);
} catch(AssertionError ae) {
System.out.println("AsseriontError catched");
}
System.out.println("go on");
}
private void assertMe(boolean boo) {
assert boo?true:false;
System.out.println("true condition");
}
}
assert还有另外一种表达的方式,就是assert exp1:exp2;其中exp1是个boolean返回值得表达式,而exp2可以是原始的数据类型或者对象都可以例如:
boolean boo = true;
String str = null;
assert boo = false:str="error";
我们刚开始讲得assert exp1得形式,当exp1是false得时候,AssertionError得默认构造器会被调用,但是assert exp1:exp2这样的形式,当exp1为true的时候后面exp2被或略,如果false的话,后面的表达式的结果会被计算出来并作为AssertionError得构造器参数。看下面的例子:
public > public static void main(String[] args) {
AssertTest at = new AssertTest();
at.assertMe(true);
at.assertMe(false);
}
private void assertMe(boolean boo) {
String s = null;
assert boo?true:false:s = "hello world";
System.out.println("true condition");
}
}
运行的时候会得到这样的结果:
true condition
Exception in thread "main" java.lang.AssertionError: hello world
at AssertTest.assertMe(AssertTest.java:14)
at AssertTest.main(AssertTest.java:7)
Assert最好不要滥用,原因是assert并不一定都是enable的,下面两种情况就不应该用assert
不要在public的方法里面检查参数是不是为null之类的操作,例如
public int get(String s) {
assert s != null;
}
如果需要检查也最好通过if s = null 抛出NullPointerException来检查
不要用assert来检查方法操作的返回值来判断方法操作的结果,例如
assert list.removeAll();
这样看起来好像没有问题但是想想如果assert 被disable呢,那样他就不会被执行了所以removeAll()操作就没有被执行可以这样代替
boolean boo = list.removeAl();
assert boo;
posted @
2007-10-12 13:16 保尔任 阅读(928) |
评论 (0) |
编辑 收藏
摘要: Python基础篇
整理:Jims of 肥肥世家
<jims.yang@gmail.com>
Copyright © 2004,2005,2006 本文遵从GNU 的自由文档许可证(Free Document License)的条款,欢迎转载、修改、散布。
发布时间:2004年07月10日
更新时间:20...
阅读全文
posted @
2007-09-02 16:18 保尔任 阅读(5065) |
评论 (0) |
编辑 收藏
/etc/profile:此文件为系统的每个用户设置环境信息,当用户第一次登录时,该文件被执行.并从/etc/profile.d目录的配置文件中搜集shell的设置.
/etc/bashrc:为每一个运行bash shell的用户执行此文件.当bash shell被打开时,该文件被读取.
~/.bash_profile:每个用户都可使用该文件输入专用于自己使用的shell信息,当用户登录时,该文件仅仅执行一次!默认情况下,他设置一些环境变量,执行用户的.bashrc文件.
~/.bashrc:该文件包含专用于你的bash shell的bash信息,当登录时以及每次打开新的shell时,该该文件被读取.
~/.bash_logout:当每次退出系统(退出bash shell)时,执行该文件.
另外,/etc/profile中设定的变量(全局)的可以作用于任何用户,而~/.bashrc等中设定的变量(局部)只能继承/etc/profile中的变量,他们是"父子"关系.
~/.bash_profile 是交互式、login 方式进入 bash 运行的
~/.bashrc 是交互式 non-login 方式进入 bash 运行的
通常二者设置大致相同,所以通常前者会调用后者。
posted @
2007-07-16 10:52 保尔任 阅读(389) |
评论 (0) |
编辑 收藏
一,安装jdk:
(这里的方法是用于ubuntu或debian的,把下载的jdk构建成deb包,我觉得是为了便于包管理,否则删除的时候都不知道删除哪些文件,很麻烦。)
1. 获取JDK
可以选择从Java官方下载: ::URL::http://java.sun.com 或者从其它网站下载.我用的版本是:jdk-1_5_0-linux-i586.bin
2. 构建打包环境
Debian专门提供了SDK 的DEB包构建工具: java-package,而Ubuntu是基于Debian的,所以
# apt-get install -u java-package fakeroot
在apt-get之前最好update一下
3. 创建.deb 软件包
这一步要以普通用户运行,如果以Root运行是不允许的.会有下面的提示:
You are real root -- unfortunately, some Java distributions have
install scripts that directly manipulate /etc, and may cause some
inconsistencies on your system. Instead, you should become a
non-root user and run:
fakeroot make-jpkg jdk-1_5_0-linux-i586.bin
which will allow no damage to be done to your system files and
still permit the Java distribution to successfully extract.
Aborting.
以普通用户执行:
$ fakeroot make-jpkg jdk-1_5_0_06-linux-i586.bin
接下来做一些必要的选择.几分钟后,就应当出现软件包创建成功的提示.你在当前目录下会发现类似:
sun-j2sdk1.5_1.5.0+update00_i386.deb的软件包
4. 安装
切换回root执行以下命令:
# dpkg -i sun-j2sdk1.5_1.5.0+update06_i386.deb
5.配置环境
在 ~/.bashrc脚本文件中加入类似如下内容
PATH=$PATH:/usr/lib/j2sdk1.5-sun/bin:/usr/lib/j2sdk1.5-sun/jre/bin
JAVA_HOME=/usr/lib/j2sdk1.5-sun
JRE_HOME=/usr/lib/j2sdk1.5-sun/jre
CLASSPATH=.:/usr/lib/j2sdk1.5-sun/lib/tools.jar:/usr/lib/j2sdk1.5-sun/lib/dt.jar
export PATH
export JRE_HOME
export JAVA_HOME
export CLASSPATH
6. 测试
创建一个简单的java程序(Hello.java)
public class Hello
{
public Hello()
{
}
public static void main(String[] args)
{
System.out.println("Hello World!";
}
}
然后
$javac Hello.java
检查当前目录会生成一个Hello.class的文件, 然后运行
$java Hello
Hello World!
OK,测试成功!
7. 中文化安装中文字体:
在 $JAVA_HOME/jre/lib/fonts/ 目录下创建一个fallback目录.
复制中文字体(例如:simsun.ttf 至此目录.
8. 安装插件
对于此种方法安装的Java环境, 浏览器插件文件位置应当位于:
/usr/lib/j2sdk1.5-sun/jre/plugin/i386/ns7/libjavaplugin_oji.so
以 firefox1.5.0.1为例:
# cd /usr/lib/mozilla-firefox/plugins
# ln -s \
/usr/lib/j2sdk1.5-sun/jre/plugin/i386/ns7/libjavaplugin_oji.so
卸载JDK:
# apt-get remove --purge sun-j2sdk1.5
卸载插件, 直接删除符号链接:
# rm /usr/lib/mozilla-firefox/plugins/libjavaplugin_oji.so
二,安装jython:
1,http://www.jython.org/Project/installation.html下载jython安装文件,运行命令“java -jar jython_installer-2.2rc2.jar”,jython即安装成功。比如安装在/home/justin/java/jython2.2目录下
2,把jython包加入classpath,即把上面的classpath改为:CLASSPATH=.:/usr/lib/j2sdk1.5-sun/lib/tools.jar:/usr/lib/j2sdk1.5-sun/lib/dt.jar:/home/justin/java/jython2.2/jython.jar
此后就可以在java文件中加入python库了,例如:
import org.python.util.PythonInterpreter;
import org.python.core.*;
public class SimpleEmbedded {
public static void main(String []args)
throws PyException
{
PythonInterpreter interp =
new PythonInterpreter();
System.out.println("Hello, brave new world");
interp.exec("import sys");
interp.exec("print sys");
interp.set("a", new PyInteger(42));
interp.exec("print a");
interp.exec("x = 2+2");
PyObject x = interp.get("x");
System.out.println("x: "+x);
System.out.println("Goodbye, cruel world");
}
}
3,将选择的/home/justin/java/jython2.2/jython安装路径添加到 PATH 环境变量。现在只要输入“jython”就可以运行交互式 PATH :
$ jython
Jython 2.1 on java1.4.0_01 (JIT: null)
Type "copyright", "credits" or "license" for more information.
>>># 通过 Jython 访问标准 Java 库
>>> from java.util import Random
>>> rng = Random()
>>> i = rng.nextBoolean()
>>> print i
jython 解释器对于快速检查和作提示都很方便,但您不必在这其中完成所有工作 ― Jython 还允许您在源文件中编写代码,并随后运行该代码(
from java.util import Random
rng = Random()
#This is a comment in Jython
print "Flipping a coin..."
if rng.nextBoolean():
print "Came up heads"
else:
print "Came up tails"
用jython运行该文件,即可
posted @
2007-07-13 15:42 保尔任 阅读(590) |
评论 (0) |
编辑 收藏
一,网络时间服务:
1. 与一个已知的时间服务器同步
公司配置:
#synchronize time with fw.exoweb.net
00 0 1 * * root rdate -s fw.exoweb.net
2. 配置网络时间协议(ntp)
1. 让linux自动同步时间
vi /etc/crontab
加上一句:
00 0 1 * * root rdate -s time.nist.gov
time.nist.gov 是一个时间服务器.
2. 时间服务器配置(192.168.10.1)
1). # rpm -ivh ntp-4.1.2-4.EL3.1.i386.rpm
2). # vi /etc/ntp.conf
注释一行
restrict default ignore
加入一行
restrict 192.168.10.0 mask 255.255.255.0 notrust nomodify notrap
3). # vi /etc/ntp/step-tickers
加入一行
pool.ntp.org
这样每次ntpd启动时,会自动连接该国际标准时间服务器;
4). # service ntpd start
5). # netstat -an |grep 123
确保该端口以udp方式开放
时间客户端配置(192.168.10.2)
1). # ntpdate 192.168.10.2
应该显示同步成功
2). # crond -e
加入
0-59/10 * * * * /usr/sbin/ntpdate 192.168.10.1
表示每隔10分钟同步一次时间
二, 出现 must be setuid root 错误
解决办法:
ls -l /usr/bin/sudo
chown root:root /usr/bin/sudo
chmod 4755 /usr/bin/sudo
reboot
三,用nohup命令让Linux下程序永远在后台执行
Unix/Linux下一般想让某个程序在后台运行,很多都是使用 & 在程序结尾来让程序自动运行。比如我们要运行mysql在后台:
/usr/local/mysql/bin/mysqld_safe --user=mysql &
但是我们很多程序并不象mysqld一样可以做成守护进程,可能我们的程序只是普通程序而已,一般这种程序即使使用 &
结尾,如果终端关闭,那么程序也会被关闭。为了能够后台运行,我们需要使用nohup这个命令,比如我们有个start.sh需要在后台运行,并且希望在
后台能够一直运行,那么就使用nohup:
nohup /root/start.sh &
四, python反编译工具
decompyle
五,rpm包转deb包工具: fakeroot and alien
fakeroot alien -d *.rpm
六, 保存ftest信息并查看
nohup ./nordicbetsite ftest -v2 >ftest_result 2>&1 &
tail -f ftest_result
七, ip信息
ifconfig
八, dpkg命令
查看python2.5是否安装: dpkg -l python2.5
查看名称含有python的所有软件: dpkg -l | grep python
查看python2.5软件包的位置: dpkg -L python2.5
九, 分区情况
查看所有分区情况: df -h
查看某个软件在哪个分区: df -h ***
posted @
2007-05-08 16:47 保尔任 阅读(599) |
评论 (0) |
编辑 收藏
命令行下载工具 ,转自:http://blog.chinaunix.net/u/9465/showart.php?id=186155,方便在虚拟机上开发,不用再从外面拷贝到虚拟机上了。
对于喜欢命令行操作及追求高效率、高速度下载的朋友,推荐使用命令行下载工具。命令行工具不但使用方便,而且大多具有很高的下载速度及下载效率,尤其适合
于大批量下载文件。下面就为大家详细介绍一下这些工具。
Wget
Wget是一个十分常用命令行下载工具,多数Linux发行版本都默认包含这个工具。如果没有安装可在http://www.gnu.org/software/wget/wget.html下
载最新版本,并使用如下命令编译安装:
#tar zxvf wget-1.9.1.tar.gz
#cd wget-1.9.1
#./configure
#make #make install
它的用法很简单,Wget使用格式如下: #wget [选项] [下载地址]
1.Wget常用参数 ◆-b:后台下载,Wget默认的是把文件下载到当前目录。 ◆-O:将文件下载到指定的目录中。
◆-P:保存文件之前先创建指定名称的目录。 ◆-t:尝试连接次数,当Wget无法与服务器建立连接时,尝试连接多少次。
◆-c:断点续传,如果下载中断,那么连接恢复时会从上次断点开始下载。
除了上述常用功能,Wget还支持HTTP和FTP代理功能,编辑其配置文件“/etc/wgetrc”即可。具体方法是使用VI编辑器打开上述文件,将
“http_proxy”和“ftp_proxoy”前的#去掉,然后在这两项后输入相应的代理服务器的地址,保存退出即可。此外,Wget还可下载整个
网站,如下载http://man.chinaunix.net整个Man手册中心。只需输入如下命令即可: #wget -r -p -np -k http://man.chinaunix.net
其中-r参数是指使用递归下载,-p是指下载所有显示完整网页所以需要的文件,如图片等,-np是指不搜索上层目录,-k则是指将绝对链接转换为相对链
接。
Prozilla Prozilla也是一个十分流行的命令行下载工具,支持多线程下载和断点续传功能。可到http://prozilla.genesys.ro/下
载最新的1.3.7.4安装包,下载安装包后使用如下命令进行安装:
#tar zxvf prozilla-1.3.7.4.tar.gz
#cd
prozilla-1.3.7.4
#./configure #make
#make install
Prozilla命令格式如下: #proz
[参数] [下载地址] 常用的选项有: ◆-k=n :设置n个线程下载。不加此参数指定线程数,Prozilla默认为4线程下载。 ◆-P,
--directory-prefix=DIR:指定将下载的文件保存在DIR/目录。 ◆-r,
--resume:继续下载未完成的文件。如果要指定线程数下载可用如下命令: #proz -k=5 http://64.12.204.21/pub/mozilla.org/firefox/releases/1.0/linux-i686/zh-CN/firefox-1.0.installer.tar.gz
这样便以5线程进行文件的下载,并将文件保存到当前目录。和Wget一样,Prozilla也提供了续传功能,下载中断后,重新输入上述命令,就会出现提
示续传,按R键就可继续下载了。
MyGet
MyGet目标设计成一个可扩展的,拥有丰富界面的多线程下载工具,它支持HTTP、FTP、HTTPS、MMS、RTSP等协议。在http://myget.sourceforge.net/release/myget-0.1.0.tar.bz2下
载其最新版本0.1.0,下载后使用如下命令安装:
#tar jxvf myget-0.1.0.tar.bz2
#cd myget-0.1.0
#./configure
#make
#make install
MyGet命令格式如下: #mytget [选项] [下载地址]
常用的选项: ◆-d [目录]:指定下载到的文件在本地存放的位置,默认当前目录。 ◆-f [文件]:指定下载文件名称。 ◆-h:帮助选项。
◆-n [线程数]:下载线程数量,默认为4个。 ◆-x [代理服务器地址]:设置代理服务器地址,如“-x http://user:password@host:port”。 MyGet常用的形式如下: #mytget -d /root/ -n 10 http://lumaqq.linuxsir.org/download/patch/lumaqq_2004t_patch_2005.07.21.00.00.zip
Linuxdown Linuxdown是一个命令行多线程下载工具,最多可支持30线程的下载。在https://gro.clinux.org/frs/download.php/1015/linuxdown-1.0.0.tar.gz下
载最新的1.1.0版本。然后使用如下命令进行编译安装:
#tar zxvf linuxdown-1.1.0.tar.gz
#cd
dandelion/
#make
#make install
linuxdown格式为: #linuxdown [下载地址] [选项]
[线程数] 需要注意的是下载地址和选项都需要西文引号括起来,线程数不可超过30个。一个典型的下载如下: #linuxdown
"http://lumaqq.linuxsir.org/download/patch/lumaqq_2004t_patch_2005.07.21.00.00.zip"
30
Curl Curl也是Linux下不错的命令行下载工具,小巧、高速,唯一的缺点是不支持多线程下载。在http://curl.haxx.se/download/curl-7.14.0.tar.gz下
载最新版本。下载后便可使用如下命令编译安装: #tar zxvf curl-7.14.0.tar.gz
#cd curl-7.14.0/
#./configure
#make
#make test
#make install
Curl使用格式如下: #curl
[选项][下载地址] Curl典型下载如下: #curl -O http://10.1.27.10/~kennycx/tools/lumaqq_2004-linux_gtk2_x86_with_jre.tar.gz 使用Curl下载一个文件并保存到当前目录。此外,Curl虽然不支持多线程下载,但它可同时下载多个文件或下载文件的某一部分,可使用如下命令实现: #curl -r 0-199 http://www.netscape.com/ 获得文件的前200 bytes。 对于常用的代理下载Curl也可轻松实现,具体操作如下: #curl -x 10.1.27.10:1022 ftp://ftp.funet.fi/README 使用代理地址为10.1.27.10端口为1022的代理服务器下载一个文件。 #curl -U user:passwd -x 10.1.27.10:1022 ftp://ftp.funet.fi/README 如果代理服务器需要特别的验证,则需要在user:passwd处输入合法的帐号和密码。
Axel Axel是命令行下的多线程下载工具,支持断点续传,速度通常情况下是Wget的几倍。可在http://www.linuxfans.org/nuke/modules.php?name=Site_Downloads&op=mydown&did=1697下
载。下载后使用如下命令编译安装:
#tar zxvf axel-1.0a.tar.gz
#cd axel-1.0a/
#./configure
#make
#make install
基本的用法如下: #axel [选项] [下载目录] [下载地址] 一个典型下载如下: #alex
-n 10 -o /home/kennycx/ http://10.1.27.10/~kennycx/tools/lumaqq_2004-linux_gtk2_x86_with_jre.tar.gz 用10线程将指定路径的文件下载到/home/kennycx/这个目录下。 本文详细介绍了Linux中常用的下载工具,这些下载工具功能上各有千秋,使用上都比较简单,所以无论是初学者还是Linux高手总有一款适合你。
posted @
2007-04-25 10:03 保尔任 阅读(399) |
评论 (0) |
编辑 收藏
Hashtable和HashMap类有三个重要的不同之处。第一个不同主要是历史原因。Hashtable是基于陈旧的Dictionary类的,HashMap是Java 1.2引进的Map接口的一个实现。
也许最重要的不同是Hashtable的方法是同步的,而HashMap的方法不是。这就意味着,虽然你可以不用采取任何特殊的行为就可以在一个多线程的应用程序中用一个Hashtable,但你必须同样地为一个HashMap提供外同步。一个方便的方法就是利用Collections类的静态的synchronizedMap()方法,它创建一个线程安全的Map对象,并把它作为一个封装的对象来返回。这个对象的方法可以让你同步访问潜在的HashMap。这么做的结果就是当你不需要同步时,你不能切断Hashtable中的同步(比如在一个单线程的应用程序中),而且同步增加了很多处理费用。
第三点不同是,只有HashMap可以让你将空值作为一个表的条目的key或value。HashMap中只有一条记录可以是一个空的key,但任意数量的条目可以是空的value。这就是说,如果在表中没有发现搜索键,或者如果发现了搜索键,但它是一个空的值,那么get()将返回null。如果有必要,用containKey()方法来区别这两种情况。
一些资料建议,当需要同步时,用Hashtable,反之用HashMap。但是,因为在需要时,HashMap可以被同步,HashMap的功能比Hashtable的功能更多,而且它不是基于一个陈旧的类的,所以有人认为,在各种情况下,HashMap都优先于Hashtable。
关于Properties 有时侯,你可能想用一个hashtable来映射key的字符串到value的字符串。DOS、Windows和Unix中的环境字符串就有一些例子,如key的字符串PATH被映射到value的字符串C:\WINDOWS;C:\WINDOWS\SYSTEM。Hashtables是表示这些的一个简单的方法,但Java提供了另外一种方法。
Java.util.Properties类是Hashtable的一个子类,设计用于String keys和values。Properties对象的用法同Hashtable的用法相象,但是类增加了两个节省时间的方法,你应该知道。
Store()方法把一个Properties对象的内容以一种可读的形式保存到一个文件中。Load()方法正好相反,用来读取文件,并设定Properties对象来包含keys和values。
注意,因为Properties扩展了Hashtable,你可以用超类的put()方法来添加不是String对象的keys和values。这是不可取的。另外,如果你将store()用于一个不包含String对象的Properties对象,store()将失败。作为put()和get()的替代,你应该用setProperty()和getProperty(),它们用String参数。
好了,我希望你现在可以知道如何用hashtables来加速你的处理了。
下面再转一篇关于两个类的区别,比较简单的过一下
最近同学找工作,经常被问到这个问题rt,所以。。。。。。
HashTable的应用非常广泛,HashMap是新框架中用来代替HashTable的类,也就是说建议使用HashMap,不要使用HashTable。
这里简单分析他们的区别。 1.HashTable的方法是同步的,HashMap未经同步,所以在多线程场合要手动同步HashMap这个区别就像Vector和ArrayList一样。(最主要的区别)
2.HashTable不允许null值(key和value都不可以),HashMap允许null值(key和value都可以,只容许有一个null值的key,可以有多个null值的value)。
3.HashTable有一个contains(Object value),功能和containsValue(Object value)功能一样。
4.HashTable使用Enumeration,HashMap使用Iterator。
以上只是表面的不同,它们的实现也有很大的不同。
5.HashTable中hash数组默认大小是11,增加的方式是 old*2+1。HashMap中hash数组的默认大小是16,而且一定是2的指数。
6.哈希值的使用不同,HashTable直接使用对象的hashCode,代码是这样的: int hash = key.hashCode(); int index = (hash & 0x7FFFFFFF) % tab.length; 而HashMap重新计算hash值,而且用与代替求模: int hash = hash(k); int i = indexFor(hash, table.length);
static int hash(Object x) { int h = x.hashCode();
h += ~(h << 9); h ^= (h >>> 14); h += (h << 4); h ^= (h >>> 10); return h; } static int indexFor(int h, int length) { return h & (length-1); } 以上只是一些比较突出的区别,当然他们的实现上还是有很多不同的,比如 HashMap对null的操作。
|
|
posted @
2007-03-29 20:32 保尔任 阅读(368) |
评论 (0) |
编辑 收藏
java.math.Math类常用的常量和方法:
Math.PI 记录的圆周率
Math.E记录e的常量
Math.abs 求绝对值
Math.sin 正弦函数 Math.asin 反正弦函数
Math.cos 余弦函数 Math.acos 反余弦函数
Math.tan 正切函数 Math.atan 反正切函数 Math.atan2 商的反正切函数
Math.toDegrees 弧度转化为角度 Math.toRadians 角度转化为弧度
Math.ceil 得到不小于某数的最大整数
Math.floor 得到不大于某数的最大整数
Math.IEEEremainder 求余
Math.max 求两数中最大
Math.min 求两数中最小
Math.sqrt 求开方
Math.pow 求某数的任意次方, 抛出ArithmeticException处理溢出异常
Math.exp 求e的任意次方
Math.log10 以10为底的对数
Math.log 自然对数
Math.rint 求距离某数最近的整数(可能比某数大,也可能比它小)
Math.round 同上,返回int型或者long型(上一个函数返回double型)
Math.random 返回0,1之间的一个随机数
java.math.BigInteger(大整数):
BigInteger bi1=new BigInteger("1234567890123456890");
BigInteger bi2=BigInteger.valueOf(123L);
bi1=bi1.add(bi2);//b1+b2
bi1=bi1.multiply(bi2);//b1*b2
bi1=bi1.subtract(bi2);//b1-b2
bi1=bi1.divide(bi2);// b1/b2
java.math.BigDecimal(大浮点数):
BigDecimal bd = new BigDecimal("3.1415926");
bd = bd.setScale(2,BigDecimal.ROUND_DOWN);//取3.1415926小数点后面二位
posted @
2007-03-16 15:54 保尔任 阅读(4467) |
评论 (1) |
编辑 收藏
1、classpath不用再定义.;***\lib\tools.jar;***\lib\rt.jar,因为jre会自动寻找lib目录
2、如果想要用jdk5.0编译出jdk1.4可运行的class文件需要带-source和-target两个参数
eg: javac -source 1.4 -target 1.4 Hello.java
3、命令行读入int i = System.in.read();//读入输入字符串的第一个字符的int值;
读整个字符串时:
public class Test{
public static void main(String[] args){
byte[] a = new byte[100];
try {
System.in.read(a);
} catch (IOException e) {
e.printStackTrace();
}
System.out.println(new String(a));
}
}
jdk5.0中命令行读入的方法更好,可以读成不同类型的数据:
//Scanner取得输入的依据是:空格键、Tab键或Enter键
import java.util.Scanner;
public class ScannerDemo{
public static void main(String[] args){
Scanner scanner = new Scanner(System.in);
System.out.print("请输入姓名");
System.out.printf("您好%s!\n", scanner.next());
System.out.print("请输入年龄");
System.out.printf("您好%d!\n", scanner.nextInt());
//还有scanner.nextFloat(),scanner.nextBoolean();
}
}
//BufferReader取得输入的依据是:Enter键
import java.io.*;
public class BufferReaderDemo{
public static void main(String[] args){
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in));
System.out.print("请输入一系列文字");
String text = bufferedReader.readLine();
System.out.print("您输入的是:" + text);
}
}
}
4、aotuboxing和unboxing,jdk5.0可以自动对基本类型和它们的包装类型自动转换。
5、数组
数组的索引值:由0开始的原因:索引值表示偏移量,第一个值的偏移为0.
数组的初始化:byte/short/int = 0; long = ol; float = o.0f; double = 0.0d; char = \u0000; boolean = false; Objective = null;
一维数组:
法一:int[] i = {1,2,3};
法二:int[] i = new int[]{1,2,3};
法三:int[] i = new int[3]; i[0] = 1; i[1] = 2; i[2] = 3;
多维数组:
法一:int[][] i = {{...},...,{...}};
法二:int[][] i = int[][]{{...},...,{...}};
法三:int[][] i = int[3][]; i[0] = {1,2,3}; i[0] = {1,2,3}; i[0] = {1,2,3};
法四:int[][] i = int[3][3];
不规则数组:行列不等
数组的常用方法:都是java.util.Arrays类的方法
sort()//制定数组快速排序
binarySearch()//对已排序的数组搜索,找到返回索引,否则返回负值
fill()//根据数组的数据类型填默认值
equals()//比较两数组
jdk1.5中新增:
deepEquals()//深度比较
deepToString()//深度输出
foreach与数组:
String[] a = {"asd","efge","efg"};
for(String s : a)
System.out.println(s);
5、字符串
java.lang.StringBuilder是jdk5.0新增的类,它与StringBuffer具有相同接口,只是单机非多线程情况下用StringBuilder效率较高,因为StringBuilder没处理同步问题;多线程下用StringBuffer好。
字符串分离:
String s = "23/twomen/tlai/t jeje";
String[] a = s.split("/t");
for(int i = 0; i < a.length; i++){
System.out.print(a[i] + " ");
}
输出结构:23 women lai jeje
由于工作关系学习jdk5.0的步伐暂时停止,以后有机会继续看《jdk5.0学习笔记》,回来写我的总结。
posted @
2007-03-08 15:06 保尔任 阅读(449) |
评论 (0) |
编辑 收藏
/**/
/*
* 题目:
* 编写一个截取字符串的函数,输入为一个字符串和字节数,输出为按字节截取的字符串。 但是要保证汉字不被截半个,如“我ABC”4,应该截为“我AB”,输入“我ABC汉DEF”,6,应该输出为“我ABC”而不是“我ABC+汉的半个”。
*
* 解释:
* 此处的编码方式应该是操作系统默认的GB编码,即汉字占2个字节且第一个字节的最高位是1,
* 如果理解为有符号数的话,就是负数;而英文占1个字节,符合ASC2码。
*/
class
SplitString
{
private
String str;
private
int
byteNum;
public
SplitString()
{}
public
SplitString(String str,
int
byteNum)
{
this
.str
=
str;
this
.byteNum
=
byteNum;
}
public
void
splitIt()
{
byte
bt[]
=
str.getBytes();
System.out.println(
"
Length of this String ===>
"
+
bt.length);
if
(byteNum
>=
1
)
{
if
(bt[byteNum]
<
0
)
{
String substrx
=
new
String(bt,
0
,
--
byteNum);
System.out.println(substrx);
}
else
{
String substrex
=
new
String(bt,
0
,byteNum);
System.out.println(substrex);
}
}
else
{
System.out.println(
"
输入错误!!!请输入大于零的整数:
"
);
}
}
}
public
class
TestSplitString
{
public
static
void
main(String args[])
{
String str
=
"
我ABC汉DEF
"
;
int
num
=
6
;
SplitString sptstr
=
new
SplitString(str,num);
sptstr.splitIt();
}
}
posted @
2007-03-06 17:17 保尔任 阅读(1689) |
评论 (1) |
编辑 收藏
/**/
/*
求两个字符串的最大公共子串
String s1 = "abcdefghigj";
String s2 = "xyzabcdeigj";
则输出abcde
*/
public
class
Test
{
public
String search(String s1,String s2)
{
String max
=
""
;
for
(
int
i
=
0
; i
<
s1.length(); i
++
)
{
for
(
int
j
=
i
+
1
; j
<=
s1.length(); j
++
)
{
String sub
=
s1.substring(i,j);
if
((s2.indexOf(sub)
!=
-
1
)
&&
sub.length()
>
max.length())
{
max
=
sub;
}
}
}
return
max;
}
public
static
void
main(String[] args)
{
String s1
=
"
abedafghigj
"
;
String s2
=
"
xyzabfddfigj
"
;
String output
=
new
Test().search(s1,s2);
System.out.println(output);
}
}
posted @
2007-03-05 15:50 保尔任 阅读(900) |
评论 (0) |
编辑 收藏
1 术语定义
在字符串匹配问题中,我们期待察看串T中是否含有串P。
其中串T被称为目标串,串S被称为模式串。
2 朴素匹配算法
进行字符串匹配,最简单的一个想法是:
public
class
SimpleMatch
{
public
int
StringMatch(String target,String patten)
{
int
tl
=
target.length();
int
pl
=
patten.length();
int
i
=
0
;
int
j
=
0
;
while
(i
<
tl
-
pl
&&
j
<
pl)
{
if
(patten.charAt(j)
==
target.charAt(i
+
j))
j
++
;
else
{
j
=
0
;
i
++
;
}
}
if
(j
==
pl)
return
i;
return
-
1
;
}
public
static
void
main(String[] args)
{
String t
=
"
123456789
"
;
String p
=
"
456
"
;
SimpleMatch sm
=
new
SimpleMatch();
System.out.println(sm.StringMatch(t, p));
}
}
可以看见,这个算法(假定m>>n)的复杂度是O(mn),其中m是T的长度,n是P的长度。这种算法的缺陷是匹配过程中带有回溯——准确地说是T串存在回溯,也就是当匹配不成功的时候,之前进行的匹配完全变为无用功,所有的比较需要重新开始。
3 KMP算法
KMP算法是D.E.Knuth、J.H.Morris和V.R.Pratt提出的无回溯的字符串匹配算法,算法的核心思想就是设法在匹配失败的时候,尽量利用之前的匹配结果,消除T串的回溯问题。那么如何消除回溯呢?请看下面的例子:
假设P=abacd,如果T=abax...,当从头开始匹配到字符c时,若c=x,显然,匹配过程继续;当c≠x时,按照朴素的匹配算法,T串会发生回溯,之后T串会从第2个字符b开始重新匹配,而不是从匹配失败的字符x开始继续。但是显然,对于上述的匹配过程,T串不需要从b开始重新匹配,它只需要从x开始和P的b字符继续匹配即可。如下:
匹配过程:
P=abacd
T=abax....
^----比较到此处时发生匹配失败
朴素匹配算法:
P= abacd
T=abax...
^----回溯到b,重新开始和P的匹配
KMP算法:
P= abacd
T=abax...
^----T串不回溯,从x处继续匹配
现在的问题是,按照KMP算法,匹配失败的时候,P串需要重新调整位置,但是调整的依据是什么?Knuth等人发现,P调整位置的依据和P的构造有关,和T无关。具体来说,定义失效函数:f(j)=k,其中0<=k<=j,且k是使得p0p1...pk-1 = pj-k+1pj-k+2...pj成立的最大整数。建立失效函数的算法如下:
public void Build() {
if(pattern == null)
throw new Exception("KMP Exception : null pattern");
array = new int[pattern.Length];
int i = 0, s = pattern.Length;
if(s > 1)
array[0] = 0;
for(i = 1; i < s; i++) {
if(pattern[i] == pattern[array[i - 1]])
array[i] = array[i - 1] + 1;
else
array[i] = 0;
}
}
匹配过程如下:
public int Match(String target, int start) {
if(array == null || pattern == null || target == null)
return -1;
int target_index = start;
int pattern_index = 0;
int token_length = target.Length;
int pattern_length = pattern.Length;
while(target_index < token_length && pattern_index < pattern_length) {
if(target[target_index] == pattern[pattern_index]) {
target_index++;
pattern_index++;
} else {
if(pattern_index == begin)
target_index++;
else
pattern_index = array[pattern_index - 1];
}
}
if(pattern_index == pattern_length)
return target_index - pattern_length;
return -1;
}
4 支持通配符?和*的KMP算法
KMP算法虽然能够进行字符串匹配,但是,在实践中字符串匹配往往还要支持通配符,MS系统中最常见的通配符是?和*。其中,?可以代表一个字符(不能没有),*可以代表任意多个字符(可以为空)。经典的KMP算法针对通配符是无能为力的,但是经过简单的改造,KMP算法也可以识别通配符。
首先是?,根据?的功能,?表示任意字符,也就是说在匹配过程中,?永远匹配成功。因此对匹配函数的修改十分简单:
...
while(target_index < token_length && pattern_index < pattern_length) {
if(target[target_index] == pattern[pattern_index]|| pattern[pattern_index] == '?') {
target_index++;
pattern_index++;
} else {
...
建立失效函数的过程和匹配过程类似,修改如下:
...
for(i = 1; i < s; i++) {
if(pattern[i] == pattern[array[i - 1]]|| pattern[i] == '?' || pattern[array[i - 1]] == '?')
array[i] = array[i - 1] + 1;
...
本质上,?并没有修改算法,而仅仅修改了匹配规则——遇到?则一定匹配。然而*与此不同,*的作用是匹配任意多个字符,显然我们不能简单的修改匹配过程而满足要求。如果我们重新思考*的作用,我们会发现*的另一个作用就是分割P串,即如果P=P1*P2,那么与其说*代表匹配任意多个字符,不如说P的匹配条件是在匹配P1子串后再匹配P2子串。
现在回顾失效函数的作用,如果当匹配到P的j+1位时匹配失败,那么重新开始匹配的时候,P串的位置调整到f(j)位,直到P串的位置调整到0,则匹配重新开始。但当P=P1*P2,假如P1已经匹配成功,而在P2中发生匹配失败,那么P串要需要调整位置,但P串无论如何调整,此时也不应该调整到0,最多调整到P2的开始处,因为P1已经匹配,只需匹配P2即可。假如P=abcab*abcab,失效函数应该是(注意之前提到*的作用):
a b c a b * a b c a b
0 0 0 1 2 - 6 6 6 7 8
因此,要想让KMP支持*,那么关键是要重新设计失效函数的建立算法,如下:
public void Build() {
if(pattern == null)
throw new Exception("KMP Exception : null pattern");
array = new int[pattern.Length];
int i = 0, s = pattern.Length;
if(s > 1)
array[0] = 0;
int begin = 0;
for(i = 1; i < s; i++) {
if(pattern[i] == '*') {
array[i] = i;
begin = i + 1;
} else if(pattern[i] == pattern[array[i - 1]] || pattern[i] == '?' || pattern[array[i - 1]] == '?')
array[i] = array[i - 1] + 1;
else
array[i] = begin;
}
}
算法中begin表示每段字符串的开始位置。此外,匹配过程也应该进行相应的修改,因为字符*对于匹配没有任何帮助,它属于占位符,因此需要跳过,匹配算法如下:
public int Match(String target, int start) {
if(array == null || pattern == null || target == null)
return -1;
int target_index = start;
int pattern_index = 0;
int token_length = target.Length;
int pattern_length = pattern.Length;
int begin = 0;
while(target_index < token_length && pattern_index < pattern_length) {
if(pattern[pattern_index] == '*') {
begin = pattern_index + 1;
pattern_index++;
} else if(target[target_index] == pattern[pattern_index] || pattern[pattern_index] == '?') {
target_index++;
pattern_index++;
} else {
if(pattern_index == begin)
target_index++;
else
pattern_index = array[pattern_index - 1];
}
}
if(pattern_index == pattern_length)
return target_index - pattern_length + begin;
return -1;
}
5 正则语言和确定状态自动机
一个数字逻辑的问题:设计一个识别11011的电路,解这个问题的关键就是设计出这个电路的DFA,如下:
仔细看看这个状态机,是不是和KMP的算法有几分类似呢?这并不是巧合,因为KMP算法中的失效函数总可以等价的转化为一个DFA。当然KMP的DFA远比识别11011的DFA要复杂,原因在于KMP接受的输入是全体字符集合,识别11011的DFA只接受0和1这两个输入。我们知道,一个正则语言和一个DFA是等价的,而KMP计算失效函数的算法,实际上等价于求DFA的过程,f(j)的值实际上表明状态j+1接受到不正确的字符时应该回溯到的状态(注意此时输入流并没有前进)。普通的字符串都能看成是一个正则语言,含有通配符?和*的字符串也可以等价的转换为一个正则表达式。但是,正则语言的集合远比KMP算法所能支持的模式集合的更大,期间原因还是刚才提过的输入问题。试想P=p1p2...pn,当匹配到pj的时候,如果下一个输入字符正是pj,那么状态机进入下一个状态,如果不是pj,那么状态机按照实效函数的指示转移到状态f(j-1),也就是说KMP状态机的每个状态只能根据输入是否为pj来进行转移。而正则表达式所对应的状态机则有所不同,如果正则语言L=l1l2...ln,假设这些都是字母,当匹配到lj位的时候,如果下一个输入字符正是lj,那么状态机进入下一个状态,否则它还可以根据输入的值进行转移,例如lj=c1时转换到状态x,lj=c2时状态转换到y等等。
6 结语
字符串匹配问题是老问题了,并没有太多新意可言,只不过虽然KMP算法十分简单,但它的内在含义还是十分深刻的。横向比较KMP、DFA和正则语言、正则表达式我们会发现,它们之间存在很多的关联,而这种比较也有利于我们更好的理解这些算法,或者改进这些算法。最后说一句,试图利用目前的框架使得KMP算法支持全部种类的通配符(对应于正则表达式就是x?、x*、x+、{m,n}等等)是不可能,而我们也不需要这么做,因为我们还有正则表达式嘛。
posted @
2007-03-05 15:29 保尔任 阅读(5700) |
评论 (2) |
编辑 收藏
/**/
/*
* 整形数组平衡点问题:平衡点指左边的整数和等于右边的整数和,
* 求出平衡点位置,要求输入的数组可能是GB级
*
* 本题要求找出整型数组的一个平衡点(如果要找出所有平衡点的话,按此方法需要把每一个平衡点都存起来)
*/
public
class
Test
{
public
int
findBalanceableNod(
int
[] a)
{
if
(a
==
null
)
{
return
-
1
;
}
long
sum
=
0l
;
long
subSum
=
0l
;
for
(
int
i
=
0
; i
<
a.length; i
++
)
{
sum
+=
a[i];
}
for
(
int
i
=
0
; i
<
a.length; i
++
)
{
if
(subSum
==
sum
-
subSum
-
a[i])
{
return
i;
}
else
{
subSum
+=
a[i];
}
}
return
-
1
;
}
public
static
void
main(String[] args)
{
//
测试用例:平衡点为0位,为n-1位,为中间位,a的每个为存了Integer.MAX_VALUE(所以用sum,subSum用long型)
int
[] a
=
{
-
1
}
;
Test t
=
new
Test();
System.out.println(t.findBalanceableNod(a));
}
}
posted @
2007-03-05 10:40 保尔任 阅读(1148) |
评论 (0) |
编辑 收藏
/**/
/*
* 原题如下:用1、2、2、3、4、6这六个数字,用java写一个main函数,打印出所有不同的排列,
* 如:612234、412346等,要求:"4"不能在第三位,"3"与"6"不能相连.
*
* 1 把问题归结为图结构的遍历问题。实际上6个数字就是六个结点,把六个结点连接成无向连通图,对于每一个结点求这个图形的遍历路径,
* 所有结点的遍历路径就是最后对这6个数字的排列组合结果集。
* 2 显然这个结果集还未达到题目的要求。从以下几个方面考虑:
* 1. 3,6不能相连:实际要求这个连通图的结点3,5之间不能连通, 可在构造图结构时就满足改条件,然后再遍历图。
* 2. 不能有重复: 考虑到有两个2,明显会存在重复结果,可以把结果集放在TreeSet中过滤重复结果
* 3. 4不能在第三位: 仍旧在结果集中去除满足此条件的结果。
*/
import
java.util.Iterator;
import
java.util.TreeSet;
public
class
Test
{
private
String[] b
=
new
String[]
{
"
1
"
,
"
2
"
,
"
2
"
,
"
3
"
,
"
4
"
,
"
6
"
}
;
private
int
n
=
b.length;
private
boolean
[] visited
=
new
boolean
[n];
private
int
[][] a
=
new
int
[n][n];
private
String result
=
""
;
private
TreeSet set
=
new
TreeSet();
public
static
void
main(String[] args)
{
new
Test().start();
}
private
void
start()
{
//
Initial the map a[][]
for
(
int
i
=
0
; i
<
n; i
++
)
{
for
(
int
j
=
0
; j
<
n; j
++
)
{
if
(i
==
j)
{
a[i][j]
=
0
;
}
else
{
a[i][j]
=
1
;
}
}
}
//
3 and 5 can not be the neighbor.
a[
3
][
5
]
=
0
;
a[
5
][
3
]
=
0
;
//
Begin to depth search.
for
(
int
i
=
0
; i
<
n; i
++
)
{
this
.depthFirstSearch(i);
}
//
Print result treeset.
Iterator it
=
set.iterator();
while
(it.hasNext())
{
String string
=
(String) it.next();
System.out.println(string);
}
}
private
void
depthFirstSearch(
int
startIndex)
{
visited[startIndex]
=
true
;
result
=
result
+
b[startIndex];
if
(result.length()
==
n)
{
//
"4" can not be the third position.
if
(result.indexOf(
"
4
"
)
!=
2
)
{
//
Filt the duplicate value.
set.add(result);
}
}
for
(
int
j
=
0
; j
<
n; j
++
)
{
if
(a[startIndex][j]
==
1
&&
visited[j]
==
false
)
{
depthFirstSearch(j);
}
}
//
restore the result value and visited value after listing a node.
result
=
result.substring(
0
, result.length()
-
1
);
visited[startIndex]
=
false
;
}
}
只要这样定义图,根本不用在代码中写IF ELSE语句。
实际上基于图的算法好处在于,只要你能定义好满足题目要求的图结构,遍历的结果就是你要的结果,不用任何对遍历结果做任何处理。包括本题中的:4不能在第三位置,3,5不能相连,唯一性要求,其实都可以在体现在构造的图形结构里,然后直接遍历图取得自己要的结果。而不用再次处理结果集。只是说这里实际上对其它要求要体现在图结构里有困难(理论上是可以的),但起码3,5不能相接是很好构造的,就是上面的代码段来解释的。
关于图形数据结构建议先看看数据结构的书,主要是将如何利用二维数组描述图结构,再看看图的深度遍历实现原理。最后再应用到这个问题上来,自然就不难明白了。
posted @
2007-03-02 17:37 保尔任 阅读(2370) |
评论 (0) |
编辑 收藏
(转自:http://chinavery.100steps.net/chengxuyuan/4759.html)
动态规划是本书介绍的五种算法设计方法中难度最大的一种,它建立在最优原则的基础上。采用动态规划方法,可以优雅而高效地解决许多用贪婪算法或分而治之算法无法解决的问题。在介绍动态规划的原理之后,本章将分别考察动态规划方法在解决背包问题、图象压缩、矩阵乘法链、最短路径、无交叉子集和元件折叠等方面的应用。
3.1 算法思想
和贪婪算法一样,在动态规划中,可将一个问题的解决方案视为一系列决策的结果。不同的是,在贪婪算法中,每采用一次贪婪准则便做出一个不可撤回的决策,而在动态规划中,还要考察每个最优决策序列中是否包含一个最优子序列。
例3-1 [最短路经] 考察图1 2 - 2中的有向图。假设要寻找一条从源节点s= 1到目的节点d= 5的最短路径,即选择此路径所经过的各个节点。第一步可选择节点2,3或4。假设选择了节点3,则此时所要求解的问题变成:选择一条从3到5的最短路径。如果3到5的路径不是最短的,则从1开始经过3和5的路径也不会是最短的。例如,若选择的子路径(非最短路径)是3,2,5 (耗费为9 ),则1到5的路径为1,3,2,5 (耗费为11 ),这比选择最短子路径3,4,5而得到的1到5的路径1,3,4,5 (耗费为9) 耗费更大。
所以在最短路径问题中,假如在的第一次决策时到达了某个节点v,那么不管v 是怎样确定的,此后选择从v 到d 的路径时,都必须采用最优策略。
例3-2 [0/1背包问题] 考察1 3 . 4节的0 / 1背包问题。如前所述,在该问题中需要决定x1 .. xn的值。假设按i = 1,2,.,n 的次序来确定xi 的值。如果置x1 = 0,则问题转变为相对于其余物品(即物品2,3,.,n),背包容量仍为c 的背包问题。若置x1 = 1,问题就变为关于最大背包容量为c-w1 的问题。现设r?{c,c-w1 } 为剩余的背包容量。
在第一次决策之后,剩下的问题便是考虑背包容量为r 时的决策。不管x1 是0或是1,[x2 ,.,xn ] 必须是第一次决策之后的一个最优方案,如果不是,则会有一个更好的方案[y2,.,yn ],因而[x1,y2,.,yn ]是一个更好的方案。
假设n=3, w=[100,14,10], p=[20,18,15], c= 11 6。若设x1 = 1,则在本次决策之后,可用的背包容量为r= 116-100=16 。[x2,x3 ]=[0,1] 符合容量限制的条件,所得值为1 5,但因为[x2,x3 ]= [1,0] 同样符合容量条件且所得值为1 8,因此[x2,x3 ] = [ 0,1] 并非最优策略。即x= [ 1,0,1] 可改进为x= [ 1,1,0 ]。若设x1 = 0,则对于剩下的两种物品而言,容量限制条件为11 6。总之,如果子问题的结果[x2,x3 ]不是剩余情况下的一个最优解,则[x1,x2,x3 ]也不会是总体的最优解。
例3-3 [航费] 某航线价格表为:从亚特兰大到纽约或芝加哥,或从洛杉矶到亚特兰大的费用为$ 1 0 0;从芝加哥到纽约票价$ 2 0;而对于路经亚特兰大的旅客,从亚特兰大到芝加哥的费用仅为$ 2 0。从洛杉矶到纽约的航线涉及到对中转机场的选择。如果问题状态的形式为(起点,终点),那么在选择从洛杉矶到亚特兰大后,问题的状态变为(亚特兰大,纽约)。从亚特兰大到纽约的最便宜航线是从亚特兰大直飞纽约,票价$ 1 0 0。而使用直飞方式时,从洛杉矶到纽约的花费为$ 2 0 0。不过,从洛杉矶到纽约的最便宜航线为洛杉矶-亚特兰大-芝加哥-纽约,其总花费为$ 1 4 0(在处理局部最优路径亚特兰大到纽约过程中选择了最低花费的路径:亚特兰大-芝加哥-纽约)。
如果用三维数组(t a g,起点,终点)表示问题状态,其中t a g为0表示转飞, t a g为1表示其他情形,那么在到达亚特兰大后,状态的三维数组将变为( 0,亚特兰大,纽约),它对应的最优路径是经由芝加哥的那条路径。
当最优决策序列中包含最优决策子序列时,可建立动态规划递归方程( d y n a m i c -programming recurrence equation),它可以帮助我们高效地解决问题。
例3-4 [0/1背包] 在例3 - 2的0 / 1背包问题中,最优决策序列由最优决策子序列组成。假设f (i,y) 表示例1 5 - 2中剩余容量为y,剩余物品为i,i + 1,.,n 时的最优解的值,即:和利用最优序列由最优子序列构成的结论,可得到f 的递归式。f ( 1 ,c) 是初始时背包问题的最优解。可使用( 1 5 - 2)式通过递归或迭代来求解f ( 1 ,c)。从f (n, * )开始迭式, f (n, * )由(1 5 - 1)式得出,然后由( 1 5 - 2)式递归计算f (i,*) ( i=n- 1,n- 2,., 2 ),最后由( 1 5 - 2)式得出f ( 1 ,c)。
对于例1 5 - 2,若0≤y<1 0,则f ( 3 ,y) = 0;若y≥1 0,f ( 3 ,y) = 1 5。利用递归式(1 5 - 2),可得f (2, y) = 0 ( 0≤y<10 );f(2,y)= 1 5(1 0≤y<1 4);f(2,y)= 1 8(1 4≤y<2 4)和f(2,y)= 3 3(y≥2 4)。因此最优解f ( 1 , 11 6 ) = m a x {f(2,11 6),f(2,11 6 - w1)+ p1} = m a x {f(2,11 6),f(2,1 6)+ 2 0 } = m a x { 3 3,3 8 } = 3 8。
现在计算xi 值,步骤如下:若f ( 1 ,c) =f ( 2 ,c),则x1 = 0,否则x1 = 1。接下来需从剩余容量c-w1中寻求最优解,用f (2, c-w1) 表示最优解。依此类推,可得到所有的xi (i= 1.n) 值。
在该例中,可得出f ( 2 , 11 6 ) = 3 3≠f ( 1 , 11 6 ),所以x1 = 1。接着利用返回值3 8 -p1=18 计算x2 及x3,此时r = 11 6 -w1 = 1 6,又由f ( 2 , 1 6 ) = 1 8,得f ( 3 , 1 6 ) = 1 4≠f ( 2 , 1 6 ),因此x2 = 1,此时r= 1 6 -w2 = 2,所以f (3,2) =0,即得x3 = 0。
动态规划方法采用最优原则( principle of optimality)来建立用于计算最优解的递归式。所谓最优原则即不管前面的策略如何,此后的决策必须是基于当前状态(由上一次决策产生)的最优决策。由于对于有些问题的某些递归式来说并不一定能保证最优原则,因此在求解问题时有必要对它进行验证。若不能保持最优原则,则不可应用动态规划方法。在得到最优解的递归式之后,需要执行回溯(t r a c e b a c k)以构造最优解。
编写一个简单的递归程序来求解动态规划递归方程是一件很诱人的事。然而,正如我们将在下文看到的,如果不努力地去避免重复计算,递归程序的复杂性将非常可观。如果在递归程序设计中解决了重复计算问题时,复杂性将急剧下降。动态规划递归方程也可用迭代方式来求解,这时很自然地避免了重复计算。尽管迭代程序与避免重复计算的递归程序有相同的复杂性,但迭代程序不需要附加的递归栈空间,因此将比避免重复计算的递归程序更快。
3.2 应用
3.2.1 0/1背包问题
1. 递归策略
在例3 - 4中已建立了背包问题的动态规划递归方程,求解递归式( 1 5 - 2)的一个很自然的方法便是使用程序1 5 - 1中的递归算法。该模块假设p、w 和n 为输入,且p 为整型,F(1,c) 返回f ( 1 ,c) 值。
程序15-1 背包问题的递归函数
int F(int i, int y)
{// 返回f ( i , y ) .
if (i == n) return (y < w[n]) ? 0 : p[n];
if (y < w[i]) return F(i+1,y);
return max(F(i+1,y), F(i+1,y-w[i]) + p[i]);
}
程序1 5 - 1的时间复杂性t (n)满足:t ( 1 ) =a;t(n)≤2t(n- 1)+b(n>1),其中a、b 为常数。通过求解可得t (n) =O( 2n)。
例3-5 设n= 5,p= [ 6 , 3 , 5 , 4 , 6 ],w=[2,2,6,5,4] 且c= 1 0 ,求f ( 1 , 1 0 )。为了确定f ( 1 , 1 0 ),调用函数F ( 1 , 1 0 )。递归调用的关系如图1 5 - 1的树型结构所示。每个节点用y值来标记。对于第j层的节点有i=j,因此根节点表示F ( 1 , 1 0 ),而它有左孩子和右孩子,分别对应F ( 2 , 1 0 )和F ( 2 , 8 )。总共执行了2 8次递归调用。但我们注意到,其中可能含有重复前面工作的节点,如f ( 3 , 8 )计算过两次,相同情况的还有f ( 4 , 8 )、f ( 4 , 6 )、f ( 4 , 2 )、f ( 5 , 8 )、f ( 5 , 6 )、f ( 5 , 3 )、f (5,2) 和f ( 5 , 1 )。如果保留以前的计算结果,则可将节点数减至1 9,因为可以丢弃图中的阴影节点。
正如在例3 - 5中所看到的,程序1 5 - 1做了一些不必要的工作。为了避免f (i,y)的重复计算,必须定义一个用于保留已被计算出的f (i,y)值的表格L,该表格的元素是三元组(i,y,f (i,y) )。在计算每一个f (i,y)之前,应检查表L中是否已包含一个三元组(i,y, * ),其中*表示任意值。如果已包含,则从该表中取出f (i,y)的值,否则,对f (i,y)进行计算并将计算所得的三元组(i,y,f (i,y) )加入表L。L既可以用散列(见7 . 4节)的形式存储,也可用二叉搜索树(见11章)的形式存储。
2. 权为整数的迭代方法
当权为整数时,可设计一个相当简单的算法(见程序1 5 - 2)来求解f ( 1 ,c)。该算法基于例3 - 4所给出的策略,因此每个f (i,y) 只计算一次。程序1 5 - 2用二维数组f [ ][ ]来保存各f 的值。而回溯函数Tr a c e b a c k用于确定由程序1 5 - 2所产生的xi 值。函数K n a p s a c k的复杂性为( n c),而Tr a c e b a c k的复杂性为( n )。
程序15-2 f 和x 的迭代计算
template<class T>
void Knapsack(T p[], int w[], int c, int n, T** f)
{// 对于所有i和y计算f [ i ] [ y ]
// 初始化f [ n ] [ ]
for (int y = 0; y <= yMax; y++)
f[n][y] = 0;
for (int y = w[n]; y <= c; y++)
f[n][y] = p[n];
// 计算剩下的f
for (int i = n - 1; i > 1; i--) {
for (int y = 0; y <= yMax; y++)
f[i][y] = f[i+1][y];
for (int y = w[i]; y <= c; y++)
f[i][y] = max(f[i+1][y], f[i+1][y-w[i]] + p[i]);
}
f[1][c] = f[2][c];
if (c >= w[1])
f[1][c] = max(f[1][c], f[2][c-w[1]] + p[1]);
}
template<class T>
void Traceback(T **f, int w[], int c, int n, int x[])
{// 计算x
for (int i = 1; i < n; i++)
if (f[i][c] == f[i+1][c]) x[i] = 0;
else {x[i] = 1;
c -= w[i];}
x[n] = (f[n][c]) ? 1 : 0;
}
3. 元组方法( 选读)
程序1 5 - 2有两个缺点:1) 要求权为整数;2) 当背包容量c 很大时,程序1 5 - 2的速度慢于程序1 5 - 1。一般情况下,若c>2n,程序1 5 - 2的复杂性为W (n2n )。可利用元组的方法来克服上述两个缺点。在元组方法中,对于每个i,f (i, y) 都以数对(y, f (i, y)) 的形式按y的递增次序存储于表P(i)中。同时,由于f (i, y) 是y 的非递减函数,因此P(i) 中各数对(y, f (i, y)) 也是按f (i, y) 的递增次序排列的。
例3-6 条件同例3 - 5。对f 的计算如图1 5 - 2所示。当i= 5时,f 由数对集合P( 5 ) = [ ( 0 , 0 ) , ( 4 , 6 ) ]表示。而P( 4 )、P( 3 )和P( 2 )分别为[ ( 0 , 0 ) , ( 4 , 6 ) , ( 9 , 1 0 ) ]、[ ( 0 , 0 ) ( 4 , 6 ) , ( 9 , 1 0 ) , ( 1 0 , 11)] 和[ ( 0 , 0 ) ( 2 , 3 ) ( 4 , 6 ) ( 6 , 9 ) ( 9 , 1 0 ) ( 1 0 , 11 ) ]。
为求f ( 1 , 1 0 ),利用式(1 5 - 2)得f ( 1 , 1 0 ) = m a x{f ( 2 , 1 0 ),f ( 2 , 8 ) + p 1}。由P( 2 )得f ( 2 , 1 0 ) = 11、f (2,8)=9 (f ( 2 , 8 ) = 9来自数对( 6,9 ) ),因此f ( 1 , 1 0 ) = m a x{11 , 1 5}= 1 5。现在来求xi 的值,因为f ( 1 , 1 0 ) =f ( 2 , 6 ) +p1,所以x1 = 1;由f ( 2 , 6 ) =f ( 3 , 6 - w 2 ) +p2 =f ( 3 , 4 ) +p2,得x2 = 1;由f ( 3 , 4 ) =f ( 4 , 4 ) =f ( 5 , 4 )得x3=x4 = 0;最后,因f ( 5 , 4 )≠0得x5= 1。
检查每个P(i) 中的数对,可以发现每对(y,f (i,y)) 对应于变量xi , ., xn 的0/1 赋值的不同组合。设(a,b)和(c,d)是对应于两组不同xi , ., xn 的0 / 1赋值,若a≥c且b<d,则(a, b) 受(b, c) 支配。被支配者不必加入P(i)中。若在相同的数对中有两个或更多的赋值,则只有一个放入P(i)。假设wn≤C,P(n)=[(0,0), (wn , pn ) ],P(n)中对应于xn 的两个数对分别等于0和1。对于每个i,P(i)可由P(i+ 1 )得出。首先,要计算数对的有序集合Q,使得当且仅当wi≤s≤c且(s-wi , t-pi )为P(i+1) 中的一个数对时,(s,t)为Q中的一个数对。现在Q中包含xi = 1时的数对集,而P(i+ 1 )对应于xi = 0的数对集。接下来,合并Q和P(i+ 1 )并删除受支配者和重复值即可得到P(i)。
例3-7 各数据同例1 5 - 6。P(5)=[(0,0),(4,6)], 因此Q= [ ( 5 , 4 ) , ( 9 , 1 0 ) ]。现在要将P( 5 )和Q合并得到P( 4 )。因( 5 , 4 )受( 4 , 6 )支配,可删除( 5 , 4 ),所以P(4)=[(0,0), (4,6), (9,10)]。接着计算P( 3 ),首先由P( 4 )得Q=[(6,5), (10,11 ) ],然后又由合并方法得P(3)=[(0,0), (4,6), (9,10), (10,11 ) ]。最后计算P( 2 ):由P( 3 )得Q= [ ( 2 , 3 ),( 6 , 9 ) ],P( 3 )与Q合并得P(2)=[(0,0), (2,3), (4,6), (6,9), (9,10). (10,11 ) ]。因为每个P(i) 中的数对对应于xi , ., xn 的不同0 / 1赋值,因此P(i) 中的数对不会超过2n-i+ 1个。计算P(i) 时,计算Q需消耗( |P(i+ 1 ) |)的时间,合并P(i+1) 和Q同样需要( |P(i+ 1 ) | )的时间。计算所有P(i) 时所需要的总时间为: (n ?i=2|P(i + 1)|= O ( 2n )。当权为整数时,|P(i) |≤c+1, 此时复杂性为O ( m i n {n c, 2n } )。
如6 . 4 . 3节定义的,数字化图像是m×m的像素阵列。假定每个像素有一个0 ~ 2 5 5的灰度值。因此存储一个像素至多需8位。若每个像素存储都用最大位8位,则总的存储空间为8m2 位。为了减少存储空间,我们将采用变长模式( variable bit scheme),即不同像素用不同位数来存储。像素值为0和1时只需1位存储空间;值2、3各需2位;值4,5,6和7各需3位;以此类推,使用变长模式的步骤如下:
1) 图像线性化根据图15-3a 中的折线将m×m维图像转换为1×m2 维矩阵。
2) 分段将像素组分成若干个段,分段原则是:每段中的像素位数相同。每个段是相邻像素的集合且每段最多含2 5 6个像素,因此,若相同位数的像素超过2 5 6个的话,则用两个以上的段表示。
3) 创建文件创建三个文件:S e g m e n t L e n g t h, BitsPerPixel 和P i x e l s。第一个文件包含在2 )中所建的段的长度(减1 ),文件中各项均为8位长。文件BitsPerPixel 给出了各段中每个像素的存储位数(减1),文件中各项均为3位。文件Pixels 则是以变长格式存储的像素的二进制串。
4) 压缩文件压缩在3) 中所建立的文件,以减少空间需求。
上述压缩方法的效率(用所得压缩率表示)很大程度上取决于长段的出现频率。
例3-8 考察图15-3b 的4×4图像。按照蛇形的行主次序,灰度值依次为1 0,9,1 2,4 0,5 0,3 5,1 5,1 2,8,1 0,9,1 5,11,1 3 0,1 6 0和2 4 0。各像素所需的位数分别为4,4,4,6,6,6,4,4,4,4,4,4,4,8,8和8,按等长的条件将像素分段,可以得到4个段[ 1 0,9,1 2 ]、[ 4 0,5 0,3 5 ]、[15, 12, 8, 10, 9, 15, 11] 和[130, 160, 240]。因此,文件SegmentLength 为2,2,6,2;文件BitsPerSegment 的内容为3,5,3,7;文件P i x e l s包含了按蛇形行主次序排列的1 6个灰度值,其中头三个各用4位存储,接下来三个各用6位,再接下来的七个各用4位,最后三个各用8位存储。因此存储单元中前3 0位存储了前六个像素:
1010 1001 1100 111000 110010 100011
这三个文件需要的存储空间分别为:文件SegmentLength 需3 2位;BitsPerSegment 需1 2位;Pixels 需8 2位,共需1 2 6位。而如果每个像素都用8位存储,则存储空间需8×1 6 = 1 2 8位,因而在本例图像中,节省了2位的空间。
假设在2) 之后,产生了n 个段。段标题(segment header)用于存储段的长度以及该段中每个像素所占用的位数。每个段标题需11位。现假设li 和bi 分别表示第i 段的段长和该段每个像素的长度,则存储第i 段像素所需要的空间为li *bi 。在2) 中所得的三个文件的总存储空间为11 n+n ?i = 1li bi。可通过将某些相邻段合并的方式来减少空间消耗。如当段i 和i+ 1被合并时,合并后的段长应为li +li + 1。此时每个像素的存储位数为m a x {bi,bi +1 } 位。尽管这种技术增加了文件P i x e l s的空间消耗,但同时也减少了一个段标题的空间。
例3-9 如果将例1 5 - 8中的第1段和第2段合并,合并后,文件S e g m e n t L e n g t h变为5,6,2,BitsPerSegment 变为5,3,7。而文件Pixels 的前3 6位存储的是合并后的第一段:001010 001001 001100 111000 110010 100011其余的像素(例1 5 - 8第3段)没有改变。因为减少了1个段标题,文件S e g m e n t L e n g t h和BitsPerPixel 的空间消耗共减少了11位,而文件Pixels 的空间增加6位,因此总共节约的空间为5位,空间总消耗为1 2 1位。
我们希望能设计一种算法,使得在产生n 个段之后,能对相邻段进行合并,以便产生一个具有最小空间需求的新的段集合。在合并相邻段之后,可利用诸如L Z W法(见7 . 5节)和霍夫曼编码(见9 . 5 . 3节)等其他技术来进一步压缩这三个文件。
令sq 为前q 个段的最优合并所需要的空间。定义s0 = 0。考虑第i 段(i>0 ),假如在最优合并C中,第i 段与第i- 1,i- 2,.,i-r+1 段相合并,而不包括第i-r 段。合并C所需要的空间消耗等于:第1段到第i-r 段所需空间+ l s u m (i-r+ 1 ,i) * b m a x (i-r+ 1 ,i) + 11
其中l s u m(a, b)=b ?j =a
lj,bmax (a, b)= m a x {ba , ..., bb }。假如在C中第1段到第i-r 段的合并不是最优合并,那么需要对合并进行修改,以使其具有更小的空间需求。因此还必须对段1到段i-r 进行最优合并,也即保证最优原则得以维持。故C的空间消耗为:
si = si-r +l s u m(i-r+1, i)*b m a x(i-r+1, i)+ 11
r 的值介于1到i 之间,其中要求l s u m不超过2 5 6 (因为段长限制在2 5 6之内)。尽管我们不知道如何选择r,但我们知道,由于C具有最小的空间需求,因此在所有选择中, r 必须产生最小的空间需求。
假定k a yi 表示取得最小值时k 的值,sn 为n 段的最优合并所需要的空间,因而一个最优合并可用kay 的值构造出来。
例3-10 假定在2) 中得到五个段,它们的长度为[ 6,3,1 0,2,3 ],像素位数为[ 1,2,3,2,1 ],要用公式(1 5 - 3)计算sn,必须先求出sn-1,.,s0 的值。s0 为0,现计算s1:s1 =s0 +l1 *b1+ 11 = 1 7k a y1 = 1s2 由下式得出:
s2 = m i n {s1 +l2 b2 , s0 + (l1 +l2 ) * m a x {b1 , b2} } + 11 = m i n { 1 7 + 6 , 0 + 9 * 2 } + 11 = 2 9
k a y2 = 2
以此类推,可得s1.s5 = [ 1 7,2 9,6 7,7 3,82] ,k a y1.k a y5 = [ 1,2,2,3,4 ]。因为s5 = 8 2,所以最优空间合并需8 2位的空间。可由k a y5 导出本合并的方式,过程如下:因为k a y5 = 4,所以s5 是由公式(1 5 - 3)在k=4 时取得的,因而最优合并包括:段1到段( 5 - 4 ) = 1的最优合并以及段2,3,4和5的合并。最后仅剩下两个段:段1以及段2到段5的合并段。
1. 递归方法
用递归式(1 5 - 3)可以递归地算出si 和k a yi。程序1 5 - 3为递归式的计算代码。l,b,和k a y是一维的全局整型数组,L是段长限制( 2 5 6),h e a d e r为段标题所需的空间( 11 )。调用S ( n )返回sn 的值且同时得出k a y值。调用Tr a c e b a c k ( k a y, n )可得到最优合并。
现讨论程序1 5 - 3的复杂性。t( 0 ) =c(c 为一个常数): (n>0),因此利用递归的方法可得t (n) = O ( 2n )。Tr a c e b a c k的复杂性为(n)。
程序15-3 递归计算s , k a y及最优合并
int S(int i)
{ / /返回S ( i )并计算k a y [ i ]
if (i == 0 ) return 0;
//k = 1时, 根据公式( 1 5 - 3)计算最小值
int lsum = l[i],bmax = b[i];
int s = S(i-1) + lsum * bmax;
kay[i] = 1;
/ /对其余的k计算最小值并求取最小值
for (int k = 2; k <= i && lsum+l[i-k+1] <= L; k++) {
lsum += l[i-k+1];
if (bmax < b[i-k+1]) bmax = b[i-k+1];
int t = S(i-k);
if (s > t + lsum * bmax) {
s = t + lsum * bmax;
kay[i] = k;}
}
return s + header;
}
void Traceback(int kay[], int n)
{// 合并段
if (n == 0) return;
Tr a c e b a c k ( k a y, n-kay[n]);
cout << "New segment begins at " << (n - kay[n] + 1) << endl;
}
2. 无重复计算的递归方法
通过避免重复计算si,可将函数S的复杂性减少到(n)。注意这里只有n个不同的si。
例3 - 11 再考察例1 5 - 1 0中五个段的例子。当计算s5 时,先通过递归调用来计算s4,.,s0。计算s4 时,通过递归调用计算s3,.,s0,因此s4 只计算了一次,而s3 计算了两次,每一次计算s3要计算一次s2,因此s2 共计算了四次,而s1 重复计算了1 6次!可利用一个数组s 来保存先前计算过的si 以避免重复计算。改进后的代码见程序1 5 - 4,其中s为初值为0的全局整型数组。
程序15-4 避免重复计算的递归算法
int S(int i)
{ / /计算S ( i )和k a y [ i ]
/ /避免重复计算
if (i == 0) return 0;
if (s[i] > 0) return s[i]; //已计算完
/ /计算s [ i ]
/ /首先根据公式(1 5 - 3)计算k = 1时最小值
int lsum = l[i], bmax = b[i];
s[i] =S(i-1) + lsum * bmax;
kay[i] = 1;
/ /对其余的k计算最小值并更新
for (int k = 2; k <= i && lsum+l[i-k+1] <= L; k++) {
lsum += l[i-k+1];
if (bmax < b[i-k+1]) bmax = b[i-k+1];
int t = S(i-k);
if (s[i] > t + lsum * bmax) {
s[i] = t + lsum * bmax;
kay[i] = k;}
}
s[i] += header;
return s[i];
}
为了确定程序1 5 - 4的时间复杂性,我们将使用分期计算模式( amortization scheme)。在该模式中,总时间被分解为若干个不同项,通过计算各项的时间然后求和来获得总时间。当计算si 时,若sj 还未算出,则把调用S(j) 的消耗计入sj ;若sj 已算出,则把S(j) 的消耗计入si (这里sj依次把计算新sq 的消耗转移至每个sq )。程序1 5 - 4的其他消耗也被计入si。因为L是2 5 6之内的常数且每个li 至少为1,所以程序1 5 - 4的其他消耗为( 1 ),即计入每个si 的量是一个常数,且si 数目为n,因而总工作量为(n)。
3. 迭代方法
倘若用式(1 5 - 3)依序计算s1 , ., sn,便可得到一个复杂性为(n)的迭代方法。在该方法中,在si 计算之前, sj 必须已计算好。该方法的代码见程序1 5 - 5,其中仍利用函数Tr a c e b a c k(见程序1 5 - 3)来获得最优合并。
程序15-5 迭代计算s和k a y
void Vbits (int l[], int b[], int n, int s[], int kay[])
{ / /计算s [ i ]和k a y [ i ]
int L = 256, header = 11 ;
s[0] = 0;
/ /根据式(1 5 - 3)计算s [ i ]
for (int i = 1; i <= n; i++) {
// k = 1时,计算最小值
int lsum = l,
bmax = b[i];
s[i] = s[i-1] + lsum * bmax;
kay[i] = 1;
/ /对其余的k计算最小值并更新
for (int k=2; k<= i && lsum+l[i-k+1]<= L; k++) {
lsum += l[i-k+1];
if (bmax < b[i-k+1]) bmax = b[i-k+1];
if (s[i] > s[i-k] + lsum * bmax){
s[i] = s[i-k] + lsum * bmax;
kay[i] = k; }
}
s[i] += header;
}
}
3.2.3 矩阵乘法链
m×n矩阵A与n×p矩阵B相乘需耗费(m n p)的时间(见第2章练习1 6)。我们把m n p作为两个矩阵相乘所需时间的测量值。现假定要计算三个矩阵A、B和C的乘积,有两种方式计算此乘积。在第一种方式中,先用A乘以B得到矩阵D,然后D乘以C得到最终结果,这种乘法的顺序可写为(A*B) *C。第二种方式写为A* (B*C) ,道理同上。尽管这两种不同的计算顺序所得的结果相同,但时间消耗会有很大的差距。
例3-12 假定A为1 0 0×1矩阵,B为1×1 0 0矩阵,C为1 0 0×1矩阵,则A*B的时间耗费为10 0 0 0,得到的结果D为1 0 0×1 0 0矩阵,再与C相乘所需的时间耗费为1 000 000,因此计算(A*B) *C的总时间为1 010 000。B*C的时间耗费为10 000,得到的中间矩阵为1×1矩阵,再与A相乘的时间消耗为1 0 0,因而计算A*(B*C)的时间耗费竟只有10 100!而且,计算( A*B)*C时,还需10 000个单元来存储A*B,而A*(B*C)计算过程中,只需用1个单元来存储B*C。
下面举一个得益于选择合适秩序计算A*B*C矩阵的实例:考虑两个3维图像的匹配。图像匹配问题的要求是,确定一个图像需旋转、平移和缩放多少次才能逼近另一个图像。实现匹配的方法之一便是执行约1 0 0次迭代计算,每次迭代需计算1 2×1个向量T:
T=?A(x, y, z) *B(x, y, z)*C(x, y, z )
其中A,B和C分别为1 2×3,3×3和3×1矩阵。(x , y, z) 为矩阵中向量的坐标。设t 表示计算A(x , y, z) *B(x , y, z) *C(x , y, z)的计算量。假定此图像含2 5 6×2 5 6×2 5 6个向量,在此条件中,这1 0 0个迭代所需的总计算量近似为1 0 0 * 2 5 63 * t≈1 . 7 * 1 09 t。若三个矩阵是按由左向右的顺序相乘的,则t = 1 2 * 3 * 3 + 1 2 * 3 *1= 1 4 4;但如果从右向左相乘, t = 3 * 3 * 1 + 1 2 * 3 * 1 = 4 5。由左至右计算约需2 . 4 * 1 011个操作,而由右至左计算大概只需7 . 5 * 1 01 0个操作。假如使用一个每秒可执行1亿次操作的计算机,由左至右需4 0分钟,而由右至左只需1 2 . 5分钟。
在计算矩阵运算A*B*C时,仅有两种乘法顺序(由左至右或由右至左),所以可以很容易算出每种顺序所需要的操作数,并选择操作数比较少的那种乘法顺序。但对于更多矩阵相乘来说,情况要复杂得多。如计算矩阵乘积M1×M2×.×Mq,其中Mi 是一个ri×ri + 1 矩阵( 1≤i≤q)。不妨考虑q=4 的情况,此时矩阵运算A*B*C*D可按以下方式(顺序)计算:
A* ( (B*C) *D) A* (B* (C*D)) (A*B) * (C*D) (A* (B*C) ) *D
不难看出计算的方法数会随q 以指数级增加。因此,对于很大的q 来说,考虑每一种计算顺序并选择最优者已是不切实际的。
现在要介绍一种采用动态规划方法获得矩阵乘法次序的最优策略。这种方法可将算法的时间消耗降为(q3 )。用Mi j 表示链Mi×.×Mj (i≤j)的乘积。设c(i,j) 为用最优法计算Mi j 的消耗,k a y(i, j) 为用最优法计算Mi j 的最后一步Mi k×Mk+1, j 的消耗。因此Mij 的最优算法包括如何用最优算法计算Mik 和Mkj 以及计算Mik×Mkj 。根据最优原理,可得到如下的动态规划递归式:k a y(i,i+s)= 获得上述最小值的k. 以上求c 的递归式可用递归或迭代的方法来求解。c( 1,q) 为用最优法计算矩阵链的消耗,k a y( 1 ,q) 为最后一步的消耗。其余的乘积可由k a y值来确定。
1. 递归方法
与求解0 / 1背包及图像压缩问题一样,本递归方法也须避免重复计算c (i, j) 和k a y(i, j),否则算法的复杂性将会非常高。
例3-13 设q= 5和r =(1 0 , 5 , 1 , 1 0 , 2 , 1 0),式中待求的c 中有四个c的s= 0或1,因此用动态规划方法可立即求得它们的值: c( 1 , 1 ) =c( 5 , 5 ) = 0 ;c(1,2)=50; c( 4 , 5 ) = 2 0 0。现计算C( 2,5 ):c( 2 , 5 ) = m i n {c( 2 , 2 ) +c(3,5)+50, c( 2 , 3 ) +c(4,5)+500, c( 2 , 4 ) +c( 5 , 5 ) + 1 0 0 } (1 5 - 5)其中c( 2 , 2 ) =c( 5 , 5 ) = 0;c( 2 , 3 ) = 5 0;c(4,5)=200 。再用递归式计算c( 3 , 5 )及c( 2 , 4 ) :c( 3 , 5 ) = m i n {c( 3 , 3 ) +c(4,5)+100, c( 3 , 4 ) +c( 5 , 5 ) + 2 0 } = m i n { 0 + 2 0 0 + 1 0 0 , 2 0 + 0 + 2 0 } = 4 0c( 2 , 4 ) = m i n {c( 2 , 2 ) +c( 3 , 4 ) + 1 0 ,c( 2 , 3 ) +c( 4 , 4 ) + 1 0 0 } = m i n { 0 + 2 0 + 1 0 , 5 0 + 1 0 + 2 0 } = 3 0由以上计算还可得k a y( 3 , 5 ) = 4,k ay( 2 , 4 ) = 2。现在,计算c(2,5) 所需的所有中间值都已求得,将它们代入式(1 5 - 5)得:
c(2,5)=min{0+40+50, 50+200+500, 30+0+100}=90且k a y( 2 , 5 ) = 2
再用式(1 5 - 4)计算c( 1 , 5 ),在此之前必须算出c( 3 , 5 )、c(1,3) 和c( 1 , 4 )。同上述过程,亦可计算出它们的值分别为4 0、1 5 0和9 0,相应的k a y 值分别为4、2和2。代入式(1 5 - 4)得:
c(1,5)=min{0+90+500, 50+40+100, 150+200+1000, 90+0+200}=190且k a y( 1 , 5 ) = 2
此最优乘法算法的消耗为1 9 0,由k a y(1,5) 值可推出该算法的最后一步, k a y(1,5) 等于2,因此最后一步为M1 2×M3 5,而M12 和M35 都是用最优法计算而来。由k a y( 1 , 2 ) = 1知M12 等于M11×M2 2,同理由k a y( 3 , 5) = 4得知M35 由M3 4×M55 算出。依此类推,M34 由M3 3×M44 得出。因而此最优乘法算法的步骤为:
M11×M2 2 = M1 2
M3 3×M4 4 = M3 4
M3 4×M5 5 = M3 5
M1 2×M3 5 = M1 5
计算c(i, j) 和k a y (i, j) 的递归代码见程序1 5 - 6。在函数C中,r 为全局一维数组变量, k a y是全局二维数组变量,函数C返回c(i j) 之值且置k a y [a] [b] =k ay (a , b) (对于任何a , b),其中c(a , b)在计算c(i,j) 时皆已算出。函数Traceback 利用函数C中已算出的k a y值来推导出最优乘法算法的步骤。
设t(q)为函数C的复杂性,其中q=j-i+ 1(即Mij 是q个矩阵运算的结果)。当q为1或2时,t(q) =d,其中d 为一常数;而q> 2时,t (q)=2q-1?k = 1t (k ) +e q,其中e 是一个常量。因此当q>2时,t(q)>2t (q- 1 ) +e,所以t (q)= W ( 2q)。函数Traceback 的复杂性为(q)。
程序15-6 递归计算c (i, j) 和kay (i, j)
int C(int i, int j)
{ / /返回c(i,j) 且计算k(i,j) = kay[i][j]
if (i==j) return 0; //一个矩阵的情形
if (i == j-1) { //两个矩阵的情形
kay[i][i+1] = i;
return r[i]*r[i+1]*r[r+2];}
/ /多于两个矩阵的情形
/ /设u为k = i 时的最小值
int u = C(i,i) + C(i+1,j) + r[i]*r[i+1]*r[j+1];
kay[i][j] = i;
/ /计算其余的最小值并更新u
for (int k = i+1; k < j; k++) {
int t = C(i,k) + C(k+1,j) + r[i]*r[k+1]*r[j+1];
if (r < u) {//小于最小值的情形
u = t;
kay[i][j] = k;
}
return u;
}
void Traceback (int i, int j ,int **kay)
{ / /输出计算Mi j 的最优方法
if ( i == j) return;
Traceback(i, kay[i][j], kay);
Traceback(kay[i][j]+1, j, kay);
cout << "Multiply M" << i << ", "<< kay[i][j];
cout << " and M " << (kay[i][j]+1) << ", " << j << end1;
}
2. 无重复计算的递归方法
若避免再次计算前面已经计算过的c(及相应的k a y),可将复杂性降低到(q3)。而为了避免重复计算,需用一个全局数组c[ ][ ]存储c(i, j) 值,该数组初始值为0。函数C的新代码见程序1 5 - 7:
程序15-7 无重复计算的c (i, j) 计算方法
int C(int i,int j)
{ / /返回c(i,j) 并计算k a y ( i , j ) = k a y [ I ] [ j ]
/ /避免重复计算
/ /检查是否已计算过
if (c[i][j] >) return c[i][j];
/ /若未计算,则进行计算
if(i==j) return 0; //一个矩阵的情形
i f ( i = = j - 1 ) { / /两个矩阵的情形
kay[i][i+1]=i;
c [ i ] [ j ] = r [ i ] * r [ i + 1 ] * r [ i + 2 ] ;
return c[i][j];}
/ /多于两个矩阵的情形
/ /设u为k = i 时的最小值
int u=C(i,i)+C(i+1,j)+r[i]*r[i+1]*r[j+1];
k a y [ i ] [ j ] = i ;
/ /计算其余的最小值并更新u
for (int k==i+1; k<j;k++){
int t=C(i,k)+C(k+1,j)+r[i]*r[k+1]*r[j+1];
if (t<u) {// 比最小值还小
u = t ;
k a y [ i ] [ j ] = k ; }
}
c [ i ] [ j ] = u ;
return u;
}
为分析改进后函数C 的复杂性,再次使用分期计算方法。注意到调用C(1, q) 时每个c (i, j)(1≤i≤j≤q)仅被计算一次。要计算尚未计算过的c(a,b),需附加的工作量s =j-i>1。将s 计入第一次计算c (a, b) 时的工作量中。在依次计算c(a, b) 时,这个s 会转计到每个c (a, b) 的第一次计算时间c 中,因此每个c (i, i) 均被计入s。对于每个s,有q-s+ 1个c(i, j) 需要计算,因此总的工作消耗为q-1 ?s=1(q-s+ 1) = (q3 )。
3. 迭代方法
c 的动态规划递归式可用迭代的方法来求解。若按s = 2,3,.,q-1 的顺序计算c (i, i+s),每个c 和kay 仅需计算一次。
例3-14 考察例3 - 1 3中五个矩阵的情况。先初始化c (i, i) (0≤i≤5) 为0,然后对于i=1, ., 4分别计算c (i, i+ 1 )。c (1, 2)= r1 r2 r3 = 5 0,c (2, 3)= 5 0,c ( 3,4)=20 和c (4, 5) = 2 0 0。相应的k ay 值分别为1,2,3和4。
当s= 2时,可得:
c( 1 , 3 ) = m i n {c( 1 , 1 ) +c(2,3)+ r1 r2 r4 , c( 1 , 2 ) +c( 3 ,3 )+r1r3r4 }=min
=150
且k a y( 1 , 3 ) = 2。用相同方法可求得c( 2 , 4 )和c( 3 , 5 )分别为3 0和4 0,相应k a y值分别为2和3。
当s= 3时,需计算c(1,4) 和c( 2 , 5 )。计算c(2,5) 所需要的所有中间值均已知(见( 1 5 - 5 )式),代入计算公式后可得c( 2 , 5 ) = 9 0,k a y( 2 , 5 ) = 2。c( 1 , 4 )可用同样的公式计算。最后,当s= 4时,可直接用(1 5 - 4)式来计算c( 1 , 5 ),因为该式右边所有项都已知。
计算c 和kay 的迭代程序见函数M a t r i x C h a i n(见程序1 5 - 8),该函数的复杂性为(q3 )。计算出kay 后同样可用程序1 5 - 6中的Traceback 函数推算出相应的最优乘法计算过程。
程序15-8 c 和kay 的迭代计算
void MatrixChain(int r[], int q, int **c, int **kay)
{// 为所有的Mij 计算耗费和k a y
// 初始化c[i][i], c[i][i+1]和k a y [ i ] [ i + 1 ]
for (int i = 1; i < q; i++) {
c[i][i] = 0;
c[i][i+1] = r[i]*r[i+1]*r[i+2];
kay[i][i+1] = i;
}
c[q][q] = 0;
/ /计算余下的c和k a y
for (int s = 2; s < q; s++)
for (int i = 1; i <= q - s; i++) {
// k = i时的最小项
c[i][i+s] = c[i][i] + c[i+1][i+s] + r[i]*r[i+1]*r[i+s+1];
kay[i][i+s] = i;
// 余下的最小项
for (int k = i+1; k < i + s; k++) {
int t = c[i][k] + c[k+1][i+s] + r[i]*r[k+1]*r[i+s+1];
if (t < c[i][i+s]) {// 更小的最小项
c[i][i+s] = t;
kay[i][i+s] = k;}
}
}
}
3.2.4 最短路径
假设G为有向图,其中每条边都有一个长度(或耗费),图中每条有向路径的长度等于该路径中各边的长度之和。对于每对顶点(i, j),在顶点i 与j 之间可能有多条路径,各路径的长度可能各不相同。我们定义从i 到j 的所有路径中,具有最小长度的路径为从i 到j 的最短路径。
例3-15 如图1 5 - 4所示。从顶点1到顶点3的路径有
1) 1,2,5,3
2) 1,4,3
3) 1,2,5,8,6,3
4) 1,4,6,3
由该图可知,各路径相应的长度为1 0、2 8、9、2 7,因而路径3) 是该图中顶点1到顶点3的最短路径。
在所有点对最短路径问题( a l l - p a i r sshorest-paths problem)中,要寻找有向图G中每对顶点之间的最短路径。也就是说,对于每对顶点(i, j),需要寻找从i到j 的最短路径及从j 到i 的最短路径。因此对于一个n 个顶点的图来说,需寻找p =n(n-1) 条最短路径。假定图G中不含有长度为负数的环路,只有在这种假设下才可保证G中每对顶点(i, j) 之间总有一条不含环路的最短路径。当有向图中存在长度小于0的环路时,可能得到长度为-∞的更短路径,因为包含该环路的最短路径往往可无限多次地加上此负长度的环路。
设图G中n 个顶点的编号为1到n。令c (i, j, k)表示从i 到j 的最短路径的长度,其中k 表示该路径中的最大顶点。因此,如果G中包含边<i, j>,则c(i, j, 0) =边<i, j> 的长度;若i= j ,则c(i,j, 0)=0;如果G中不包含边<i, j>,则c (i, j, 0)= +∞。c(i, j, n) 则是从i 到j 的最短路径的长度。
例3-16 考察图1 5 - 4。若k=0, 1, 2, 3,则c (1, 3, k)= ∞;c (1, 3, 4)= 2 8;若k = 5, 6, 7,则c (1, 3,k) = 1 0;若k=8, 9, 10,则c (1, 3, k) = 9。因此1到3的最短路径长度为9。对于任意k>0,如何确定c (i, j, k) 呢?中间顶点不超过k 的i 到j 的最短路径有两种可能:该路径含或不含中间顶点k。若不含,则该路径长度应为c(i, j, k- 1 ),否则长度为c(i, k, k- 1) +c (k, j, k- 1 )。c(i, j, k) 可取两者中的最小值。因此可得到如下递归式:
c( i, j, k)= m i n {c(i, j, k-1), c (i, k, k- 1) +c (k, j, k- 1 ) },k>0
以上的递归公式将一个k 级运算转化为多个k-1 级运算,而多个k-1 级运算应比一个k 级运算简单。如果用递归方法求解上式,则计算最终结果的复杂性将无法估量。令t (k) 为递归求解c (i, j, k) 的时间。根据递归式可以看出t(k) = 2t(k- 1 ) +c。利用替代方法可得t(n) = ( 2n )。因此得到所有c (i, j, n) 的时间为(n2 2n )。
当注意到某些c (i, j, k-1) 值可能被使用多次时,可以更高效地求解c (i, j, n)。利用避免重复计算c(i, j, k) 的方法,可将计算c 值的时间减少到(n3 )。这可通过递归方式(见程序1 5 - 7矩阵链问题)或迭代方式来实现。出迭代算法的伪代码如图1 5 - 5所示。
/ /寻找最短路径的长度
/ /初始化c(i,j,1)
for (int i=1; i < = n ; i + +)
for (int j=1; j<=n; j+ + )
c ( i ,j, 0 ) = a ( i ,j); // a 是长度邻接矩阵
/ /计算c ( i ,j, k ) ( 0 < k < = n )
for(int k=1;k<=n;k++)
for (int i=1;i<=n;i++)
for (int j= 1 ;j< = n ;j+ + )
if (c(i,k,k-1)+c(k,j, k - 1 ) < c ( i ,j, k - 1 ) )
c ( i ,j, k ) = c ( i , k , k - 1 ) + c ( k ,j, k - 1 ) ;
else c(i,j, k ) = c ( i ,j, k - 1 ) ;
图15-5 最短路径算法的伪代码
注意到对于任意i,c(i,k,k) =c(i,k,k- 1 )且c(k,i,k) =c(k,i,k- 1 ),因而,若用c(i,j)代替图1 5 - 5的c(i,j,k),最后所得的c(i,j) 之值将等于c(i,j,n) 值。此时图1 5 - 5可改写成程序1 5 - 9的C + +代码。程序1 5 - 9中还利用了程序1 2 - 1中定义的AdjacencyWDigraph 类。函数AllPairs 在c 中返回最短路径的长度。若i 到j 无通路,则c [i] [j]被赋值为N o E d g e。函数AllPairs 同时计算了k a y [ i ] [ j ],其中kay[i][j] 表示从i 到j 的最短路径中最大的k 值。在后面将看到如何根据kay 值来推断出从一个顶点到另一顶点的最短路径(见程序1 5 - 1 0中的函数O u t p u t P a t h)。
程序1 5 - 9的时间复杂性为(n3 ),其中输出一条最短路径的实际时间为O (n)。
程序15-9 c 和kay 的计算
template<class T>
void AdjacencyWDigraph<T>::Allpairs(T **c, int **kay)
{ / /所有点对的最短路径
/ /对于所有i和j,计算c [ i ] [ j ]和k a y [ i ] [ j ]
/ /初始化c [ i ] [ j ] = c(i,j,0)
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++) {
c[i][j] = a[i][j];
kay[i][j] = 0;
}
for (i = 1; i <= n; i++)
c[i][i] = 0;
// 计算c[i][j] = c(i,j,k)
for (int k = 1; k <= n; k++)
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++) {
T t1 = c[i][k];
T t2 = c[k][j];
T t3 = c[i][j];
if (t1 != NoEdge && t2 != NoEdge && (t3 == NoEdge || t1 + t2 < t3)) {
c[i][j] = t1 + t2;
kay[i][j] = k;}
}
}
程序15-10 输出最短路径
void outputPath(int **kay, int i, int j)
{// 输出i 到j 的路径的实际代码
if (i == j) return;
if (kay[i][j] == 0) cout << j << ' ';
else {outputPath(kay, i, kay[i][j]);
o u t p u t P a t h ( k a y, kay[i][j], j);}
}
template<class T>
void OutputPath(T **c, int **kay, T NoEdge, int i, int j)
{// 输出从i 到j的最短路径
if (c[i][j] == NoEdge) {
cout << "There is no path from " << i << " to " << j << endl;
r e t u r n ; }
cout << "The path is" << endl;
cout << i << ' ';
o u t p u t P a t h ( k a y, i , j ) ;
cout << endl;
}
例3-17 图15-6a 给出某图的长度矩阵a,15-6b 给出由程序1 5 - 9所计算出的c 矩阵,15-6c 为对应的k a y值。根据15-6c 中的kay 值,可知从1到5的最短路径是从1到k a y [ 1 ] [ 5 ] = 4的最短路径再加上从4到5的最短路径,因为k a y [ 4 ] [ 5 ] = 0,所以从4到5的最短路径无中间顶点。从1到4的最短路径经过k a y [ 1 ] [ 4 ] = 3。重复以上过程,最后可得1到5的最短路径为:1,2,3,4,5。
3.2.5 网络的无交叉子集
在11 . 5 . 3节的交叉分布问题中,给定一个每边带n 个针脚的布线通道和一个排列C。顶部的针脚i 与底部的针脚Ci 相连,其中1≤i≤n,数对(i, Ci ) 称为网组。总共有n 个网组需连接或连通。假设有两个或更多的布线层,其中有一个为优先层,在优先层中可以使用更细的连线,其电阻也可能比其他层要小得多。布线时应尽可能在优先层中布设更多的网组。而剩下的其他网组将布设在其他层。当且仅当两个网组之间不交叉时,它们可布设在同一层。我们的任务是寻找一个最大无交叉子集(Maximum Noncrossing Su b s e t,M N S )。在该集中,任意两个网组都不交叉。因(i, Ci ) 完全由i 决定,因此可用i 来指定(i, Ci )。
例3-18 考察图1 5 - 7(对应于图1 0 - 1 7)。( 1 , 8 )和( 2 , 7 )(也即1号网组和2号网组)交叉,因而不能布设在同一层中。而( 1 , 8 ),(7,9) 和(9,10) 未交叉,因此可布设在同一层。但这3个网组并不能构成一个M N S,因为还有更大的不交叉子集。图1 0 - 1 7中给出的例子中,集合{ ( 4 , 2 ) ,( 5 , 5 ) , ( 7 , 9 ) , ( 9 , 1 0 )}是一个含4个网组的M N S。
设M N S(i, j) 代表一个M N S,其中所有的(u, Cu ) 满足u≤i,Cu≤j。令s i z e(i,j) 表示M N S(i,j)的大小(即网组的数目)。显然M N S(n,n)是对应于给定输入的M N S,而s i z e(n,n)是它的大小。
例3-19 对于图1 0 - 1 7中的例子,M N S( 1 0 , 1 0 )是我们要找的最终结果。如例3 - 1 8中所指出的,s i z e( 1 0 , 1 0 ) = 4,因为( 1 , 8 ),( 2 , 7 ),( 7 , 9 ),( 8 , 3 ),( 9 , 1 0 )和( 1 0 , 6 )中要么顶部针脚编号比7大,要么底部针脚编号比6大,因此它们都不属于M N S( 7 , 6 )。因此只需考察剩下的4个网组是否属于M N S( 7 , 6 ),如图1 5 - 8所示。子集{( 3 , 4 ) , ( 5 , 5 )}是大小为2的无交叉子集。没有大小为3的无交叉子集,因此s i z e( 7 , 6) = 2。
当i= 1时,( 1 ,C1) 是M N S( 1 ,j) 的唯一候选。仅当j≥C1 时,这个网组才会是M N S( 1 ,j) 的一个成员.
下一步,考虑i>1时的情况。若j<Ci,则(i,Ci ) 不可能是M N S( i,j) 的成员,所有属于M N S(i,j) 的(u, Cu ) 都需满足u<i且Cu<j,因此:s i z e(i,j) =s i z e(i- 1 ,j), j<Ci (1 5 - 7)
若j≥Ci,则(i,Ci ) 可能在也可能不在M N S(i,j) 内。若(i,Ci ) 在M N S(i,j) 内,则在M N S(i,j)中不会有这样的(u,Cu ):u<i且Cu>Ci,因为这个网组必与(i, Ci ) 相交。因此M N S(i,j) 中的其他所有成员都必须满足条件u<i且Cu<Ci。在M N S(i,j) 中这样的网组共有Mi- 1 , Ci- 1 个。若(i,Ci ) 不在M N S(i,j)中,则M N S(i,j) 中的所有(u, Cu ) 必须满足u<i;因此s i z e(i,j)=s i z e(i- 1 ,j)。虽然不能确定(i, Ci )是否在M N S(i,j) 中,但我们可以根据获取更大M N S的原则来作出选择。因此:s i z e(i,j) = m a x {s i z e(i-1 ,j), s i z e(i- 1 ,Ci-1)+1}, j≥Ci (1 5 - 8)
虽然从(1 5 - 6)式到( 1 5 - 8)式可用递归法求解,但从前面的例子可以看出,即使避免了重复计算,动态规划递归算法的效率也不够高,因此只考虑迭代方法。在迭代过程中先用式(1 5 - 6)计算出s i ze ( 1 ,j),然后再用式(1 5 - 7)和(1 5 - 8)按i=2, 3, ., n 的顺序计算s i ze (i,j),最后再用Traceback 来得到M N S(n, n) 中的所有网组。
例3-20 图1 5 - 9给出了图1 5 - 7对应的s i z e(i,j) 值。因s i z e( 1 0 , 1 0) = 4,可知M N S含4个网组。为求得这4个网组,先从s i ze ( 1 0 , 1 0 )入手。可用(1 5 - 8)式算出s i z e( 1 0 , 1 0 )。根据式(1 5 - 8)时的产生原因可知s i ze ( 1 0 , 1 0)=s i z e( 9 , 1 0 ),因此现在要求M NS ( 9 , 1 0 )。由于M NS ( 1 0 , 1 0 )≠s i z e( 8 , 1 0 ),因此M NS (9,10) 中必包含9号网组。M N S(9,10) 中剩下的网组组成M NS ( 8 , C9- 1)=M N S( 8 , 9 )。由M N S( 8 , 9 ) =M NS (7,9) 知,8号网组可以被排除。接下来要求M N S( 7 , 9 ),因为s i z e( 7 , 9 )≠s i z e( 6 , 9 ),所以M N S中必含7号网组。M NS (7,9) 中余下的网组组成M NS ( 6 , C7- 1 ) =M N S( 6 , 8 )。根据s i z e( 6 , 8 ) =s i z e( 5 , 8 )可排除6号网组。按同样的方法, 5号网组,3号网组加入M N S中,而4号网组等其他网组被排除。因此回溯过程所得到的大小为4的M N S为{ 3 , 5 , 7 , 9 }。
注意到在回溯过程中未用到s i z e( 1 0 ,j) (j≠1 0 ),因此不必计算这些值。
程序1 5 - 11给出了计算s i z e ( i , j ) 的迭代代码和输出M N S的代码。函数M N S用来计算s i ze (i,j) 的值,计算结果用一个二维数组M N来存储。size[i][j] 表示s i z e(i,j),其中i=j= n 或1≤i<n,0≤j≤n,计算过程的时间复杂性为(n2 )。函数Traceback 在N et[0 : m - 1]中输出所得到的M N S,其时间复杂性为(n)。因此求解M M S问题的动态规划算法的总的时间复杂性
为(n2 )。
程序1 5 - 11 寻找最大无交叉子集
void MNS(int C[], int n, int **size)
{ / /对于所有的i 和j,计算s i z e [ i ] [ j ]
/ /初始化s i z e [ 1 ] [ * ]
for (int j = 0; j < C[1]; j++)
size[1][j] = 0;
for (j = C[1]; j <= n; j++)
size[1][j] = 1;
// 计算size[i][*], 1 < i < n
for (int i = 2; i < n; i++) {
for (int j = 0; j < C[i]; j++)
size[i][j] = size[i-1][j];
for (j = C[i]; j <= n; j++)
size[i][j] = max(size[i-1][j], size[i-1][C[i]-1]+1);
}
size[n][n] = max(size[n-1][n], size[n-1][C[n]-1]+1);
}
void Traceback(int C[], int **size, int n, int Net[], int& m)
{// 在N e t [ 0 : m - 1 ]中返回M M S
int j = n; // 所允许的底部最大针脚编号
m = 0; // 网组的游标
for (int i = n; i > 1; i--)
// i 号n e t在M N S中?
if (size[i][j] != size[i-1][j]){// 在M N S中
Net[m++] = i;
j = C[i] - 1;}
// 1号网组在M N S中?
if (j >= C[1])
Net[m++] = 1; // 在M N S中
}
3.2.6 元件折叠
在设计电路的过程中,工程师们会采取多种不同的设计风格。其中的两种为位-片设计(bit-slice design)和标准单元设计(standard-cell design)。在前一种方法中,电路首先被设计为一个元件栈(如图15-10a 所示)。每个元件Ci 宽为wi ,高为hi ,而元件宽度用片数来表示。图15-10a 给出了一个四片的设计。线路是按片来连接各元件的,即连线可能连接元件Ci 的第j片到元件Ci+1 的第j 片。如果某些元件的宽度不足j 片,则这些元件之间不存在片j 的连线。当图1 5 -10a 的位-片设计作为某一大系统的一部分时,则在V L SI ( Very Large Scale Integrated) 芯片上为它分配一定数量的空间单元。分配是按空间宽度或高度的限制来完成的。现在的问题便是如何将元件栈折叠到分配空间中去,以便尽量减小未受限制的尺度(如,若高度限制为H时,必须折叠栈以尽量减小宽度W)。由于其他尺度不变,因此缩小一个尺度(如W)等价于缩小面积。可用折线方式来折叠元件栈,在每一折叠点,元件旋转1 8 0°。在图15-10b 的例子中,一个1 2元件的栈折叠成四个垂直栈,折叠点为C6 , C9 和C1 0。折叠栈的宽度是宽度最大的元件所需的片数。在图15-10b 中,栈宽各为4,3,2和4。折叠栈的高度等于各栈所有元件高度之和的最大值。在图15-10b 中栈1的元件高度之和最大,该栈的高度决定了包围所有栈的矩形高度。
实际上,在元件折叠问题中,还需考虑连接两个栈的线路所需的附加空间。如,在图1 5 -10b 中C5 和C6 间的线路因C6 为折叠点而弯曲。这些线路要求在C5 和C6 之下留有垂直空间,以便能从栈1连到栈2。令ri 为Ci 是折叠点时所需的高度。栈1所需的高度为5 ?i =1hi +r6,栈2所需高度为8 ?i=6hi +r6+r9。
在标准单元设计中,电路首先被设计成为具有相同高度的符合线性顺序的元件排列。假设此线性顺序中的元件为C1,.,Cn,下一步元件被折叠成如图1 5 - 11所示的相同宽度的行。在此图中, 1 2个标准单元折叠成四个等宽行。折叠点是C4,C6 和C11。在相邻标准单元行之间,使用布线通道来连接不同的行。折叠点决定了所需布线通道的高度。设li 表示当Ci 为折叠点时所需的通道高度。在图1 5 - 11的例子中,布线通道1的高度为l4,通道2的高度为l6,通道3的高度为l11。位-片栈折叠和标准单元折叠都会引出一系列的问题,这些问题可用动态规划方法来解决。
1. 等宽位-片元件折叠
定义r1 = rn+1 =0。由元件Ci 至Cj 构成的栈的高度要求为j ?k= ilk+ ri+ rj + 1。设一个位-片设计中所有元件有相同宽度W。首先考察在折叠矩形的高度H给定的情况下,如何缩小其宽度。设Wi
为将元件Ci 到Cn 折叠到高为H的矩形时的最小宽度。若折叠不能实现(如当ri +hi>H时),取Wi =∞。注意到W1 可能是所有n 个元件的最佳折叠宽度。
当折叠Ci 到Cn 时,需要确定折叠点。现假定折叠点是按栈左到栈右的顺序来取定的。若第一点定为Ck+ 1,则Ci 到Ck 在第一个栈中。为了得到最小宽度,从Ck+1 到Cn 的折叠必须用最优化方法,因此又将用到最优原理,可用动态规划方法来解决此问题。当第一个折叠点k+ 1已知时,可得到以下公式:
Wi =w+ Wk + 1 (1 5 - 9)
由于不知道第一个折叠点,因此需要尝试所有可行的折叠点,并选择满足( 1 5 - 9)式的折叠点。令h s u m(i,k)=k ?j = ihj。因k+ 1是一个可行的折叠点,因此h s u m(i, k) +ri +rk+1 一定不会超过H。
根据上述分析,可得到以下动态规划递归式:
这里Wn+1 =0,且在无最优折叠点k+ 1时Wi 为∞。利用递归式(1 5 - 1 0),可通过递归计算Wn , Wn- 1., W2 , W1 来计算Wi。Wi 的计算需要至多检查n-i+ 1个Wk+ 1,耗时为O (n-k)。因此计算所有Wi 的时间为O (n2 )。通过保留式(1 5 - 1 0)每次所得的k 值,可回溯地计算出各个最优的折叠点,其时间耗费为O (n)。
现在来考察另外一个有关等宽元件的折叠问题:折叠后矩形的宽度W已知,需要尽量减小其高度。因每个折叠矩形宽为w,因此折叠后栈的最大数量为s=W / w。令Hi, j 为Ci , ., Cn 折叠成一宽度为jw 的矩形后的最小高度, H1, s 则是所有元件折叠后的最小高度。当j= 1时,不允许任何折叠,因此:Hi,1 =h s u m(i,n) +ri , 1≤i≤n
另外,当i=n 时,仅有一个元件,也不可能折叠,因此:Hn ,j=hn+rn , 1≤j≤s
在其他情况下,都可以进行元件折叠。如果第一个折叠点为k+ 1,则第一个栈的高度为
h s u m(i,k) +ri +rk+ 1。其他元件必须以至多(j- 1 ) *w 的宽度折叠。为保证该折叠的最优性,其他元件也需以最小高度进行折叠.
因为第一个折叠点未知,因此必须尝试所有可能的折叠点,然后从中找出一个使式(1 5 - 11)的右侧取最小值的点,该点成为第一个折叠点。
可用迭代法来求解Hi, j ( 1≤i≤n, 1≤j≤s),求解的顺序为:先计算j=2 时的H i, j,再算j= 3,.,以此类推。对应每个j 的Hi, j 的计算时间为O (n2 ),所以计算所有H i, j 的时间为O(s n2 )。通过保存由( 1 5 - 1 2)式计算出的每个k 值,可以采用复杂性为O (n) 的回溯过程来确定各个最优的折叠点。
2. 变宽位-片元件的折叠
首先考察折叠矩形的高度H已定,欲求最小的折叠宽度的情况。令Wi 如式(1 5 - 1 0)所示,按照与(1 5 - 1 0)式相同的推导过程,可得:
Wi = m i n {w m i n(i, k) +Wk+1 | h s u m(i,k)+ ri +rk+ 1≤H, i≤k≤n} (1 5 - 1 3)
其中Wn+1=0且w m i n(i,k)= m ini≤j≤k{wj }。可用与(1 5 - 1 0)式一样的方法求解(1 5 - 1 3)式,所需时间为O(n2 )。
当折叠宽度W给定时,最小高度折叠可用折半搜索方法对超过O(n2 )个可能值进行搜索来实现,可能的高度值为h(i,j)+ri +rj + 1。在检测每个高度时,也可用( 1 5 - 1 3)式来确定该折叠的宽度是否小于等于W。这种情况下总的时间消耗为O (n2 l o gn)。
3. 标准单元折叠
用wi 定义单元Ci 的宽度。每个单元的高度为h。当标准单元行的宽度W 固定不变时,通过减少折叠高度,可以相应地减少折叠面积。考察Ci 到Cn 的最小高度折叠。设第一个折叠点是Cs+ 1。从元件Cs+1 到Cn 的折叠必须使用最小高度,否则,可使用更小的高度来折叠Cs+1 到Cn,从而得到更小的折叠高度。所以这里仍可使用最优原理和动态规划方法。
令Hi , s 为Ci 到Cn 折叠成宽为W的矩形时的最小高度,其中第一个折叠点为Cs+ 1。令w s u m(i, s)=s ?j = iwj。可假定没有宽度超过W的元件,否则不可能进行折叠。对于Hn,n 因为只有一个元件,不存在连线问题,因此Hn, n =h。对于H i, s(1≤i<s≤n)注意到如果w s u m(i, s )>W,不可能实现折叠。若w s u m(i,s)≤W,元件Ci 和C j + 1 在相同的标准单元行中,该行下方布线通道的高度为ls+ 1(定义ln+1 = 0)。因而:Hi, s = Hi+1, k (1 5 - 1 4)
当i=s<n 时,第一个标准单元行只包含Ci 。该行的高度为h 且该行下方布线通道的高度为li+ 1。因Ci+ 1 到Cn 单元的折叠是最优的.
为了寻找最小高度折叠,首先使用式( 1 5 - 1 4)和(1 5 - 1 5)来确定Hi, s (1≤i≤s≤n)。最小高度折叠的高度为m in{H1 , s}。可以使用回溯过程来确定最小高度折叠中的折叠点。
posted @
2007-02-27 17:10 保尔任 阅读(1505) |
评论 (0) |
编辑 收藏
//
建立二叉树并先根遍历的代码
public
class
BinaryTreeTest
{
public
static
void
main(String args[])
{
//
主方法
BinaryTreeTest b
=
new
BinaryTreeTest();
int
data[]
=
{
12
,
11
,
34
,
45
,
67
,
89
,
56
,
43
,
22
,
98
}
;
BinaryTree root
=
new
BinaryTree(data[
0
]);
System.out.print(
"
二叉树的中的数据:
"
);
//
建立二叉树
for
(
int
i
=
1
; i
<
data.length; i
++
)
{
root.insertTree(root, data[i]);
System.out.print(data[i
-
1
]
+
"
;
"
);
}
System.out.println(data[data.length
-
1
]);
int
key
=
Integer.parseInt(args[
0
]);
if
(b.searchkey(root, key))
{
System.out.println(
"
找到了:
"
+
key);
}
else
{
System.out.println(
"
没有找到:
"
+
key);
}
}
public
boolean
searchkey(BinaryTree root,
int
key)
{
//
查询
boolean
bl
=
false
;
if
(root
==
null
)
{
bl
=
false
;
return
bl;
}
else
if
(root.data
==
key)
{
bl
=
true
;
return
bl;
}
else
if
(key
>=
root.data)
{
return
searchkey(root.rightpoiter, key);
}
return
searchkey(root.leftpoiter, key);
}
}
class
BinaryTree
{
//
二叉树类
int
data;
BinaryTree leftpoiter;
BinaryTree rightpoiter;
BinaryTree(
int
data)
{
this
.data
=
data;
leftpoiter
=
null
;
rightpoiter
=
null
;
}
public
void
insertTree(BinaryTree root,
int
data)
{
//
插入节点
if
(data
>=
root.data)
{
if
(root.rightpoiter
==
null
)
{
root.rightpoiter
=
new
BinaryTree(data);
}
else
{
insertTree(root.rightpoiter, data);
}
}
else
{
if
(root.leftpoiter
==
null
)
{
root.leftpoiter
=
new
BinaryTree(data);
}
else
{
insertTree(root.leftpoiter, data);
}
}
}
}
//
end
讲解:一个寻找关键字--searchkey
另一个是插入一个结点:insertTree
另外这是一个完全的先序遍历二叉树的语法。先根结点,再左结点,如无再右结点,如些加归至搜索完毕。
posted @
2007-02-27 11:41 保尔任 阅读(403) |
评论 (0) |
编辑 收藏
10, 增加eclipse内存:
更改ECLIPSE文件夹下的ECLIPSE.INI文件内容如下:
-vmargs
-Xms128m
-Xmx512m
-XX:PermSize=128m
-XX:MaxPermSize=256m
或者:
在eclipse目录下建个批处理文件eclipse.bat,用文本编辑器打开,写入如下内容:
eclipse.exe -vmargs -Xms128m -Xmx512m -XX:PermSize=128m -XX:PermSize=256m
然后保存.以后运行eclipse的时候就执行这个批处理就行了.
解释下参数的意思:
-vmargs 说明后面的参数都是java虚拟机(vm)的参数
-Xms128m 虚拟机占用系统的最小内存
-Xmx512m 虚拟机占用系统的最大内存
-XX:PermSize=64m 最小堆大小.一般报内存不足时,都是说这个太小,堆空间剩余小于5% 就会警告,建议把这个稍微设大一点,不过要视自己机器内存大小来设置
-XX:PermSize=128m 最大堆大小.这个也适当变大些
在快捷方式中设置也可:
eclipse.exe -vmargs -Xverify:none -XX:+UseParallelGC -XX:PermSize=20M -Xms64M -Xmx512M
9,代码风格设置
可以通过Window->Perferences->Java->Code Style->Code
Formatter来设定代码的编写格式,然后只要用快捷键Ctrl+Shift+F就可以将不标准的代码自动转化成所设定的标准格式。
8,显示行号,显示限制列的线(默认80列)
window -> Preferences -> General -> Editors -> Text Editors: Show line numbers; Show print margin
7.打包成jar文件时,需要根据自定义的文件生成MANIFEST.MF,其中每行的冒号后面都有一个空格,否则出错。例:Manifest-Version: 1.0(1.0前有空格,其他行也是如此)
6.由数据库中的表自动建立.java和.hbm.xml文件
a.建立项目:打开带HibernateTools插件的eclipse,建立一个名为“test”的java project,内部新建一个名字为src的source folder。
b.建立hibernate配置文件:新建“hibernate configuration file”,输出路径选择“test项目的src目录”,然后的对话框填写配置文件(包括database dialect,driver class,connection url,username,password,creat a console configuration),下一个对话框先填写name(即console configuration name),再点“add external jars”,选择数据库驱动的jar文件,看到src中有“hibernate.cfg.xml”就是配置文件建立成功。
c.建立目标文件:点工具栏hibernate图标,选择“hibernate code generation...”,在弹出的对话框中点击左侧“新建”,把名字改为“test”,console configuration选刚才建立的console configuration name,package填想生成的包结构,点reveng.xml的“setup”,接下来对话框选择test的src目录,然后导入需要的数据库表(有关联的就要导入,即外键的表也要导入),然后点“finish”;选择main右边的exporters,选中generate domain code,generate mappings三项,run,刷新项目,看到包中生成的.java和.hbm.xml文件,成功,把它们拷入myeclipse的相应项目里。
d.删除Console Configuration:打开Hibernate Console的透视图(perspective),在左侧Hibernate Configuration的视图(view)中右键单击,就可以删除。
删除Hivernate Code Generation:点击工具栏Hibernate图标,左侧即可删除。
5.*.service.spring包中的*ServiceImpl.java文件中有dao对象属性,必须包括这个对象的get/set方法,否则出错。
4.从一个.jsp文件转到另一个包含有form表单.jsp文件时,出错信息为form表单的action找不到mapping,在两个页面之间加一个action即可找到。
3.eclipse与tomcat代码不同步的问题
搜索tomcat中有此项目名的所有文件,全部删除。在实验应该会成功。
2.字符集框手动输入
我把“eclipse 的window-->prefrences -->general -->content type”设为了UTF8,是为了不让每个项目再选一遍UTF8,结果单个项目选择时就没有GBK选项了。解决办法就是在单个项目让你选字符集的地方手动输入GBK,就ok了!!!
1.debug工具条灰色
debug模式下,eclipse用debug透视图打开断点页面,但debug工具条却显示灰色,应该转到其他透视图在转会来就可以了。比如debug -> java -> debug
posted @
2007-02-13 11:21 保尔任 阅读(456) |
评论 (0) |
编辑 收藏
作者一记录:
?
编辑
作用域 功能 快捷键
全局 查找并替换 Ctrl+F
文本编辑器 查找上一个 Ctrl+Shift+K
文本编辑器 查找下一个 Ctrl+K
全局 撤销 Ctrl+Z
全局 重做 Ctrl+Y
全局 复制 Ctrl+C
全局 剪切 Ctrl+X
全局 粘贴 Ctrl+V
全局 全部选中 Ctrl+A
全局 删除 Delete
全局 快速修正 Ctrl+1
全局 内容辅助 Alt+/
全局 上下文信息 Alt+? Alt+Shift+? Ctrl+Shift+Space
Java编辑器 显示工具提示描述 F2
全局 恢复上一个选择 Alt+Shift+↓
Java编辑器 选择封装元素 Alt+Shift+↑
Java编辑器 选择上一个元素 Alt+Shift+←
Java编辑器 选择下一个元素 Alt+Shift+→
文本编辑器 增量查找 Ctrl+J
文本编辑器 增量逆向查找 Ctrl+Shift+J
文本编辑器 改写切换 Insert
文本编辑器 上滚行 Ctrl+↑
文本编辑器 下滚行 Ctrl+↓
?
查看
作用域 功能 快捷键
全局 放大 Ctrl+=
全局 缩小 Ctrl+-
?
窗口
作用域 功能 快捷键
全局 激活编辑器 F12
全局 切换编辑器 Ctrl+Shift+W
全局 上一个编辑器 Ctrl+Shift+F6
全局 上一个视图 Ctrl+Shift+F7
全局 上一个透视图 Ctrl+Shift+F8
全局 下一个编辑器 Ctrl+F6
全局 下一个视图 Ctrl+F7
全局 下一个透视图 Ctrl+F8
文本编辑器 显示标尺上下文菜单 Ctrl+W
全局 显示视图菜单 Ctrl+F10
全局 显示系统菜单 Alt+-
?
导航
作用域 功能 快捷键
Java编辑器 打开结构 Ctrl+F3
全局 打开类型 Ctrl+Shift+T
全局 打开类型层次结构 F4
全局 打开声明 F3
全局 打开外部javadoc Shift+F2
全局 打开资源 Ctrl+Shift+R
全局 后退历史记录 Alt+←
全局 前进历史记录 Alt+→
全局 上一个 Ctrl+,
全局 下一个 Ctrl+.
Java编辑器 显示大纲 Ctrl+O
全局 在层次结构中打开类型 Ctrl+Shift+H
全局 转至匹配的括号 Ctrl+Shift+P
全局 转至上一个编辑位置 Ctrl+Q
Java编辑器 转至上一个成员 Ctrl+Shift+↑
Java编辑器 转至下一个成员 Ctrl+Shift+↓
文本编辑器 转至行 Ctrl+L
搜索
作用域 功能 快捷键
全局 出现在文件中 Ctrl+Shift+U
全局 打开搜索对话框 Ctrl+H
全局 工作区中的声明 Ctrl+G
全局 工作区中的引用 Ctrl+Shift+G
?
文件
作用域 功能 快捷键
全局 保存 Ctrl+S
全局 打印 Ctrl+P
全局 关闭 Ctrl+F4
全局 全部保存 Ctrl+Shift+S
全局 全部关闭 Ctrl+Shift+F4
全局 属性 Alt+Enter
全局 新建 Ctrl+N
项目
作用域 功能 快捷键
全局 全部构建 Ctrl+B
源代码
作用域 功能 快捷键
Java编辑器 格式化 Ctrl+Shift+F
Java编辑器 取消注释 Ctrl+\
Java编辑器 注释 Ctrl+/
Java编辑器 添加导入 Ctrl+Shift+M
Java编辑器 组织导入 Ctrl+Shift+O
Java编辑器 使用try/catch块来包围未设置,太常用了,所以在这里列出,建议自己设置。
也可以使用Ctrl+1自动修正。
?
运行
作用域 功能 快捷键
全局 单步返回 F7
全局 单步跳过 F6
全局 单步跳入 F5
全局 单步跳入选择 Ctrl+F5
全局 调试上次启动 F11
全局 继续 F8
全局 使用过滤器单步执行 Shift+F5
全局 添加/去除断点 Ctrl+Shift+B
全局 显示 Ctrl+D
全局 运行上次启动 Ctrl+F11
全局 运行至行 Ctrl+R
全局 执行 Ctrl+U
重构
作用域 功能 快捷键
全局 撤销重构 Alt+Shift+Z
全局 抽取方法 Alt+Shift+M
全局 抽取局部变量 Alt+Shift+L
全局 内联 Alt+Shift+I
全局 移动 Alt+Shift+V
全局 重命名 Alt+Shift+R
全局 重做 Alt+Shift+Y
作者二记录:
让我们按照使用频率来看看我最爱用的一些热键组合。(注:以下内容在Eclipse3.02及一上版本通过测试)
1. Control-Shift-T: 打开类型(Open type)。如果你不是有意磨洋工,还是忘记通过源码树(source tree)打开的方式吧。
2. Control-Shift-R: 打开资源(不只是用来寻找Java文件)。小提示:利用Navigator视图的黄色双向箭头按钮让你的编辑窗口和导航器相关联。这会让你打开的文件对应显示在导航器的层级结构中,这样便于组织信息。如果这影响了速度,就关掉它。
3. F3: 打开申明(Open declaration)。或者,利用Declaration Tab(在Java视图模式下,选择Windows --> Show View -- > Declaration)。当你选中代码中的一个方法,然后按这个按键,它会把整个方法在申明方框里显示出来。
4. Alt-left arrow: 在导航历史记录(Navigation History)中后退。就像Web浏览器的后退按钮一样,在利用F3跳转之后,特别有用。(用来返回原先编译的地方)
5. Alt-right arrow: 导航历史记录中向前。
6. Control-Q: 回到最后依次编辑的地方。这个快捷键也是当你在代码中跳转后用的。特别是当你钻的过深,忘记你最初在做什么的时候。
7. Control-Shift-G: 在workspace中搜索引用(reference)。这是重构的前提。对于方法,这个热键的作用和F3恰好相反。它使你在方法的栈中,向上找出一个方法的所有调用者。一个与此相关的功能是开启“标记”功能(occurrence marking) 。选择Windows->Preferences->Java-> Editor-> Mark Occurrences,勾选选项。这时,当你单击一个元素的时候,代码中所有该元素存在的地方都会被高亮显示。我个人只使用“标记本地变量”(Mark Local Variables)。注意:太多的高亮显示会拖慢Eclipse。
8. Control-Shift-F: 根据代码风格设定重新格式化代码。我们的团队有统一的代码格式,我们把它放在我们的wiki上。要这么做,我们打开Eclipse,选择Window?Preferences?Java?Code Style,然后设置Code Formatter,Code Style和Organize Imports。利用导出(Export)功能来生成配置文件。我们把这些配置文件放在wiki上,然后团队里的每个人都导入到自己的Eclipse中。
9. Control-O: 快速概要(quick outline)。通过这个快捷键,你可以迅速的跳到一个方法或者属性,只需要输入名字的头几个字母。
10. Control-/: 对一行注释或取消注释。对于多行也同样适用。
11. Control-Alt-down arrow: 复制高亮显示的一行或多行。
12. Alt-down arrow: 将一行或多行向下移动。Alt-up arrow会向上移动。
其他的热键在菜单里有。你可以通过按下Control-Shift-L(从3.1版本开始),看到所有快捷键的列表。按下Control-Shift-L两次,会显示热键对话框(Keys Preferences dialog),你可以在这里自己设置热键。我欢迎你在Talkback部分发表你的Eclipse提示。
其他的Eclipse窍门
我总结了几个相关的小窍门:
锁定命令行窗口:在命令行视图中(Window ? Show View ? Other ? Basic ? Console),试试看用滚动锁定按钮来锁定控制台输出不要滚屏。
使用Ant视图:在我的Java或Debug模式下,我喜欢显示出Ant视图,这样我就可以迅速的运行Ant任务。通过Window ? Show View ? Other ? Ant可以找到该视图。把Ant视图放在屏幕的一角,通过“添加编译文件(Add Buildfiles)”按钮来添加build.xml文件。在3.1版本中,甚至支持Ant调试脚本语言。
自动遍历一个集合:for + Control-Space: 如果你还不知道,那么你应该记住Control-Space是自动完成功能。在Eclipse中,你还可以自动完成结构。在一个数组或集合范围内,试试看输入“for”然后按下Control-Space键。Eclipse会问你你想要遍历哪一个集合然后自动完成循环代码。
使用分级布局:在包浏览视图(Package Explorer view)中默认的布局(扁平式)方式让我困惑,它把包的全名显示在导航树(navigation tree)中。我更喜欢我源码的包和文件系统视图,在Eclipse中叫做分级布局(Hierarchical Layout)。要切换到这种模式,点击包浏览视图中向下的按钮,选择布局(Layout),然后选择分级(Hierarchial)。
一次显示多个文件:你可以一次浏览多个文件。把不在激活状态的编辑窗口拖到激活窗口的底部或侧边的滚动条上,就可以打开该编辑窗口。这是我能描述该窍门的最好方式了。
同时打开两个Eclipse:要将改动从一个CVS分支上合并到另外一个上,我喜欢通过同时打开两个工作目录(Workspace)不同Eclipse来实现。这样我可以通过比较CVS上的最新版本看到所有的变化(右键单击工程,然后选择Compare with ? Lastest from HEAD)然后把每一个变化都合并到另外一个CVS分支上。启动多个Eclipse的最简单的方法是利用Eclipse Launcher。
Implementors插件:安装一个能够跳到一个接口的实现的插件。如果你是个dependency injection 粉丝,或者正在基于编写优良的接口工作,那么你需要一个这样的插件来加速代码导航。你可以在SourceForge找到这个插件。
posted @
2007-02-13 11:20 保尔任 阅读(721) |
评论 (0) |
编辑 收藏
可以
【方法一】简单地把plugin放到eclipse SDK本身的features和plugins目录下来进行plugin的安装,但是这种方法并不利于plugin的管理:
- 虽然可以通过【方法二】eclipse SDK的update功能来升级自身,然而因为速度的原因我们一般还是会选择完全下载新版本,这样就需要把后来安装到eclipse SDK目录下的plugin都挑选出来并拷贝到新版本的eclipse SDK目录下,如果这样的plugin比较多的话将会有些麻烦。
- 有时候会共存多个版本的eclipse SDK,显然我们并不想把这些plugin拷贝到每个版本的eclipse SDK里
【方法三】eclipse platform是支持把plugin安装到其他目录的,不过它对这些目录是有要求的:该目录必须有一个名为eclipse的子目录,eclipse子目录下必须有一个.eclipseextension文件,plugin本身放在eclipse子目录下的features和plugins目录下。这样的一个位置就是一个eclipse extension,.eclipseextension文件描述了这个eclipse extension,包括三项name、id和version;可以有多个eclipse extension,具体创建几个eclipse extension,每个eclipse extension包含哪些plugin,完全视情况而定,比如可以把关系比较密切的几个plugin放在一个eclipse extension中。
显然我们必须告诉eclipse platform这些eclipse extension的位置才行,这有两种方法:
- 当eclipse启动后用,打开Help->Software Updates/Manager Configuration,用Add an Extension Location来添加eclipse extesnion,指定的位置将会被存放到当前的configuration里
- 在eclipse platform所在的eclipse目录下建一个links目录,多个插件可以定义一个***.link,一个path=location一行;或者定义多个***.link文件,每个包含一个path=location。路径分隔符为正斜杠,如果用反斜杠必须用两个以转义
第一种方法是把eclipse extension的位置保存在当前configuration中,因此用这种方法指定的eclipse extension是特定于configuration的,不同的configuration可以具有不同的eclipse extension配置,可以在启动时用-configuration选项来选择一个configuration,但是似乎当添加完eclipse extension后是不能删除的,只能disable,而且多个configuration也带来了管理的负担;第二种方法比较明了,但它是 configuration insensitive的,不管以哪个configuration运行这些eclipse extension都是可见的,这里不用担心内存的占用问题,因为eclipse的plugin都是lazy loading的,用不到的plugin是并不会占用内存空间的,不过可能会有plugin冲突问题,比如两个插件在同一个extension point处扩展,而对extension point的处理又是不可配置的,比如选择extension的策略是找到的第一个extension,而此时如果我们希望运行的extension恰好排在第二位,那么就有问题了,这时可能就需要两种方法都用到了。
配置好eclipse extension后,这些eclipse extension中的plugin就和eclipse platform/sdk中的plugin,按照extension和extension point的关系,共同形成了一个插件网络,这时各个plugin的位置已经没有区别了,你甚至可以指定运行位于eclipse extension中的product。
一般的plugin包都会把eclipse目录打进去,这样只要把该包直接解压到选定的 eclipse extension目录中即可,不过如前所述,要成为真正的eclipse extension目录,还需要一个.eclipseextension文件,除了手工建立外,当从update site安装plugin时还可以让eclipse来建立它,只要在安装对话框弹出时选择change location指定一个目录即可。
posted @
2007-02-13 11:18 保尔任 阅读(303) |
评论 (0) |
编辑 收藏
-1 subversion开发
subclipse:
http://subclipse.tigris.org/update_1.2.x
按照提示安装完毕后,我们就可以打开Subversion的资源库了。选择Eclipse菜单Window->Show View->Other…,选择SVN->SVN Repository,然后添加一个新的资源库,例如
http://livebookstore.googlecode.com/svn/trunk
0 python开发
pydev:http://www.fabioz.com/pydev/updates
1 Eclipse下载
EMF,GEF - Graphical Editor Framework,UML2,VE - Visual Editor都在这里下载
http://www.eclipse.org/downloads/index.php
2 lomboz J2EE插件,开发JSP,EJB
http://forge.objectweb.org/projects/lomboz
3 MyEclipse J2EE开发插件,支持SERVLET/JSP/EJB/数据库操纵等
http://www.myeclipseide.com/
4 Properties Editor 编辑java的属性文件,并可以自动存盘为Unicode格式
http://propedit.sourceforge.jp/index_en.html
5 Colorer Take 为上百种类型的文件按语法着色
http://colorer.sourceforge.net/
6 XMLBuddy 编辑xml文件
http://www.xmlbuddy.com/
7 Code Folding 加入多种代码折叠功能(比eclipse自带的更多)
http://www.coffee-bytes.com/servlet/PlatformSupport
8 Easy Explorer 从eclipse中访问选定文件、目录所在的文件夹
http://easystruts.sourceforge.net/
9 Fat Jar 打包插件,可以方便的完成各种打包任务,可以包含外部的包等
http://fjep.sourceforge.net/
10 RegEx Test 测试正则表达式
http://brosinski.com/stephan/archives/000028.php
11 JasperAssistant 报表插件(要钱的哦~)
http://www.jasperassistant.com/
12 Jigloo GUI Builder JAVA的GUI编辑插件
http://cloudgarden.com/jigloo/
13 Profiler 性能跟踪、测量工具,能跟踪、测量B/S程序
http://sourceforge.net/projects/eclipsecolorer/
14 AdvanQas 提供对if/else等条件语句的提示和快捷帮助(自动更改结构等)
http://eclipsecolorer.sourceforge.net/advanqas/index.html
15 Log4E Log4j插件,提供各种和Log4j相关的任务,如为方法、类添加一个logger等
http://log4e.jayefem.de/index.php/Main_Page
16 VSSPlugin VSS插件
http://sourceforge.net/projects/vssplugin
17 Implementors 提供跳转到一个方法的实现类,而不是接口的功能(实用!)
http://eclipse-tools.sourceforge.net/implementors/
18 Call Hierarchy 显示一个方法的调用层次(被哪些方法调,调了哪些方法)
http://eclipse-tools.sourceforge.net/call-hierarchy/index.html
19 EclipseTidy 检查和格式化HTML/XML文件
http://eclipsetidy.sourceforge.net/
20 Checkclipse 检查代码的风格、写法是否符合规范
http://www.mvmsoft.de/content/plugins/checkclipse/checkclipse.htm
21 Hibernate Synchronizer Hibernate插件,自动映射等
http://www.binamics.com/hibernatesync/
22 VeloEclipse Velocity插件
http://propsorter.sourceforge.net/
23 EditorList 方便的列出所有打开的Editor
http://editorlist.sourceforge.net/
24 MemoryManager 内存占用率的监视
http://cloudgarden.com/memorymanager/
25 swt-designer java的GUI插件
http://www.swt-designer.com/
26 TomcatPlugin 支持Tomcat插件
http://www.sysdeo.com/eclipse/tomcatPlugin.html
27 XML Viewer
http://tabaquismo.freehosting.net/ignacio/eclipse/xmlview/index.html
28 quantum 数据库插件
http://quantum.sourceforge.net/
29 Dbedit 数据库插件
http://sourceforge.net/projects/dbedit
30 clay.core 可视化的数据库插件
http://www.azzurri.jp/en/software/index.jsp
http://www.azzurri.jp/eclipse/plugins
31 hiberclipse hibernate插件
http://hiberclipse.sourceforge.net/
http://www.binamics.com/hibernatesync
32 struts-console Struts插件
http://www.jamesholmes.com/struts/console/
33 easystruts Struts插件
http://easystruts.sourceforge.net/
34 veloedit Velocity插件
http://veloedit.sourceforge.net/
35 jalopy 代码整理插件
http://jalopy.sourceforge.net/
36 JDepend 包关系分析
http://andrei.gmxhome.de/jdepend4eclipse/links.html
37 Spring IDE Spring插件
http://springide-eclip.sourceforge.net/updatesite/
38 doclipse 可以产生xdoclet 的代码提示
http://beust.com/doclipse/
39 SQLExplorer,在Eclipse 中连接各种数据库进行操作使用
http://dev2dev.bea.com.cn/bbs/thread.jspa?forumID=124&threadID=31124
===========================================================\
以下为7月13日转贴更新
JSEclipse
eclipseME
http://eclipseme.org/updates/
Eclipse加速插件KeepResident
http://suif.stanford.edu/pub/keepresident/
MyEclipse J2EE开发插件,支持SERVLET/JSP/EJB/数据库操纵等
www.myeclipseide.com
Properties Editor 编辑java的属性文件,并可以自动存盘为Unicode格式
http://propedit.sourceforge.jp/index_en.html
http://propedit.sourceforge.jp/eclipse/updates/
Colorer Take 为上百种类型的文件按语法着色
http://colorer.sourceforge.net/
XMLBuddy 编辑xml文件
www.xmlbuddy.com
Code Folding 加入多种代码折叠功能(比eclipse自带的更多)
http://www.coffee-bytes.com/servlet/PlatformSupport
Easy Explorer 从eclipse中访问选定文件、目录所在的文件夹
http://easystruts.sourceforge.net/
Fat Jar 打包插件,可以方便的完成各种打包任务,可以包含外部的包等
http://fjep.sourceforge.net/
RegEx Test 测试正则表达式
http://brosinski.com/stephan/archives/000028.php
JasperAssistant 报表插件(强,要钱的)
http://www.jasperassistant.com/
Jigloo GUI Builder JAVA的GUI编辑插件
http://cloudgarden.com/jigloo/
Profiler 性能跟踪、测量工具,能跟踪、测量BS程序
http://sourceforge.net/projects/eclipsecolorer/
AdvanQas 提供对if/else等条件语句的提示和快捷帮助(自动更改结构等)
http://eclipsecolorer.sourceforge.net/advanqas/index.html
Log4E Log4j插件,提供各种和Log4j相关的任务,如为方法、类添加一个logger等
http://log4e.jayefem.de/index.php/Main_Page
VSSPlugin VSS插件
http://sourceforge.net/projects/vssplugin
Implementors 提供跳转到一个方法的实现类,而不是接中的功能(实用!)
http://eclipse-tools.sourceforge.net/implementors/
Call Hierarchy 显示一个方法的调用层次(被哪些方法调,调了哪些方法)
http://eclipse-tools.sourceforge.net/call-hierarchy/index.html
EclipseTidy 检查和格式化HTML/XML文件
http://eclipsetidy.sourceforge.net/
Checkclipse 检查代码的风格、写法是否符合规范
http://www.mvmsoft.de/content/plugins/checkclipse/checkclipse.htm
Hibernate Synchronizer Hibernate插件,自动映射等
http://www.binamics.com/hibernatesync/
spring updatesite 插件
http://springide.org/updatesite/
VeloEclipse Velocity插件
http://propsorter.sourceforge.net/
EditorList 方便的列出所有打开的Editor
http://editorlist.sourceforge.net/
MemoryManager 内存占用率的监视
http://cloudgarden.com/memorymanager/
Eclipse的游戏插件
http://eclipse-games.sourceforge.net/
JBoss-IDE
http://jboss.sourceforge.net/jbosside/updates/
自动反编译class,安装后要设定class文件缺省关联到jode
http://www.technoetic.com/eclipse/update
jigloo swing/sw设计工具,里面自带的form/anchor布局很好用!
http://cloudgarden.soft-gems.net/update-site/
jinto的资源文件编辑工具,同时编辑多种语言,而且自动转换成iso8859-1编码。很好用!
http://www.guh-software.de/eclipse/
posted @
2007-02-13 11:17 保尔任 阅读(671) |
评论 (0) |
编辑 收藏
一、exe4j
说明:exe4j可以将Jar文件制作成exe文件,但需jre支持,也可将Jar文件放在外面。
软件性质:共享软件
下载地址:http://www.ej-technologies.com/products/exe4j/overview.html
二、JBuilder
说明:新版本的JBuilder可以直接把工程制作成各系统的可执行文件,包括Windows系统。
软件性质:商业软件
下载地址:略。我是从eMule下载的。
三、NativeJ
说明:与exe4j功能类似。
软件性质:共享软件
下载地址:http://www.dobysoft.com/products/nativej/download.html
四、Excelsior JET
说明:可以直接将Java类文件制作成exe文件,除AWT和Swing及第三方图形接口外可不需jre支持(Java5.0不行)。
软件性质:共享软件
下载地址:http://excelsior-usa.com/home.html
五、jshrink
说明:可将Jar文件打包进exe文件。同时具有混淆功能(这才是它的主要功能)。
软件性质:共享软件
下载地址:http://www.e-t.com/jshrink.html
六、InstallAnywhere
说明:打包工具,对Java打包最好用。可打包成各操作系统运行包。包括Windows系统。
软件性质:商业软件。
下载地址:http://www.zerog.com/
七、InstallShieldX
说明:与InstallAnywhere类似,但比InstallAnywhere功能强大。相对的,比较复杂,不易上手,我现在还没学会。
posted @
2007-02-13 11:16 保尔任 阅读(288) |
评论 (0) |
编辑 收藏
一、代码转换工具:
native2ascii -encoding gb2312 application_temp.properties application_zh_CN.properties
注释:-encoding gb2312 表示读application_temp.properties 的编码方式,application_temp.properties 存的是中文资源文件,application_zh_CN.properties
存的是转成ascii码后的资源文件。
二、反编译工具jad.exe:
以下假设jad.exe在c:\java目录下
1、基本用法
Usage: jad [option(s)] <filename(s)>
直接输入类文件名,且支持通配符,如下所示。
c:\java\>jad example1.class
c:\java\>jad *.class
结果是将example1.class反编译为example1.jad。将example1.jad改为example1.java即得源文件。
2、Option -o
不提示,覆盖源文件
3、Option -s
c:\java\>jad -sjava example1.class
反编译结果以.java为扩展名。
4、Option -p
将反编译结果输出到屏幕
c:\java\>jad -p example1.class
将反编译结果重定向到文件
c:\java\>jad -p example1.class>example1.java
5、Option -d
指定反编译的输出文件目录
c:\java\>jad -o -dtest -sjava *.class
三、文档生成工具javadoc.exe
大家都知道,J2SE5中的Javadoc.exe的命令行可选参数多达五十余个,其复杂性可想而知,是不是看着头都大了呢?但通常情况下,我们不想那么麻烦!
假设源代码在 C:\src 目录下,其中 com.liigo 是主包,其下可能有数十个子包,数百(千)个Java文件。目录结构大约是这样的:
- C:\
| src\
| com\
| liigo\
| ***
怎么才能以最简捷的方式生成所有的API文档呢?
c:\>
c:\>cd src
c:\src>javadoc -d doc -subpackages com.liigo
这样就搞定了,最终生成的API文档位于 c:\src\doc 目录(该目录是由javadoc.exe自动生成的)。
上面的用法利用了“当前目录”和“相对路径”,当然也可以用绝对路径:
...>javadoc -d c:\doc -sourcepath c:\src -subpackages com.liigo
最终生成的API文档位于 c:\doc 目录(该目录同样是由javadoc.exe自动生成的)。
总结一下:
我们只用到了javadoc的三个参数: -d,-subpackages,-sourcepath,其中:
参数 说明
-d 指定API文档的输出目录,默认是当前目录。建议总是指定该参数。
-sourcepath 指定源代码路径,默认是当前目录。 此参数通常是必须的。
-subpackages 以递归的方式处理各子包。关键参数!如果不使用本参数,每次只能处理一个子包(或需手工列出所有子包)。
四、运行jvm时改变内存或堆的大小
-Xms<size> set initial Java heap size
-Xmx<size> set maximum Java heap size
-Xss<size> set java thread stack size
比如:java -Xmx512M HelloWorld.class,让jvm使用512Mheap内存.
posted @
2007-02-13 11:16 保尔任 阅读(295) |
评论 (0) |
编辑 收藏
作者:江南白衣,原文出处: http://blog.csdn.net/calvinxiu/archive/2007/01/27/1495778.aspx,转载请保留出处。
Unix系统永远只会越来越多,开发人员就没必要特意学习它们的安装、配置和管理了,就全部交给集成人员吧。
但开发人员行走于Unix之间,依然有四样东西要熟练。
一、VI
虽然Unix上的文本编辑器已经越来越好用,但不在Console前面,网速也不够连XWindows的时候,还是要依赖VI。
回想VI的时代背景,发现VI对开发人员已经周到得离谱了,热键多到你双手不离键盘就能完成大半编辑工作。
建议自己制作一张自己认为有用,但又经常忘记的命令的sheet,拿出考试的力气把它背熟。
二、文本处理
开发人员在Unix下干得最多的除了Make和除Bug外,大概就是处理日志文件、业务文件进行查错和统计了。
只会more和grep是不够的,开发老手会把awk,sed,grep,sort,uniq,wc,head,tail这些文本处理命令,通过管道玩具式的拆卸拼装,最后完成一件原本以为非编写大段代码不可的工作。周到的参数设定,让人再一次感叹那个简单的年代,这样复杂到极致的设计.......怪不得《Unix 编程艺术》的作者有那么骄傲的自觉。
比如车东的每月访问TOP10 统计脚本:
awk -F '
t' '{
print
$
4
}' 2004_2
.
txt| grep chedong
.
com
/
tech
/
|
uniq -c|
sort
-rn|head -
10
- awk -F '\t' 将2004_2.txt访问纪录文件,用TAB分割,打印第4列
- grep chedong.com/tech 只列出chedong.com/tech笔记目录下的文档
- uniq -c 汇总计数
- sort -rn 按数值排序
- head -10 TOP 10
三、Bash Shell 编程
编程是开发人员的天赋本能,不论什么语言,看看参考手册应该就能上手。
见Bash新手指南中文版,一份写给新手看的包含很多老手知识的指南。
四、Make与AutoMake
用过Java的Ant后,想起Make就觉得很烦,很厌倦。总归还是会的,见GNU Make 3.8.0 中文手册
不过即使make已经精通到变态,每个人写出来的MakeFile还是千奇百怪,再看看开源项目们个个都是automake+autoconf了,我们自己也长进一点吧。手工编写MakeFile.am,让auotomake变成MakeFile.in,再让用户./configure 生成最终的MakeFile。
生成的MakeFile既能跨越平台,又是标准的写法,最重要的是,编写MakeFile.am的工作量比MakeFile少多了,只要简单的定义目标文件,先要处理的子目录,需要的源文件,头文件与库文件就可以了。如果看完下面两篇还是不懂,直接看ACE里的Makefile.am就懂了。
入门文章:使用AutoMake轻松生成Makefile
进阶文章:IBM DW:例解 autoconf 和 automake 生成 Makefile 文件
完整的免费电子书: GNU Autoconf, Automake and Libtool
另外,ACE里还贡献了一个更厉害的MPC(Makefile, Project, and Workspace Creator ), 自动的生成了MakeFile.am或者VC的项目文件。
附录A:我的VI易忘命令手册
上下左右:
ctrl+u/d 上下半屏,ctrl+f/b,上下一屏
H/G屏幕头/文章末 ,0/$ 行首行末
增删改:
yy/dd 复制/删除 一行,p/P:将yy/dd的内容paste出来
I/A 在行首/末添加, o/O 开新行,d0/d$ 删除到行首,行末
u:undo
查:
? 向前查找, n/N 重复上一次查找
附录B: 文本处理命令小结
awk:处理结构化的文本(每行以固定符号分成若干列),提取打印某些字段,如:
ls -l|awk '{print $1}' --将ls-l结果的第一列打印出来
awk -F":" '{print $1" "$6}' /etc/passwd ,将以:分割的/etc/passwd文件的第1,6列打印出来,中间以空格分开
详见IBM DW中国的AWK实例(共3篇) 或 Bash新手指南中文版第6章。
grep:过滤,大家用得最多的命令,支持正则表达式。参数有:
-i忽略大小写,-n显示line number,-c 统计在每个文件的出现次数,-l只显示符合的文件的名字。
sed:流编辑器,主要用于替换,如:
sed -e '1,10s/foo/bar/g' myfile2.txt 将1到10行的文本中的foo 替换成bar,s代表替换,g代表全局替换
支持正则的替换字符串,可以只替换某个范围内的内容。
用法不算简单,详见IBM DW中国的Sed实例(共3篇)或 Bash新手指南中文版第5章。
sort:排序,参数有:
-r逆序, -n 数字比较 , -M 日历比较 Feb,Dec, -f 忽略大小写
同样支持结构化文件,如
sort -t : -k 1,1 /etc/passwd,以: 分割,只按第1列排序
sort -t : -k 1,1 -k2.2,3.4 /etc/passwd ,以:分割,先按第1列排序,再按第2列的第二个字符到第3列的第4个字符排序。
uniq:去除重复行。
除了正常用法外,还有-c统计重复次数,和-u (唯一)和 -d (重复)两个参数,只显示唯一的和重复的行。
wc: 统计。
-l 行,-m 字符,-w 单词
posted @
2007-01-29 17:31 保尔任 阅读(310) |
评论 (0) |
编辑 收藏