关于图的路径寻找问题
图与树的最大差别在于图没有层次结构,说不定当前的节点的子节点就是上上层的某一个节点。所以说,对于图来说,如何查重是很重要的。
对于路径寻找问题,深度优先可以帮助我们找到一条路径,而广度优先可以帮助我们找到一条最短的路径。
八数码问题
代码
// 八数码,使用STL集合,最好写
#include<cstdio>
#include<cstring>
#include<set>
using namespace std;
typedef int State[9]; // 为int [9]这个数组取一个别名 State
const int MAXSTATE = 1000000;
State st[MAXSTATE], goal; // 状态数组,所有状态都保存在这里
int dist[MAXSTATE]; // 距离数组
set<int> vis;
// 初始化查找表
void init_lookup_table(){ vis.clear(); }
// 插入表并判断是否成功
int try_to_insert(int s){
int v = 0;
for(int i=0; i<9; i++) v = v * 10 + st[s][i];
if(vis.count(v)) return 0;
vis.insert(v);
return 1;
}
const int dx[] = {-1, 1, 0, 0};
const int dy[] = {0, 0, -1, 1};
int bfs() {
init_lookup_table();
// 不使用下标0,因为下标0被看作不存在
// 头节点(指向队列第一个元素)、尾节点(指向队列最后一个元素的后一个位置),这里构造了一个最简单的队列
int front = 1, rear = 2;
// 队列非空
while(front < rear) {
// 取出队头元素
State& s = st[front];
// 判断goal和s中的元素是否一致
// memcmp在cstring中,参数为(指针1,指针2,比较字节数)
if(memcmp(goal, s, sizeof(s)) == 0) return front;
int z;
// 找到0在一维数组中的位置
for(z = 0; z < 9; z++) if(!s[z]) break;
// 将其转化为在二维数组中的位置
int x = z/3, y = z%3;
// 上下左右四个方向寻找
for(int d = 0; d < 4; d++) {
int newx = x + dx[d];
int newy = y + dy[d];
// 将新得到的二维坐标转化为一维坐标
int newz = newx * 3 + newy;
// 判断一下是否越界
if(newx >= 0 && newx < 3 && newy >= 0 && newy < 3) {
State& t = st[rear];
// 将新的状态加入到队尾
// 先把队头元素加入队尾
memcpy(&t, &s, sizeof(s));
// 然后把对应的位置改一改
t[newz] = s[z];
t[z] = s[newz];
// 更新一下距离数组
dist[rear] = dist[front] + 1;
// 判断新产生的状态是否已经走过
if(try_to_insert(rear)) rear++;
}
}
front++;
}
return 0;
}
int main(int argc, const char * argv[]) {
for(int i = 0; i < 9; i++)
scanf("%d", &st[1][i]);
for(int i = 0; i < 9; i++)
scanf("%d", &goal[i]);
int ans = bfs();
if(ans > 0) printf("%d\n", dist[ans]);
else printf("-1\n");
return 0;
}
流程图
重点
- 关于图之间的上下左右移动,采取两个矩阵的形式;
- 代码中采用了最原始的队列表示方法,判断条件是队首元素和队尾元素重合;
- 关于一维数组和二维数组的转化,这里采用了C++内存的传统方式:
x=z/3,y=z%3
,z=x*3+y
; - 这是一个没有直接给出邻接矩阵的隐式图结构。
查重
查重的方法有三种:
- 设计编码和解码的函数;
- 哈希技术;
- 采用STL中的set。
第三种方法实现起来最简单,但是却最慢(其底层是红黑树,find时间为O(logn)),最快的是第二种方法,代码在这里给出。
哈希技术
const int hashsize = 100003;
int head[hashsize], next[maxstate];
void init_lookup_table(){memset(head, 0, sizeof(head));}
int hash(State& s){
int v = 0;
for(int i=0; i<9; i++) v = v * 10 + s[i];
return v % hashsize;
}
int try_to_insert(int s){
// 这里采用了哈希表的链式结构
int h = hash(st[s]);
int u = head[h];
while(u){
if(memcmp(st[u],st[s],sizeof(st[s]))==0) return 0;
u = next[u];
}
next[s] = head[h];
head[h] = s;
return 1;
}
倒水问题
#include <iostream>
#include <cstring>
#include <queue>
using namespace std;
struct Node{
int state[3];
int dist;
bool operator < (const Node& rhs) const{
return dist > rhs.dist;
}
Node() =default;
Node(int theA, int theB, int theC, int theDist){
state[0]=theA;
state[1]=theB;
state[2]=theC;
dist=theDist;
}
};
const int maxn = 200 + 5;
int vis[maxn][maxn], cap[3], ans[maxn]; //访问矩阵、容量矩阵、目标矩阵
void update_ans(Node &node){
// 对每一个容量都算一下该容量所需要的距离
for(int i=0; i<3; i++){
int d = node.state[i];
if(ans[d]<0||node.dist<ans[d]) ans[d] = node.dist;
}
}
void solve(int a, int b, int c, int d){
// d是目标水量
cap[0] = a; cap[1] = b ; cap[2] = c;
memset(vis,0,sizeof(vis));
memset(ans,-1,sizeof(ans));
Node start = Node(0,0,c,0);
priority_queue<Node> q;
q.push(start);
vis[0][0] = 1;
while(!q.empty()){
Node node = q.top();
q.pop();
update_ans(node);
if(ans[d]>=0) break;
for(int i=0;i<3;i++)
for(int j=0;j<3;j++) if(i!=j){
// 倒水杯为空或接水杯为满
if(node.state[i]==0 || node.state[j]==cap[j]) continue;
// 倒水量
int amount = min(cap[j],node.state[i]+node.state[j]) - node.state[j];
Node new_node;
new_node.dist = node.dist+amount;
memcpy(&new_node,&node,sizeof(node));
new_node.state[i] -= amount;
new_node.state[j] += amount;
new_node.dist += amount;
if(!vis[new_node.state[0]][new_node.state[1]]){
vis[new_node.state[0]][new_node.state[1]] = 1;
q.push(new_node);
}
}
}
while(d>=0){
if(ans[d]>=0){
cout << ans[d] << ' '<< d << endl;
return;
}
d--;
}
}
int main(int argc, const char * argv[]) {
solve(1,12,15,7);
std::cout << "Hello, World!\n";
return 0;
}
注意
加权图的搜索。还是BFS的思路,不过将queue改为priority_queue。
这里值得注意的有:
- memcpy、memset、memcmp;
- 本质上是状态空间的搜索,所以自然而然想到图的路径搜索策略;
- 回顾一下这一类问题,离不开这些意义:状态表示、是否访问、距离、某些需要存储的结果,如果需要返回路径的话,还要一个存储父亲的数据结构。