天天看點

PostgreSQL 生成任意基數數獨 - 4

标簽

PostgreSQL , 數獨 , 求解 , 動态規劃

https://github.com/digoal/blog/blob/master/201803/20180321_01.md#%E8%83%8C%E6%99%AF 背景

使用

《PostgreSQL 生成任意基數數獨 - 3》

 提供的方法,可以生成有解數獨。在不知道數獨答案的情況下,如何暴力破解呢?

實際上可以修改一下

《PostgreSQL 生成任意基數數獨 - 2》

 裡面的随機生成數獨的函數,破解數獨。

https://github.com/digoal/blog/blob/master/201803/20180321_01.md#%E7%A0%B4%E8%A7%A3%E6%95%B0%E7%8B%AC%E5%87%BD%E6%95%B0%E5%A6%82%E4%B8%8B 破解數獨函數如下

create or replace function resolve_sudoku(             vsudoku int[]  -- 求解數獨       ) returns int[] as $$           declare             res int[];           -- 結果           dims int := array_length(vsudoku,1);   -- X,Y,BOX集合元素個數           dim int := sqrt(dims);  -- 基數         vxyb xyb[];          -- 存儲每個像素在XYB方向上未填充的元素個數           x int;               -- 從xyb[]集合中,按指定方法選中一個像素。  X坐标           y int;               -- 從xyb[]集合中,按指定方法選中一個像素。  Y坐标           vloops int := 2*dims;     -- 計算N次(實際上就是随機多少次能覆寫到所有的值,值的取值空間為dims,通常來說執行DIMS次,能覆寫到所有的随機數)           vloop int :=0;            -- 計算N次計數器           cnt int := 0;             -- 統計目前數獨總共填充了多少個元素           rand int;                 -- 随機值         begin             -- 求解         res := vsudoku;         loop             -- 生成每個像素X,Y,B方向的未知值個數             select comp_xyb(res, dim) into vxyb;             -- 選擇下一個要填充的像素(根據未知值個數排行,從總未知值最多,按單軸最多的位置中随機取一個位置)             select ax,ay into x,y from                unnest(vxyb) t              where                t.x+t.y+t.b <> 0              order by                (t.x+t.y+t.b) desc ,                greatest(t.x,t.y,t.b) desc              limit 1;               -- 如果全部為0,0,0,說明已解完,傳回res。             if not found then               raise notice '計算有解,計算%次,結束。', cnt;               return res;             end if;             -- 初始化以下計算循環次數             vloop := 0;             loop                 -- 生成随機值                 rand := 1+(random()*(dims-1))::int;                 -- 這輪循環無法生成并傳回空                if vloop >= vloops then                   raise notice '本像素已循環%次,計算無解。已填充%個元素。無解數獨如下: %', vloop, cnt, res;         	-- return res;         	return null;               end if;                 -- 循環次數+1               vloop := vloop+1;                 -- 橫向驗證                 perform 1 where array(select res[x][generate_series(1,dims)]) && array[rand];                 if found then                   continue;                 end if;                 -- 縱向驗證                 perform 1 where array(select res[generate_series(1,dims)][y]) && array[rand];                 if found then                   continue;                 end if;                 -- BOX驗證                 perform 1 where                  array(                   select res[xx][yy] from                      (select generate_series(((((x-1)/dim)::int)*dim)+1, ((((x-1)/dim)::int)*dim)+dim) xx) t1,                      (select generate_series(((((y-1)/dim)::int)*dim)+1, ((((y-1)/dim)::int)*dim)+dim) yy) t2                 ) && array[rand];                 if found then                   continue;                 end if;                 -- 這個像素值,通過驗證                 res[x][y] := rand;                 -- raise notice 'res[%][%] %', x, y, rand;                 -- 通過驗證并跳出循環,找下一個需要填充的像素               cnt := cnt+1;               exit;               end loop;             end loop;           end;         $$ language plpgsql strict volatile;           

https://github.com/digoal/blog/blob/master/201803/20180321_01.md#%E4%BE%8B%E5%AD%90 例子

1、首先按難易程度生成幾個數獨

