blogjava-凯发k8网页登录

blogjava-凯发k8网页登录http://www.blogjava.net/killme2008/category/20137.html生活、程序、未来zh-cntue, 02 nov 2010 07:47:52 gmttue, 02 nov 2010 07:47:52 gmt60一道面试题注记http://www.blogjava.net/killme2008/archive/2010/10/28/336357.htmldennisdennisthu, 28 oct 2010 02:53:00 gmthttp://www.blogjava.net/killme2008/archive/2010/10/28/336357.htmlhttp://www.blogjava.net/killme2008/comments/336357.htmlhttp://www.blogjava.net/killme2008/archive/2010/10/28/336357.html#feedback8http://www.blogjava.net/killme2008/comments/commentrss/336357.htmlhttp://www.blogjava.net/killme2008/services/trackbacks/336357.html     javaeye的一个帖子介绍一道,取数组的最大元素和前n个大元素,取最大元素很简单,遍历即可。取前n大元素,可以利用排序,最简单的实现:

    public static int[] findtopnvalues(int[] anyoldordervalues, int n) {
        arrays.sort(anyoldordervalues);
        
int[] result = new int[n];
        system.arraycopy(anyoldordervalues, anyoldordervalues.length 
- n,
                result, 
0, n);
        
return result;
    }
   
     arrays.sort(int[])使用的是快排,平均的时间复杂度是o( n lg(n)),在一般情况下已经足够好。那么有没有平均情况下o(n)复杂度的算法?这个还是有的,这道题目其实是选择算法的变形,选择一个数组中的第n大元素,可以采用类似快排的方式划分数组,然后只要在一个子段做递归查找就可以,平均状况下可以做到o(n)的时间复杂度,对于这道题来说稍微变形下算法即可解决:

    /**
     * 求数组的前n个元素
     * 
     * 
@param anyoldordervalues
     * 
@param n
     * 
@return
     
*/
    
public static int[] findtopnvalues(int[] anyoldordervalues, int n) {
        
int[] result = new int[n];
        findtopnvalues(anyoldordervalues, 
0, anyoldordervalues.length - 1, n,
                n, result);
        
return result;
    }

    
public static final void findtopnvalues(int[] a, int p, int r, int i,
            
int n, int[] result) {
        
// 全部取到,直接返回
        if (i == 0)
            
return;
        
// 只剩一个元素,拷贝到目标数组
        if (p == r) {
            system.arraycopy(a, p, result, n 
- i, i);
            
return;
        }
        
int len = r - p  1;
        
if (i > len || i < 0)
            
throw new illegalargumentexception();
        
// if (len < 7) {
        
// arrays.sort(a, p, r 1);
        
// system.arraycopy(a, r - i 1 , result, n - i, i);
        
// return;
        
// }

        
// 划分
        int q = medpartition(a, p, r);
        
// 计算右子段长度
        int k = r - q  1;
        
// 右子段长度恰好等于i
        if (i == k) {
            
// 拷贝右子段到结果数组,返回
            system.arraycopy(a, q, result, n - i, i);
            
return;
        } 
else if (k > i) {
            
// 右子段比i长,递归到右子段求前i个元素
            findtopnvalues(a, q  1, r, i, n, result);
        } 
else {
            
// 右子段比i短,拷贝右子段到结果数组,递归左子段求前i-k个元素
            system.arraycopy(a, q, result, n - i, k);
            findtopnvalues(a, p, q 
- 1, i - k, n, result);
        }
    }

    
public static int medpartition(int x[], int p, int r) {
        
int len = r - p  1;
        
int m = p  (len >> 1);
        
if (len > 7) {
            
int l = p;
            
int n = r;
            
if (len > 40) { // big arrays, pseudomedian of 9
                int s = len / 8;
                l 
= med3(x, l, l  s, l  2 * s);
                m 
= med3(x, m - s, m, m  s);
                n 
= med3(x, n - 2 * s, n - s, n);
            }
            m 
= med3(x, l, m, n); // mid-size, med of 3
        }
        
if (m != r) {
            
int temp = x[m];
            x[m] 
= x[r];
            x[r] 
= temp;
        }
        
return partition(x, p, r);
    }

    
private static int med3(int x[], int a, int b, int c) {
        
return x[a] < x[b] ? (x[b] < x[c] ? b : x[a] < x[c] ? c : a)
                : x[b] 
> x[c] ? b : x[a] > x[c] ? c : a;
    }

    
public static void swap(int[] a, int i, int j) {
        
int temp = a[i];
        a[i] 
= a[j];
        a[j] 
= temp;
    }

    
public static int partition(int a[], int p, int r) {
        
int x = a[r];
        
int m = p - 1;
        
int j = r;
        
while (true) {
            
do {
                j
--;
            } 
while (j>=p&&a[j] > x);
            
do {
                m
;
            } 
while (a[m] < x);
            
            
if (j < m)
                
break;
            swap(a, m, j);
        }
        swap(a, r, j 
 1);
        
return j  1;
    }
 选择算法还有最坏情况下o(n)复杂度的实现,有兴趣可以读算法导论和。题外,我测试了下这两个实现,在我的机器上大概有2倍多的差距,还是很明显。



dennis 2010-10-28 10:53
]]>
linkedlist的局限http://www.blogjava.net/killme2008/archive/2010/09/16/332168.htmldennisdennisthu, 16 sep 2010 05:51:00 gmthttp://www.blogjava.net/killme2008/archive/2010/09/16/332168.htmlhttp://www.blogjava.net/killme2008/comments/332168.htmlhttp://www.blogjava.net/killme2008/archive/2010/09/16/332168.html#feedback6http://www.blogjava.net/killme2008/comments/commentrss/332168.htmlhttp://www.blogjava.net/killme2008/services/trackbacks/332168.html     java.util.linkedlist是双向链表,这个大家都知道,比如java的基础面试题喜欢问arraylist和linkedlist的区别,在什么场景下用。大家都会说linkedlist随机增删多的场景比较合适,而arraylist的随机访问多的场景比较合适。更进一步,我有时候会问,linkedlist.remove(object)方法的时间复杂度是什么?有的人回答对了,有的人回答错了。回答错的应该是没有读过源码。

    理论上说,双向链表的删除的时间复杂度是o(1),你只需要将要删除的节点的前节点和后节点相连,然后将要删除的节点的前节点和后节点置为null即可,
//伪代码
node.prev.next=node.next;
node.next.prev
=node.prev;
node.prev
=node.next=null;

这个操作的时间复杂度可以认为是o(1)级别的。但是linkedlist的实现是一个通用的数据结构,因此没有暴露内部的节点entry对象,remove(object)传入的object其实是节点存储的value,这里还需要一个查找过程:
  public boolean remove(object o) {
        
if (o==null) {
            
for (entry<e> e = header.next; e != header; e = e.next) {
                
if (e.element==null) {
                    remove(e);
                    
return true;
                }
            }
        } 
else {
            
//查找节点entry
            for (entry<e> e = header.next; e != header; e = e.next) {
                
if (o.equals(e.element)) {
                    
//删除节点
                    remove(e);
                    
return true;
                }
            }
        }
        
return false;
    }


    删除节点的操作就是刚才伪代码描述的:
   private e remove(entry<e> e) {
        e result 
= e.element;
    e.previous.next 
= e.next;
    e.next.previous 
= e.previous;
        e.next 
= e.previous = null;
        e.element 
= null;
    size
--;
    modcount
;
        
return result;
    }

    因此,显然,linkedlist.remove(object)方法的时间复杂度是o(n) o(1),结果仍然是o(n)的时间复杂度,而非推测的o(1)复杂度。最坏情况下要删除的元素是最后一个,你都要比较n-1次才能找到要删除的元素。

    既然如此,说linkedlist适合随机删减有个前提,链表的大小不能太大,如果链表元素非常多,调用remove(object)去删除一个元素的效率肯定有影响,一个简单测试,插入100万数据,随机删除1000个元素:
        final list<integer> list = new linkedlist<integer>();
        
final int count = 1000000;
        
for (int i = 0; i < count; i) {
            list.add(i);
        }
        
final random rand=new random();
        
long start=system.nanotime();
        
for(int i=0;i<1000;i){
            
//这里要强制转型为integer,否则调用的是remove(int)
            list.remove((integer)rand.nextint(count));
        }
        system.out.println((system.nanotime()
-start)/math.pow(109));
   
    在我的机器上耗时近9.5秒,删除1000个元素耗时9.5秒,是不是很恐怖?注意到上面的注释,产生的随机数强制转为integer对象,否则调用的是remove(int)方法,而非remove(object)。如果我们调用remove(int)根据索引来删除:
        for(int i=0;i<1000;i){
            list.remove(rand.nextint(list.size()
-1));
        }
    随机数范围要递减,防止数组越界,换成remove(int)效率提高不少,但是仍然需要2.2秒左右(包括了随机数产生开销)。这是因为remove(int)的实现很有技巧,它首先判断索引位置在链表的前半部分还是后半部分,如果是前半部分则从head往前查找,如果在后半部分,则从head往后查找(linkedlist的实现是一个环):
        entry<e> e = header;
        
if (index < (size >> 1)) {
            
//前一半,往前找
            for (int i = 0; i <= index; i)
                e 
= e.next;
        } 
else {
            
//后一半,往后找
            for (int i = size; i > index; i--)
                e 
= e.previous;
        }

     最坏情况下要删除的节点在中点左右,查找的次数仍然达到n/2次,但是注意到这里没有比较的开销,并且比remove(object)最坏情况下n次查找还是好很多。

    总结下,linkedlist的两个remove方法,remove(object)和remove(int)的时间复杂度都是o(n),在链表元素很多并且没有索引可用的情况下,linkedlist也并不适合做随机增删元素。在对性能特别敏感的场景下,还是需要自己实现专用的双向链表结构,真正实现o(1)级别的随机增删。更进一步,jdk5引入的concurrentlinkedqueue是一个非阻塞的线程安全的双向队列实现,同样有本文提到的问题,有兴趣可以测试一下在大量元素情况下的并发随机增删,效率跟自己实现的特定类型的线程安全的链表差距是惊人的。

    题外,arraylist比linkedlist更不适合随机增删的原因是多了一个数组移动的动作,假设你删除的元素在m,那么除了要查找m次之外,还需要往前移动n-m-1个元素。


   


dennis 2010-09-16 13:51
]]>
快速排序及优化http://www.blogjava.net/killme2008/archive/2010/09/08/quicksort_optimized.htmldennisdenniswed, 08 sep 2010 11:10:00 gmthttp://www.blogjava.net/killme2008/archive/2010/09/08/quicksort_optimized.htmlhttp://www.blogjava.net/killme2008/comments/331404.htmlhttp://www.blogjava.net/killme2008/archive/2010/09/08/quicksort_optimized.html#feedback6http://www.blogjava.net/killme2008/comments/commentrss/331404.htmlhttp://www.blogjava.net/killme2008/services/trackbacks/331404.html阅读全文

