转载丶用双链表实现搜索的优化

来源:互联网 发布:虚拟机多网络通信 编辑:程序博客网 时间:2024/04/28 05:40

     搜索是一种基本而又有效的方法,当遇到一个没有很好解决方法的题目时候想到的可能还是搜索。搜索就是把可能的出现的方法依照其搜索标准一一列出,找出需要的解。搜索中为避免无效搜索,常采用剪枝优化,提高搜索效率,如果能在扩展结点处把一些不可能搜索到的结点去掉,利用双链表将可行的解链接起来,避免无效的循环,则可以大大的提高搜索的效率。
下面以USACO1.4跳棋的挑战为例,来讲解一下如何使用循环链表来实现搜索的优化。
[题目描述]
检查一个如下的6 x 6的跳棋棋盘,有六个棋子被放置在棋盘上,使得每行,每列,每条对角线(包括两条主对角线的所有对角线)上都至多有一个棋子。
列号
1 2 3 4 5 6
-------------------------
1 | | O | | | | |
-------------------------
2 | | | | O | | |
-------------------------
3 | | | | | | O |
-------------------------
4 | O | | | | | |
-------------------------
5 | | | O | | | |
-------------------------
6 | | | | | O | |
-------------------------
上面的布局可以用序列2 4 6 1 3 5来描述,第i个数字表示在第i行的相应位置有一个棋子,如下:
行号 1 2 3 4 5 6
列号 2 4 6 1 3 5
这只是跳棋放置的一个解。请遍一个程序找出所有跳棋放置的解。并把它们以上面的序列方法输出。解按字典顺序排列。请输出前3个解。最后一行是解的总个数。
特别注意: 对于更大的N(棋盘大小N x N)你的程序应当改进得更有效。不要事先计算出所有解然后只输出,这是作弊。如果你坚持作弊,那么你登陆USACO Training的帐号将被无警告删除
PROGRAM NAME: checker
INPUT FORMAT
一个数字N (6≤N≤13) 表示棋盘是N x N大小的。
SAMPLE INPUT(checker.in)
6
OUTPUT FORMAT
前三行为前三个解,每个解的两个数字之间用一个空格隔开。第四行只有一个数字,表示解的总数。
SAMPLE OUTPUT(checker.out)
2 4 6 1 3 5
3 6 2 5 1 4
4 1 5 2 6 3
4
我们先来看一下一般的N皇后的程序:
const maxn=15;
var a:array[1..maxn] of integer;
     b:array[1..maxn] of boolean;
     c:array[1..2*maxn] of boolean;
     d:array[-maxn+1..maxn-1] of boolean;
     i,j,sum,n:longint;
procedure try(t:integer);
   var i:integer;
   begin
     for i:=1 to n do//每一个t皇后,再循环N次
       if b[i] and c[t+i] and d[t-i] then begin
         b[i]:=false; c[t+i]:=false; d[t-i]:=false;
         a[t]:=i;
         if t=n then begin
           inc(sum);
           if sum<=3 then begin
             write(a[1]);
             for j:=2 to n do write(' ',a[j]);
             writeln;
           end;
        end
        else try(t+1);
        b[i]:=true; c[t+i]:=true; d[t-i]:=true;
        a[t]:=0;
     end;
   end;
begin
   assign(input,'checker.in'); reset(input);
   assign(output,'checker.out'); rewrite(output);
   readln(n);
   fillchar(b,sizeof(b),true);
   fillchar(c,sizeof(c),true);
   fillchar(d,sizeof(d),true);
   sum:=0;
  try(1);
   writeln(sum);
   close(input); close(output);
end.
[分析]
这题是我们常见的N皇后问题,如果使用一般的搜索程序,对于n=13的数据很难在1S内求出解来,如何提高它的搜索效率呢?对于每一个棋子,都要循环n次进行判断,如果我们用双链表把不可能的答案去掉,就可以避免无效的循环,则大大的提高了搜索的效率。
使用双链表的一般模式:
(1)刚开始时,进行双链表的初始化:
for i:=1 to n do begin
   pre[i]:=i-1; next[i]:=i+1;
