普林斯顿大学算法第一周个人总结2
2013-09-01 15:51
323 查看
第一周的编程作业是实现一个Percolation渗透模型。
模型描述:
有一个四方的模型,由 N*N 个区域(site)组成,每个区域有两个状态,开启(open)或关闭(blocked),相邻的开启区域能构成一条通路,当最上层区域能够通过开启区域连成的通路,和最下层互通的时候,则整个模型为渗透状态(percolated)。
如上图所示,白色和蓝色的区域为开启状态,黑色的为关闭状态,当开启的白色区域和顶层互通的时候,称为区域满(full),左侧顶层和底层能够互通,则整个模型已渗透,右侧则是非渗透的情况。
这种模型应用在实际中可类比渗水、导电等。
编程作业的要求则是实现这么一种模型,并计算出每次模型渗透时,开启区域占总区域个数的百分比,这个百分比近似于某一常数,通过多次模拟采样计算该常数。
模型分析:
(1)数据结构:给定 N 的个数,可以得到一个 N * N 个区域的模型,如果将此模型的每个区域从0进行编号,可以得到 0 ~ N^2-1 的数据,将此模型平坦成一维模型,就是一个数组的形式。
(2)数据操作:
a. 如何模拟区域的开启:开启某一个区域,将其状态标记为开启,然后四周如果有开启的区域,应该将它们连在一起。
b. 如何模拟模型的渗透:当顶层的任一区域和底层的任一区域连接在一起时,则模型渗透。实际操作时,这是一个双重for循环,可以虚拟两个区域,一个和顶层所有开启的区域互连,一个和底层所有开启的区域互连,当这两个区域连接在一起时,则模型为渗透状态,避免了双重for,N * N的数组访问操作。
作业只能用 Java 语言提交,除了此要求以外,还有其他诸如变量命名等程序风格规范、内存使用限制、运行时间限制等。完成作业以后,我将整个模型用 C 又实现了一次,并用 C 重写了算法的实现(参见个人总结1)。这篇算是个人的心得体会。
从 C 转向 Java 这个面向过程语言的时候,许多地方都不适应,磕磕绊绊在 Java 程序的编写上耽误了不少时间。作业提交以后,尝试用 C 重新完成这个任务的时候,对抽象数据类型 ADT(Abstract Data Type) 的运用感觉更加清晰。按照 Java 作业的要求,渗透模型需要完成的操作有:
ADT 是对程序正交性的实现,要求尽量将各模块之间的耦合度降低,使得修改某个模块的实现不会影响到其外部的调用。在忽略算法实现的前提下,将模型数据结构放在 C 文件,头文件中只提供调用模块的类型以及模块调用结构。
模块数据结构的内容(percolate.c文件):
模块提供的外部类型和接口(percolation.h文件):
虽然 C 做不到严格意义上的面向对象的封装,但是这些接口的提供也足以使调用者完全不用关心内部实现。头文件(percolation.h)
一行,防止编译器的警告,预先声明这样一个类型,类型的实现则完全隐藏在C文件中。
设计好这样的结构之后,先写测试程序,然后完成一个模型实现的框架,就可以继续完善模型的实现部分。
之后在渗透模型需要调用的算法实现时,也同样按照这个思想,让渗透模块完全不用关心算法的具体实现,只需要调用相应的接口完成相能的功能即可。
假设渗透模型使用 Quick-Find 算法来维护管理所有的区域。
算法的数据结构实现(quick-find.c 文件):
算法提供的外部接口(quick-find.h 文件):
这样的好处很明显,假如一开始采用 Quick-Union算法来实现,即使后来觉得算法效率不高,修改为Weighted-Quick-Union实现,只要接口不变,那么渗透模型完全不需要做任何改动。
详细算法的实现不在此贴出,我已经将代码全部推送到个人在 GitHub 的远程仓库,页面地址:https://github.com/Revil/algs-percolation/tree/master,仓库地址:git@github.com:Revil/algs-percolation.git,总共有四大分支,主线
master 是采用 Java 语言实现的。三种算法的 C 实现分别放在了三个不同的分支。下面是分支的列表:
在总结1的时候提到三种算法的执行效率问题。我用一个模型渗透的测试程序,测试传入不同 N 值从模型全封闭到模型开启所用的时间。
这是测试程序的主要实现部分:
测试的效果如下:
Quick-Find 测试的时候,传入 800 的时偶迟迟不见程序结束,后来直接强制关闭程序了,根据从200到400的时间增长的趋势来看,估计要运行数星期的时间(是星期,不是小时),1600 就更不用测试了。而到 Quick-Union 的时候,消耗时间已经显著下降,到 Weighted-Quick-Union 的时候,即使数量成四倍的增长(注意传入的N值在渗透模型种使用的是 N * N,所以对算法来说,实际增长数不只是翻倍而已),所用时间也没有很明显的增加。
附言:
关于这个模型,其实存在一个问题,在算法课程的论坛上,讨论的热度很高。问题是这样的:
由于引入虚拟的顶层区域和虚拟的底层区域,那么当模型渗透的时候,可能会出现下图的情况
如右边图所示,由于所有的底层区域都和虚拟底层区域相连,所以一旦当区域渗透,则和其他的底层开启区域相连的区域也显示为区域满状态。而实际的情况应该是按照左图所示。这个问题称为 backwash,个人把这个翻译成“回流”。引入虚拟底层区域,很难避免这个问题。讨论的结果,有两种方式可以改进:
1. 不使用虚拟底层区域,只保留顶层,判断是否渗透的时候用虚拟顶层和一个for循环来判断。
2. 保留虚拟底层区域,另外加一个不使用虚拟底层的模型,将两个模型结合在一起来判断是否渗透,通过浪费一些内存来保证效率。
由于作业要求 isOpen 和 isFull 这两个接口算法效率都只能是常数值,所以后来大家只能采用看起来很奇怪的第2种方式来实现(backwash测试不通过的话只能拿87分)。
模型描述:
有一个四方的模型,由 N*N 个区域(site)组成,每个区域有两个状态,开启(open)或关闭(blocked),相邻的开启区域能构成一条通路,当最上层区域能够通过开启区域连成的通路,和最下层互通的时候,则整个模型为渗透状态(percolated)。
如上图所示,白色和蓝色的区域为开启状态,黑色的为关闭状态,当开启的白色区域和顶层互通的时候,称为区域满(full),左侧顶层和底层能够互通,则整个模型已渗透,右侧则是非渗透的情况。
这种模型应用在实际中可类比渗水、导电等。
编程作业的要求则是实现这么一种模型,并计算出每次模型渗透时,开启区域占总区域个数的百分比,这个百分比近似于某一常数,通过多次模拟采样计算该常数。
模型分析:
(1)数据结构:给定 N 的个数,可以得到一个 N * N 个区域的模型,如果将此模型的每个区域从0进行编号,可以得到 0 ~ N^2-1 的数据,将此模型平坦成一维模型,就是一个数组的形式。
(2)数据操作:
a. 如何模拟区域的开启:开启某一个区域,将其状态标记为开启,然后四周如果有开启的区域,应该将它们连在一起。
b. 如何模拟模型的渗透:当顶层的任一区域和底层的任一区域连接在一起时,则模型渗透。实际操作时,这是一个双重for循环,可以虚拟两个区域,一个和顶层所有开启的区域互连,一个和底层所有开启的区域互连,当这两个区域连接在一起时,则模型为渗透状态,避免了双重for,N * N的数组访问操作。
作业只能用 Java 语言提交,除了此要求以外,还有其他诸如变量命名等程序风格规范、内存使用限制、运行时间限制等。完成作业以后,我将整个模型用 C 又实现了一次,并用 C 重写了算法的实现(参见个人总结1)。这篇算是个人的心得体会。
从 C 转向 Java 这个面向过程语言的时候,许多地方都不适应,磕磕绊绊在 Java 程序的编写上耽误了不少时间。作业提交以后,尝试用 C 重新完成这个任务的时候,对抽象数据类型 ADT(Abstract Data Type) 的运用感觉更加清晰。按照 Java 作业的要求,渗透模型需要完成的操作有:
public class Percolation { public Percolation(int N) // create N-by-N grid, with all sites blocked public void open(int i, int j) // open site (row i, column j) if it is not already public boolean isOpen(int i, int j) // is site (row i, column j) open? public boolean isFull(int i, int j) // is site (row i, column j) full? public boolean percolates() // does the system percolate? }
ADT 是对程序正交性的实现,要求尽量将各模块之间的耦合度降低,使得修改某个模块的实现不会影响到其外部的调用。在忽略算法实现的前提下,将模型数据结构放在 C 文件,头文件中只提供调用模块的类型以及模块调用结构。
模块数据结构的内容(percolate.c文件):
struct percolation { QuickFind grid; /* data structure */ char* site_state; /* record the state of every site */ int n; /* grid side count */ };
模块提供的外部类型和接口(percolation.h文件):
struct percolation; typedef struct percolation *Perco; /* initialize a percolation model */ Perco perco_init(int n); /* open site of position(x, y) in percolation model. * Position (0,0) is on the topleft */ int perco_open(Perco pl, int x, int y); /* return true if (x,y) site is opened */ int is_open(Perco pl, int x, int y); /* return true if (x,y) site is full */ int is_full(Perco pl, int x, int y); /* return true if the model is percolated */ int is_percolated(Perco pl); /* end the percolation model, must be called * after you are done with the model */ void perco_end(Perco pl);
虽然 C 做不到严格意义上的面向对象的封装,但是这些接口的提供也足以使调用者完全不用关心内部实现。头文件(percolation.h)
struct percolation;
一行,防止编译器的警告,预先声明这样一个类型,类型的实现则完全隐藏在C文件中。
设计好这样的结构之后,先写测试程序,然后完成一个模型实现的框架,就可以继续完善模型的实现部分。
之后在渗透模型需要调用的算法实现时,也同样按照这个思想,让渗透模块完全不用关心算法的具体实现,只需要调用相应的接口完成相能的功能即可。
假设渗透模型使用 Quick-Find 算法来维护管理所有的区域。
算法的数据结构实现(quick-find.c 文件):
struct qf { int* qf_array; int count; };
算法提供的外部接口(quick-find.h 文件):
struct qf; typedef struct qf* QuickFind; /* Initialize the data structure, must be called first */ QuickFind qf_init(int n); /* connect two components to which p and q belong */ int qf_union(QuickFind qf, int p, int q); /* check whether p and q elements are connected */ int qf_connected(QuickFind qf, int p, int q); /* must be called after you are done with the data structure */ void qf_end(QuickFind qf);
这样的好处很明显,假如一开始采用 Quick-Union算法来实现,即使后来觉得算法效率不高,修改为Weighted-Quick-Union实现,只要接口不变,那么渗透模型完全不需要做任何改动。
详细算法的实现不在此贴出,我已经将代码全部推送到个人在 GitHub 的远程仓库,页面地址:https://github.com/Revil/algs-percolation/tree/master,仓库地址:git@github.com:Revil/algs-percolation.git,总共有四大分支,主线
master 是采用 Java 语言实现的。三种算法的 C 实现分别放在了三个不同的分支。下面是分支的列表:
c-implementation-quick-find c-implementation-quick-union c-implementation-weighted-quick-union * master
在总结1的时候提到三种算法的执行效率问题。我用一个模型渗透的测试程序,测试传入不同 N 值从模型全封闭到模型开启所用的时间。
这是测试程序的主要实现部分:
clock_t ts, te; if (argc < 2) { fprintf(stderr, "Usage: %s <n>\n", argv[0]); exit(EXIT_FAILURE); } n = atoi(argv[1]); printf("n = %d\n", n); srand(time(NULL)); ts = clock(); pl = perco_init(n); do { p = rand() % n + 1; q = rand() % n + 1; if (is_open(pl, p, q)) continue; perco_open(pl, p, q); } while (!is_percolated(pl)); perco_end(pl); te = clock(); printf("Time elasped: %f\n", (double) (te - ts) / CLOCKS_PER_SEC);
测试的效果如下:
N | Quick-Find(s) | Quick-Union(s) | Weighted-Quick-Union(s) |
200 | 3.07 | 0.04 | 0.01 |
400 | 51.53 | 0.21 | 0.04 |
800 | ... | 2.27 | 0.14 |
1600 | ... | 17.53 | 0.75 |
附言:
关于这个模型,其实存在一个问题,在算法课程的论坛上,讨论的热度很高。问题是这样的:
由于引入虚拟的顶层区域和虚拟的底层区域,那么当模型渗透的时候,可能会出现下图的情况
如右边图所示,由于所有的底层区域都和虚拟底层区域相连,所以一旦当区域渗透,则和其他的底层开启区域相连的区域也显示为区域满状态。而实际的情况应该是按照左图所示。这个问题称为 backwash,个人把这个翻译成“回流”。引入虚拟底层区域,很难避免这个问题。讨论的结果,有两种方式可以改进:
1. 不使用虚拟底层区域,只保留顶层,判断是否渗透的时候用虚拟顶层和一个for循环来判断。
2. 保留虚拟底层区域,另外加一个不使用虚拟底层的模型,将两个模型结合在一起来判断是否渗透,通过浪费一些内存来保证效率。
由于作业要求 isOpen 和 isFull 这两个接口算法效率都只能是常数值,所以后来大家只能采用看起来很奇怪的第2种方式来实现(backwash测试不通过的话只能拿87分)。
相关文章推荐
- 普林斯顿大学算法第一周个人总结2
- 普林斯顿大学算法第一周个人总结1
- 普林斯顿大学算法第一周个人总结1
- 普林斯顿大学算法课程第二周个人总结
- 普林斯顿大学算法课程第三周个人总结
- PCA降维算法总结以及matlab实现PCA(个人的一点理解)
- 牛客网左程云老师的算法视频个人总结
- 算法之个人总结:Hash表之简单应用
- PCA降维算法总结以及matlab实现PCA(个人的一点理解)
- 普林斯顿大学算法Week2:Deques and Randomized Queues(95分)--总结及代码
- PCA降维算法总结以及matlab实现PCA(个人的一点理解)
- PCA降维算法总结以及matlab实现PCA(个人的一点理解)
- 机器学习常见算法个人总结(面试用)
- 机器学习常见算法个人总结(面试用)
- PCA降维算法总结以及matlab实现PCA(个人的一点理解)
- 机器学习常见算法个人总结
- 个人总结-----非贪心算法的图的m着色判断及优化问题
- 普林斯顿大学算法第一部分学习总结(Week4-Priority Queue)
- 机器学习常见算法个人总结
- 算法之个人总结:Hash表之简单应用