移動內存算法

4
120 程序员 Technology 技术 本期的问题是: 对于有K个元素的数组int a[K]={....}; 写一个高效算法将 数组内容循环左移 m位 比如:int a[6] ={1,2,3,4,5,6}, 循环左移 3位得到结果 {456123}, 要求: 1 .不允许另外申请数组空间,但可以申请少许变量; 2 .不允许采用每次左移。 这是一个有趣的问题,有朋友给出过一个很简单的解法: 1 .将整个数组倒排; 2 .将前k-m个元素和剩下的m个元素分别倒排。 这个算法需要对每个数组元素做两次写操作,具体而言是 2n次堆变量写操作和 n次栈变量写操作(因为倒排时调用交换 算法需要一个中间变量),有没有一种方法,只对数组元素进行 一次写操作就完成移动? 最直观的想法,就是从第一个元素开始,把它一步移动到 最终的目的位置,而该位置原有的元素的值取出,移动到它的 新位置。递归进行这个步骤。 首先,我们在数学上很容易理解,这是一个一一对应的映 射,绝不会在一个位置上出现两次移动。所以不会出现移动的 递归过程中途指向了已经移动过的元素。 那么,这个递归过程唯一的终止条件就是,当前移动的元 素,目的位置是移动过程的起始位置。 有兴趣的朋友不妨在纸上推演一下这个过程,并不复杂。 多试验几种组合,细心的朋友也许会发现,这个递归过程有时 候可以遍历整个数组,有时候则会跳过若干元素。这其中有没 有什么规律? 如果按照元素的索引下标标示元素,0到k-1 中的任意元素 i,会移动到什么位置? 如果 i小于 m,它会移动到 k-m+i ,否则,它的新位置是i- m。这个过程其实可以统一在一个算式下: 对于任意的i,它的新位置i' = ((k - m) + i )%k 那么,我们可以定义这个循环链:取整数 i0 ,使得 0=<i0 <m,定义 i1=((k-m)+i0)%k ,i2=((k-m) + i1)%k ⋯⋯ix = ((k-m) + ix-1)%k 。当 ix=i0 时循环终止。此时有i0 = ix = ((k-m) + ix-1)%k = ⋯⋯=(x(k-m)+i0)%k 因为我们知道 0=<i0<m ,所以有 0 = x(k-m)%k ,也就 是说,x(k-m) = yk ,因为我们是遇到第一个x(k-m) = yk 终止,显然等号左右的值就是 k-m和 k的最小公倍数。根据数 论知识,我们知道,k,m,k-m这三个数有最大公约数q,使 得 k=aq ,m=bq ,k-m=cq ,a,b,c三个数两两互质。进而推出 x = a ,y = c 。也就是说,这个递归过程会经历a步。如果k 和 m互质,a=k ,递归过程会遍历整个区间。那么,它可以完成 对整个区间的移动操作。而如果k和 m的最大公约数q>1 呢? 在这里有一个现象,任意两个 i,它们的差要么是 0,要么是 k 和 m的最大公因是 q的整倍数。有兴趣的朋友可以尝试证明一 下。因为我们知道整个过程一共 a步,k=aq ,那么,i到ix 的序 列会形成一个步长为q的等差序列。所以,我们要移动整个0到 k-1 区间,应该对 0到 q-1 的元素应用这个递归算法。 这样的算法只需要一次堆变量写操作,但是因为需要一个 中间变量储存移动的元素,需要2n次栈变量写操作。再加上计 算目的地址,每次移动时所需的计算量略大于两次倒排,但是 在堆变量操作方面提高了效率。 本次大部分合格的参赛作品都采用了冒泡算法,通过两两交 换邻接元素来移动数组。有朋友还在来信中说明冒泡排序是线性 复杂度。这是错误的。冒泡法是 o(n2) 的,比起前面两种算法,冒 泡的效率太过低下。很多朋友在m大于或小于k/2 时采用的不同 的左 /右位移代码,其实只要理清了算法,不必要做此判断。只 有一位ID 为 njwind 的朋友想到了利用数论知识。虽然他的代码还 达不到实用标准,但是这应该是本次比赛最好的作品了: #include <stdio.h> #include <stdlib.h> #define Len 6 /* 求最大公约数的函数 */ int CommonFactor(int m,int n) { int tmp; if(m<n) { tmp = m; m = n; n = tmp; } tmp = m % n; 移动内存算法 □ 文 /刘鑫 智慧擂台 >>>