end;
pre[n+1]:=n; next[0]:=1;
(2)在搜索中,对双链表进行修改:
next[pre[i]]:=next[i]; pre[next[i]]:=pre[i]; {去掉i这个无效结点}
next[pre[i]]:=i; pre[next[i]]:=i;  对双链表进行还原
使用双链表后的程序:
const maxn=20;
var a:array[1..maxn] of integer;
    pre,next:array[0..maxn+1] of integer;
    c:array[1..2*maxn] of boolean;
    d:array[-maxn+1..maxn-1] of boolean;
    i,j,sum,n:longint;
procedure try(t:integer);
  var i:integer;
  begin
    i:=0;
    repeat
      i:=next[i];
      if i>n then exit;
      if c[t+i] and d[t-i] then begin
        c[t+i]:=false; d[t-i]:=false;
        next[pre[i]]:=next[i]; pre[next[i]]:=pre[i];//对双链表进行修改,去掉i结点
        a[t]:=i;
        if t=n then begin
          inc(sum);
          if sum<=3 then begin
            write(a[1]);
            for j:=2 to n do write(' ',a[j]);
            writeln;
          end;
        end
        else try(t+1);
        c[t+i]:=true; d[t-i]:=true;
        next[pre[i]]:=i; pre[next[i]]:=i;//还原双链表
      end;
    until false;
  end;
begin
  assign(input,'checker.in'); reset(input);
  assign(output,'checker.out'); rewrite(output);
  readln(n);
  fillchar(c,sizeof(c),true);
  fillchar(d,sizeof(d),true);
  for i:=1 to n do begin//设置双链表
    pre[i]:=i-1; next[i]:=i+1;
  end;
  pre[n+1]:=n; next[0]:=1;
  sum:=0;
  try(1);
  writeln(sum);
  close(input); close(output);
end.
我们来看一下,两者之间的时间效率,为了突出比较,多增加了一个n=14的数据。
从上面的结果,可以看出使用双链表可以较大的提高效率,下面我们再以2004年noip提高组最后一题虫食算为例,来看一下如何实现其编程。
虫食算(alpha.pas/dpr/c/cpp)
【问题描述】
所谓虫食算,就是原先的算式中有一部分被虫子啃掉了,需要我们根据剩下的数字来判定被啃掉的字母。来看一个简单的例子:
       43#98650#45
+   8468#6633
       44445506978
其中#号代表被虫子啃掉的数字。根据算式,我们很容易判断:第一行的两个数字分别是5和3,第二行的数字是5。
现在,我们对问题做两个限制:
首先,我们只考虑加法的虫食算。这里的加法是N进制加法,算式中三个数都有N位,允许有前导的0。
其次,虫子把所有的数都啃光了,我们只知道哪些数字是相同的。我们将相同的数字用相同的字母表示,不同的数字用不同的字母表示。如果这个算式是N进制的,我们就取英文字母表中的前N个大写字母来表示这个算式中的0到N-1这N个不同的数字(但是这N个字母并不一定顺序地代表0到N-1)。输入数据保证N个字母分别至少出现一次。
       BADC
+   CBDA
       DCCC
上面的算式是一个4进制的算式。很显然,我们只要让ABCD分别代表0123,便可以让这个式子成立了。你的任务是,对于给定的N进制加法算式,求出N个不同的字母分别代表的数字,使得该加法算式成立。输入数据保证有且仅有一组解。
【输入文件】
输入文件alpha.in包含4行。第一行有一个正整数N(N <= 26),后面的3行每行有一个由大写字母组成的字符串,分别代表两个加数以及和。这3个字符串左右两端都没有空格,从高位到低位,并且恰好有N位。
【输出文件】
输出文件alpha.out包含一行。在这一行中,应当包含唯一的那组解。解是这样表示的:输出N个数字,分别表示A,B,C……所代表的数字,相邻的两个数字用一个空格隔开,不能有多余的空格。
【样例输入】
5
ABCED
BDACE
EBBAA
【样例输出】
1 0 3 4 2
【数据规模】
对于30%的数据,保证有N ≤ 10;
对于50%的数据,保证有N  ≤ 15;
对于全部的数据,保证有N  ≤26。
[参考程序]
var
   num:array['A'..'Z']of byte;
   used:array[0..25]of boolean;
   pre,next:array[-1..26]of shortint;
   s:array[1..3]of string;
   n,i:byte;
