PLSQL计算质数[转帖]_MySQL, Oracle及数据库讨论区_Weblogic技术|Tuxedo技术|中间件技术|Oracle论坛|JAVA论坛|Linux/Unix技术|hadoop论坛_联动北方技术论坛  
网站首页 | 关于我们 | 服务中心 | 经验交流 | 公司荣誉 | 成功案例 | 合作伙伴 | 联系我们 |
联动北方-国内领先的云技术服务提供商
»  游客             当前位置:  论坛首页 »  自由讨论区 »  MySQL, Oracle及数据库讨论区 »
总帖数
1
每页帖数
101/1页1
返回列表
0
发起投票  发起投票 发新帖子
查看: 3947 | 回复: 0   主题: PLSQL计算质数[转帖]        下一篇 
wayne
注册用户
等级:中校
经验:1690
发帖:221
精华:0
注册:2011-7-21
状态:离线
发送短消息息给wayne 加好友    发送短消息息给wayne 发消息
发表于: IP:您无权察看 2011-8-18 14:03:22 | [全部帖] [楼主帖] 楼主

前两天给开发人员进行了PL/SQL的培训,为了帮助他们熟悉PL/SQL的语法,最后留了一个小的练习题,列出100以内的质数。

其实算法很简单,两个循环就搞定了。但是发现使用不同的算法,执行效率差别之大相当惊人,特别是数据量级很大的时候。

下面就是最常见的一种写法:(也是最差的一种)

SQL> SET SERVEROUT ON
SQL> DECLARE
2 V_FLAG BOOLEAN;
3 BEGIN
4 FOR I IN 2 .. 100 LOOP
5 V_FLAG := TRUE;
6 FOR J IN 2 .. I - 1 LOOP
7 IF MOD(I,J) = 0 THEN
8 V_FLAG := FALSE;
9 END IF;
10 END LOOP;
11
12 IF V_FLAG = TRUE THEN
13 DBMS_OUTPUT.PUT_LINE(I);
14 END IF;
15 END LOOP;
16 END;
17 /
2
3
5
7
11
13
17
19
23
29
31
37
41
43
47
53
59
61
67
71
73
79
83
89
97


PL/SQL 过程已成功完成。

已用时间: 00: 00: 00.09

由于屏幕输出操作比较慢,为了避免影响,将屏幕输出关闭。并将数据量增大到100000,以下所有的测试都在这个相同条件下进行。

SQL> DECLARE
2 V_FLAG BOOLEAN;
3 BEGIN
4 FOR I IN 2 .. 100000 LOOP
5 V_FLAG := TRUE;
6 FOR J IN 2 .. I - 1 LOOP
7 IF MOD(I,J) = 0 THEN
8 V_FLAG := FALSE;
9 END IF;
10 END LOOP;
11
12 IF V_FLAG = TRUE THEN
13 --DBMS_OUTPUT.PUT_LINE(I);
14 NULL;
15 END IF;
16 END LOOP;
17 END;
18 /


PL/SQL 过程已成功完成。

已用时间: 02: 02: 58.73

这种方法在100000数据量的用时居然达到了2个小时。

如果稍微仔细考虑一下,就会发现,系统做了很多没有必要的工作,首先判断是否能整除的时候不需要循环到I – 1,只要执行到I的平方根就可以了,而且,如果I可以被整除就不需要继续循环,可以马上跳出内层循环了。经过简单优化后:

SQL> DECLARE
2 V_FLAG BOOLEAN;
3 BEGIN
4 FOR I IN 2 .. 100000 LOOP
5 V_FLAG := TRUE;
6 FOR J IN 2 .. TRUNC(POWER(I, 0.5)) LOOP
7 IF MOD(I,J) = 0 THEN
8 V_FLAG := FALSE;
9 EXIT;
10 END IF;
11 END LOOP;
12
13 IF V_FLAG = TRUE THEN
14 --DBMS_OUTPUT.PUT_LINE(I);
15 NULL;
16 END IF;
17 END LOOP;
18 END;
19 /


PL/SQL 过程已成功完成。

已用时间: 00: 00: 16.21

效果十分的明显,从2个多小时,缩减到了16秒。

算法还可以进一步优化,考虑到质数中只有2是偶数,其他都是奇数,可以将2单独处理,然后将循环的步长设置为2,这样外层循环次数就减少了一半。

