转载丶用双链表实现搜索的优化
2010-08-23 15:15
141 查看
搜索是一种基本而又有效的方法,当遇到一个没有很好解决方法的题目时候想到的可能还是搜索。搜索就是把可能的出现的方法依照其搜索标准一一列出,找出需要的解。搜索中为避免无效搜索,常采用剪枝优化,提高搜索效率,如果能在扩展结点处把一些不可能搜索到的结点去掉,利用双链表将可行的解链接起来,避免无效的循环,则可以大大的提高搜索的效率。
下面以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: 我转的这个网不让用右键 咋办捏 我就百度了一下 得知在地址栏内 输入如下代码
下面以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
相关文章推荐
- tyvj-1080 链表优化搜索
- 搜索二叉树实现->迭代器实现->转化为双向链表
- Android应用启动优化:一种DelayLoad的实现和原理(上篇)(转载)
- 搜索链表优化(dancing links?)
- WinEdt6+SumatraPDF--实现TeX和PDF文件正反向搜索!(转载)
- 查找算法集:顺序查找、二分查找、插值查找、动态查找(数组实现、链表实现) (转载)
- Android应用启动优化:一种DelayLoad的实现和原理(下篇)(转载)
- 转载的标准文档:C语言实现一个简单的单向链表list
- dfa最小化,上一个版本采用的是moore的打表法,这个版本采用的是hopcroft的方法,但是实现中采用链表而不是栈来优化。
- 迷宫路径搜索问题(栈的链表实现)
- (转载)solr实现满足指定距离范围条件的搜索
- compass实现搜索、订单载入功能、velocity模版静态化、OSCache优化性能、SSI实现
- 【转载】利用CPU缓存的特性优化数据存储---数组vs链表
- 单链表的逆置-C++实现(转载)
- 用VB函数Dir实现递归搜索目录(转载)
- [C语言]链表实现贪吃蛇及部分模块优化
- compass实现搜索、订单载入功能、velocity模版静态化、OSCache优化性能、SSI实现
- 创建属于自己的内核PROC文件实现链表搜索并打印iscsi信息
- 常见内排序实现汇总(含部分优化实现,基于链表的实现),以及性能比较
- 二叉树深度搜索-递归和非递归的实现(转载)