PL/SQL计算质数



PL/SQL计算质数  

2008-01-08 10:48:31|  分类: 默认分类 |  标签: |举报 |字号 订阅

看到别人写的一片计算质数文章, 感觉不错;转过来学习一下。

目标很简单,列出100以内的质数。

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

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

PHP code:


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  /

PL/SQL 过程已成功完成。

已用时间:  00: 00: 00.09

.


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

PHP code:


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可以被整除就不需要继续循环,可以马上跳出内层循环了。经过简单优化后:

PHP code:


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,这样外层循环次数就减少了一半。

PHP code:


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

.


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

PHP code:


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),下面将这个表达式转换一下,避免使用这个函数:

PHP code:


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秒。

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

PHP code:


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开始。

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

PHP code:


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函数转换一下,性能还会有一个相对明显的提升

PHP code:


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

.


再来一次尝试:

PHP code:


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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值