SQL> DECLARE
2 I NUMBER DEFAULT 3;
3 V_FLAG BOOLEAN;
4 BEGIN
5 --DBMS_OUTPUT.PUT_LINE(2);
6 WHILE I < 100000 LOOP
7 V_FLAG := TRUE;
8 FOR J IN 2 .. TRUNC(POWER(I, 0.5)) LOOP
9 IF MOD(I,J) = 0 THEN
10 V_FLAG := FALSE;
11 EXIT;
12 END IF;
13 END LOOP;
14
15 IF V_FLAG = TRUE THEN
16 --DBMS_OUTPUT.PUT_LINE(I);
17 NULL;
18 END IF;
19 I := I + 2;
20 END LOOP;
21 END;
22 /


PL/SQL 过程已成功完成。

已用时间: 00: 00: 09.37

仔细考虑一下,其实用来被整除的数是质数就足够了,不需要对所有奇数进行判断。在下面的过程中,使用索引表来保存计算得到的所有的质数:

SQL> DECLARE
2 TYPE T_RECORD IS TABLE OF NUMBER INDEX BY BINARY_INTEGER;
3 V_RESULT T_RECORD;
4 V_FLAG BOOLEAN;
5 V_CNT NUMBER;
6 I NUMBER DEFAULT 3;
7 BEGIN
8 V_RESULT(1) := 2;
9 --DBMS_OUTPUT.PUT_LINE(V_RESULT(1));
10 WHILE(I < 100000) LOOP
11 V_FLAG := TRUE;
12 V_CNT := V_RESULT.COUNT;
13 FOR J IN 1..V_CNT LOOP
14 IF V_RESULT(J) > POWER(I, 0.5) THEN
15 EXIT;
16 END IF;
17 IF MOD(I,V_RESULT(J)) = 0 THEN
18 V_FLAG := FALSE;
19 EXIT;
20 END IF;
21 END LOOP;
22 IF V_FLAG THEN
23 -- DBMS_OUTPUT.PUT_LINE(I);
24 V_RESULT(V_CNT+1) := I;
25 END IF;
26 I := I + 2;
27 END LOOP;
28 END;
29 /


PL/SQL 过程已成功完成。

已用时间: 00: 00: 06.68

已经将速度提高到了6秒左右,还能不能更快呢?注意到在最内层循环中调用了一个函数POWER(I, 0.5),下面将这个表达式转换一下,避免使用这个函数:

SQL> DECLARE
2 TYPE T_RECORD IS TABLE OF NUMBER INDEX BY BINARY_INTEGER;
3 V_RESULT T_RECORD;
4 V_FLAG BOOLEAN;
5 I NUMBER DEFAULT 3;
6 BEGIN
7 --DBMS_OUTPUT.PUT_LINE(2);
8 V_RESULT(1) := 2;
9 WHILE(I < 100000) LOOP
10 V_FLAG := TRUE;
11 FOR J IN 1..V_RESULT.COUNT LOOP
12 IF V_RESULT(J) * V_RESULT(J) > I THEN
13 EXIT;
14 END IF;
15 IF MOD(I,V_RESULT(J)) = 0 THEN
16 V_FLAG := FALSE;
17 EXIT;
18 END IF;
19 END LOOP;
20 IF V_FLAG THEN
21 -- DBMS_OUTPUT.PUT_LINE(I);
22 V_RESULT(V_RESULT.COUNT + 1) := I;
23 END IF;
24 I := I + 2;
25 END LOOP;
26 END;
27 /


PL/SQL 过程已成功完成。

已用时间: 00: 00: 01.03

难以置信吧,一个执行两个小时的PL/SQL,通过算法的调整可以优化到了1秒。

其实能优化的地方还有很多,不过这些优化能带来的性能提升已经很小了。比如:

SQL> DECLARE
2 TYPE T_RECORD IS TABLE OF NUMBER INDEX BY BINARY_INTEGER;
3 V_RESULT T_RECORD;
4 V_FLAG BOOLEAN;
5 I NUMBER DEFAULT 3;
6 BEGIN
7 --DBMS_OUTPUT.PUT_LINE(2);
8 V_RESULT(0) := 2;
9 WHILE(I < 100000) LOOP
10 V_FLAG := TRUE;
11 FOR J IN 1..V_RESULT.COUNT - 1 LOOP
12 IF V_RESULT(J) * V_RESULT(J) > I THEN
13 EXIT;
14 END IF;
15 IF MOD(I,V_RESULT(J)) = 0 THEN
16 V_FLAG := FALSE;
17 EXIT;
18 END IF;
19 END LOOP;
20 IF V_FLAG THEN
21 -- DBMS_OUTPUT.PUT_LINE(I);
22 V_RESULT(V_RESULT.COUNT) := I;
23 END IF;
24 I := I + 2;
25 END LOOP;
26 END;
27 /