Upload: chui-wen-chiu

Post on 19-May-2015

800 views

Category:

Documents


7 download

TRANSCRIPT

Page 1: 移動內存算法

120 程序员

Technology技术

本期的问题是:

对于有K个元素的数组int a[K]={....};写一个高效算法将

数组内容循环左移m位

比如:int a[6] ={1,2,3,4,5,6},循环左移3位得到结果

{456123},

要求:

1.不允许另外申请数组空间,但可以申请少许变量;

2.不允许采用每次左移。

这是一个有趣的问题,有朋友给出过一个很简单的解法:

1.将整个数组倒排;

2.将前k-m个元素和剩下的m个元素分别倒排。

这个算法需要对每个数组元素做两次写操作,具体而言是

2n次堆变量写操作和n次栈变量写操作(因为倒排时调用交换

算法需要一个中间变量),有没有一种方法,只对数组元素进行

一次写操作就完成移动?

最直观的想法,就是从第一个元素开始,把它一步移动到

最终的目的位置,而该位置原有的元素的值取出,移动到它的

新位置。递归进行这个步骤。

首先,我们在数学上很容易理解,这是一个一一对应的映

射,绝不会在一个位置上出现两次移动。所以不会出现移动的

递归过程中途指向了已经移动过的元素。

那么,这个递归过程唯一的终止条件就是,当前移动的元

素,目的位置是移动过程的起始位置。

有兴趣的朋友不妨在纸上推演一下这个过程,并不复杂。

多试验几种组合,细心的朋友也许会发现,这个递归过程有时

候可以遍历整个数组,有时候则会跳过若干元素。这其中有没

有什么规律?

如果按照元素的索引下标标示元素,0到k-1中的任意元素

i,会移动到什么位置?

如果i小于m,它会移动到k-m+i,否则,它的新位置是i-

m。这个过程其实可以统一在一个算式下:

对于任意的i,它的新位置i' = ((k - m) + i )%k。

那么,我们可以定义这个循环链:取整数i0,使得0=<i0

<m,定义i1=((k-m)+i0)%k,i2=((k-m) + i1)%k⋯⋯ix =

((k-m) + ix-1)%k。当ix=i0时循环终止。此时有i0 = ix =

((k-m) + ix-1)%k = ⋯⋯=(x(k-m)+i0)%k。

因为我们知道0=<i0<m,所以有0 = x(k-m)%k,也就

是说,x(k-m) = yk,因为我们是遇到第一个x(k-m) = yk就

终止,显然等号左右的值就是k-m和k的最小公倍数。根据数

论知识,我们知道,k,m,k-m这三个数有最大公约数q,使

得k=aq,m=bq,k-m=cq,a,b,c三个数两两互质。进而推出

x = a,y = c。也就是说,这个递归过程会经历a步。如果k

和m互质,a=k,递归过程会遍历整个区间。那么,它可以完成

对整个区间的移动操作。而如果k和 m的最大公约数q>1呢?

在这里有一个现象,任意两个i,它们的差要么是0,要么是k

和m的最大公因是q的整倍数。有兴趣的朋友可以尝试证明一

下。因为我们知道整个过程一共a步,k=aq,那么,i到 ix的序

列会形成一个步长为q的等差序列。所以,我们要移动整个0到

k-1区间,应该对0到q-1的元素应用这个递归算法。

这样的算法只需要一次堆变量写操作,但是因为需要一个

中间变量储存移动的元素,需要2n次栈变量写操作。再加上计

算目的地址,每次移动时所需的计算量略大于两次倒排,但是

在堆变量操作方面提高了效率。

本次大部分合格的参赛作品都采用了冒泡算法,通过两两交