dennis 2010-09-08 19:10
]]>
缓存的分代http://www.blogjava.net/killme2008/archive/2010/06/05/322822.htmldennisdennissat, 05 jun 2010 02:13:00 gmthttp://www.blogjava.net/killme2008/archive/2010/06/05/322822.htmlhttp://www.blogjava.net/killme2008/comments/322822.htmlhttp://www.blogjava.net/killme2008/archive/2010/06/05/322822.html#feedback5http://www.blogjava.net/killme2008/comments/commentrss/322822.htmlhttp://www.blogjava.net/killme2008/services/trackbacks/322822.html     类似分代的思想在很多地方可以用到,分代的本质是根据对象生命周期的不同做区别处理,而不是采取一刀切的方式来提高系统的处理效率。推而广之,比如缓存的使用,现在很多web应用都采用了类似memcached这样的缓存挡在数据库前面分担负载,那么你可以将memcached理解成new代,而数据库是old代。memcached中存储的是查询的热点数据,新鲜火热,但是易失,并且在数据更新的时候被移除;而数据库保存了所有的数据,当缓存没有命中的时候才去查询数据库并存储到缓存。new和old这只是简单的二代划分,事实上现在越来越多的系统是多级缓存,页面缓存、memcached缓存、jvm内部缓存、查询缓存等等直到数据库,从web页面到后端是一个越来越old的过程,缓存对象持续的生命周期逐渐增长直到persist状态。
    具体到jvm内部缓存,我们通常使用lru算法来实现一个安全有限的缓存,如直接继承linkedhashmap将可以实现一个简单的lrumap。基于内存使用上的考虑,我们会给lrumap设定一个最大的capacity,当数据量超过这个capacity的时候将最近最少访问的元素移除来容纳新的元素。这样处理产生的问题是这个capactity不能设置得太大,因为怕内存不够用,但是不够大的结果可能是命中率没有那么高(跟你的场景相关),那么如何在保存内存安全的前提下更进一步缓存更多的对象呢?答案也是分代。lrumap默认存储的都是对象的强引用,我们知道java还有其他3种引用类:soft,weak和phantom。其中soft引用是在jvm内存不够的时候进行回收,weak引用是在垃圾回收碰到的时候会被回收,显然weak->soft->strong三类引用的生命周期可以划分为3个代,我们将可以实现一个可以容纳更多对象的lrumap:lrumap设置两个阀值,一个是强引用的最大阀值,这个不能太大,一个是软引用的最大数目,当超过第一个阀值的时候,不是将lru替换出来的对象移除,而是替代转换为软引用存储;如果软引用的数目也超过阀值,那么可以将软引用这个map里的对象lru替换成weak引用存储或者简单移除。处理元素查询的时候,多了一个步骤,在查询强引用未果的情况下,需要再去查询软引用集合,不过都是o(1)复杂度的查询,不会成为明显的瓶颈。通过将缓存对象分代,我们实现了容难更多缓存对象的目标,大部分对象以强引用的形式存储,被lru替换出去最近最少访问的元素以软引用存储,它们在内存不够的时候被垃圾回收,保证了内存使用上的安全性。我们在系统中采用了类似这样的缓存,缓存的命中率有了明显的提高。
    题目是《缓存的分代》,其实谈的是分代这种常见的设计或者说技巧,在需要处理大量对象的场景中,不采用一刀切的方式,而是根据对象的特点进行适当的分代处理,带来的效率提升可能是惊人的。

    ps.关于这个罗嗦两句,我是这个小组的成员,有人质疑我的目的是为了赚推荐费,这个不能说没有,不过主要目的还是招人,我们很缺人。那么多要求可以归结为一句话:我们找java基础良好、对并发通信有丰富实践经验、写代码相对靠谱、为人相对靠谱的人。那些要求并非硬性,如果你觉的合适,尽管投简历,谢谢。我们小组做的东西我认为还是有价值的,也很有挑战,淘宝内部的很多应用都在使用,如果你希望你做的产品被成千上万的人每天使用,欢迎加入。

     

   



dennis 2010-06-05 10:13
]]>
project euler 18题解答http://www.blogjava.net/killme2008/archive/2009/09/27/296582.htmldennisdennissat, 26 sep 2009 18:52:00 gmthttp://www.blogjava.net/killme2008/archive/2009/09/27/296582.htmlhttp://www.blogjava.net/killme2008/comments/296582.htmlhttp://www.blogjava.net/killme2008/archive/2009/09/27/296582.html#feedback2http://www.blogjava.net/killme2008/comments/commentrss/296582.htmlhttp://www.blogjava.net/killme2008/services/trackbacks/296582.htmlby starting at the top of the triangle below and moving to adjacent numbers on the row below, the maximum total from top to bottom is 23.

3
7 4
4 6
8 5 9 3

that is, 3 7 4 9 = 23.

find the maximum total from top to bottom of the triangle below:

75
95 64
17 47 82
18 35 87 10
20 04 82 47 65
19 01 23 75 03 34
88 02 77 73 07 63 67
99 65 04 28 06 16 70 92
41 41 26 56 83 40 80 70 33
41 48 72 33 47 32 37 16 94 29
53 71 44 65 25 43 91 52 97 51 14
70 11 33 28 77 73 17 78 39 68 17 57
91 71 52 38 17 14 91 43 58 50 27 29 48
63 66 04 68 89 53 67 30 73 16 69 87 40 31
04 62 98 27 23 09 70 98 73 93 38 53 60 04 23

note: as there are only 16384 routes, it is possible to solve this problem by trying every route. however, , is the same challenge with a triangle containing one-hundred rows; it cannot be solved by brute force, and requires a clever method! ;o)

    最简单的方法就是穷举,从根节点出发,每个节点都有两个分叉,到达底部的路径有估计有2的指数级的数目(有会算的朋友请留言,我的组合数学都还给老师了),不过这道题显然是符合动态规划的特征,往下递增一层的某个节点的最佳结果f[i][j]肯定是上一层两个入口节点对应的最佳结果的最大值,也就是f[i-1][j]或者f[i-1][j 1],递归的边界就是定点f[0][0]=75。因此我的解答如下,考虑了金字塔边界的情况,数据按照金字塔型存储在numbers.txt中,

import java.io.bufferedreader;
import java.io.inputstreamreader;

public class euler18problem {

    
public static void maxsun(int[][] a, int rows, int cols) {
        
// 结果列表
        int[][] f = new int[15][15];
        
// 路径,用于输出计算路径
        int[][] path = new int[15][15];
        
// 递归边界
        f[0][0= a[0][0];
        path[
0][0= 0;
        
// 递推
        for (int i = 1; i < rows; i) {
            
int col = i  1;
            
// 决策
            for (int j = 0; j < col; j) {
                
// 左边界
                if (j - 1 < 0) {
                    f[i][j] 
= f[i - 1][j]  a[i][j];
                    path[i][j] 
= j;
                } 
else if (j  1 > col) { // 右边界
                    f[i][j] = f[i - 1][j - 1 a[i][j];
                    path[i][j] 
= j - 1;
                } 
else {
                    
// 处于中间位置
                    if (f[i - 1][j] <= f[i - 1][j - 1]) {
                        f[i][j] 
= f[i - 1][j - 1 a[i][j];
                        path[i][j] 
= j - 1;
                    } 
else {
                        f[i][j] 
= f[i - 1][j]  a[i][j];
                        path[i][j] 
= j;
                    }
                }
            }
        }
        
// 求出结果
        int result = 0, col = 0;
        
for (int i = 0; i < cols; i) {
            
if (f[14][i] > result) {
                result 
= f[14][i];
                col 
= i;
            }
        }
        
// 输出路径
        system.out.println("row=14,col="  col  ",value="  a[14][col]);
        
for (int i = rows - 2; i >= 0; i--) {
            col 
= path[i][col];
            system.out.println(
"row="  i  ",col="  col  ",value="
                    
 a[i][col]);
        }

        system.out.println(result);

    }

    
public static void main(string[] args) throws exception {
        
int rows = 15;
        
int cols = 15;
        
int[][] a = new int[rows][cols];

        bufferedreader reader 
= new bufferedreader(new inputstreamreader(
                euler18problem.
class.getresourceasstream("/numbers.txt")));
        string line 
= null;
        
int row = 0;
        
while ((line = reader.readline()) != null && !line.trim().equals("")) {
            string[] numbers 
= line.split(" ");
            
for (int i = 0; i < numbers.length; i) {
                a[row][i] 
= integer.parseint(numbers[i]);
            }
            row
;
        }
        reader.close();

        maxsun(a, rows, cols);

    }
}
     执行结果如下,包括了路径输出:
row=14,col=9,value=93
row
=13,col=8,value=73
row
=12,col=7,value=43
row
=11,col=6,value=17
row
=10,col=5,value=43
row
=9,col=4,value=47
row
=8,col=3,value=56
row
=7,col=3,value=28
row
=6,col=3,value=73
row
=5,col=2,value=23
row
=4,col=2,value=82
row
=3,col=2,value=87
row
=2,col=1,value=47
row
=1,col=0,value=95
row
=0,col=0,value=75
1074

    ps.并非我闲的蛋疼在半夜做题,只是被我儿子折腾的无法睡觉了,崩溃。


dennis 2009-09-27 02:52
]]>
os的进程调度(读书笔记)http://www.blogjava.net/killme2008/archive/2009/06/28/284459.htmldennisdennissun, 28 jun 2009 05:28:00 gmthttp://www.blogjava.net/killme2008/archive/2009/06/28/284459.htmlhttp://www.blogjava.net/killme2008/comments/284459.htmlhttp://www.blogjava.net/killme2008/archive/2009/06/28/284459.html#feedback0http://www.blogjava.net/killme2008/comments/commentrss/284459.htmlhttp://www.blogjava.net/killme2008/services/trackbacks/284459.html 一、调度的目标
1、首先要区分程序使用cpu的三种模式:io密集型、计算密集型和平衡型。对于io密集型程序来说,响应时间非常重要;对于cpu密集型来说,cpu的周转时间就比较重要;对于平衡型程序来说,响应和周转之间的平衡是最重要的。
2、cpu的调度就是要达到极小化平均响应时间、极大化系统吞吐率、保持系统各个功能部件均处于繁忙状态和提供某种公平的机制。
3、对于实时系统来说,调度的目标就是要达到截止时间前完成所应该完成的任务和提供性能的可预测性。

二、调度算法

1、fcfs(first come first serve),或者称为fifo算法,先来先处理。这个算法的优点是简单,实现容易,并且似乎公平;缺点在于短的任务有可能变的非常慢,因为其前面的任务占用很长时间,造成了平均响应时间非常慢。

2、时间片轮询算法,这是对fifo算法的改进,目的是改善短程序(运行时间短)的响应时间,其方法就是周期性地进行进程切换。这个算法的关键点在于时间片的选择,时间片过大,那么轮转就越接近fifo,如果太小,进程切换的开销大于执行程序的开销,从而降低了系统效率。因此选择合适的时间片就非常重要。选择时间片的两个需要考虑的因素:一次进程切换所使用的系统消耗以及我们能接受的整个系统消耗、系统运行的进程数。
    时间片轮询看上起非常公平,并且响应时间非常好,然而时间片轮转并不能保证系统的响应时间总是比fifo短,这很大程度上取决于时间片大小的选择,以及这个大小与进程运行时间的相互关系。

3、stcf算法(short time to complete first),顾名思义就是短任务优先算法。这种算法的核心就是所有的程序都有一个优先级,短任务的优先级比长任务的高,而os总是安排优先级高的进程运行。
    stcf又分为两类:非抢占式和抢占式。非抢占式stcf就是让已经在cpu上运行的程序执行到结束或者阻塞,然后在所有的就绪进程中选择执行时间最短的来执行;而抢占式stcf就不是这样,在每进来一个新的进程时,就对所有进程(包括正在cpu上执行的进程)进行检查,谁的执行时间短,就运行谁。

    stcf总是能提供最优的响应时间,然而它也有缺点,第一可能造成长任务的程序无法得到cpu时间而饥饿,因为os总是优先执行短任务;其次,关键问题在于我们怎么知道程序的运行时间,怎么预测某个进程需要的执行时间?通常有两个办法:使用启发式方法估算(例如根据程序大小估算),或者将程序执行一遍后记录其所用的cpu时间,在以后的执行过程中就可以根据这个测量数据来进行stcf调度。

4、优先级调度,stcf遇到的问题是长任务的程序可能饥饿,那么优先级调度算法可以通过给长任务的进程更高的优先级来解决这个问题;优先级调度遇到的问题可能是短任务的进程饥饿,这个可以通过动态调整优先级来解决。实际上动态调整优先级(称为权值) 时间片轮询的策略正是linux的进程调度策略之一的 sched_other分时调度策略,它的调度过程如下:

(1)创建任务指定采用分时调度策略,并指定优先级nice值(-20~19)。

(2)将根据每个任务的nice值确定在cpu上的执行时间(counter)。

(3)如果没有等待资源,则将该任务加入到就绪队列中。

(4)调度程序遍历就绪队列中的任务,通过对每个任务动态优先级的计算(counter 20-nice)结果,选择计算结果最大的一个去运行,当这个时间片用完后(counter减至0)或者主动放弃cpu时,该任务将被放在就绪队列末尾(时间片用完)或等待队列(因等待资源而放弃cpu)中。

(5)此时调度程序重复上面计算过程,转到第4步。

(6)当调度程序发现所有就绪任务计算所得的权值都为不大于0时,重复第2步。

linux还有两个实时进程的调度策略:fifo和rr,实时进程会立即抢占非实时进程。

5、显然,没有什么调度算法是毫无缺点的,因此现代os通常都会采用混合调度算法。例如将不同的进程分为几个大类,每个大类有不同的优先级,不同大类的进程的调度取决于大类的优先级,同一个大类的进程采用时间片轮询来保证公平性。

6、其他调度算法,保证调度算法保证每个进程享用的cpu时间完全一样;彩票调度算法是一种概率调度算法,通过给进程“发彩票”的多少,来赋予不同进程不同的调用时间,彩票调度算法的优点是非常灵活,如果你给短任务发更多“彩票”,那么就类似stcf调度,如果给每个进程一样多的“彩票”,那么就类似保证调度;用户公平调度算法,是按照每个用户,而不是按照每个进程来进行公平分配cpu时间,这是为了防止贪婪用户启用了过多进程导致系统效率降低甚至停顿。

7、实时系统的调度算法,实时系统需要考虑每个具体任务的响应时间必须符合要求,在截止时间前完成。
(1)edf调度算法,就是最早截止任务优先(earliest deadline first)算法,也就是让最早截止的任务先做。当新的任务过来时,如果它的截止时间更靠前,那么就让新任务抢占正在执行的任务。edf算法其实是贪心算法的一种体现。如果一组任务可以被调度(也就是所有任务的截止时间在理论上都可以得到满足),那么edf可以满足。如果一批任务不能全部满足(全部在各自的截止时间前完成),那edf满足的任务数最多,这就是它最优的体现。edf其实就是抢占式的stcf,只不过将程序的执行时间换成了截止时间。edf的缺点在于需要对每个任务的截止时间做计算并动态调整优先级,并且抢占任务也需要消耗系统资源。因此它的实际效果比理论效果差一点。

(2)rms调度算法,edf是动态调度算法,而rms(rate monotonic scheduling)算法是一种静态最优算法;该算法在进行调度前先计算出所有任务的优先级,然后按照计算出来的优先级进行调度,任务执行中间既不接收新任务,也不进行优先级调整或者cpu抢占。因此它的优点是系统消耗小,缺点就是不灵活了。对于rms算法,关键点在于判断一个任务组是否能被调度,这里有一个定律,如果一个系统的所有任务的cpu利用率都低于ln2,那么这些任务的截止时间均可以得到满足,ln2约等于0.693147,也就是此时系统还剩下有30%的cpu时间。这个证明是liu和kayland在1973年给出的。

三、优先级反转
1、什么是优先级反转?
    优先级反转是指一个低优先级的任务持有一个被高优先级任务所需要的共享资源。高优先任务由于因资源缺乏而处于受阻状态,一直等到低优先级任务释放资源为止。而低优先级获得的cpu时间少,如果此时有优先级处于两者之间的任务,并且不需要那个共享资源,则该中优先级的任务反而超过这两个任务而获得cpu时间。如果高优先级等待资源时不是阻塞等待,而是忙循环,则可能永远无法获得资源,因为此时低优先级进程无法与高优先级进程争夺cpu时间,从而无法执行,进而无法释放资源,造成的后果就是高优先级任务无法获得资源而继续推进。

2、凯发天生赢家一触即发官网的解决方案:
(1)设置优先级上限,给临界区一个高优先级,进入临界区的进程都将获得这个高优先级,如果其他试图进入临界区的进程的优先级都低于这个高优先级,那么优先级反转就不会发生。

(2)优先级继承,当一个高优先级进程等待一个低优先级进程持有的资源时,低优先级进程将暂时获得高优先级进程的优先级别,在释放共享资源后,低优先级进程回到原来的优先级别。嵌入式系统vxworks就是采用这种策略。
    这里还有一个八卦,1997年的美国的火星探测器(使用的就是vxworks)就遇到一个优先级反转问题引起的故障。简单说下,火星探测器有一个信息总线,有一个高优先级的总线任务负责总线数据的存取,访问总线都需要通过一个互斥锁(共享资源出现了);还有一个低优先级的,运行不是很频繁的气象搜集任务,它需要对总线写数据,也就同样需要访问互斥锁;最后还有一个中优先级的通信任务,它的运行时间比较长。平常这个系统运行毫无问题,但是有一天,在气象任务获得互斥锁往总线写数据的时候,一个中断发生导致通信任务被调度就绪,通信任务抢占了低优先级的气象任务,而无巧不成书的是,此时高优先级的总线任务正在等待气象任务写完数据归还互斥锁,但是由于通信任务抢占了cpu并且运行时间比较长,导致气象任务得不到cpu时间也无法释放互斥锁,本来是高优先级的总线任务也无法执行,总线任务无法及时执行的后果被探路者认为是一个严重错误,最后就是整个系统被重启。vxworks允许优先级继承,然而遗憾的工程师们将这个选项关闭了。

(3)第三种方法就是使用中断禁止,通过禁止中断来保护临界区,采用此种策略的系统只有两种优先级:可抢占优先级和中断禁止优先级。前者为一般进程运行时的优先级,后者为运行于临界区的优先级。火星探路者正是由于在临界区中运行的气象任务被中断发生的通信任务所抢占才导致故障,如果有临界区的禁止中断保护,此一问题也不会发生。
 




dennis 2009-06-28 13:28
]]>
java.util.hashmap源码要点浅析http://www.blogjava.net/killme2008/archive/2009/04/15/265721.htmldennisdenniswed, 15 apr 2009 04:33:00 gmthttp://www.blogjava.net/killme2008/archive/2009/04/15/265721.htmlhttp://www.blogjava.net/killme2008/comments/265721.htmlhttp://www.blogjava.net/killme2008/archive/2009/04/15/265721.html#feedback0http://www.blogjava.net/killme2008/comments/commentrss/265721.htmlhttp://www.blogjava.net/killme2008/services/trackbacks/265721.html
for (entry<k,v> e = table[indexfor(hash, table.length)];
             e 
!= null;
             e 
= e.next) {
            object k;
            
if (e.hash == hash &&
                ((k 
= e.key) == key || (key != null && key.equals(k))))
                
return e;
 }
   hashmap采用链表法而不是开放地址法,猜想可能的原因是从实用角度出发,对空间和时间效率做出的折中选择。采用开放地址法,无论是线性探测或者二次探测都可能造成群集现象,而双重散列会要求散列表的装填程度比较低的情况下会有比较好的查找效率,容易造成空间的浪费。