procedure out;
   var
     c:char;
   begin
     for c:='A' to chr(63+n) do
       write(num[c],' ');
     writeln(num[chr(64+n)]);
     close(input); close(output);
     halt;
   end;
procedure search(l,p,d:byte);
   var
     i,t:shortint;
   function check:boolean;
     var
       i,x,y,z,t:byte;
     begin
       check:=false;
       x:=num[s[1,1]];y:=num[s[2,1]];z:=num[s[3,1]];
       case ord(x<26)*4+ord(y<26)*2+ord(z<26) of
         7:if x+y>z then exit;
         6:if x+y>=n then exit;[j1]
        5:if x>z then exit;
        3:if y>z then exit;
      end;
      for i:=1 to l-1 do begin
        x:=num[s[1,i]];y:=num[s[2,i]];z:=num[s[3,i]];
        case ord(x<26)*4+ord(y<26)*2+ord(z<26) of
          7:if (n+z-x-y) mod n>1 then exit;
          6:if used[(x+y) mod n] and used[(x+y+1) mod n] then exit;
          5:if used[(n+z-x) mod n] and used[(n+z-1-x) mod n] then exit;
          3:if used[(n+z-y) mod n] and used[(n+z-1-y) mod n] then exit;
        end;
      end;
      check:=true;
   end;
  begin
    if l=0 then begin out;exit;end;
    if p=3 then begin
      t:=num[s[1,l]]+num[s[2,l]]+d;
      if num[s[3,l]]<26 then
        if num[s[3,l]]=t mod n then search(l-1,1,ord(t>=n)) else exit
      else
        if used[t mod n] then exit else begin
          num[s[3,l]]:=t mod n;used[t mod n]:=true;
          if check then begin
            pre[next[t mod n]]:=pre[t mod n];next[pre[t mod n]]:=next[t mod n];
            search(l-1,1,ord(t>=n));
            pre[next[t mod n]]:=t mod n;next[pre[t mod n]]:=t mod n;
          end;
          num[s[3,l]]:=26;used[t mod n]:=false;
        end;
   end
    else
      if num[s[p,l]]<26 then
        search(l,p+1,d)
      else begin
        i:=next[-1];
         repeat
          if not used[i] then begin
            num[s[p,l]]:=i;used[i]:=true;
            if check then begin
              pre[next[i]]:=pre[i];next[pre[i]]:=next[i];
              search(l,p+1,d);
              pre[next[i]]:=i;next[pre[i]]:=i;
            end;
            num[s[p,l]]:=26;used[i]:=false;
          end;
          i:=next[i];
        until i=26;
      end;
  end;
begin
  assign(input,'alpha.in'); reset(input);
  assign(output,'alpha.out'); rewrite(output);
  fillchar(num,sizeof(num),26);
  fillchar(used,sizeof(used),0);
  readln(n);
  for i:=0 to n-1 do begin
    pre[i]:=i+1;next[i]:=i-1;
  end;
  next[-1]:=n-1;next[0]:=26;pre[26]:=0;
  readln(s[1]);readln(s[2]);readln(s[3]);
  search(n,1,0);
end.

ps: 我转的这个网不让用右键 咋办捏 我就百度了一下 得知在地址栏内 输入如下代码

javascript:alert(document.oncontextmenu='')
会出个对话框 点确定即可
把这个网站发出来 让大家实验 http://www.icnoi.cn/news2.asp?id=1504&sg=414