换邻接元素来移动数组。有朋友还在来信中说明冒泡排序是线性

复杂度。这是错误的。冒泡法是o(n2)的,比起前面两种算法,冒

泡的效率太过低下。很多朋友在m大于或小于k/2时采用的不同

的左/右位移代码,其实只要理清了算法,不必要做此判断。只

有一位ID为njwind 的朋友想到了利用数论知识。虽然他的代码还

达不到实用标准,但是这应该是本次比赛最好的作品了:

#include <stdio.h>#include <stdlib.h>#define Len 6

/* 求最大公约数的函数 */int CommonFactor(int m,int n){ int tmp; if(m<n) { tmp = m; m = n; n = tmp; } tmp = m % n;

移动内存算法□ 文/刘鑫

智慧擂台 >>>

Page 2: 移動內存算法

Programmer 121

while(tmp != 0) { m = n; n = tmp; tmp = m % n; } return n;}

void display(int *pa){ int i; printf("array: "); for(i=0;i<Len; i++) printf("%d ",pa[i]); printf("\n");}

void swap(int *n1, int *n2){ int tmp; tmp = *n1; *n1 = *n2; *n2 = tmp;}/* *p数据指针,aLen:数据长度,k:左循环位数 */void LeftRotation(int *p, int aLen, int k){ int i,tmp,factor; k = k % aLen; if ( k == 0) return; k = aLen - k; factor = CommonFactor(aLen, k); if (factor == 1){ i = 0; do{ i = (i+k)%aLen; swap(p, p + i ) ; } while(i != 0); } else{ for (i = 0;i < factor; i++){ tmp = i; do{ tmp = (tmp+k)%aLen; swap(&p[i], p + tmp ) ; } while(tmp != i); } }}int main(int argc, char *argv[]){ int i,k,a[Len]={1,2,3,4,5,6}; for(i=0;i<8;i++){ for( k = 0; k<Len ;k++) /* 数组赋值 */ a[k] = k+1; k = i; display(a); printf("Len: %d, left rotation: %d\n",Len,k); LeftRotation(a,Len,k); display(a); printf("---------------\n"); } system("PAUSE"); return 0;}

以下是用C++实现的一次移动算法代码,并进行了泛化,

使之可以适用于不同的类型,包括用不同整型变量表示k,m等

参数的应用:

求最大公因数,辗转相除,可以应用于任何求最大公因式/

数的场合,只要参数类型支持求余和赋值:

template<typename intT>intT HCF(const intT & x, const intT & y) { intT a = x, b = y, r = a%b; while(r != 0) { a = b; b = r; r = a%b; } return b;};

区间移动,支持任何类型的数组:

template<typename T,typename U>void Carry(T point[], const U & len, const U & m){ U start = 0,p = 0; T pv; U hloc = HCF(len, m); U step = len - m; for (U i = 0; i<hloc; i++) { p = i; start = p; pv = point[p] ; do { p = (step+p)%len; std::swap(point[p] , pv); }while(p != start); }};

用迭代器实现的区间移动,可以应用于STL容器等场合:

template<typename iterT,typename U>void Carry(iterT &iter, const U & len, const U & m) { iterT nb = iter + (len-m); iterT p; iterT start = point; iterT pm = point + m; iterator_traits<iterT>::value_type pv; U hloc = HCF(len, m); for (U i = 0; i<hloc; i++) { p = point + i; start = p; pv = *p ; do { if (p < pm) p = nb + (p - point); else p -= m; std::swap(*p , pv); }while(p != start); }};

代码中使用了std::swap 函数来交换变量值,这个函数在

<algorithm>中。另外为了析取迭代器的值,我使用了<iterator>

中的iterator_traits操作。有兴趣的朋友不妨编写一个适用于普通

指针的偏特化版本。

两步倒排算法的实现很简单,在这里就不写出来了。重点

是采用一个足够简单的交换和计算中界的代码。这样的功能,C

风格的代码就可以做到足够好了。

有些朋友认为直接改变索引就可以重排数组,对于某些高

级语言,例如Python中的内置线性容器list,这是可以的。但是