2、什么是负载因子?负载因子a定义为
     a=散列表的实际元素数目(n)/ 散列表的容量(m)

负载因子衡量的是一个散列表的空间的使用程度,负载因子越大表示散列表的装填程度越高,反之愈小。对于使用链表法的散列表来说,查找一个元素的平均时间是 o(1 a),因此如果负载因子越大,对空间的利用更充分,然而后果是查找效率的降低;如果负载因子太小,那么散列表的数据将过于稀疏,对空间造成严重浪费。

回到hashmap的实现,hashmap中的loadfactor其实定义的就是该map对象允许的最大的负载因子,如果超过这个系数将重新resize。这个是通过threshold字段来判断,看threshold的计算:
threshold = (int)(capacity * loadfactor);

结合上面的负载因子的定义公式可知,threshold就是在此loadfactor和capacity对应下允许的最大元素数目,超过这个数目就重新resize,以降低实际的负载因子。默认的的负载因子0.75是对空间和时间效率的一个平衡选择。注意到的一点是resize的规模是现有 capacity的两倍:
  if (size >= threshold)
            resize(
2 * table.length);
 
3、可能你也注意到了,java.util.hashmap对key的hash值多做了一步处理,而不是直接使用hashcode:

static int hash(int h) {
        h 
^= (h >>> 20^ (h >>> 12);
        
return h ^ (h >>> 7^ (h >>> 4);
  }

这个处理的原因在于hashmap的容量总是采用2的p次幂,而取index(槽位)的方法是
static int indexfor(int h, int length) {
        
return h & (length-1);
 }

这一运算等价于对length取模,也就是
       h % 2^p
返回的将是h的p个最低位组成的数字,我们假设hash输入是符合简单一致散列,然而这一假设并不能推论出hash的p个最低位也会符合简单一致散列,也许h的这p个最低位相同的几率很大,那么冲突的几率就非常大了。优秀的散列函数应该需要考虑所有的位。

因此为了防止这些“坏”的散列函数造成效率的降低,hashmap预先对hash值做了处理以考虑到所有的位,根据注释也可以知道。这个处理我看不懂,留待高人解释,也许来自于某本算法书也不一定。

4、我们知道java.util.hashmap不是线程安全的,因此如果在使用迭代器的过程中有其他线程修改了map,那么将抛出concurrentmodificationexception,这就是所谓fail-fast策略(速错),这一策略在源码中的实现是通过 modcount域,modcount顾名思义就是修改次数,对hashmap内容的修改都将增加这个值,那么在迭代器初始化过程中会将这个值赋给迭代器的expectedmodcount,

 hashiterator() {
            expectedmodcount 
= modcount;
            
if (size > 0) { // advance to first entry
                entry[] t = table;
                
while (index < t.length && (next = t[index]) == null)
                    ;
            }
        }

在迭代过程中,判断modcount跟expectedmodcount是否相等,如果不相等就表示已经有其他线程修改了map

  
final entry<k,v> nextentry() {
            
if (modcount != expectedmodcount)
                
throw new concurrentmodificationexception();
 注意到modcount声明为volatile,保证线程之间修改的可见性。
 






dennis 2009-04-15 12:33
]]>
sicp4.1.1-4.1.5节部分习题尝试解答(update)http://www.blogjava.net/killme2008/archive/2008/06/01/205155.htmldennisdennissun, 01 jun 2008 07:51:00 gmthttp://www.blogjava.net/killme2008/archive/2008/06/01/205155.htmlhttp://www.blogjava.net/killme2008/comments/205155.htmlhttp://www.blogjava.net/killme2008/archive/2008/06/01/205155.html#feedback0http://www.blogjava.net/killme2008/comments/commentrss/205155.htmlhttp://www.blogjava.net/killme2008/services/trackbacks/205155.html 习题4.2,如果将application?判断放在define?判断之前,那么求值(define x 3)将把define当作一般的procedure应用于参数x和3,可是define是特殊的语法形式,而非一般过程,导致出错。
习题4.4,我的解答,eval增加两个判断:
 ((and? exp)
   (eval
-and (and-exps exp) env))
 ((or
? exp)
   (eval
-or (or-exps exp) env))
实现谓词和各自的过程:
(define (and? exp) 
  (tagged
-list? exp 'and))
(define (and-exps exp)
  (cdr exp))
(define (eval
-and exps env)
  (cond ((
null? exps)
          
'true)
        (else
          (let ((result (eval (car exps) env)))
            (
if (not result)
                result
                (eval
-and (cdr exps) env))))))
(define (or
? exp)
  (tagged
-list? exp 'or))
(define (or-exps exp) (cdr exp))
(define (eval
-or exps env)
  (cond ((
null? exps)
         
'false)
        (else
         (let ((result (eval (car exps) env)))
           (
if result
               result
               (eval
-or (cdr exps) env))))))

如果用宏实现就简单了:
(define-syntax and
  (syntax
-rules ()
      ((_) #t)
      ((_ e) e)
      ((_ e1 e2 e3 )
        (
if e1 (and e2 e3 ) #f))))
(define
-syntax or
   (syntax
-rules ()
             ((_) #f)
             ((_ e) e)
             ((_ e1 e2 e3 )
              (let ((t e1))
                  (
if t t (or e2 e3 ))))))

习题4.5,cond的扩展形式,也不难,在else子句之外增加个判断,是否带有=>符号,修改expand-clauses过程:
(define (cond-extended-clauses? clause)
  (and (> (length clause) 2) (eq? (cadr clause) '=>)))
(define (extended-cond-test clause)
  (car clause))
(define (extended-cond-recipient clause)
  (caddr clause)
(define (expand
-clauses clauses)
  (
if (null? clauses)
      
'false
      (let ((first (car clauses))
            (rest (cdr clauses)))
        (cond ((cond
-else-clauses? first)
                (
if (null? rest)
                    (sequence
->exp (cond-actions first))
                    (error 
"else clause is not last" clauses)))
              ((cond
-extended-clauses? first)  ;判断是否扩展形式
               (make
-if
                   (extended
-cond-test first)
                    (list
                      (extended
-cond-recipient first)
                      (extended
-cond-test first))
                      (expand
-clauses rest)))
              (
else
               (make
-if (cond-predicate first)
                        (sequence
->exp (cond-actions first))
                        (expand
-clauses rest)))))))

习题4.6,let如果用宏定义,类似这样:
(define-syntax let
  (syntax
-rules ()
    ((_ ((x v) ) e1 e2 )
     ((
lambda (x ) e1 e2 ) v ))))
求值器扩展,实现let->combination过程:
(define (let? exp)
  (tagged
-list? exp 'let))
(define (let->combination exp)
  (let ((vars (map car (cadr exp)))
        (vals (map cadr (cadr exp)))
        (body (caddr exp)))
  (cons (make
-lambda vars (list body)) vals)))
我们做的仅仅是syntax transform,将let转成对应的lambda形式。

习题4.7,这里的关键在于let*->netsted-lets要递归调用,从let*的宏定义可以看出:
(define-syntax let*
     (syntax
-rules()
       ((_ ((var1 val1)) e1 )
        (let ((var1 val1)) e1 ))
       ((_ ((var1 val1) (var2 val2) ) e1 )
        (let ((var1 val1))
          (let
* ((var2 val2) )
            e1 )))))
那么,let*->nested-lets可以定义为:
(define (let*? exp)
  (tagged
-list? exp 'let*))
(define (let*->nested-lets exp)
     (let ((pairs (cadr exp))
           (body (caddr exp)))
       (
if (null? (cdr pairs))
           (list 
'let pairs body)
           (list 'let (list (car pairs)) (let*->nested-lets (list 'let* (cdr pairs) body))))))
测试一下:
(let* ((x 1) (y ( x 3))) ( x y)) =》5

习题4.8,命名let,修改let->combination过程,判断cadr是pair还是symbol,如果是前者,那就是一般的let,如果是symbol就是命名let语句,那么需要定义一个名为(cadr exp)的过程放在body里,注意,我们是在做语法转换,因此,这个定义也应该描述成符号,定义一个make-define过程来生成define语句:
(define (make-define var parameters body)
  (list 
'define (cons var parameters) body))
然后修改let->combination过程,如上所述:
(define (let->combination exp)
  (
if (symbol? (cadr exp))
      (let ((var (cadr exp))
            (vars (map car (caddr exp)))
            (vals (map cadr (caddr exp)))
            (pairs (caddr exp))
            (body (cadddr exp)))
        (cons (make
-lambda vars (list (make-define var vars body) body)) vals))
      (let ((vars (map car (cadr exp)))
            (vals (map cadr (cadr exp)))
            (body (caddr exp)))
              (cons (make
-lambda vars (list body)) vals))))


习题4.1.4,原生的map过程接受的procedure,是以scheme内在数据结构表示的procedure,而在我们的求值器中,procedure的内在数据结构取决于我们,与原生的结构不同,导致原生的map无法接受自制求值器的procedure,如果map也以求值器中的结构定义,那么就没有这个问题。因此,本质上的困难就在于两个求值器对procedure的数据结构表示的不同。

习题4.1.5,著名的图灵停机问题,先是假设存在halts?过程可以正确地判断任何过程p和对象a是否p对a终止,定义了try过程:
(define (try p)
   (if (halts? p p)
       (run-forever)
        'halted))
当执行(try try),如果这个过程可终止,那么(halts? try try)应该返回false,也就是try过程对try不会终止,这与一开始的假设矛盾;如果这个过程将无穷运行下去,那么(halts? try try)应该返回true,也就是try对try终止,这也与(try try)将无穷运行的假设矛盾。因此,可以推论出,我们不可能写出一个过程halts?,使它能正确地判断任何过程p和对象a是否p对a终止。



dennis 2008-06-01 15:51
]]>
使用rope来高效处理长字符串http://www.blogjava.net/killme2008/archive/2008/05/05/198532.htmldennisdennismon, 05 may 2008 10:41:00 gmthttp://www.blogjava.net/killme2008/archive/2008/05/05/198532.htmlhttp://www.blogjava.net/killme2008/comments/198532.htmlhttp://www.blogjava.net/killme2008/archive/2008/05/05/198532.html#feedback0http://www.blogjava.net/killme2008/comments/commentrss/198532.htmlhttp://www.blogjava.net/killme2008/services/trackbacks/198532.htmlropes:理论与实践》。这两天为了提高工作中某个系统对外接口的效率,才认真学习了一番。本质上ropes是将字符串表示为一棵二叉树,特别适用于长字符串的处理,貌似c stl库中也有这么个实现。具体实现和原理还是看这篇。《》一文中给出的测试数据相当惊人,ropes比之string和stringbuffer在append,insert,delete等操作上的效率都有一个数量级以上的差距。跑下作者给出的测试程序,其实在测试的字符串不是很长的情况下,这个差距并没有那么大,这也从侧面说明了rope的应用范围:即只有在大量修改大型字符串的应用程序中才能看到明显的性能提升。那么是否可以用rope替代stringbuffer做append生成字符串(比如我要的生成xml)。作者也说啦:
  “由于 rope 的附加性能通常比 stringbuffer 好,这时使用 rope 是否有意义呢?答案还是否。不论何时将输入的数据组合在一起形成格式化输出时,最漂亮最有效的方法是使用模板引擎(例如 stringtemplate 或 freemarker)。这种方法不仅能干净地将表示标记与代码分开,而且模板只进行一次编译(通常编译为 jvm 字节码),以后可以重用,从而使它们拥有极佳的性能特征。”

    我用rope for java替代了stringbuffer做xml生成,效率提升在5%-30%左右,xml字符串不是很长,这个提升显然有限,也带来了不必要的复杂度。因此最后还是用velocity模板引擎来生成xml,测试的结果效率并没有多少改善,但是显然更容易维护和开发了。回到rope的话题,我用ruby实现了个版本,rubyforge上有一个rope的实现,但是看了源码,与paper所述算法有点差异,因此照着rope for java也实现了一个。测试的结果证明在长字符串的累积操作上,rope4r的append比之string的 =性能可以快上3倍左右,而如果采用string的<<操作,不是immutable的,当然是最快了;比较郁闷的是slice和insert操作都比string的慢上几倍,因为ruby的string、array的内建对象都是直接用c写成并做了优化的,我猜测原因在这。




dennis 2008-05-05 18:41
]]>
善用表驱动法http://www.blogjava.net/killme2008/archive/2008/04/17/193852.htmldennisdennisthu, 17 apr 2008 11:50:00 gmthttp://www.blogjava.net/killme2008/archive/2008/04/17/193852.htmlhttp://www.blogjava.net/killme2008/comments/193852.htmlhttp://www.blogjava.net/killme2008/archive/2008/04/17/193852.html#feedback1http://www.blogjava.net/killme2008/comments/commentrss/193852.htmlhttp://www.blogjava.net/killme2008/services/trackbacks/193852.html

游戏人数

第一名获得赌注

第二名获得赌注

第三名获得赌注

第四名获得赌注

二人

100%

0%

二人(出现2个第1名时)

50%

50%

 

 

三人

70%

30%

0%

三人出现3个第1名时

33.3333%

33.3333%

33.3333%

 

三人(出现2个第1名时)

50%×2

0%

 

 

......
......
    这些奖励规则没有什么规律,随着人数增多,就越发复杂了,并且业务人员可能随时改变这些规则。
    显然,奖励规则可以采用策略模式,定义策略接口,根据游戏人数定义不同的规则,本质上就是利用动态的多态调用。可以想见,还是少不了复杂的case...when语句,以及繁多的代码。恰好最近读《unix编程艺术》和《代码大全2》,两者都提到一个结论:人类阅读复杂数据结构远比复杂的控制流程容易,或者说数据驱动开发是非常有价值的。《代码大全2》声称这个是表驱动法。因此,这个奖励系数的计算,能否转化成一个查表过程呢?注意到,在游戏中,名次是根据个人的积分在所有玩家中的排位来决定,大概会有这么个排序的玩家积分数组[100,50,3],这个数组表示3个玩家,第一名100分,第二名50分,第三名才3分。依据规则,第一名的奖励系数就是0.7,第二名就是0.3。我想到类似这样的数组其实都有个形式表示它们的内置结构,比如[100,50,3]数组的“结构”是"111",代表3个位置都有一个人。将"111"作为关键码去查表不就ok了?
    将每个排好序的积分数组解码为这样的关键码,然后去查预先写好的奖励系数表,这个奖励系数表大概类似:
  @@award_rate_hash={
    :
"2"=>{
      :
"11"=>{:"1"=>1,:"2"=>0},
      :
"20"=>{:"1"=>0.5,:"2"=>0.5}
    },
    :
"3"=>{
      :
"111"=>{:"1"=>0.7,:"2"=>0.3,:"3"=>0},
      :
"300"=>{:"1"=>0.33},
      :
"201"=>{:"1"=>0.5,:"3"=>0},
      :
"120"=>{:"1"=>1,:"2"=>0}
    },
    :
"4"=>{
      :
"1111"=>{:"1"=>0.65,:"2"=>0.30,:"3"=>0.05,:"4"=>0},
      :
"4000"=>{:"1"=>0.25},
      :
"3001"=>{:"1"=>0.33,:"4"=>0},
      :
"1300"=>{:"1"=>1,:"2"=>0},
      :
"2020"=>{:"1"=>0.5,:"3"=>0},
      :
"1201"=>{:"1"=>0.7,:"2"=>0.15,:"4"=>0},
      :
"1120"=>{:"1"=>0.7,:"2"=>0.3,:"3"=>0},
      :
"2011"=>{:"1"=>0.35,:"3"=>0.3,:"4"=>0}
    }      
  }
    一个三级hash表,首先根据玩家人数查到特定的系数表,比如要查3个玩家、积分数组是[100,50,3]的奖励系数表就是  @@award_rate_hash[:"3"],然后积分数组[100,50,3]解码为:"111",继续查,如此规则的奖励系数表就是@@award_rate_hash[:"3"][:"111"]——也就是{:"1"=>0.7,:"2"=>0.3,:"3"=>0},那么查积分是100的玩家系数就是@@award_rate_hash[:"3"][:"111"][([100,50,3].index(100) 1).to_s.to_sym],也就是:"1"=>0.7[100,50,3].index(100) 1就是积分100的玩家在数组中的名次(即1),也就是:"1"指向的结果0.7。其他玩家的查表过程与此类似,不细说了。
    这样,我们所有的奖励规则就是维护这么一张hash表,这个表看起来复杂,其实完全可以自动生成,让业务人员来提供样例数据,解码样例数据并生成这个表是很简单的事情。积分数组的“解码”,我给一个ruby版本:
   #解码数组为字符串关键码
  def decode_array(array)
    len
=array.size
    trace_list
=[]
    result
=[]
    len.times do 
|time|
      result[time]
=0   
      trace_list[time]
=false
    end
    array.each_with_index do 
|item,index|
      result[index]
=count_times(array,trace_list,index,len)
    end
    
return result.join('').to_sym
  end
  
def count_times(array,trace_list,index,len)
    item
=array[index]
    result
=0
     (index..len).each do 
|i|
      
if array[i]==item and !trace_list[i]
        result
=1
        trace_list[i]
=true
      end
    end
    
return result
  end




dennis 2008-04-17 19:50
]]>
关于binary searchhttp://www.blogjava.net/killme2008/archive/2008/04/02/190285.htmldennisdenniswed, 02 apr 2008 02:08:00 gmthttp://www.blogjava.net/killme2008/archive/2008/04/02/190285.htmlhttp://www.blogjava.net/killme2008/comments/190285.htmlhttp://www.blogjava.net/killme2008/archive/2008/04/02/190285.html#feedback2http://www.blogjava.net/killme2008/comments/commentrss/190285.htmlhttp://www.blogjava.net/killme2008/services/trackbacks/190285.html1946年就有人发表binary search,但直到1962第一个正确运行的算法才写出来。尽管算法描述看起来简单明了,但是要写的正确却是有许多地方要仔细考虑。作者着重强调的是对程序正确性的分析方法,仔细分析方法的pre-condition, invariant,和post-condition。g9老大翻译过joshua bloch在google blog上的文章《》,原文在。今天看了下原文,有更新,对于求中值索引的c/c 方法原文仍然是有错的,本来是这样:

int mid = ((unsigned) (low  high)) >> 1

但是在c99标准中,对于两个signed数值之和,如果溢出结果是未定义的(undefined),也就是说与编译器实现相关。上面的代码应该修改为:

mid = ((unsigned int)low  (unsigned int)high)) >> 1;

我查了下jdk6的java.util.arrays的源码,joshua bloch说的这个问题已经解决,现在的binary search如下:

  // like public version, but without range checks.
    private static int binarysearch0(int[] a, int fromindex, int toindex,
                     
int key) {
    
int low = fromindex;
    
int high = toindex - 1;

    
while (low <= high) {
        
int mid = (low  high) >>> 1;
        
int midval = a[mid];

        
if (midval < key)
        low 
= mid  1;
        
else if (midval > key)
        high 
= mid - 1;
        
else
        
return mid; // key found
    }
    
return -(low  1);  // key not found.
    }

    如原文所讨论的,采用了>>>操作符替代除以2

dennis 2008-04-02 10:08
]]>
位图排序http://www.blogjava.net/killme2008/archive/2008/01/07/173362.htmldennisdennismon, 07 jan 2008 07:30:00 gmthttp://www.blogjava.net/killme2008/archive/2008/01/07/173362.htmlhttp://www.blogjava.net/killme2008/comments/173362.htmlhttp://www.blogjava.net/killme2008/archive/2008/01/07/173362.html#feedback3http://www.blogjava.net/killme2008/comments/commentrss/173362.htmlhttp://www.blogjava.net/killme2008/services/trackbacks/173362.html 输入:   一个包含n个正整数的文件,每个正整数小于n,n等于10的7次方(一千万)。并且文件内的正整数没有重复和关联数据。

输出:  输入整数的升序排列
 
约束: 限制在1m左右内存,充足的磁盘空间

    假设整数占32位,1m内存可以存储大概250000个整数,第一个方法就是采用基于磁盘的合并排序算法,第二个办法就是将0-9999999切割成40个区间,分40次扫描(10000000/250000),每次读入250000个在一个区间的整数,并在内存中使用快速排序。书中提出的第三个解决办法是采用bitmap(或者称为bit vector)来表示所有数据集合(注意到条件,数据没有重复),这样就可以一次性将数据读入内存,减少了扫描次数。算法的伪代码如下:
阶段1:初始化一个空集合
     for i=[0,n)
           bit[i]=0;
阶段2:读入数据i,并设置bit[i]=1
    for each i in the input file
           bit[i]=1;
阶段3:输出排序的结果
   for i=[0,n)
          if bit[i]==1
              write i on the output file

这个算法的时间复杂度在o(n),用c语言写的版本可以在10秒内完成任务!c语言的源码在该书凯发k8网页登录主页上有,这里给一个java的测试版,加上我的理解注释:

/**
 * created by intellij idea.
 * user: zhuangxd
 * date: 2008-1-7
 * time: 14:30:44
 
*/
public class bitsorttest {
    
private static final int bitsperword = 32;  //整数位数
    private static final int shift = 5;
    
private static final int mask = 0x1f;  //5位遮蔽 0b11111
    private static final int n = 10000000;
    
//用int数组来模拟位数组,总计(1   n / bitsperword)*bitsperword位,足以容纳n
    private static int[] a = new int[(1  n / bitsperword)];

    
public static void main(string[] args) {
        bitsort(
new int[]{11002100009999456778902});
    }

    
public static void bitsort(int[] array) {
        
for (int i = 0; i < n; i)
            clr(i);   
//位数组所有位清0
        for (int i = 0; i < array.length; i)
            set(array[i]);  
//阶段2
        for (int i = 0; i < n; i)
            
if (test(i))
                system.out.println(i);
    }

    
//置a[i>>shift]的第(i & mask)位为1,也就是位数组的第i位为1
    public static void set(int i) {
        a[i 
>> shift] |= (1 << (i & mask));
    }

    
//置a[i>>shift]的第(i & mask)位为0,也就是位数组的第i位为0
    public static void clr(int i) {
        a[i 
>> shift] &= ~(1 << (i & mask));
    }

    
//测试位数组的第i位是否为1
    public static boolean test(int i) {
        
return (a[i >> shift] & (1 << (i & mask))) == (1 << (i & mask));
    }
}





dennis 2008-01-07 15:30
]]>
简单lru算法实现缓存-update2http://www.blogjava.net/killme2008/archive/2007/09/29/149645.htmldennisdennissat, 29 sep 2007 09:49:00 gmthttp://www.blogjava.net/killme2008/archive/2007/09/29/149645.htmlhttp://www.blogjava.net/killme2008/comments/149645.htmlhttp://www.blogjava.net/killme2008/archive/2007/09/29/149645.html#feedback5http://www.blogjava.net/killme2008/comments/commentrss/149645.htmlhttp://www.blogjava.net/killme2008/services/trackbacks/149645.html update2:一个错误,老是写错关键字啊,lrucache的maxcapacity应该声明为volatile,而不是transient。
  
   最简单的lru算法实现,就是利用jdk的linkedhashmap,覆写其中的removeeldestentry(map.entry)方法即可,如下所示:
import java.util.arraylist;
import java.util.collection;
import java.util.linkedhashmap;
import java.util.concurrent.locks.lock;
import java.util.concurrent.locks.reentrantlock;
import java.util.map;


/**
 * 类说明:利用linkedhashmap实现简单的缓存, 必须实现removeeldestentry方法,具体参见jdk文档
 * 
 * 
@author dennis
 * 
 * 
@param 
 * 
@param 
 
*/
public class lrulinkedhashmap<k, v> extends linkedhashmap<k, v> {
    
private final int maxcapacity;

    
private static final float default_load_factor = 0.75f;

    
private final lock lock = new reentrantlock();

    
public lrulinkedhashmap(int maxcapacity) {
        
super(maxcapacity, default_load_factor, true);
        
this.maxcapacity = maxcapacity;
    }

    @override
    
protected boolean removeeldestentry(java.util.map.entry<k, v> eldest) {
        
return size() > maxcapacity;
    }
    @override
    
public boolean containskey(object key) {
        
try {
            lock.lock();
            
return super.containskey(key);
        } 
finally {
            lock.unlock();
        }
    }

    
    @override
    
public v get(object key) {
        
try {
            lock.lock();
            
return super.get(key);
        } 
finally {
            lock.unlock();
        }
    }

    @override
    
public v put(k key, v value) {
        
try {
            lock.lock();
            
return super.put(key, value);
        } 
finally {
            lock.unlock();
        }
    }

    
public int size() {
        
try {
            lock.lock();
            
return super.size();
        } 
finally {
            lock.unlock();
        }
    }

    
public void clear() {
        
try {
            lock.lock();
            
super.clear();
        } 
finally {
            lock.unlock();
        }
    }

    
public collection<map.entry<k, v>> getall() {
        
try {
            lock.lock();
            
return new arraylist<map.entry<k, v>>(super.entryset());
        } 
finally {
            lock.unlock();
        }
    }
}
    如果你去看linkedhashmap的源码可知,lru算法是通过双向链表来实现,当某个位置被命中,通过调整链表的指向将该位置调整到头位置,新加入的内容直接放在链表头,如此一来,最近被命中的内容就向链表头移动,需要替换时,链表最后的位置就是最近最少使用的位置。
    lru算法还可以通过计数来实现,缓存存储的位置附带一个计数器,当命中时将计数器加1,替换时就查找计数最小的位置并替换,结合访问时间戳来实现。这种算法比较适合缓存数据量较小的场景,显然,遍历查找计数最小位置的时间复杂度为o(n)。我实现了一个,结合了访问时间戳,当最小计数大于mini_acess时(这个参数的调整对命中率有较大影响),就移除最久没有被访问的项:
package net.rubyeye.codelib.util.concurrency.cache;

import java.io.serializable;
import java.util.arraylist;
import java.util.collection;
import java.util.hashmap;
import java.util.iterator;
import java.util.map;
import java.util.set;
import java.util.concurrent.atomic.atomicinteger;
import java.util.concurrent.atomic.atomiclong;
import java.util.concurrent.locks.lock;
import java.util.concurrent.locks.readwritelock;
import java.util.concurrent.locks.reentrantlock;
import java.util.concurrent.locks.reentrantreadwritelock;

/**
 * 
 * 
@author dennis 类说明:当缓存数目不多时,才用缓存计数的传统lru算法
 * 
@param 
 * 
@param 
 
*/
public class lrucache<k, v> implements serializable {

    
private static final int default_capacity = 100;

    
protected map<k, valueentry> map;

    
private final readwritelock lock = new reentrantreadwritelock();

    
private final lock readlock = lock.readlock();

    
private final lock writelock = lock.writelock();

    
private finalvolatileint maxcapacity;  //保持可见性

    
public static int mini_access = 5;

    
public lrucache() {
        
this(default_capacity);
    }

    
public lrucache(int capacity) {
        
if (capacity <= 0)
            
throw new runtimeexception("缓存容量不得小于0");
        
this.maxcapacity = capacity;
        
this.map = new hashmap<k, valueentry>(maxcapacity);
    }

    
public boolean containskey(k key) {
        
try {
            readlock.lock();
            
return this.map.containskey(key);
        } 
finally {
            readlock.unlock();
        }
    }

    
public v put(k key, v value) {
        
try {
            writelock.lock();
            
if ((map.size() > maxcapacity - 1&& !map.containskey(key)) {
                
// system.out.println("开始");
                set<map.entry<k, valueentry>> entries = this.map.entryset();
                removerencentlyleastaccess(entries);
            }
            valueentry new_value 
= new valueentry(value);
            valueentry old_value 
= map.put(key, new_value);
            
if (old_value != null) {
                new_value.count 
= old_value.count;
                
return old_value.value;
            } 
else
                
return null;
        } 
finally {
            writelock.unlock();
        }
    }

    
/**
     * 移除最近最少访问
     
*/
    
protected void removerencentlyleastaccess(
            set
<map.entry<k, valueentry>> entries) {
        
// 最小使用次数
        long least = 0;
        
// 访问时间最早
        long earliest = 0;
        k toberemovedbycount 
= null;
        k toberemovedbytime 
= null;
        iterator
<map.entry<k, valueentry>> it = entries.iterator();
        
if (it.hasnext()) {
            map.entry
<k, valueentry> valueentry = it.next();
            least 
= valueentry.getvalue().count.get();
            toberemovedbycount 
= valueentry.getkey();
            earliest 
= valueentry.getvalue().lastaccess.get();
            toberemovedbytime 
= valueentry.getkey();
        }
        
while (it.hasnext()) {
            map.entry
<k, valueentry> valueentry = it.next();
            
if (valueentry.getvalue().count.get() < least) {
                least 
= valueentry.getvalue().count.get();
                toberemovedbycount 
= valueentry.getkey();
            }
            
if (valueentry.getvalue().lastaccess.get() < earliest) {
                earliest 
= valueentry.getvalue().count.get();
                toberemovedbytime 
= valueentry.getkey();
            }
        }
        
// system.out.println("remove:"   toberemoved);
        
// 如果最少使用次数大于mini_access,那么移除访问时间最早的项(也就是最久没有被访问的项)
        if (least > mini_access) {
            map.remove(toberemovedbytime);
        } 
else {
            map.remove(toberemovedbycount);
        }
    }

    
public v get(k key) {
        
try {
            readlock.lock();
            v value 
= null;
            valueentry valueentry 
= map.get(key);
            
if (valueentry != null) {
                
// 更新访问时间戳
                valueentry.updatelastaccess();
                
// 更新访问次数
                valueentry.count.incrementandget();
                value 
= valueentry.value;
            }
            
return value;
        } 
finally {
            readlock.unlock();
        }
    }

    
public void clear() {
        
try {
            writelock.lock();
            map.clear();
        } 
finally {
            writelock.unlock();
        }
    }

    
public int size() {
        
try {
            readlock.lock();
            
return map.size();
        } 
finally {
            readlock.unlock();
        }
    }

    
public long getcount(k key) {
        
try {
            readlock.lock();
            valueentry valueentry 
= map.get(key);
            
if (valueentry != null) {
                
return valueentry.count.get();
            }
            
return 0;
        } 
finally {
            readlock.unlock();
        }
    }

    
public collection<map.entry<k, v>> getall() {
        
try {
            readlock.lock();
            set
<k> keys = map.keyset();
            map
<k, v> tmp = new hashmap<k, v>();
            
for (k key : keys) {
                tmp.put(key, map.get(key).value);
            }
            
return new arraylist<map.entry<k, v>>(tmp.entryset());
        } 
finally {
            readlock.unlock();
        }
    }

    
class valueentry implements serializable {
        
private v value;

        
private atomiclong count;

        
private atomiclong lastaccess;

        
public valueentry(v value) {
            
this.value = value;
            
this.count = new atomiclong(0);
            lastaccess 
= new atomiclong(system.nanotime());
        }

        
public void updatelastaccess() {
            
this.lastaccess.set(system.nanotime());
        }

    }
}





dennis 2007-09-29 17:49
]]>
模仿st_table写的sttable类http://www.blogjava.net/killme2008/archive/2007/09/18/146234.htmldennisdennistue, 18 sep 2007 11:28:00 gmthttp://www.blogjava.net/killme2008/archive/2007/09/18/146234.htmlhttp://www.blogjava.net/killme2008/comments/146234.htmlhttp://www.blogjava.net/killme2008/archive/2007/09/18/146234.html#feedback0http://www.blogjava.net/killme2008/comments/commentrss/146234.htmlhttp://www.blogjava.net/killme2008/services/trackbacks/146234.html阅读全文

dennis 2007-09-18 19:28
]]>
scheme实现huffman编码的完整代码http://www.blogjava.net/killme2008/archive/2007/07/23/131788.htmldennisdennismon, 23 jul 2007 00:56:00 gmthttp://www.blogjava.net/killme2008/archive/2007/07/23/131788.htmlhttp://www.blogjava.net/killme2008/comments/131788.htmlhttp://www.blogjava.net/killme2008/archive/2007/07/23/131788.html#feedback0http://www.blogjava.net/killme2008/comments/commentrss/131788.htmlhttp://www.blogjava.net/killme2008/services/trackbacks/131788.html
(define (make-leaf symbol weight)
  (list 
'leaf symbol weight))
(define (leaf? object)
  (eq? (car object) 
'leaf))
(define (symbol-leaf x) (cadr x))
(define (weight
-leaf x) (caddr x))
;合并最低权重的两个节点
(define (make
-code-tree left right)
  (list left right (append (symbols left) (symbols right)) (
 (weight left) (weight right))))
(define (left
-branch tree) (car tree))
(define (right
-branch tree) (cadr tree))
(define (symbols tree)
  (
if (leaf? tree)
      (list (symbol
-leaf tree))
      (caddr tree)))
(define (weight tree)
  (
if (leaf? tree)
      (weight
-leaf tree)
      (cadddr tree)))
;解码
(define (decode bits tree)
  (define (decode
-1 bits current-branch)
    (
if (null? bits)
        
'()
        (let ((next-branch
              (choose
-branch (car bits) current-branch)))
          (
if (leaf? next-branch)
              (cons (symbol
-leaf next-branch)
                    (decode
-1 (cdr bits) tree))
              (decode
-1 (cdr bits) next-branch)))))
  (decode
-1 bits tree))
(define (choose
-branch bit branch)
  (cond ((
= bit 0) (left-branch branch))
        ((
= bit 1) (right-branch branch))
        (
else (display "bad bit --choose-branch"))))
(define (adjoin
-set x set)
  (cond ((null? set) (list x))
        ((
< (weight x) (weight (car set))) (cons x set))
        (
else
           (cons (car set) (adjoin
-set x (cdr set))))))
(define (make
-leaf-set pairs)
  (
if (null? pairs)
      
'()
      (let ((pair (car pairs)))
        (adjoin
-set (make-leaf (car pair) (cadr pair)) (make-leaf-set (cdr pairs))))))

;编码
(define (encode message tree)
  (
if (null? message)
      
'()
      (append (encode-symbol (car message) tree)
              (encode (cdr message) tree))))
(define (encode
-symbol symbol tree)
  (define (iter branch)
    (
if (leaf? branch)
        
'()
        (if (memq symbol (symbols (left-branch branch)))
            (cons 0 (iter (left
-branch branch)))
            (cons 
1 (iter (right-branch branch))))
        ))
  (
if (memq symbol (symbols tree))
      (iter tree)
      (display 
"bad symbol -- unknown symbol")))
;生成hufman树
(define (generate
-huffman-tree pairs)
  (successive
-merge (make-leaf-set pairs)))

(define (successive
-merge leaf-set)
  (
if (null? (cdr leaf-set))
      (car leaf
-set)
      (successive
-merge (adjoin-set (make-code-tree (car leaf-set)
                                                    (cadr leaf
-set))
                                    (cddr leaf
-set)))))





dennis 2007-07-23 08:56
]]>
sicp 2.4节小题尝试解答http://www.blogjava.net/killme2008/archive/2007/07/20/131469.htmldennisdennisfri, 20 jul 2007 06:32:00 gmthttp://www.blogjava.net/killme2008/archive/2007/07/20/131469.htmlhttp://www.blogjava.net/killme2008/comments/131469.htmlhttp://www.blogjava.net/killme2008/archive/2007/07/20/131469.html#feedback0http://www.blogjava.net/killme2008/comments/commentrss/131469.htmlhttp://www.blogjava.net/killme2008/services/trackbacks/131469.html    这一节那是相当的有趣,抽象数据的多重表示:采用标志(tag)来区分和数据导向(data-directed)技术,稍微提了下消息传递。通过一张二维表格将类型、操作的分派机制介绍的很清楚,静态oo语言正是通过类型来决定消息的分派,而消息传递以列进行划分,每个类型都以过程来表征,也就是所谓的“智能数据对象”,两者各有优缺点。当类型增加频繁时,消息传递风格的分派更容易扩展,当操作增加频繁时,反而是显式的类型分派更为容易扩展,这一点如果有oo设计经验应该很容易体会。
看看习题:

习题2.73,将求导程序以数据导向方式进行修改:
a)题目已经说了,基于被求导表达式的类型进行分派,加法表达式或者乘法表达式都有标志来区分,而number?和variable?并没有标志进行区分,因此无法加入数据导向分派。
b)选择函数不变,将求导过程提取出来,根据表达式类型划分,然后用put过程安装:
(define (deriv-sum exp var)
  (make
-sum (deriv (addend exp) var)
            (deriv (augend 
exp) var)))

(define (deriv
-prod exp var)
  (make
-sum
   (make
-product (multiplier exp)
                 (deriv (multiplicand 
exp) var))
   (make
-product (deriv (multiplier exp) var)
                 (multiplicand 
exp))))

(define (install
-deriv-package)
  (put 
'deriv ' deriv-sum)
  (put 
'deriv '* deriv-prod)
  
'done)

c)我没做,不过和b差不了多少

习题2.75,很简单,跟着书上来:
(define (make-from-mag-ang r a)
  (define (dispatch op)
    (cond ((eq
? op 'real-part) (* r (cos a)))
          ((eq? op 
'imag-part) (* r (sin a)))
          ((eq
? op 'magnitude) r)
          ((eq? op 
'angle) a)
          (
else
             display 
"unknow op")))
  dispatch)
将极角坐标系表示的复数用dispatch过程来表示,这正是消息传递风格。

习题2.76,在本文开头已经提了。


dennis 2007-07-20 14:32
]]>
c#实现二叉查找树http://www.blogjava.net/killme2008/archive/2007/04/02/108018.htmldennisdennismon, 02 apr 2007 09:29:00 gmthttp://www.blogjava.net/killme2008/archive/2007/04/02/108018.htmlhttp://www.blogjava.net/killme2008/comments/108018.htmlhttp://www.blogjava.net/killme2008/archive/2007/04/02/108018.html#feedback0http://www.blogjava.net/killme2008/comments/commentrss/108018.htmlhttp://www.blogjava.net/killme2008/services/trackbacks/108018.html二叉查找树(binary search tree)

1)概念:对于树中的每个节点n,其左子节点中保存的所有数值都小于n保存的数值,右子节点保存的数值都大于n保存的数值。

2)二叉查找树可以实现更为优越的查找性能,主要实现方式有数组和链表结构,相比较而言,链表实现更为容易,因为数组实现删除和添加功能需要移动数组元素(如填补删除空位等)


今天下午在打印问题搞定后用c#实现了一下,比java版本比较有趣的使用c#的delegate来代替遍历二叉树时的visit方法,这样一来可以在遍历时对节点进行你所想要的任何操作。我们知道c#的delegate是类型化的函数指针,而c 的函数指针可以模仿动态语言的闭包或者匿名函数。这里也有这样的味道。

代码如下,只实现了整数型的,节点定义:
  public  class bstintnode
    {
        
public int value;
        
public bstintnode left;
        
public bstintnode right;

        
public bstintnode(int value, bstintnode left, bstintnode right)
        {
            
this.value = value;
            
this.left = left;
            
this.right = right;
        }

        
public bstintnode(int value)
        {
            
this.value = value;
            
this.left = null;
            
this.right = null;
        }
    }

然后定义一个delegate,作为遍历时的访问方法:

 public delegate void visit(bstintnode node);

然后就是二叉树的实现,删除算法只实现了复制删除法:

public class bstinttree
    {
        
protected bstintnode root;
      
        
public visit visit;

        
public bstinttree()
        {
            
this.root = null;
        }

        
private bstintnode search(bstintnode node, int el)
        {
            
while (node != null)
            {
                
if (el == node.value)
                    
return node;
                
else if (el < node.value)
                    node 
= node.left;
                
else
                    node 
= node.right;
            }
            
return null;
        }

        
//查找
        public bstintnode search(int el)
        {
            
return search(root, el);
        }

        
//广度优先遍历,利用队列实现,至上而下,至左而右
        public void breadthfirst()
        {
            bstintnode p 
= root;
            queue queue 
= new listqueue();
            
if (p != null)
            {
                queue.enqueue(p);
                
while (!queue.isempty())
                {
                    p 
= (bstintnode)queue.dequeue();
                    visit(p);
                    
if (p.left != null)
                        queue.enqueue(p.left);
                    
if (p.right != null)
                        queue.enqueue(p.right);
                }
            }
        }

        
//深度优先遍历,递归实现线序,中序和后序

        
//先序
        protected void preorder(bstintnode p)
        {
            
if (p != null)
            {
                visit(p);
                preorder(p.left);
                preorder(p.right);
            }
        }

        
public void preorder()
        {
            preorder(root);
        }
        
//中序
        protected void inorder(bstintnode p)
        {
            
if (p != null)
            {
                inorder(p.left);
                visit(p);
                inorder(p.right);
            }
        }

        
public void inorder()
        {
            inorder(root);
        }

        
//后序
        protected void postorder(bstintnode p)
        {
            
if (p != null)
            {
                postorder(p.left);
                postorder(p.right);
                visit(p);
            }
        }

        
public void postorder()
        {
            postorder(root);
        }

        
//插入节点操作
        public void insert(int el)
        {
            bstintnode p 
= root, prev = null;

            
//查找节点位置
            while (p != null)
            {
                prev 
= p;
                
if (p.value < el)
                    p 
= p.right;
                
else
                    p 
= p.left;
            }

            
if (root == null)  //空树
                root = new bstintnode(el);
            
else if (prev.value < el)   //大于节点,插入右子树
                prev.right = new bstintnode(el);
            
else
                prev.left 
= new bstintnode(el);
        }

        
//复制删除法的实现,归并删除法可能改变树的高度
        public void delete(int el)
        {
            bstintnode node, p 
= root, prev = null;

            
//查找节点位置
            while (p != null&&p.value!=el)
            {
                prev 
= p;
                
if (p.value < el)
                    p 
= p.right;
                
else
                    p 
= p.left;
            }
            node 
= p;
            
if (p != null && p.value == el)
            {
                
if (node.right == null)
                    node 
= node.left;
                
else if (node.left == null)
                    node 
= node.right;
                
else
                {
                    bstintnode temp 
= node.left;
                    bstintnode previous 
= node;
                    
while (temp.right != null)  //查找左字节数的最右子节点
                    {
                        previous 
= temp;
                        temp 
= temp.right;
                    }
                    node.value 
= temp.value;
                    
if (previous == node)
                        previous.left 
= temp.left;
                    
else
                        previous.right 
= temp.left;
                }
                
if (p == root)
                    root 
= node;
                
else if (prev.left == p)
                    prev.left 
= node;
                
else
                    prev.right 
= node;
            }
            
else if (root != null)
            {
                console.writeline(
"没有找到节点:{0}", el);
            }
            
else
                console.writeline(
"树为空!");
        }

    }

注意,在树中我们维持了一个visit的delegate,看看使用方法:

 public static void main(string[] args)
        {
           bstinttree tree
=new bstinttree();
           
int []num={10,20,6,12,23,15,8};
           
for (int i = 0; i < num.length; i)
               tree.insert(num[i]);
           
//添加遍历处理函数,可以有多个 
           tree.visit  = new visit(printnode);
          
           console.writeline(
"广度优先遍历");
           tree.breadthfirst();
           console.writeline(
"先序");
           tree.preorder();
           console.writeline(
"中序");
           tree.inorder();
           console.writeline(
"后序");
           tree.postorder();

           tree.delete(
8);
           tree.delete(
15);
           console.writeline(
"删除后广度优先遍历");
           tree.breadthfirst();

        }
        
public static void printnode(bstintnode node)
        {
            console.writeline(
"访问节点:{0}", node.value);
        }

可以看到,c#的delegate机制非常有趣,如果在java中恐怕需要用inner class来实现了。




dennis 2007-04-02 17:29
]]>
c#实现栈和队列http://www.blogjava.net/killme2008/archive/2007/03/30/107409.htmldennisdennisfri, 30 mar 2007 01:44:00 gmthttp://www.blogjava.net/killme2008/archive/2007/03/30/107409.htmlhttp://www.blogjava.net/killme2008/comments/107409.htmlhttp://www.blogjava.net/killme2008/archive/2007/03/30/107409.html#feedback0http://www.blogjava.net/killme2008/comments/commentrss/107409.htmlhttp://www.blogjava.net/killme2008/services/trackbacks/107409.htmlc#链表的实现,实现栈和队列易如反掌。

栈,利用单向链表实现:
public abstract class abstractstack
    {
        
public abstract object pop();
        
public abstract void push(object obj);
        
public abstract bool isempty();
        
public abstract object top();
        
public abstract void clear();
    }

    
public class stack : abstractstack
    {
        
private slist list;
        
public stack()
        {
            list 
= new slist();
        }
        
public override bool isempty()
        {
            
return list.isempty();
        }
        
public override void push(object obj)
        {
            list.push(obj);
        }
        
public override object pop()
        {
            
return list.pop();
        }
        
public override object top()
        {
            
return list.gettail();
        }
        
public override void clear()
        {
            list.clear(); 
        }
    }

队列的实现,通过双向链表实现,对于环形数组的实现请参考《》:
 public interface queue
    {
        
bool isempty();
        
void enqueue(object obj);
        object dequeue();
        object first();
    }

    
public class listqueue:queue
    {
        
private linkedlist list;
        
public listqueue()
        {
            list 
= new linkedlist();
        }

        
public bool isempty()
        {
            
return list.isempty();
        }

        
public void enqueue(object obj)
        {
            list.push(obj);
        }
        
public object dequeue()
        {
            
return list.shift();
        }

        
public object first()
        {
            
return list.gethead();
        }
    }



dennis 2007-03-30 09:44
]]>
c#实现链表http://www.blogjava.net/killme2008/archive/2007/03/29/107261.htmldennisdennisthu, 29 mar 2007 09:02:00 gmthttp://www.blogjava.net/killme2008/archive/2007/03/29/107261.htmlhttp://www.blogjava.net/killme2008/comments/107261.htmlhttp://www.blogjava.net/killme2008/archive/2007/03/29/107261.html#feedback0http://www.blogjava.net/killme2008/comments/commentrss/107261.htmlhttp://www.blogjava.net/killme2008/services/trackbacks/107261.html阅读全文

dennis 2007-03-29 17:02
]]>
数据结构之堆 http://www.blogjava.net/killme2008/archive/2007/02/20/100243.htmldennisdennistue, 20 feb 2007 04:59:00 gmthttp://www.blogjava.net/killme2008/archive/2007/02/20/100243.htmlhttp://www.blogjava.net/killme2008/comments/100243.htmlhttp://www.blogjava.net/killme2008/archive/2007/02/20/100243.html#feedback1http://www.blogjava.net/killme2008/comments/commentrss/100243.htmlhttp://www.blogjava.net/killme2008/services/trackbacks/100243.html1)每个节点的值都大于(或者都小于,称为最小堆)其子节点的值
2)树是完全平衡的,并且最后一层的树叶都在最左边
这样就定义了一个最大堆。

2。堆可以用一个数组表示,有如下性质:
heap[i]>=heap[2*i 1]  其中0<=i<=(n-1)/2
heap[i]>=heap[2*i 2]  其中0<=i<=(n-2)/2

3。用数组实现堆,
1)插入操作
自顶向下,伪代码:
  heapenqueue(el)
      将el放在堆尾
      
while el不在根节点并且el>parent(el)
          交换el及其父节点

自底向上,伪代码:
 floydalgrithm(data[])
     
for i=最后一个非叶节点的下标,i>=0;i--
      调用movedown(data,i,n
-1)恢复以data[i]为根的树的堆性质
  
2)movedown的方法实现,此方法是堆排序的关键,也是删除操作的关键。删除操作,将根节点删除,并把最末的树叶换到根节点,通过movedown方法找到正确的位置,恢复堆性质。

4。堆的一个实现:
// heap.java
// demonstrates heaps
// to run this program: c>java heapapp
import java.io.*;
////////////////////////////////////////////////////////////////
class node
{
private int idata; // data item (key)
// -------------------------------------------------------------
public node(int key) // constructor
{ idata = key; }
// -------------------------------------------------------------
public int getkey()
{ return idata; }
// -------------------------------------------------------------
public void setkey(int id)
{ idata = id; }
// -------------------------------------------------------------
} // end class node
////////////////////////////////////////////////////////////////
class heap
{
private node[] heaparray;
private int maxsize; // size of array
private int currentsize; // number of nodes in array
// -------------------------------------------------------------
public heap(int mx) // constructor
{
maxsize = mx;
currentsize = 0;
heaparray = new node[maxsize]; // create array
}
// -------------------------------------------------------------
public boolean isempty()
{ return currentsize==0; }
// -------------------------------------------------------------
public boolean insert(int key)
{
if(currentsize==maxsize)
return false;
node newnode = new node(key);
heaparray[currentsize] = newnode;
trickleup(currentsize );
return true;
} // end insert()
// -------------------------------------------------------------
public void trickleup(int index)
{
int parent = (index-1) / 2;
node bottom = heaparray[index];

while( index > 0 &&
heaparray[parent].getkey() < bottom.getkey() )
{
heaparray[index] = heaparray[parent]; // move it down
index = parent;
parent = (parent-1) / 2;
} // end while
heaparray[index] = bottom;
} // end trickleup()
// -------------------------------------------------------------
public node remove() // delete item with max key
{ // (assumes non-empty list)
node root = heaparray[0];
heaparray[0] = heaparray[--currentsize];
trickledown(0);
return root;
} // end remove()
// -------------------------------------------------------------
public void trickledown(int index)
{
int largerchild;
node top = heaparray[index]; // save root
while(index < currentsize/2) // while node has at
{ // least one child,
int leftchild = 2*index 1;
int rightchild = leftchild 1;
// find larger child
if(rightchild < currentsize && // (rightchild exists?)
heaparray[leftchild].getkey() <
heaparray[rightchild].getkey())
largerchild = rightchild;
else
largerchild = leftchild;
// top >= largerchild?
if( top.getkey() >= heaparray[largerchild].getkey() )
break;
// shift child up
heaparray[index] = heaparray[largerchild];
index = largerchild; // go down
} // end while
heaparray[index] = top; // root to index
} // end trickledown()
// -------------------------------------------------------------
public boolean change(int index, int newvalue)
{
if(index<0 || index>=currentsize)
return false;
int oldvalue = heaparray[index].getkey(); // remember old
heaparray[index].setkey(newvalue); // change to new

if(oldvalue < newvalue) // if raised,
trickleup(index); // trickle it up
else // if lowered,
trickledown(index); // trickle it down
return true;
} // end change()
// -------------------------------------------------------------
public void displayheap()
{
system.out.print("heaparray: "); // array format
for(int m=0; m if(heaparray[m] != null)
system.out.print( heaparray[m].getkey() " ");
else
system.out.print( "-- ");
system.out.println();
// heap format
int nblanks = 32;
int itemsperrow = 1;
int column = 0;
int j = 0; // current item
string dots = "...............................";
system.out.println(dots dots); // dotted top line

while(currentsize > 0) // for each heap item
{
if(column == 0) // first item in row?
for(int k=0; k system.out.print(' ');
// display item
system.out.print(heaparray[j].getkey());

if( j == currentsize) // done?
break;

if( column==itemsperrow) // end of row?
{
nblanks /= 2; // half the blanks
itemsperrow *= 2; // twice the items
column = 0; // start over on
system.out.println(); // new row
}
else // next item on row
for(int k=0; k system.out.print(' '); // interim blanks
} // end for
system.out.println("/n" dots dots); // dotted bottom line
} // end displayheap()
// -------------------------------------------------------------
} // end class heap
////////////////////////////////////////////////////////////////
class heapapp
{
public static void main(string[] args) throws ioexception
{
int value, value2;
heap theheap = new heap(31); // make a heap; max size 31
boolean success;

theheap.insert(70); // insert 10 items
theheap.insert(40);
theheap.insert(50);
theheap.insert(20);
theheap.insert(60);
theheap.insert(100);
theheap.insert(80);
theheap.insert(30);
theheap.insert(10);
theheap.insert(90);

while(true) // until [ctrl]-[c]
{
system.out.print("enter first letter of ");
system.out.print("show, insert, remove, change: ");
int choice = getchar();
switch(choice)
{
case 's': // show
theheap.displayheap();
break;
case 'i': // insert
system.out.print("enter value to insert: ");
value = getint();
success = theheap.insert(value);
if( !success )
system.out.println("can't insert; heap full");
break;
case 'r': // remove
if( !theheap.isempty() )
theheap.remove();
else
system.out.println("can't remove; heap empty");
break;
case 'c': // change
system.out.print("enter current index of item: ");
value = getint();
system.out.print("enter new key: ");
value2 = getint();
success = theheap.change(value, value2);
if( !success )
system.out.println("invalid index");
break;
default:
system.out.println("invalid entry/n");
} // end switch
} // end while
} // end main()
//-------------------------------------------------------------
public static string getstring() throws ioexception
{
inputstreamreader isr = new inputstreamreader(system.in);
bufferedreader br = new bufferedreader(isr);
string s = br.readline();
return s;
}
//-------------------------------------------------------------
public static char getchar() throws ioexception
{
string s = getstring();
return s.charat(0);
}
//-------------------------------------------------------------
public static int getint() throws ioexception
{
string s = getstring();
return integer.parseint(s);
}
//-------------------------------------------------------------
} // end class heapapp
////////////////////////////////////////////////////////////////


dennis 2007-02-20 12:59
]]>
数据结构之avl树 http://www.blogjava.net/killme2008/archive/2007/02/20/100242.htmldennisdennistue, 20 feb 2007 04:57:00 gmthttp://www.blogjava.net/killme2008/archive/2007/02/20/100242.htmlhttp://www.blogjava.net/killme2008/comments/100242.htmlhttp://www.blogjava.net/killme2008/archive/2007/02/20/100242.html#feedback0http://www.blogjava.net/killme2008/comments/commentrss/100242.htmlhttp://www.blogjava.net/killme2008/services/trackbacks/100242.html
1。概念:avl树,或者说可适应树,是指树中每个节点的的平衡因子的绝对值不大于1,即只能为-1,0,1
平衡因子:节点的右子树的高度减去左子树的高度

2。avl树的插入:从新插入节点到根的路径上,修改遇到的节点的平衡因子即可,对其他部分没影响
1)向右子女的右子树插入一个节点,单旋转就可以
2)向右子女的左子树插入一个节点,双旋转,先围绕父节点,再围绕祖父节点

3。avl树的删除:从删除节点到根的路径上,任何不平衡因子的节点都需要修改,与插入不同,需要o(lgn)次旋转。

4。一个java实现:
http://www.koders.com/java/fid3b5247d34968077a6efd4216589026d343559ff9.aspx?s=avl+tree

dennis 2007-02-20 12:57
]]>
数据结构之递归 http://www.blogjava.net/killme2008/archive/2007/02/20/100241.htmldennisdennistue, 20 feb 2007 04:56:00 gmthttp://www.blogjava.net/killme2008/archive/2007/02/20/100241.htmlhttp://www.blogjava.net/killme2008/comments/100241.htmlhttp://www.blogjava.net/killme2008/archive/2007/02/20/100241.html#feedback0http://www.blogjava.net/killme2008/comments/commentrss/100241.htmlhttp://www.blogjava.net/killme2008/services/trackbacks/100241.html递归的定义由两部分组成:
1)称作定位点(anchor)或者基本情况(ground case),它们是一些基本元素,这些基本元素是集合序列中其他所有对象的基础。
2)给出除基本元素或者已创建对象之外的新对象的构造规则,可以再三使用这个规则不断产生新的对象。

