【问题描述】采用深度优先搜索算法求解TSP问题,并在搜索过程中,使用界限条件(当前结点已经走过的路径长度要小于已求得的最短路径)进行“剪枝”操作(不再对后续结点进行遍历),从而提高搜索效率。采用queue模块中的栈(LifoQueue)来实现深度优先搜索。
【输入形式】在屏幕上输入顶点个数和连接顶点间的边的邻接矩阵,边上的权可能有小数点。
【输出形式】在整个算法过程中的先后搜索路径(最多输出20次最先搜索的路径),最优值和其中一条最优路径。
【样例1输入】
4
0 30 6 4
30 0 5 10
6 5 0 20
4 10 20 0
【样例1输出】
1
1->2
1->2->3
1->2->3->4
1->2->4
1->3
1->3->2
1->3->2->4
1->3->4
1->4
1->4->2
1->4->2->3
1->4->3
25: 1->3->2->4
【样例说明】
输入:顶点个数为4。连接顶点间边的邻接矩阵大小为4行4列,位置[i,j]上元素值表示第i个顶点到第j个顶点的距离,0表示两个顶点间没有边连接。
输出:在整个算法过程中的先后搜索路径,最优值为25,最优路径为:1->3->2->4。
【评分标准】根据输入得到准确的输出。
带有详细注释的python版代码:
from queue import LifoQueue # 使用LifoQueue实现深度优先搜索
n = int(input()) # 输入节点数
maze = [[v if v else float('inf') for v in list(map(eval, input().split()))] for i in range(n)] # 输入邻接矩阵
class Node: # 解空间的一个状态,此方法好处是思路清晰,坏处是空间消耗大
def __init__(self, cost=0, path=None, visited=None): # cost为当前路径的总长度,path记录当前路径,visited为已访问的节点
self.cost = cost # 从0开始编号
self.path = path if path else [] # path中存储的是节点的编号减1
self.visited = visited if visited else [0] * n # visited中存储的是节点的编号减1位置是否被访问过
def add_vertex(self, vertex): # 添加一个节点,并在一个独立的新对象中返回新的状态
new_node = Node(self.cost, self.path.copy(), self.visited.copy()) # (copy.deepcopy会超时,具体原因不明)
if new_node.path: # 如果不是第一个节点
new_node.cost += maze[new_node.path[-1]][vertex] # 更新cost
new_node.visited[vertex] = 1 # 更新visited
new_node.path.append(vertex) # 更新path
return new_node # 返回新的状态结点
def nexts(self): # 返回当前状态下的所有下一状态(此处不包含剪枝判断)
return [i for i in range(n) if self.visited[i] == 0] # 返回当前状态下的所有未访问的节点
def __repr__(self): # 重写__repr__方法,使得print可直接输出
return '->'.join([str(i + 1) for i in self.path]) # 输出当前路径
best_cost = float('inf') # 最优解,初始值无穷大
best_node = None # 最优解对应的状态结点
count = 20 # 输出前20个途中的路径
open = LifoQueue() # 使用LifoQueue创建open表(其实不需要close表,因为每个状态都是独立的)
open.put(Node().add_vertex(0)) # 将初始状态加入open表
while not open.empty(): # 当open表不为空时
cur = open.get() # 取出open表中的一个状态
if count > 0: # 输出前20个途中的路径
print(cur) # 输出当前状态
count -= 1 # 可输出个数减1
if cur.cost >= best_cost: # 剪枝,如果当前状态的cost大于等于最优解,则不需要再搜索下去
continue # 跳过当前状态
if len(cur.path) == n: # 如果当前状态已经访问了所有节点
if cur.cost + maze[cur.path[-1]][0] < best_cost: # 如果当前状态的cost加上回到起点的cost小于最优解
best_cost = cur.cost + maze[cur.path[-1]][0] # 更新最优解
best_node = cur # 更新最优解对应的状态结点
else: # 如果当前状态没有访问所有节点
for next in cur.nexts()[::-1]: # 逆序遍历当前状态的所有下一状态
node = cur.add_vertex(next) # 添加一个节点,并在一个独立的新对象中返回新的状态
if node.cost < best_cost: # 如果新的状态的cost小于最优解
open.put(node) # 将新的状态加入open表
print('%.3f' % best_cost if type(best_cost) == float else best_cost, ': ', best_node, sep='') # 输出最优解,如果是浮点数则保留3位小数
C++版代码:
#include<iostream>
#include<algorithm>
#include<vector>
#include<stack>
#include<iomanip>
typedef double ll;
using namespace std;
int n;
ll maze[10][10];
class Node {
public:
Node add_vertex(int vertex) {
auto node = new Node(*this);
if (!node->path.empty())
node->cost += maze[node->path.back()][vertex];
node->visited[vertex] = 1;
node->path.emplace_back(vertex);
return *node;
}
vector<int> nexts() {
vector<int> nexts;
for (int i = 0; i < n; i++)
if (!visited[i])
nexts.emplace_back(i);
return nexts;
}
ll cost = 0;
vector<int> path = vector<int>();
vector<int> visited = vector<int>(n);
};
ostream& operator<<(ostream& os, const Node& node) {
os << node.path[0] + 1;
for (size_t i = 1; i < node.path.size(); i++)
os << "->" << node.path[i] + 1;
return os;
}
int main() {
cin >> n;
for (int i = 0; i < n; i++)
for (int j = 0; j < n; j++) {
cin >> maze[i][j];
if (maze[i][j] == 0) {
maze[i][j] = ll(0xffffffffffffffff);
}
}
ll best_cost = ll(0xffffffffffffffff);
Node best_node;
int count = 20;
stack<Node> open = stack<Node>();
open.push(Node().add_vertex(0));
while (!open.empty()) {
Node cur = open.top();
open.pop();
if (count > 0) {
cout << cur << endl;
count -= 1;
}
if (cur.cost >= best_cost)
continue;
if (cur.path.size() == size_t(n)) {
if (cur.cost + maze[cur.path.back()][0] < best_cost) {
best_cost = cur.cost + maze[cur.path.back()][0];
best_node = cur;
}
}
else {
auto nexts = cur.nexts();
for (int i = nexts.size() - 1; i >= 0; i--) {
auto node = cur.add_vertex(nexts[i]);
if (node.cost < best_cost)
open.push(node);
}
}
}
if (fabs(int(best_cost) - best_cost) < 1e-7)
cout << best_cost << ": " << best_node;
else
cout << fixed << setprecision(3) << best_cost << ": " << best_node;
return 0;
}