在C/C++中,数组和链表是不同的概念,数组是内存堆中的连

续块,不可以用这种方法来对待。对于C/C++程序,我们经常

要直接操作内存,要关注算法的复杂度,这并不是缺点,相反,

无论高级的虚拟机语言有多少好处,总需要有底层的技术来管

理内存,用尽可能高效的算法处理面向硬件的问题。否则,用

什么来开发虚拟机呢?这个算法问题实际上在内存管理,例如

虚拟机的实现方面,就有很大意义。在虚拟机内存管理中,我

们面对的总是不够快的CPU和不够大的内存,需要有一个高效

的算法来移动虚拟机管理的数据。对于Java或C#程序员,这

个过程通常是透明的,但是这不表示它不存在。而默默支撑起

虚拟机的,正是这些C/C++代码。

>>> 智慧擂台

Page 3: 移動內存算法

Programmer 123

特性并把这个构件放到一个特定的地方,

这个构件就不仅仅会收到一个位置协调的

消息,并且会记住为什么它会被抓取。例

如,如果它被抓取同另外一个构件的左边

对齐,这个时候该构件会始终保持它们的

左对齐格式,即使它们的绝对位置发生了

改变。

NetBeans中有双路编辑——使用一个可

视化设计器和一个文本编辑器。一些可视化

设计器已经实现了这种方式。但是NetBeans

IDE使用保护模块来防止用户编辑代码。

Eclipse的Visual Editor

Visual Editor是一个开源的Eclipse编

辑器。它同JDT、PDE等其它Eclipse的工

具项目一样,是一个全新的Eclipse工具项

目。它可以进行可视化的编辑Java GUI程

序,也能编辑可视化的Java Bean组件。它

能与Eclipse的Java Editor集成在一起,当

在Visual Editor中编辑图形界面时,会立

即反馈到Java Editor中的代码,反之亦

然,即无保护的双路编辑。

Visual Editor支持Swing和AWT的可视

Java组件开发。由于这个Framework设计的

具有通用性,它也可以很容易的实现C++

或其它语言下可视化开发。其将来的版本

(从1.0开始),将会支持SWT的开发。

Visual Editor目前支持所有的传统的

布局管理器。另外,SWT Designer也是流

行的GUI编辑器,不过是收费的。

JBuilder 的布局管理器

除了经典的布局管理器外,JBuilder

提供了五个方便的布局管理器,它们是:

(1)XYLayout布局管理器:组件所在

的位置通过相对于左上角坐标确定,其大

小通过宽度和长度确定,当窗口大小发生

变化时,组件的位置和大小保持原位。

(2)PaneLayout布局管理器:通过百

分比规定组件所占容器空间,当用户界

面调整时,组件的大小也会相应调整。

(3)VerticalFlowLayout布局管理器:

它和FlowLayout类似,只不过它以垂直而

非水平的方式排列组件。

(4)BoxLayout2 布局管理器:它是

Swing标准包中javax.swing.BoxLayout布

局管理器的一个包裹类,允许你通过查

看器的选择,间接使用BoxLayout的功能。

BoxLayout 将几个组件组合在一起,以水

平或者垂直的方式排列,窗口大小调整

时,组件大小不会随着改变。

(5)OverLayOut2布局管理器:它是

Swing标准包中java.swing.OverLayout 布

局管理的包裹类,允许你通过察看器的

选择,间接使用OverLayout 的功能。

GUI小结

GUI设计部分,我更喜欢NetBeans,相

信很多用户也跟我一样,因为用起来真

的很方便。

曾看见有人说,在拖拽组件时,却不

能更改代码,觉得很别扭。其实我觉得受

保护的双路编辑才是应该推崇的方法,

这也是对IDE完美工作的一种保证。

二.编译、运行、调试、打包本节将比较三个平台在编译、运行、

调试、打包几个必要开发步骤中的具体

表现。

NetBeans

在代码行开头点击即可设置/ 取消

断点;支持条件断点、单步执行等流程控

制功能;支持局部变量、监视、堆栈显示等