2。递归的实现:一般是由操作系统完成的,但是大部分的计算机系统的递归定义都是利用运行时堆栈实现的。在系统内,无论何时调用一个方法都会创建一个活动记录。一个递归调用并不仅仅是一个方法调用其自身,而是方法的一个instance调用相同方法的另一个instance,在计算机内部,这些调用是用不同的活动记录表示,并由系统区分。

3。尾递归:
仅在方法的末尾实行一次递归调用,这样的递归叫尾递归。尾递归很容易被循环所替换,或者说它只是一个名字比较好听的循环,如:
void tail(int i){
  
if(i>0){
    system.out.print(i
" ");
    tail(i
-1);
  }

}

替换为循环:
void tail2(int i){
   
for(;i>0;i--)
     system.out.print(i
" ");
}


尾递归对一些没有显式循环结构的语言(如prolog)特别重要

4。非尾递归:
递归相比于迭代结构的优点就是非常清晰并易于理解,这一点可以在二叉树遍历上得到体现。3种遍历方式的递归版本与迭代版本在可读性上不可同日而语。
问题:逆序输出一行输入(以ruby语言为例)

def reverse
  s
=stdin.getc
  
if s.chr!="/n"
    
reverse
    
print s.chr
  end
end 
reverse 


运行此程序,输入一行字符,将逆序输出,本质上是利用运行时堆栈完成的递归调用