PL/SQL 过程已成功完成。

已用时间: 00: 00: 00.96

由于从3开始步长为2,因此判断随后的质数的时候,没有必要用2去整除,而直接可以从3开始。

过程仍然可以进一步优化,可以省略掉不必要的赋值和判断语句:

SQL> DECLARE
2 TYPE T_RECORD IS TABLE OF NUMBER INDEX BY BINARY_INTEGER;
3 V_RESULT T_RECORD;
4 I NUMBER DEFAULT 3;
5 BEGIN
6 --DBMS_OUTPUT.PUT_LINE(2);
7 V_RESULT(1) := 3;
8 WHILE(I < 100000) LOOP
9 FOR J IN 1..V_RESULT.COUNT LOOP
10 IF MOD(I,V_RESULT(J)) = 0 THEN
11 EXIT;
12 END IF;
13 IF V_RESULT(J) * V_RESULT(J) > I THEN
14 -- DBMS_OUTPUT.PUT_LINE(I);
15 V_RESULT(V_RESULT.COUNT + 1) := I;
16 EXIT;
17 END IF;
18 END LOOP;
19 I := I + 2;
20 END LOOP;
21 V_RESULT(0) := 2;
22 END;
23 /


PL/SQL 过程已成功完成。

已用时间: 00: 00: 00.96

但是在100000这个数量级已经看不出性能的差别了。正如Tom所说的,系统总是可以提高1%的性能,不过付出的代价会越来越大。

刚才测试发现,将MOD函数转换一下,性能还会有一个相对明显的提升

SQL> DECLARE
2 TYPE T_RECORD IS TABLE OF NUMBER INDEX BY BINARY_INTEGER;
3 V_RESULT T_RECORD;
4 I NUMBER DEFAULT 3;
5 BEGIN
6 --DBMS_OUTPUT.PUT_LINE(2);
7 V_RESULT(1) := 3;
8 WHILE(I < 100000) LOOP
9 FOR J IN 1..V_RESULT.COUNT LOOP
10 IF V_RESULT(J) * V_RESULT(J) > I THEN
11 --DBMS_OUTPUT.PUT_LINE(I);
12 V_RESULT(V_RESULT.COUNT + 1) := I;
13 EXIT;
14 END IF;
15 IF TRUNC(I/V_RESULT(J)) = I/V_RESULT(J) THEN
16 EXIT;
17 END IF;
18 END LOOP;
19 I := I + 2;
20 END LOOP;
21 V_RESULT(0) := 2;
22 END;
23 /


PL/SQL 过程已成功完成。

已用时间: 00: 00: 00.87

再来一次尝试:

SQL> DECLARE
2 TYPE T_RECORD IS TABLE OF NUMBER INDEX BY BINARY_INTEGER;
3 V_RESULT T_RECORD;
4 I NUMBER DEFAULT 3;
5 N NUMBER DEFAULT 0;
6 BEGIN
7 --DBMS_OUTPUT.PUT_LINE(2);
8 V_RESULT(1) := 3;
9 WHILE(I < 100000) LOOP
10 FOR J IN 1..V_RESULT.COUNT LOOP
11 IF V_RESULT(J) * V_RESULT(J) > I THEN
12 --DBMS_OUTPUT.PUT_LINE(I);
13 V_RESULT(V_RESULT.COUNT + 1) := I;
14 EXIT;
15 END IF;
16 IF TRUNC(I/V_RESULT(J)) = I/V_RESULT(J) THEN
17 EXIT;
18 END IF;
19 END LOOP;
20 IF N = 2 THEN
21 I := I + 4;
22 N := 1;
23 ELSE
24 I := I + 2;
25 N := N + 1;
26 END IF;
27 END LOOP;
28 V_RESULT(0) := 2;
29 END;
30 /


PL/SQL 过程已成功完成。

已用时间: 00: 00: 00.84

看来Tom说的确实没有错,优化的方法总是存在的。




赞(0)    操作        顶端 
总帖数
1
每页帖数
101/1页1
返回列表
发新帖子
请输入验证码: 点击刷新验证码
您需要登录后才可以回帖 登录 | 注册
技术讨论