功能;支持会话、线程的查看及修改;提

供了完善的远程调试功能;基于Ant,可

通过脚本支持调试。

Eclipse

带有专用的Debug视图并能自动切

换;其Debug的功能和Delphi的Debug比较

相似,Inspect、Watch等应有尽有;支持反

汇编、内存、堆栈、寄存器显示等高级功

能;支持会话、线程的查看及修改;似乎

无远程调试;可根据模块的需求扩展。

JBuilder

高度集成,十分丰富的调试环境,支

持以上2款IDE的全部功能;4种编译器及各

自特有的编译选项:Borland Make、Borland

Make(JB8)、Project javac、javac;智能单步、

智能源码、智能交换;多线程调试;内置混

淆打包;JBuilder的调试环境可谓傲视群伦,

开发纯java程序时,使用起来十分方便。

三.WEB与J2EE开发JSP、Servlet

NetBeans支持Servlet 2.4和JSP 2.0。

支持使用Tomcat 5部署和调试两层J2EE

1.4和1.3应用程序。以下是NetBeans为

Web应用程序开发提供的便利:内置Tomcat

服务器支持;生成和维护部署容易的内

容,包括添加到项目中的Servlets进行注

册;生成与维护具有编译、清除、测试、

打包、部署指令的Ant脚本。该脚本使开

发者无需手动将文件移动到服务器;用

于编辑Servlets、JSP、HTML和标签库的

代码自动完成和帮助;提供“编译JSP”命

令,使用这一命令可以帮助开发者在部

署前检测JSP文件的错误,不管这些错误

出现在编译过程中还是 J S P 文件向

Servlets转换过程中;提供全面的调试支

持,包括“步进JSP文件”以及“跟踪HTTP

请求”;大量的Web模板:JSP、Servlet、

Filter、Web应用侦听器、标签库描述、标

签、标签处理、Web Service、消息处理、

Web客户端等;描述文件web.xml可视化

编辑器;监测HTTP事务处理。

E c l i p s e 在不安装LOM B O Z 或者

MyEclipse的情况下编写Web应用程序真

是麻烦之极。首先你需要手动安装Tomcat

或者其他服务器,然后在Eclipse中配置;

接下来编写代码,再更改web.xml文件做

部署。最后很可能因为配置不好的原因,

无法启动或者部署Web服务器。好在对web

.xml编辑时也是可视化的。

JBuilder一体化的解决办法倒是很好,

并且通过OpenTools框架支持很多种Web

服务器。当然也需要手动安装这些服务

器,并且添加这些服务器的Lib到你的项

>>> SUN征文

Page 4: 移動內存算法

124 程序员

Product & Application产品&应用

框架名称

StrutsHibernate

Spring

CocoonJSF

NetBeans 5.5 Eclipse 3.x JBuilder 2005

支持,向导,文档丰富 MyEclipse 良好支持 支持,向导,文档丰富Nbxdoclet支持,5.5加 Hibernate Syn支持 手动配置入数据库结构和实体类

的相互生成和向导支持,向导,文档丰富 Spring plugin 4 Eclipse 手动配置

Spring for JBuilder2005 OpenTools

暂没找到直接支持 Lepido支持 支持,向导,文档丰富支持,向导,文档丰富 JSF Tool Project支持 支持,向导,文档丰富

目中。JBuilder还为各种部署文件提供了

方便的编辑工具:

l web.xml:Web Module DD Editor

l struts-config.xml:Struts Config Editor

l faces-config.xml:Faces Config Editor

另外JBuilder也对Jsp和Servlet提供监

听器,监视各种事件的发生。

JBuilder提供了最为完善的Web开发

环境,其次Netbeans功能也十分丰富。

EJB/J2EE

NetBeans

从NetBeans IDE 4.1版本开始,

NetBeans提供创建和编辑EJB丰富的向导

和编辑器。

NetBeans 5.0提供以下针对企业开发

的支持:建立和编辑EJB的可视化编辑器;

控制CMP和实体Bean关系的可视化编辑

器;通过鼠标点击就可以在Web模块中加