5。间接递归:
方法通过一连串的调用,然后间接地调用它自身,这样的递归称为间接递归。
6。嵌套递归
一般出现在函数的定义中,如果这个函数不仅用它自身定义,而且还江它自身作为一个参数,如:
     0            n=0
h(n)=n         n>4
h(2 h(2n))   n<=4

7。过分递归:递归带来的逻辑简单性和可读性的代价是拖长了运行时间并且相对于非递归方法,它占用了更多的运行时堆栈空间。如果递归层次太深,可能导致运行时堆栈溢出,程序非正常结束的错误。

8。回溯(backtracking技术):在某点有多条道路供选择的时候,但不清楚哪一条能解决问题。在尝试一条道路失败后,返回岔口再试另一条道路。但是必须确定所有的道路都是可尝试的,可返回的。这种技术成为回溯。
在迷宫问题中和8皇后问题中使用此技术。具体程序不再列出(好长@_@)


dennis 2007-02-20 12:56
]]>
算法之简单排序 http://www.blogjava.net/killme2008/archive/2007/02/20/100240.htmldennisdennistue, 20 feb 2007 04:54:00 gmthttp://www.blogjava.net/killme2008/archive/2007/02/20/100240.htmlhttp://www.blogjava.net/killme2008/comments/100240.htmlhttp://www.blogjava.net/killme2008/archive/2007/02/20/100240.html#feedback0http://www.blogjava.net/killme2008/comments/commentrss/100240.htmlhttp://www.blogjava.net/killme2008/services/trackbacks/100240.html1。直接插入排序:直接插入排序是一种简单的排序方法,它的基本思想是将待排序的记录按照其值的大小插入到已排好序的有序表的适当位置,直到全部插入完为止。举个整型的排序例子
2。直接插入排序的伪代码:
insertionsort(data[])
   
