homework-04
Word Search
编码规范
用c写的。因为我们最初定位就是写一个简单粗暴的方法,涉及不到很复杂的类。当然最主要的原因还是we like c!
由于本着“简单”的思想,写函数的时候尽量考虑到了功能的明确与分离,这样代码更易读。
思路
初看这个题感觉相当难。连最朴素的算法都很难写,状态太多了。老师请来的同事讲解之后也没什么收获,都是最容易想到的东西。难写不怕,求出的解差也不怕,就怕又难写结果又差,花很大功夫写出一个很难看的东西,是我最不愿意见到的。跟小伙伴纠结了好久,怎么都感觉无从下手。一直到周一晚上都没写(也有一部分原因是本系课程很紧,实在没什么时间写这个。因为之前说周二中午前交的,所以准备周一晚上和peer交流一下)。
上课的时候老师给大家展示作业,发现还是有人做的很漂亮的,说明这个题还是能做,于是晚上回去又和peer仔细讨论了一下。由于时间不多,我们准备写一个近似算法,尽量用简单的方式考虑问题,并引入一些随机化的方法。经过讨论,得出初步想法:
- 写一个程序,给定长宽限制和所有word,看是否能得出可行解。把求最优变为判定性问题。
- 先随机放一个word,之后的每个word都要和当前连起来的word相交,如果实在不行,再随机找一个位置。
- 用这个word与之前已经填好的部分的相交的字母数量衡量这种放法的好坏。每个单词都取最优的方法,不进行回溯(这是比较关键的地方,回溯的时间复杂度是很难接受的)。
- 四角先暂时不考虑。
以上的想法看起来十分简单,并且感觉效果应该很差。无奈作业总是要教的,先写出来再说。在写的过程中,为了简化程序,考虑到没有必要一个新的word一定要和之前的相交,那样的话写起来很麻烦,不如直接找一个最好的位置。这个“最好”可以直接用3中的估价函数评判。如果与之前不相交就是0。可以直接在所有的位置和方向中找到一个最好的位置放入。在这里我们又引入了随机化的方法,对位置和word摆放的方向进行10万次随机。现在算法又变成了这样:
- 同上
- 对位置和方向随机10万次,找到“最好”的位置,将word放入,如果有一个词找不到位置则认为失败,直接跳出。
这样算法变的极为简单。用57个词的例子实验,最小可以得出20x19的结果。这已经是比较优的结果了。可喜的是程序非常简单,只有70行。这比我们最初想象中的容易了太多。
之后又想到越长的单词限制越多,如果放到最后再考虑,很可能放不进去。于是我们将words按长度从大到小排了序,优先放长单词,这样结果应该会好很多。不出所料,排序后轻松跑出了18x18的矩形。
由于我们写的new程序是一个判定性的程序,根据给定的大小判断是否可行,所以还需要多次运行,手动缩小矩阵,不过这是比较容易的。题目要求四角都有单词,而我们算法中并没有限制这一点,所以也需要多次运行,找到一个符合要求的解。由于结果比较紧凑,所以找到四角都有单词的解还是很容易的。还有一个优点是每次生成的结果都不同,所以可以产生多个不同的word search。
forwil同学又做了一个test程序用来检验结果的正确性,同时给出比较可视化的输出结果。
代码
#include<stdio.h> #include<string.h> #define MAXTREESIZE 2000 #define MAXLEN 22 #define MAXNUM 63 #define MAXMAT 100 struct Node { int flag; int p[26]; }; struct Go { int x; int y; }; struct Node queue[MAXTREESIZE]; struct Go pos[8] = { {0,1},{1,1},{1,0},{1,-1},{0,-1},{-1,-1},{-1,0},{-1,1} }; /* * note for *pos* * 0 is right * 1 is right down * 2 is down * 3 is ... you know * it is deasil(顺时针) * */ char words[MAXNUM][MAXLEN],matrix[MAXMAT][MAXMAT],out[MAXMAT*2][MAXMAT*2]; int numw,n,m,numnode,boo[MAXNUM]; int ctoint(char c) { if (c >= 'a' && c<='z') return c-'a'; if (c >= 'A' && c<='Z') return c-'A'; return -1; } int newnode() { int i; queue[numnode].flag = -1; for(i = 0;i<26;i++) queue[numnode].p[i] = -1; numnode += 1; return numnode-1; } int insertnode(int flag,char s[]) { int i,id = 0,cid; for(i = 0;i<strlen(s)-1;i++) { if (ctoint(s[i]) == -1) return -1; cid = queue[id].p[ctoint(s[i])]; if (cid == -1) { cid = newnode(); queue[id].p[ctoint(s[i])] = cid; } id = cid; } queue[id].flag = flag; return id; } int searchnode(int x,int y, int p) { int id = 0,cid; for(;x>=0&&y>=0&&x<n&&y<m; x+=pos[p].x,y+=pos[p].y) { cid = queue[id].p[ctoint(matrix[x][y])]; if (cid == -1) return -1; id = cid; if (queue[id].flag != -1) return queue[id].flag; } return -1; } int testallword() { int i,j,p,k; for(i=0;i<n;i++) for(j=0;j<m;j++) for(p=0;p<8;p++) { k = searchnode(i,j,p); if (k != -1) { //printf("%d %d %c %d %s",i,j,matrix[i][j],p,words[k]); boo[k] += 1; } } for(i=0;i<numw;i++) { //printf("%d\n",boo[i]); if (boo[i]==0) return -1; } return 0; } void addtag(int x,int y,int p,int k) { int i,g; char c; for(i = 0;i < strlen(words[k])-2;i++) { if (p == 0 || p == 4) c = '-'; if (p == 1 || p == 5) c = '\\'; if (p == 2 || p == 6) c = '|'; if (p == 3 || p == 7) c = '/'; out[2*x + pos[p].x][2*y + pos[p].y] = c; x += pos[p].x; y += pos[p].y; } return ; } void outans() { int i,j,p,k; for(i=0;i<n;i++) for(j=0;j<m;j++) { out[i*2][j*2] = matrix[i][j]; out[i*2+1][j*2] = ' ' ; out[i*2][j*2+1] = ' ' ; out[i*2+1][j*2+1] = ' ' ; } for(i=0;i<n;i++) for(j=0;j<m;j++) for(p=0;p<8;p++) { k = searchnode(i,j,p); if(k!=-1) addtag(i,j,p,k); } for(i=0;i<n*2-1;i++) printf("\t%s\n",out[i]); } int testcorner() { if(out[0][1]==' ' && out[1][0]==' ' && out[1][1]==' ') return -1; if(out[2*n-1][0]==' ' && out[2*n][1]==' ' && out[2*n-1][1]==' ') return -1; if(out[0][2*m-1]==' ' && out[1][2*m]==' ' && out[1][2*m-1]==' ') return -1; if(out[2*n-1][2*m]==' ' && out[2*n][2*m-1]==' ' && out[2*n-1][2*m-1]==' ') return -1; return 0; } int main(int argc,char *argv[]) { int i,j; if (argc == 1) { freopen("sample.txt","r",stdin); } if (argc >=2) { if(freopen(argv[1],"r",stdin)==NULL) { printf("please input the corrent <file name>\n"); return -1; } } if (argc >=3) { freopen(argv[2],"w",stdout); } scanf("%d\n",&numw); newnode(); for(i = 0; i<numw;i++) { fgets(words[i],MAXLEN,stdin); if ((j = insertnode(i,words[i]))==-1) return -1; } while(fgets(matrix[n],MAXMAT,stdin)>0) n += 1; m = strlen(matrix[0])-1; if(testallword()==0) { printf("STATE1:\tis good!\n"); outans(); } else { printf("STATE1:\tbad!\n"); printf("\tYou leave the words belove(no used or used more than once):\n"); for(i=0;i<numw;i++) if(boo[i]!=1) printf("\tused times = %d , %s",boo[i],words[i]); } if(n==m) printf("\nSTATE2:\tis good! n&m = %d\n",n); else printf("\nSTATE2:\tbad! n=%d m=%d\n",n,m); if(testcorner() == 0) printf("\nSTATE3:\tis good!\n"); else printf("\nSTATE3:\tbad!\n"); return 0; }
#include <stdio.h> #include <string.h> #include <time.h> #define MAXNUM 100 #define MAXLEN 26 #define MAXMAT 100 #define MAXRANDOM 100000 int dx[8]={-1,-1,0,1,1,1,0,-1}; int dy[8]={0,1,1,1,0,-1,-1,-1}; int board[MAXMAT][MAXMAT]; char words[MAXNUM][MAXLEN]; int N,M; FILE *fin,*fout; void work(int n); int randpos(char *word, int *posx, int *posy, int *posd); int howgood(char *word, int x, int y, int drct); void placeword(char *word, int x, int y, int drct); void swap(char a[],char b[]); int main(int argc,char *argv[]) { int n,i,j; fin=fopen("words.txt","r"); fout=fopen("result.txt","w"); fscanf(fin,"%d",&n); fprintf(fout,"%d\n",n); for(i=1;i<=n;i++) { fscanf(fin,"%s",words[i]); fprintf(fout,"%s\n",words[i]); } for(i=1;i<=n;i++) for(j=i+1;j<=n;j++) if(strlen(words[i])<strlen(words[j])) swap(words[i],words[j]); N=atoi(argv[1]); M=atoi(argv[2]); work(n); for(i=1;i<=N;i++) { for(j=1;j<=M;j++) { printf("%c", board[i][j]); fprintf(fout,"%c",board[i][j]); } printf("\n"); fprintf(fout,"\n"); } return 0; } void swap(char a[],char b[]) { int i; char c; for(i = 0;i<MAXLEN;i++) { c=a[i]; a[i]=b[i]; b[i]=c; } } void work(int n) { int i,j,posx,posy,posd,can; for(i=1;i<=N;i++)for(j=1;j<=M;j++)board[i][j]=' '; srand((int)time(0)); for(i=1;i<=n;i++) { can=randpos(words[i],&posx,&posy,&posd); if(can==-1) { printf("cannot!\n"); // getchar(); } placeword(words[i],posx,posy,posd); } } int randpos(char *word, int *posx, int *posy, int *posd) { int max,i,x,y,d,temp; max=-1; for(i=1;i<=MAXRANDOM;i++) { x=rand()%N+1; y=rand()%M+1; d=rand()%8; temp=howgood(word,x,y,d); if(temp>max) { max=temp; *posx=x; *posy=y; *posd=d; } } return max; } int howgood(char *word, int x, int y, int drct) { int xx,yy,score,i; xx=x; yy=y; score=0; for(i=0;i<strlen(word);i++) { if((xx<1 || xx>N || yy<1 || yy>M) || (board[xx][yy]!=' ' && board[xx][yy]!=word[i])) return -1; if(board[xx][yy]==word[i]) score++; xx+=dx[drct]; yy+=dy[drct]; } return score; } void placeword(char *word, int x, int y, int drct) { int xx,yy,i; xx=x; yy=y; for(i=0;i<strlen(word);i++) { board[xx][yy]=word[i]; xx+=dx[drct]; yy+=dy[drct]; } }
结果
new与test的使用方法在github的readme中
18x18
L Y I-N-P-U-T-R-O-J-A-N-H-O-R-S-E
| | /
I E-T-H-E-R-N-E-T-I-Q-U-E-T-T-E B
| | / / /
N L B-A-N-D-W-I-D-T-H-S-A-R-C U
| | | \ / / /
U I M S-E-R-V-E-R E-T-Y-B-A-G-I-G
| | | | / \ /
X M E C D M B T-R-O-P E S
| | | | / \ / / |
P S D I A I D U D B S V C-P-U
| | | |/ \ \ / \ / / | |
R V N-O-I-T-U-L-O-S-E-R-A-W-D-R-A-H
| | | /| / \ / / | |
I I M L A S U C E S-O-I-B O S
| | /| | | / \ \ \ | |
N R U A B W G B U R K W L S
| | / | | | \ \ \ \ \ | |
T U M D-R-A-O-B-Y-E-K M N T S L W
| | | | | / \ \ \ \ \ |
E S N M E S D C D P E A O E O P
| | \| | | | | \ \ \ \ \| |
R P E H E N A-V-A-J R N M P R I
| | |\| | | \ \ \ | |
A Y R N P-C-I P-O-L-F-A-R-E-T E D H
| | | | |\ | | \ |
W W E U I O W E-L-I-F-P-I-Z K C
| | | | | \ |
E A V D-R-A-C-D-N-U-O-S F C C Q A
| | | | / | \ \ \| |
E-R-I-W-E-R-I-F W R-E-B-O-O-T A C
| | | | / | \ \ |\|
R E R P S E-M-O-T-I-C-O-N N P F H
| | / | \ \ |
F D K D-A-O-L-P-U T Y E
反思
这个算法实在是没什么“高端”的地方,本身用了估价函数近似最优,又省略了回溯,可以说简化了太多,但实际上却较好的解决了问题。很多时候我们容易被“最优性”所蒙蔽,苦苦思索最优解而徒劳无功。现实生活中有最优解的情况少之又少,我们应该放眼于一些简单可行的方法,不断想办法优化,一个看似极为复杂的问题,可能被一些十分简单的规律所支配。
时间记录
预计时间 | 5h | 实际用时 | 5h |
代码规范 | 0.0h | 0.0h | |
具体设计 | 0.5h | 1h | |
具体编码 | 3.5h | 2h | |
代码复审 | 0.0h | 0.5h | |
代码测试 | 0.5h | 1h | |
测试报告 | 0.5h | 0.5h |