入对EJB的调用;完整的组装、部署、运

行和调试企业应用程序的支持;注册和

测试Web Service;通过鼠标点击就可以在

Web模块或EJB中加入对Web Service的调

用;由EJB或者Java类自底向上的建立Web

Service;由WSDL文件自顶向下的建立Web

Service;可以导入其他IDE的J2EE项目,前

提是其与BluePrints标准兼容。

Eclipse与Lomboz

Lomboz是Eclipse的一个主要的开源插

件(open-source plug-in),Lomboz插件能

够使Java开发者更好的使用Eclipse去创建,

调试和部署基于J2EE的Java应用服务器。

Lomboz的主要功能有:使用HTML,

Servlets,JSP 等方式建立Web应用程序;

JS P 的编辑带有高亮显示和编码助手、

JSP语法检查;利用Wizard创建Web应用、

EJB应用和EJB客户端测试程序;支持部

署EAR、WAR和JAR;利用xDoclet开发符

合EJB 1.1、2.0和3.0的应用;能够实现

端口对端口的本地和远程的测试应用服

务;能够支持所有的有可扩展定义的Java

应用服务;能够利用强大的Java调试器调

试正在运行的服务器端代码(JSP&EJB);

通过使用Wizard 和代码生成器提高开发

效率;创建Web服务客户端的WSDL形式

的文件。

Lomboz 适用的服务器有:Apache

Tomcat,JBOSS,JOnAS,Resin,Orion,JRun,

Oracle IAS,BEA WebLogic Server和 IBM

WebSphere等。

三种平台对框架的支持比较

小 结

对于J2EE开发,Eclipse的各种支持是

对全面和及时的,但是否成熟,则未必。并

且开发者经常被成堆的插件弄得头昏脑胀。

NetBeans的最新版本提供了对最新标

准,如EJB 3.0的支持,并且内置对常见

框架支持。这说明,现在完全能够全面转

向NetBeans。

四.J2ME开发NetBeans Mobility

Netbeans IDE和Mobility Pack提供的

项目管理功能非常出色,将目标平台、应

用程序描述符、编译运行、混淆、签名等

功能集成在了一起。开发者只需要选中

项目,右键选择属性即可配置上述选项。

值得注意的一点是,当项目中使用了图

片或者媒体文件等资源的时候,应该在

“库和资源”选项中讲资源文件所在的文

件夹添加到“捆绑的库和资源”中。避免

在Java程序中访问资源的时候抛出空指

针异常。Mobility Pack 还直接集成了

Proguard混淆器,可以设置混淆的级别,

混淆的级别越高,混淆的力度就越大。

Mobility Pack提供了可视化用户界面

设计器,开发者可以用鼠标拖拽的方式

设计应用程序的用户界面,通过流程控

制器实现界面之间的跳转,而不用编写

任何代码。无线连接向导是Mobility Pack

另一新特性,能方便快速的开发出端到

端的企业级应用程序,服务器端只提供

需要导出的服务类,Netbeans IDE会自动

生成服务器端的Servlet以及客户端用于

连接网络的代码。虽然上述两个功能使

用起来非常方便,但是缺乏灵活性,你很

难再更改开发工具为你自动生成的代码。

Eclipse 和 EclipseME

不仅要下载安装EclipseME插件,还

要安装WTK,并且在Eclipse环境中配置。

使用Ec l i p s e 搭建J2M E 的开发环境比

Netbeans IDE稍显复杂。事实上,管理

Eclipse 的各种插件已经让开发者头疼不

已,有些插件的更新还很难保证。

JBuilder 和 WTK

从JBuilder 9版本开始,Borland将WTK

直接集成到了开发工具内。如果使用以

前版本的JBui lde r,那么需要首先安装

MoblieSet 插件。

小 结

其实,各种开发工具只是以自己的

方式对MIDP 应用程序的开发进行了封

装, MIDP应用程序的开发流程都是一样。

事实已经证明,除了使用手机供应商自

己的套件外(如Nokia Toolkit),WTK和

NetBeans Mobility的用户最多——这方面

SUN征文 >>>