for i=1 to data.length-1
       tmp
=data[i];
       将所有大于tmp的元素data[j]向后移动一位;
       将tmp放在正确的位置上;

3.简单例子,以整型为例。
a)ruby语言实现:
 
def insertion_sort(a)
  i
=1
  
while i<a.length
    tmp
=a[i]
    j
=i
    
while j>0
      break 
if tmp>a[j-1]
      a[j]
=a[j-1]
      j
-=1
    end
    a[j]
=tmp
    i
=1
  end
end       
a
=[10,2,3]
insertion_sort(a)
a
.each{|| print i.to_s" "}

b)java语言实现:
package com.sohu.blog.denns_zane.sort;

public class insertsort{
    
public static void main(string args[]){
        
int []data={12,2,3,56,90};
        insertsort(data);
        
for(int i=0;i<data.length;i){
            system.out.print(data[i]
" ");
        }

    }

    
public static void insertsort(int []data){
        
for(int i=1,j;i<data.length;i){
            
int tmp=data[i];
            
for(j=i;j>0&&tmp<data[j-1];j--)
               data[j]
=data[j-1];
            data[j]
=tmp;   
        }

    }

}


5。算法复杂度:
最好情况:进行n-1次比较和2(n-1)次移动,尽管他们其实都是多余的,复杂度o(n)
最坏情况:具体计算略,o(n*n)
平均情况:o(n*n),也就是接近最坏情况,在平均情况下,数组大小翻倍,它的排序工作将是原来的4倍。