postgres=# select gen_sudoku_question(20);                                                                                          gen_sudoku_question                                                                                         ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------        {{1,6,7,9,5,8,4,3,2},{9,5,8,4,3,2,1,6,7},{4,3,2,1,6,7,9,5,8},{6,7,1,5,8,9,3,2,4},{5,8,9,3,2,4,6,7,1},{3,2,4,6,7,1,5,8,9},{7,1,6,8,9,5,2,4,3},{8,9,5,2,4,3,7,1,6},{2,4,3,7,1,6,8,9,5}}        {{1,0,7,9,0,8,4,3,2},{9,0,8,0,3,2,0,6,7},{4,3,2,1,6,7,0,5,8},{6,0,1,0,8,9,0,2,4},{5,0,9,3,2,4,0,7,1},{3,2,0,6,7,1,5,8,9},{7,1,0,0,0,5,0,4,3},{0,9,5,2,4,0,7,1,6},{0,4,3,7,0,6,8,9,5}}       (2 rows)       postgres=# select gen_sudoku_question(41);        NOTICE:  relation "tmp_sudoku" already exists, skipping                                                                                         gen_sudoku_question                                                                                         ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------        {{5,3,9,2,6,7,1,4,8},{1,4,8,5,3,9,2,6,7},{2,6,7,1,4,8,5,3,9},{9,5,3,7,2,6,8,1,4},{8,1,4,9,5,3,7,2,6},{7,2,6,8,1,4,9,5,3},{3,9,5,6,7,2,4,8,1},{4,8,1,3,9,5,6,7,2},{6,7,2,4,8,1,3,9,5}}        {{5,0,0,2,0,7,1,0,8},{0,4,0,0,0,9,0,6,7},{2,0,7,0,0,0,0,0,0},{9,0,3,0,2,6,8,1,4},{0,1,0,0,0,0,7,0,6},{0,0,6,0,0,0,0,5,3},{0,9,0,6,0,2,4,0,1},{4,8,0,0,9,0,6,7,2},{6,0,2,4,0,1,0,0,5}}       (2 rows)       postgres=# select gen_sudoku_question(51);        NOTICE:  relation "tmp_sudoku" already exists, skipping                                                                                         gen_sudoku_question                                                                                         ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------        {{3,4,5,8,1,6,2,9,7},{8,1,6,2,9,7,3,4,5},{2,9,7,3,4,5,8,1,6},{4,5,3,1,6,8,9,7,2},{1,6,8,9,7,2,4,5,3},{9,7,2,4,5,3,1,6,8},{5,3,4,6,8,1,7,2,9},{6,8,1,7,2,9,5,3,4},{7,2,9,5,3,4,6,8,1}}        {{0,4,5,0,0,6,2,9,0},{8,1,0,0,0,0,0,4,0},{2,0,0,3,4,0,8,0,6},{0,0,3,1,6,8,0,0,0},{0,0,8,0,0,0,4,5,0},{0,0,0,4,0,0,1,6,0},{0,0,0,0,0,1,0,0,9},{6,0,0,7,0,0,0,3,4},{0,0,0,0,0,0,0,0,1}}       (2 rows)           

2、使用上面建立的函數破解數獨

填充20個值的數獨基本上秒解。

postgres=# select res from (select resolve_sudoku('{{1,0,7,9,0,8,4,3,2},{9,0,8,0,3,2,0,6,7},{4,3,2,1,6,7,0,5,8},{6,0,1,0,8,9,0,2,4},{5,0,9,3,2,4,0,7,1},{3,2,0,6,7,1,5,8,9},{7,1,0,0,0,5,0,4,3},{0,9,5,2,4,0,7,1,6},{0,4,3,7,0,6,8,9,5}}'::int[]) res from generate_series(1,100) ) t where t.res is not null ;           
res                                                                                                 ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------        {{1,6,7,9,5,8,4,3,2},{9,5,8,4,3,2,1,6,7},{4,3,2,1,6,7,9,5,8},{6,7,1,5,8,9,3,2,4},{5,8,9,3,2,4,6,7,1},{3,2,4,6,7,1,5,8,9},{7,1,6,8,9,5,2,4,3},{8,9,5,2,4,3,7,1,6},{2,4,3,7,1,6,8,9,5}}       (1 row)           

但是對于需要填充41個,51個的,這種暴力嘗試的方式,跑1000次也解不出來。

還是需要一個非常行之有效的。

目前的暴力破解存在的問題,一個值一旦确定下來(XYB方向滿足限制)之後,後面就不會去修正它,進而導緻了一個錯誤後面的計算全是浪費的。增加了破解成本。

https://github.com/digoal/blog/blob/master/201803/20180321_01.md#%E6%89%A9%E5%B1%95 擴充

在資料庫中執行計劃也與之類似,目前大多數執行計劃是靜态的,一旦執行計劃确定下來了,後面就隻能按照執行計劃執行,無法中途調整。

是以,動态執行計劃也成為目前很多資料庫優化器突破的方向,在執行過程中,根據執行過程中的統計資訊,不斷修正執行計劃,達到最佳的目的。

實際上,導航也與之類似,如果你根據目前的路況規劃好了導航,在行駛過程中,可能某些路段發生了變化(擁堵情況),靜态導航就無法修正這樣的問題,還是按一開始既定的線路行駛。而動态導航,可以根據實時路況進行修正,選擇下一跳。

在生産中有很多類似的例子,比如資料路由,負載均衡等等。

繼續閱讀