二。选择排序
1。算法描述:选择算法试图先查找一个放错位置的元素并将它放到最终位置上,以此来局部化数组元素的交换。选择值最小的元素并将它和第一个位置上的元素交换。在第i步中,查找data[i],...,data[n-1]中的最小元素,并将它和data[i]进行交换。重复此过程,直到所有的元素都放入正确的位置为止。

2。伪代码描述:
selectionsort(data[])
     
for i=0 to data.length-2
        从data[i],,data[n
-1]中选取最小的元素
        将它和data[i]交换
 
3。实现,以整型数组为例:
1)ruby语言实现:

def selection_sort(a)
  least
=0
  
for i in (0..(a.length-2))
    j
=i1
    least
=i
    
while j<a.length
      
if a[j]<a[least]
        least
=j
      end
      j
=1  
    end
    a[least]
,a[i]=a[i],a[least] unless least==#交换
  end
end
a
=[12,4,34,23,45,35]
selection_sort(a)
a
.each{|i| print i.to_s" "}

代码很好理解,不做解释。

2)java语言实现:
package com.sohu.blog.denns_zane.sort;

public class selectionsort{
    public static 
int[] selection_sort(int [] data){
        
int i,j,least=0;
        
for(i=0;i<data.length-1;i){
        
          
for(j=i1,least=i;j<data.length;j)
            
if (data[j]<=data[least])
                    least
=j;
          
if (least!=i)
            swap(data
,least,i);  //½»»»data[i]ºí×îð¡ôªëø
        }    
        
return data;   
    }
    public static void swap(
int[]data,int least,int i){
        
int tmp=data[least];
        data[least]
=data[i];
        data[i]
=tmp;
    }
    public static void main(string args[]){
        
int[] t={10,29,12,23,56};
        selection_sort(t);
        
for(int i:t){
            
system.out.print(i" ");
        } 
    }
}

4.算法效率:
任何情况下,都需要进行n*(n-1)/2次比较,也就是o(n*n)的复杂度
最好情况:数组已经排序,不需要交换任何元素
最坏情况:最大元素在第一个位置而其他元素有序时,需要进行3*(n-1)次交换,即o(n),也是很好的结果

三。冒泡排序
1。算法伪代码描述:
bubblesort(data[])
  
for i=0 to data.length-2
     
for j=data.length-1 downto i1
         如果顺序错误,就交换j和j
-1位置上的元素

2。实现:
1)ruby语言实现:
def bubble_sort(data)
  
for i in (0..(data.length-2))
     j
=data.length-1
     
while j>i
        
if data[j]<data[j-1]
           data[j]
,data[j-1]=data[j-1],data[j]   #交换
        end
        j
-=1
     end
  end
end
a
=[12,3,56,7,89,87]
bubble_sort(a)
a
.each{|i| print i.to_s" "}

2)java语言实现:
package com.sohu.blog.denns_zane.sort;

public class bubblesort{
    
public static void bubble_sort(int [] data){
        
for(int i=0;i<data.length-1;i)
            
for(int j=data.length-1;j>i;j--)
              
if(data[j]<data[j-1])
                swap(data,j,j
-1);
        
    }

    
public static void swap(int[]data,int least,int i){
        
int tmp=data[least];
        data[least]
=data[i];
        data[i]
=tmp;
    }

    
public static void main(string args[]){
        
int[] t={10,29,12,23,56};
        bubble_sort(t);
        
for(int i:t){
            system.out.print(i
" ");
        }
 
    }

}


3。算法效率:
冒泡排序的比较次数近似是插入排序的两倍,和选择排序相同;移动次数和插入排序相同,是选择排序的n倍。可以说,插入排序比冒泡排序快两倍。


dennis 2007-02-20 12:54
]]>
数据结构之栈与队列 http://www.blogjava.net/killme2008/archive/2007/02/20/100239.htmldennisdennistue, 20 feb 2007 04:51:00 gmthttp://www.blogjava.net/killme2008/archive/2007/02/20/100239.htmlhttp://www.blogjava.net/killme2008/comments/100239.htmlhttp://www.blogjava.net/killme2008/archive/2007/02/20/100239.html#feedback0http://www.blogjava.net/killme2008/comments/commentrss/100239.htmlhttp://www.blogjava.net/killme2008/services/trackbacks/100239.html阅读全文

dennis 2007-02-20 12:51
]]>
数据结构之二叉树http://www.blogjava.net/killme2008/archive/2007/02/20/100238.htmldennisdennistue, 20 feb 2007 04:49:00 gmthttp://www.blogjava.net/killme2008/archive/2007/02/20/100238.htmlhttp://www.blogjava.net/killme2008/comments/100238.htmlhttp://www.blogjava.net/killme2008/archive/2007/02/20/100238.html#feedback0http://www.blogjava.net/killme2008/comments/commentrss/100238.htmlhttp://www.blogjava.net/killme2008/services/trackbacks/100238.html阅读全文

dennis 2007-02-20 12:49
]]>
数据结构之链表http://www.blogjava.net/killme2008/archive/2007/02/20/100237.htmldennisdennistue, 20 feb 2007 04:44:00 gmthttp://www.blogjava.net/killme2008/archive/2007/02/20/100237.htmlhttp://www.blogjava.net/killme2008/comments/100237.htmlhttp://www.blogjava.net/killme2008/archive/2007/02/20/100237.html#feedback1http://www.blogjava.net/killme2008/comments/commentrss/100237.htmlhttp://www.blogjava.net/killme2008/services/trackbacks/100237.html阅读全文

dennis 2007-02-20 12:44
]]>
网站地图