You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1731 lines
77 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# -*- coding: utf-8 -*-
# Time : 2023/10/30 15:49
# Author : lirunsheng
# User : l'r's
# Software: PyCharm
# File : X4.py
from tkinter import Tk
import random
import math
import time
import tkinter as tk
from tkinter import *
from tkinter import ttk
from tkinter import messagebox
import numpy as np
import networkx as nx
from PIL import Image, ImageTk
MODE = 0
BLUE = "#0080FF"
BLACK = "#000000"
RED = "#FFAAAA"
YELLOW = "#FFFACD"
LINE = '#c8d2c8'
GREY = '#070b19'
GREEN = '#5ba585'
NODE = '#33a8cd'
ZERO = 'gold'
MARK_NODE = 0
Specify_Nodes = ''
Edges_list = []
NAME = []
CLICK = [0, 0, 0]
List_Image = []
class Data:
def __init__(self, source: int, num: int, mold: int):
global NAME
if num <= 0 or num > 10:
raise ValueError("num must be a positive integer")
self.styple = mold
self.edgeinfo = 1 # 是否显示详情
self.nodes_num = num # 节点个数
self.ang = 360 / self.nodes_num # 圆心顶点角度
self.R = 300 # 外接圆半径
self.bc = 2 * self.R * math.sin(math.pi / self.nodes_num) # 节点之间的正多边形距离
self.canvas_len = int(2 * self.R + 80) # 画布边长
self.center = ((self.canvas_len+220) // 2, (self.canvas_len+230) // 2) # 画布中心点坐标
self.drop = []
self.index = 0
if source == 3 or source == 2: # 指定删除
# name = [str(i) for i in range(1, num + 2)]
# print(name)
if source == 2:
NAME.pop()
else:
# print(Specify_Nodes)
NAME.remove(Specify_Nodes)
self.name = NAME
self.coordinate = self.coord_creat() # 纯节点坐标
self.Nodes = self.nodes_creat() # 创建节点列表
# print(self.Nodes)
self.Edges = self.edges_delete(source) # 创建第一条连接
else:
if source == 1:
name = str(int(NAME[-1])+1)
# print(name)
NAME.append(name)
else:
NAME = [str(i) for i in range(1, num + 1)]
# print(NAME)
self.name = NAME
self.coordinate = self.coord_creat() # 纯节点坐标
self.Nodes = self.nodes_creat() # 创建节点列表
# print(self.Nodes)
self.Edges = self.edges_creat(source) # 创建第一条连接
self.edge_add(2, source) # 创建第2条连接
self.edge_add(3, source) # 创建第3条连接
def nodes_creat(self, n_sum=None):
if n_sum == None:
n_sum = self.nodes_num
nodes = [] # 初始化node表
# 设置画布中心点坐标x0,y0
x0 = self.center[0]
y0 = self.center[1]
for i in range(n_sum): # 通过几何运算得到多边形各个顶点坐标
rad = math.radians(i * self.ang) # 计算第i个点的弧度
x = int(math.cos(rad) * self.R) # 计算第i个顶点x坐标
y = int(math.sin(rad) * self.R) # 计算第i个顶点y坐标
# name = '' + str(i + 1) # 给第i个顶点命名
name = '' + self.name[i]
mark = 0 # 节点为未标记
dominator = 1 # 设置为必经节点
nodes.append([i, name, mark, (x0 + x, y0 + y), dominator]) # 将当前节点加入node中
return nodes
def edges_delete(self, source):
edges = [] # 初始化所有链接集合
ser = 0
# print(str(int(Specify_Nodes) + 1))
for list_edge in Edges_list:
# print(list_edge)
edges.append([ser, list_edge[1], list_edge[2],list_edge[3], list_edge[4],list_edge[5], list_edge[6],
list_edge[7], list_edge[8]]) # 将连接加入到边的集合中
nox = [int(num) for num in edges[ser][5].split('-')]
# print(nox)
if len(nox) == 2:
ss = edges[ser][5]
else:
ss = edges[ser][5][:-2]
# print(ss)
if source == 2:
if str(len(self.Nodes)+1) in ss:
# print(edges[ser])
edges.pop()
ser -= 1
else:
if Specify_Nodes in ss:
# print(edges[ser])
edges.pop()
ser -= 1
ser += 1
return edges
def edges_creat(self, source):
edges = [] # 初始化所有链接集合
ser = 0 # 初始化链接对象编号
nodes = self.Nodes.copy() # 复制节点对象
nodes1 = self.Nodes.copy() # 复制节点对象
# print(nodes1)
self.drop = []
for node in nodes: # 遍历节点对象
if node in nodes1: # 删除nodes1中当期遍历的节点信息
nodes1.remove(node)
# print(nodes1)
for node1 in nodes1: # 遍历删除后的节点信息
n1 = node[0] # 节点对象编号1
n2 = node1[0] # 节点对象编号2
if self.styple == 1:
dist_c = random.randint(1, 99) # 随机生成距离成本
time_c = random.randint(1, 99) # 随机生成链接的时间
else:
if self.drop.count(n2) > 3 or self.drop.count(n1) > 3:
dist_c = random.randint(101, 120) # 随机生成距离成本
time_c = random.randint(101, 120) # 随机生成链接的时间
else:
dist_c = random.randint(10, 120) # 随机生成距离成本
time_c = random.randint(10, 120) # 随机生成链接的时间
seq = 1 # 链接序号
mark = False # 连接是否标记
tag = f"{NAME[n1]}-{NAME[n2]}" # 链接标签
enable = 1 # 连接是否可用
if dist_c >= 100 or time_c >= 100: # 设置某些节点为不显示
enable = 0
else:
self.drop.append(n1)
self.drop.append(n2)
if source == 1:
# print(n2, len(self.Nodes))
if n2 == len(self.Nodes)-1:
# print('进来了')
edges.append([ser, int(NAME[n1])-1, int(NAME[n2])-1, enable, mark, tag, dist_c, time_c, seq]) # 将连接加入到边的集合中
else:
# print(self.index)
edges.append([ser, Edges_list[self.index][1], Edges_list[self.index][2],
Edges_list[self.index][3], Edges_list[self.index][4],
Edges_list[self.index][5],Edges_list[self.index][6],
Edges_list[self.index][7], Edges_list[self.index][8]]) # 将连接加入到边的集合中
self.index += 1
else:
edges.append([ser, int(NAME[n1])-1, int(NAME[n2])-1, enable, mark, tag, dist_c, time_c, seq]) # 将连接加入到边的集合中
ser += 1 # 计数加一
return edges
def edge_add(self, no, source):
nodes = self.Nodes.copy() # 复制节点对象
nodes1 = self.Nodes.copy() # 复制节点对象
for node in nodes: # 遍历节点对象
if node in nodes1: # 删除nodes1中当期遍历的节点信息
nodes1.remove(node)
for node1 in nodes1: # 遍历删除后的节点信息
ser = len(self.Edges) # 链接序号
n1 = node[0] # 节点对象编号1
n2 = node1[0] # 节点对象编号2
show = 0 # 设置为不显示
mark = False # 连接是否标记
tag = f"{NAME[n1]}-{NAME[n2]}-{no}" # 链接标签
dist_c = random.randint(30, 100) # 距离成本
time_c = random.randint(1, 30) # 链接的时间
enable = no # 连接是否可用
if source == 1:
if n2 == len(self.Nodes)-1:
# print('进来了')
self.Edges.append([ser, int(NAME[n1])-1, int(NAME[n2])-1, show, mark, tag, dist_c, time_c, enable]) # 将连接加入到边的集合中
else:
# print(no ,self.index)
self.Edges.append([ser, Edges_list[self.index][1], Edges_list[self.index][2],
Edges_list[self.index][3], Edges_list[self.index][4],
Edges_list[self.index][5], Edges_list[self.index][6],
Edges_list[self.index][7], Edges_list[self.index][8]]) # 将连接加入到边的集合中
self.index += 1
else:
self.Edges.append([ser, int(NAME[n1])-1, int(NAME[n2])-1, show, mark, tag, dist_c, time_c, enable])
def coord_creat(self):
'''返回每个节点的坐标'''
coordinate = []
x0 = self.center[0]
y0 = self.center[1]
for i in range(self.nodes_num):
rad = math.radians(i * self.ang)
x = int(math.cos(rad) * self.R)
y = int(math.sin(rad) * self.R)
coordinate.append((x0 + x, y0 + y))
return coordinate
def node_co(self, key): # 通过节点编号返回编号的坐标
"""通过节点编号返回编号的坐标"""
nodes = self.Nodes
for n in nodes:
if n[1] == str(key + 1):
return n[3]
def node_no(self, key): # 通过节点编号返回编号的坐标
"""通过节点编号返回编号的坐标"""
nodes = self.Nodes
for n in nodes:
if n[1] == str(key + 1):
return n[0]
def node_coordinate(self, n): # 通过节点编号返回矩阵的坐标
result = []
for i in range(n):
for j in range(i + 1, n):
result.append((i, j))
return result
# G = Graph()
# print(1)
nodes = 6 # 设置初始节点数量
d = Data(0, nodes, 0)
class Graph:
# Graph定义
def edge_info(self, cv: tk.Canvas, info: str, n1: tuple, n2: tuple): # 显示路径信息
if d.edgeinfo == 0: # 若设置为不显示则直接返回
return
# print(n1)
# print(n2)
x = (n1[0] - n2[0]) // 2 + n2[0] # 计算中点x坐标
y = (n1[1] - n2[1]) // 2 + n2[1] # 计算中点y坐标
if (n2[1] - n1[1]) != 0: # 斜率为非0度时的显示
reat = math.atan((n2[0] - n1[0]) / (n2[1] - n1[1])) # 根据斜率计算弧度
ang = round(math.degrees(reat)) + 90 # 根据弧度转角度
if ang > 89: # 斜度大于90度调整坐标
text_x, text_y = x + 15, y
else: # 斜度大于90度调整坐标
text_x, text_y = x, y + 15
else: # 斜率为0度时的显示
ang = round(math.degrees(0)) # 根据弧度转角度
text_x, text_y = x - 30, y + 10
# print(info.split(' ')[-1])
cv.create_text(text_x, text_y, text=f'{info.split()[-2]}', fill="black", font=('微软雅黑', 12), angle=ang) # 根据信息显示文字
def draw(self):
cv.delete('all') # 清空画布
cv.create_image(0, 0, image=List_Image[40], anchor=tk.NW)
dis, tim = self.edges_show(cv, d.Edges) # 绘制边
self.lab_show(frame_top, len(d.Nodes), tim, dis) # 显示边信息
self.nodes_show(cv) # 绘制点
cv.update() # 更新画布
def lab_show(self, frame: tk.Frame, nodes: int, time: int, distance: int):
labs = ["节点数目", "时间", "距离(成本)"]
x0 = d.canvas_len // 3
for i in range(len(labs)):
lab1 = tk.Label(frame, text=labs[i], fg=BLACK, bg='white', font=('微软雅黑', 12))
if i == 0:
lab2 = tk.Label(frame, text=f"{nodes}", bg='#F8F8FF', fg=BLACK, font=('微软雅黑', 12))
elif i == 1:
lab2 = tk.Label(frame, text=f"{time}", bg='#F8F8FF', fg=BLACK, font=('微软雅黑', 12))
else:
lab2 = tk.Label(frame, text=f"{distance}", bg='#F8F8FF', fg=BLACK, font=('微软雅黑', 12))
lab1.place(x=10 + x0 * i, y=10, width=80, height=30)
lab2.place(x=10 + x0 * i + 80, y=10, width=80, height=30)
lab1 = tk.Label(frame, text='分钟', fg=BLACK, bg='white', font=('微软雅黑', 12))
lab1.place(x=170 + x0, y=10, width=50, height=30)
lab1 = tk.Label(frame, text='千米', fg=BLACK, bg='white', font=('微软雅黑', 12))
lab1.place(x=170 + x0 * 2, y=10, width=50, height=30)
def nodes_show(self, cv: tk.Canvas):
COL = NODE # 初始化节点颜色
for i in d.Nodes: # 遍历每一个节点
x, y = i[3] # 记录该节点坐标
if i[2] == 1 or i[2] == 2: # 如果节点被标记,则将节点标为红色
COL = RED
elif i[0] == 0: # 如果节点为起始节点,则将节点标记为特殊颜色
COL = ZERO
elif i[2] == 5: # 如果节点已经被访问,则将节点标记(用户路径功能)
COL = 'gold'
else: # 否则则使用初始颜色
COL = NODE
if i[4] == 1: # 如果是必经节点,则加黑色边框
w = 1
else: # 如果是非必经节点,则没有边框
w = 0
# 根据坐标,圆的颜色与是否有边框绘制圆圈来表示节点
cv.create_oval(x - 20, y - 20, x + 20, y + 20, fill=COL, width=w, ) # outline='#ee28a3')
cv.create_text(x, y, text=f'{i[1]}', fill=GREY, font=('华文楷体', 18))
cv.update()
def edges_show(self, cv: tk.Canvas, edges: d.Edges):
"""
显示链接
:param cv: tkcanvas对象
:return:
"""
distance = 0 # 初始化被标记的线的总路径
time1 = 0 # 初始化被标记的线的总时间
for edge in edges:
node_1 = d.node_co(edge[1]) # 链接线编号对应的坐标1
node_2 = d.node_co(edge[2]) # 链接线编号对应的坐标2
# print(node_1,node_2)
if edge[8] == 2 and edge[3] == 1: # 若该链接为两点间第二条连线
self.more_edge_show(2, edge)
elif edge[8] == 3 and edge[3] == 1: # 若为两点间第3条连线
self.more_edge_show(3, edge)
if edge[8] == 1 and edge[3] == 1: # 若为两点间第1条连线且该连接存在
self.edge_info(cv, f'{edge[5]} {edge[6]} {edge[7]}', node_1, node_2)
point = [node_1, node_2]
if edge[4] == 1 and edge[8] == 1 and edge[3] == 1: # 如果路径被标记,则用绿色绘制
if MODE == 1:
cv.create_line(point, fill=RED, width=4)
elif MODE == 2:
cv.create_line(point, fill=BLUE, width=4)
else:
cv.create_line(point, fill=GREEN, width=4)
distance += edge[6] # 更新总路径
time1 += edge[7] # 更新总时间
elif edge[8] == 1 and edge[3] == 1: # 如果是节点之间的第一条线,则用正常颜色标记
cv.create_line(point, fill=LINE, width=2)
if d.Nodes[d.node_no(edge[2])][2] + d.Nodes[d.node_no(edge[2])][2] == 3: # 若该线被用户选中,则用蓝色虚线标记
cv.create_line(point, fill=BLUE, width=5, dash=(4, 14))
return (distance, time1) # 返回标记路径的总时间与路程
# def node_edge(self, a, b): # 根据点的编号找边的编号
# if a > b:
# x = a
# a = b
# b = x
# return int(a * (2 * len(d.Nodes) - a - 3) / 2 + b - 1)
def node_edge(self, a, b): # 根据点的编号找边的编号
for edge in d.Edges:
if a == edge[1] and b == edge[2]:
return edge[0]
if b == edge[1] and a == edge[2]:
return edge[0]
def way_node_edge(self, a, b): # 根据点的编号找边的编号
way = []
for node in d.Nodes:
way.append(int(node[1]))
# print(way)
# print(a, b)
x, y = way[a]-1, way[b]-1
for edge in d.Edges:
if x == edge[1] and y == edge[2]:
return edge[0]
if y == edge[1] and x == edge[2]:
return edge[0]
def delete_all(self):
cv.delete('all') # 清空画布
cv.create_image(0, 0, image=List_Image[40], anchor=tk.NW)
self.lab_show(frame_top, 0, 0, 0) # 显示边信息
W.user_way = [0]
window(2)
class Edge:
def __init__(self):
self.mark = []
# 记录已标记的节点数目(连接操作)
self.user_cur = 0
# 记录用户所在点
def node_edge(self, no, n1, n2): # 根据节点返回编号
if n1 > n2:
n = n1
n1 = n2
n2 = n
for e in d.Edges:
if e[1] == n1 and e[2] == n2 and e[8] == no:
return e[0]
return -1
def edge_del_all(self, mode):
# mode = 1:优先最短路径
# mode = 2:优先最短时间
# cur1 = node_edge()
if mode == 1:
for e in d.Edges:
if (e[8] == 2 and e[3] == 1) or (e[8] == 3 and e[3] == 1):
self.edge_merge_cost(e[1], e[2])
else:
for e in d.Edges:
if (e[8] == 2 and e[3] == 1) or (e[8] == 3 and e[3] == 1):
self.edge_merge_time(e[1], e[2])
G.draw()
def edge_merge_cost(self, n1, n2):
cur1 = node_edge(n1, n2) # 初始化三条可能的边的编号
cur2 = node_edge(n1, n2, 2) # 初始化三条可能的边的编号
cur3 = node_edge(n1, n2, 3) # 初始化三条可能的边的编号
edges = [(cur1, d.Edges[cur1][6]), (cur2, d.Edges[cur2][6]), (cur3, d.Edges[cur3][6])] # 提出编号与路径花费
valid_edges = [(edge, cost) for edge, cost in edges if d.Edges[edge][3] == 1] # 过滤出存在的边
valid_edges.sort(key=lambda x: x[1]) # 按照 cost 排序,保留 cost 最小的边
for idx, (edge, _) in enumerate(valid_edges):
d.Edges[edge][8] = idx + 1 # 设置边的优先级
d.Edges[edge][0] = edge # 确保边的索引正确
for edge, _ in valid_edges[1:]: # 对于非最优边,设置其为不显示
d.Edges[edge][3] = 0
def edge_merge_time(self, n1, n2):
cur1 = node_edge(n1, n2) # 初始化三条可能的边的编号
cur2 = node_edge(n1, n2, 2) # 初始化三条可能的边的编号
cur3 = node_edge(n1, n2, 3) # 初始化三条可能的边的编号
edges = [(cur1, d.Edges[cur1][7]), (cur2, d.Edges[cur2][7]), (cur3, d.Edges[cur3][7])] # 提出编号与路径花费
valid_edges = [(edge, cost) for edge, cost in edges if d.Edges[edge][3] == 1] # 过滤出存在的边
valid_edges.sort(key=lambda x: x[1]) # 按照 cost 排序,保留 cost 最小的边
for idx, (edge, _) in enumerate(valid_edges):
d.Edges[edge][8] = idx + 1 # 设置边的优先级
d.Edges[edge][0] = edge # 确保边的索引正确
for edge, _ in valid_edges[1:]: # 对于非最优边,设置其为不显示
d.Edges[edge][3] = 0
class Solve:
def __init__(self):
self.ans = [] # 记录答案
self.mutians = [] # 若有多个最短路则保存在mutians中
self.way_sum = 0 # 记录遍历的路径数目
self.minn = float("inf") # 初始化最小值为正无穷
self.start = 0 # 起始点
self.end = 0 # 终点
self.mark = [] # 记录该点是否已经经过
self.user_cur = 0 # 记录用户所在点
self.cura = -1 # 记录用户选择的第一个点
self.curb = -1 # 记录用户选择的第二个点
self.con = 0 # 记录必经的节点数目
def node_greedy(self): # 贪心
self.start = time.time() # 记录开始时间
self.way_sum = 0
self.ans.clear() # 初始化答案列表
E.edge_del_all(1) # 两点间仅保留距离最短路径
for edge in d.Edges: # 清空标记
edge[4] = False
flag = [0] * len(d.Nodes) # 初始化已经过点
cur = 0 # 初始化起始点
flag[cur] = 1 # 将起始点标记为已经过
l = len(d.Nodes)
# print(l)
distances = [[math.inf for _ in range(l)] for _ in range(l)]
result = d.node_coordinate(l)
for j in d.Edges: # 每一步选择当前最短的链接加到路径中去
if j[0] >= len(result):
break
else:
if j[3] == 1:
distances[result[j[0]][0]][result[j[0]][1]] = j[6]
distances[result[j[0]][1]][result[j[0]][0]] = j[6]
# print(distances)
ways = self.greedy(distances)
self.ans = ways.copy()
# 如果找到的路径不合法,则表示贪心法无法求解得到合法路径
print(self.way_sum)
# for i in distances:
# print(i)
if len(ways) != l+1 :
tk.messagebox.askokcancel(title='结果', message='该图不存在贪心算法路径')
print(ways)
for way in range(len(ways) - 1): # 标记一条路径
d.Edges[G.way_node_edge(ways[way], ways[way + 1])][4] = True
self.ans.clear()
self.end = time.time() # 记录结束时间
return -1
for way in range(len(ways) - 1): # 标记一条路径
d.Edges[G.way_node_edge(ways[way], ways[way + 1])][4] = True
# print(ways[len(ways) - 1], i)
# d.Edges[G.way_node_edge(ways[len(ways) - 1], i)][4] = True # 标记起点终点形成环线
# print(ways)
tk.messagebox.askokcancel(title='结果', message='贪心算法路径')
self.end = time.time() # 记录结束时间
def greedy(self,distances):
num_cities = len(distances)
visited = [False] * num_cities
path = [0] # 起始城市为0
visited[0] = True
for _ in range(num_cities - 1):
curr_city = path[-1]
min_distance = float('inf')
next_city = None
for city in range(num_cities):
if not visited[city] and distances[curr_city][city] < min_distance and distances[curr_city][city] != 0:
min_distance = distances[curr_city][city]
next_city = city
self.way_sum += 1 # 遍历路径的数目加一
if next_city is not None:
path.append(next_city)
visited[next_city] = True
if distances[path[-1]][0] != np.inf:
path.append(0) # 回到起始城市形成闭合路径
# print(path)
return path
def tsp_backtracking(self):
self.start = time.time() # 记录开始时间
self.way_sum = 0
self.ans.clear() # 初始化答案列表
E.edge_del_all(1) # 两点间仅保留距离最短路径
for edge in d.Edges: # 清空标记
edge[4] = False
flag = [0] * len(d.Nodes) # 初始化已经过点
cur = 0 # 初始化起始点
flag[cur] = 1 # 将起始点标记为已经过
l = len(d.Nodes)
# print(l)
distances = [[math.inf for _ in range(l)] for _ in range(l)]
result = d.node_coordinate(l)
for j in d.Edges: # 每一步选择当前最短的链接加到路径中去
if j[0] >= len(result):
break
else:
if j[3] == 1:
distances[result[j[0]][0]][result[j[0]][1]] = j[6]
distances[result[j[0]][1]][result[j[0]][0]] = j[6]
# print(distances)
ways = self.backtracking(distances)
print(self.way_sum)
if ways: # 判断路径是否存在
self.ans = ways.copy()
for way in range(len(ways) - 1): # 标记一条路径
d.Edges[G.way_node_edge(ways[way], ways[way + 1])][4] = True
tk.messagebox.askokcancel(title='结果', message='回溯算法路径')
self.end = time.time() # 记录结束时间
else:
tk.messagebox.askokcancel(title='结果', message='该图不存在回溯算法路径')
self.end = time.time() # 记录结束时间
return -1
def backtracking(self, distances):
num_cities = len(distances)
path = [0] # 起始城市为0
visited = [False] * num_cities
visited[0] = True
min_distance = float('inf')
shortest_path = None
def backtrack(curr_city, curr_distance, visited, path):
nonlocal min_distance, shortest_path
if len(path) == num_cities:
# 到达所有城市,更新最短路径
if curr_distance + distances[curr_city][0] < min_distance:
min_distance = curr_distance + distances[curr_city][0]
shortest_path = path + [0]
else:
for next_city in range(num_cities):
if not visited[next_city]:
# 选择下一个未访问的城市
visited[next_city] = True
path.append(next_city)
new_distance = curr_distance + distances[curr_city][next_city]
self.way_sum += 1 # 遍历路径的数目加一
# 剪枝条件:当前路径已经大于最短路径,不继续搜索
if new_distance < min_distance:
backtrack(next_city, new_distance, visited, path)
# 回溯
visited[next_city] = False
path.pop()
backtrack(0, 0, visited, path)
return shortest_path
def auto_solve(self, number):
global MODE, MARK_NODE
MARK_NODE = 0
MODE = 2
if number == 1: # 回溯
self.tsp_backtracking()
elif number == 2:
self.node_greedy() # 贪心
else: # 深度优先算法
self.node_dfs()
window(1)
G.draw()
def node_dfs(self): # 遍历 深度优先算法
self.start = time.time() # 记录开始时间
self.way_sum = 0
self.ans.clear() # 初始化答案列表
E.edge_del_all(1) # 两点间仅保留距离最短路径
for edge in d.Edges: # 清空标记
edge[4] = False
i = 0 # 起始点
flag = [0] * len(d.Nodes) # 初始化已经过点
cur = i # 初始化起始点
flag[cur] = 1 # 将起始点标记为已经过
l = len(d.Nodes)
distances = [[math.inf for _ in range(l)] for _ in range(l)]
result = d.node_coordinate(l)
for j in d.Edges: # 每一步选择当前最短的链接加到路径中去
if j[0] >= len(result):
break
else:
if j[3] == 1:
distances[result[j[0]][0]][result[j[0]][1]] = j[6]
distances[result[j[0]][1]][result[j[0]][0]] = j[6]
cost, ways = self.dfs(distances)
print(self.way_sum)
self.ans = ways.copy()
if len(ways) != l + 1:
tk.messagebox.askokcancel(title='结果', message='该图不存在dfs路径')
# print(ways)
for way in range(len(ways) - 1): # 标记一条路径
d.Edges[G.way_node_edge(ways[way], ways[way + 1])][4] = True
self.end = time.time() # 记录结束时间
self.ans.clear()
return -1
for way in range(len(ways) - 1): # 标记一条路径
d.Edges[G.way_node_edge(ways[way], ways[way + 1])][4] = True
tk.messagebox.askokcancel(title='结果', message='dfs算法路径')
self.end = time.time() # 记录结束时间
def dfs_tsp(self,graph, start, current, path,
visited, cost, min_cost, min_path):
if len(path) == len(graph) and graph[
current][start] != np.inf:
path.append(start)
cost += graph[current][start]
if cost < min_cost[0]:
min_cost[0] = cost
min_path[0] = path.copy()
path.pop()
cost -= graph[current][start]
return
for next_node in range(len(graph)):
if graph[current][next_node] != np.inf and \
not visited[next_node]:
visited[next_node] = True
path.append(next_node)
cost += graph[current][next_node]
self.way_sum += 1 # 遍历路径的数目加一
self.dfs_tsp(graph, start, next_node, path,
visited, cost, min_cost, min_path)
visited[next_node] = False
path.pop()
cost -= graph[current][next_node]
def dfs(self, graph):
n = len(graph)
min_cost = [float('inf')]
min_path = [[]]
for start_node in range(n):
visited = [False] * n
path = [start_node]
cost = 0
visited[start_node] = True
self.dfs_tsp(graph, start_node, start_node, path,
visited, cost, min_cost, min_path)
return min_cost[0], min_path[0]
def find_hamiltonian_cycles(self,start, distances):
n = len(distances)
path = [start]
cycles = []
def is_valid(node, position):
if distances[path[position - 1]][node] == np.inf:
return False
if node in path:
return False
return True
def find_paths(node):
for next_node in range(n):
if is_valid(next_node, len(path)):
path.append(next_node)
if len(path) < n:
find_paths(next_node)
elif len(path) == n and distances[path[-1]][start] != np.inf:
cycles.append(path + [start])
path.pop()
find_paths(start)
return cycles
def check(self):
l = len(d.Nodes)
distances = [[math.inf for _ in range(l)] for _ in range(l)]
result = d.node_coordinate(l)
for j in d.Edges: # 将路径中的
if j[0] >= len(result):
break
else:
if j[6] < 99 and j[7] < 99:
distances[result[j[0]][0]][result[j[0]][1]] = j[6]
distances[result[j[0]][1]][result[j[0]][0]] = j[6]
all_hamiltonian_cycles = self.find_hamiltonian_cycles(0, np.array(distances))
l = len(all_hamiltonian_cycles)
if l:
for cycles in all_hamiltonian_cycles:
dis_ans = 0
for i in range(d.nodes_num):
dis_ans += d.Edges[G.way_node_edge(cycles[i], cycles[i + 1])][6]
print(cycles,dis_ans)
if all_hamiltonian_cycles:
tk.messagebox.askokcancel(title='结果', message=f'该图存在 {len(all_hamiltonian_cycles)} 条路径\n')
else:
tk.messagebox.askokcancel(title='结果', message='该图不能完全互通')
def node_edge(n1, n2, no=1): # 根据点的编号找边的编号
if n1 > n2: # 为了防止充分编号让n1小于n2
n1, n2 = n2, n1
for e in d.Edges: # 遍历所有路径,找到符合要求的返回
if e[1] == n1 and e[2] == n2 and e[8] == no:
return e[0]
return -1 # 若找不到,则返回-1
class Ways():
def __init__(self):
self.user_way = [0] # 记录用户路径
self.user_cur = 0 # 记录用户所在点
self.user_flag = set() # 记录用户已经过的点
def auto_ways(self): # 标记用户路径
for w in d.Edges: # 清空路径标记
w[4] = 0
for N in d.Nodes: # 清空节点标记
N[2] = 0
for w in self.user_way: # 标记用户已选的点
d.Nodes[w][2] = 5
# 标记用户经过的路径
for w in range(len(self.user_way) - 1):
d.Edges[G.node_edge(self.user_way[w],self.user_way[w + 1])][4] = 1
# 如果已经访问所有节点,自动回到初始节点
if len(set(self.user_way)) == len(d.Nodes):
d.Edges[G.node_edge(self.user_way[len(self.user_way) - 1], 0)][4] = 1
G.draw() # 按照标记重新绘制图形
def check_user_best(self): # 检查用户路径是不是最佳路径
s.node_dfs()
if not len(s.ans):
return -1, 0
# 长度不一样肯定不匹配
if not len(self.user_way) == len(s.ans):
return 0, 0
dis_user = 0
dis_ans = 0
num = len(self.user_way)
for i in range(num - 1):
dis_user += d.Edges[G.node_edge(self.user_way[i], self.user_way[i+1])][6]
dis_ans += d.Edges[G.way_node_edge(s.ans[i], s.ans[i + 1])][6]
return dis_ans, dis_user
def show_best_ans(self, top, best, user):
lab1 = tk.Label(top, text="最短路径:", fg=BLACK, font=('微软雅黑', 20))
b_way = ""
# print(s.ans)
for a in s.ans:
b_way += d.Nodes[a][1]
b_way += "->"
b_way += d.Nodes[0][1]
y0 = 0.05
lab2 = tk.Label(top, text=f"{b_way}", bg=BLUE, fg=BLACK, font=('微软雅黑', 12), wraplength=200, justify='left')
lab1.place(relx=0.1, rely=0.50 + y0, width=150, height=30)
lab2.place(relx=0.4, rely=0.45 + y0, width=200, height=100)
lab2 = tk.Label(top, text=f"最佳路径总成本:{best}\n用户路径总成本:{user}", bg=BLUE, fg=BLACK,
font=('微软雅黑', 12), wraplength=200, justify='left')
lab2.place(relx=0.4, rely=0.7 + y0, width=200, height=100)
def show_BEST(self):
best, user = self.check_user_best() # 计算最优路径与用户路径的总花费
if not best:
lab1 = tk.Label(frame_left, text="不存在最短路径", bg=RED, fg=BLACK, font=('微软雅黑', 12))
lab1.place(relx=0.3, rely=0.8, width=140, height=30)
elif best == user: # 如果最优路径与用户路径的总花费相等,则说明用户路径是最短路径
lab1 = tk.Label(frame_left, text="用户路径是最短路径", bg=RED, fg=BLACK, font=('微软雅黑', 12))
lab1.place(relx=0.3, rely=0.8, width=140, height=30)
else:
lab1 = tk.Label(frame_left, text="用户路径是不最短路径", bg=RED, fg=BLACK, font=('微软雅黑', 12))
lab1.place(relx=0.3, rely=0.8, width=140, height=30)
# else: # 否则说明用户路径不是最短路径
# self.servey_path_display()
def servey_path_display(self):
lab1 = tk.Label(frame_left, text="最短路径:", bg=RED, fg=BLACK, font=('微软雅黑', 12))
lab1.place(relx=0.1, rely=0.75, width=80, height=30)
# self.auto_ways()
b_way = ""
for a in s.ans:
b_way += d.Nodes[a][1]
b_way += "->"
b_way = b_way[:-2]
lab2 = tk.Label(frame_left, text=f"{b_way}", bg=BLUE, fg=BLACK, font=('微软雅黑', 12), wraplength=200,
justify='left')
lab2.place(relx=0.1, rely=0.8, width=200, height=100)
def check_user_greey(self): # 检查用户路径是不是贪心路径,不是的话返回错误点
s.node_greedy()# 计算贪心路径
if not len(s.ans):
return -2
# 路径长度不一样肯定不匹配,直接返回
if not len(self.user_way) == len(s.ans):
# self.auto_ways()
return 0
way = [(int(node[1])-1) for node in d.Nodes]
result1 = list(map(str, self.user_way))
result1 = ' '.join(result1)
ways = [way[an] for an in s.ans]
result2 = list(map(str, ways))
result3 = ' '.join(result2)
result2.reverse() # 倒序排列列表元素
result4 = ' '.join(result2)
if result4 == result1 or result3 == result1:
return -1 #-1表示是贪心路径
else:
x_1 = self.find_longest(result1, result4)
x_2 = self.find_longest(result1, result3)
return max(x_1, x_2)
def find_longest(self, list1, list2):
index = 0
while index < len(list1) and index < len(list2):
if list1[index] != list2[index]:
break
index += 1
return index
def show_greddy_ans(self, top):
lab1 = tk.Label(top, text="贪心路径:", fg=BLACK, font=('微软雅黑', 20))
b_way = ""
for a in s.ans:
b_way += d.Nodes[a][1]
b_way += "->"
b_way += d.Nodes[0][1]
y0 = 0.05
lab2 = tk.Label(top, text=f"{b_way}", bg=BLUE, fg=BLACK, font=('微软雅黑', 12), wraplength=200, justify='left')
lab1.place(relx=0.1, rely=0.50 + y0, width=150, height=30)
lab2.place(relx=0.4, rely=0.45 + y0, width=200, height=100)
# 对用户路径进行标记与记录
def way_route_markings(self, event):
global MARK_NODE
MARK_NODE = 2
# E.newedge_del_all(1)
for w in frame_left.winfo_children():
w.destroy()
# 清除右侧组件
left_window()
self.user_way = [0]
# lab = tk.Label(frame_left, text="路径操作", bg=RED, fg=BLACK, font=('微软雅黑', 20))
# lab.place(relx=0.18, rely=0.02, width=200, height=30)
y0 = 0.1
global entry1,entry2,default_value
# 定义默认值变量
default_value = tk.StringVar()
default_value.set(self.user_way[-1]+1)
laba1 = tk.Label(frame_left, text="当前节点:", bg='white', fg=BLACK, font=('微软雅黑', 15))
laba1.place(relx=0.2, rely=0.28 + y0, width=140, height=30)
# 创建第一个输入框
entry1 = tk.Entry(frame_left, textvariable=default_value, bg="#F5F5F5")
entry1.place(relx=0.6, rely=0.28 + y0, width=50, height=30)
labb1 = tk.Label(frame_left, text="当前下一节点:", bg='white', fg=BLACK, font=('微软雅黑', 15))
labb1.place(relx=0.2, rely=0.33 + y0, width=140, height=30)
# 创建第二个输入框
entry2 = tk.Entry(frame_left, bg="#F5F5F5")
entry2.place(relx=0.6, rely=0.33 + y0, width=50, height=30)
labb3 = tk.Label(frame_left, text="是否进行连接:", bg='white', fg=BLACK, font=('微软雅黑', 15))
labb3.place(relx=0.2, rely=0.4 + y0, width=140, height=30)
# 按钮
button = tk.Button(frame_left, relief="flat", image=List_Image[38], bg='white', command=lambda: self.extract_values()) # 确定
button.place(relx=0.55, rely=0.38 + y0)
button2 = tk.Button(frame_left, relief="flat", image=List_Image[36], bg='white', command=lambda: self.revoke()) # 撤销
button2.place(relx=0.18, rely=0.46 + y0)
button3 = tk.Button(frame_left, relief="flat", image=List_Image[37], bg='white', command=lambda: self.clear_all_paths()) # 清除路径
button3.place(relx=0.5, rely=0.46 + y0)
button4 = tk.Button(frame_left, relief="flat", image=List_Image[42], bg='white', command=lambda: self.greedy_path()) # 判断路径是否为贪心路径
button4.place(relx=0.18, rely=0.55 + y0)
button5 = tk.Button(frame_left, relief="flat", image=List_Image[43], bg='white', command=lambda: self.shortest_path()) # 判断路径是否为最短路径
button5.place(relx=0.18, rely=0.62 + y0)
self.user_path_display()
def extract_values(self):
global entry1, entry2, default_value
global MARK_NODE
MARK_NODE = 2
value1 = entry1.get() # 获取第一个输入框的值
value2 = entry2.get() # 获取第二个输入框的值
try:
value1 = int(value1) # 将值转换为整数
value2 = int(value2) # 将值转换为整数
print("值1:", value1)
print("值2:", value2)
# 判断两点是否连接
if abs(value2) > 10 and abs(value1) > 10:
tk.messagebox.askokcancel(title='错误', message='请重新输入正确的编号!')
return
else:
E = 0
if value1 < value2:
str_route = f'{value1}-{value2}'
else:
str_route = f'{value2}-{value1}'
for item in d.Edges:
if str_route == item[5]:
if item[6] < 100 and item[7] < 100:
if d.Edges[G.node_edge(value1-1, value2-1)][4]:
E = 2
tk.messagebox.askokcancel(title='错误', message='城市已连接!')
else:
if self.user_way[-1] == (value1-1):
E = 1
global MODE
MODE = 1
self.user_way.append(value2 - 1)
default_value.set(value2)
d.Edges[G.node_edge(value1-1, value2-1)][4] = True
entry1.config(textvariable=default_value) # 更新entry1的默认值
entry2.delete(0, tk.END)
else:
E = 3
tk.messagebox.askokcancel(title='错误', message='未连贯!')
break
print(E)
if E == 1:
G.draw()
self.user_path_display()
elif E == 0:
tk.messagebox.askokcancel(title='错误', message='城市未连接!')
except ValueError:
tk.messagebox.askokcancel(title='错误', message='请输入正确的城市编号!')
def extract_ligature(self, n1, n2):
global MARK_NODE
MARK_NODE = 2
no = G.way_node_edge(n1, n2)
flage = False
if d.Edges[no][4]:
tk.messagebox.askokcancel(title='错误', message='城市已连接!')
elif d.Edges[no][3] == 0:
tk.messagebox.askokcancel(title='错误', message='城市未连接!')
else:
if self.user_way[-1] == int(d.Nodes[n2][1]) - 1:
nx = n1
n1 = n2
n2 = nx
if self.user_way[-1] == int(d.Nodes[n1][1])-1:
global MODE
MODE = 1
self.user_way.append(int(d.Nodes[n2][1]) - 1)
d.Edges[no][4] = True
flage = True
else:
tk.messagebox.askokcancel(title='错误', message='未连贯!')
if flage:
G.draw()
self.user_path_display()
def revoke(self):
global MARK_NODE
MARK_NODE = 2
try:
if len(self.user_way) > 1:
value1 = int(self.user_way[-1]) # 将值转换为整数
value2 = int(self.user_way[-2]) # 将值转换为整数
if value1 < value2:
str_route = f'{value1+1}-{value2+1}'
else:
str_route = f'{value2+1}-{value1+1}'
# print(str_route)
for item in d.Edges:
# print(item)
if str_route == item[5]:
# print(item)
global MODE
global entry1, default_value
MODE = 1
self.user_way.pop()
print(self.user_way)
d.Edges[G.node_edge(value1, value2)][4] = False
default_value.set(value2+1)
entry1.config(textvariable=default_value) # 更新entry1的默认值
break
G.draw()
self.user_path_display()
else:
tk.messagebox.askokcancel(title='提示', message='未进行路径建设!')
except ValueError:
tk.messagebox.askokcancel(title='错误', message='请输入正确的城市编号!')
def clear_all_paths(self):
global MARK_NODE
MARK_NODE = 2
try:
if len(self.user_way) > 1:
for i in range(len(self.user_way)-1):
value1 = int(self.user_way[-1]) # 将值转换为整数
value2 = int(self.user_way[-2]) # 将值转换为整数
if value1 < value2:
str_route = f'{value1 + 1}-{value2 + 1}'
else:
str_route = f'{value2 + 1}-{value1 + 1}'
# print(str_route)
for item in d.Edges:
# print(item)
if str_route == item[5]:
# print(item)
global MODE
global entry1, default_value
MODE = 3
self.user_way.pop()
print(self.user_way)
d.Edges[G.node_edge(value1, value2)][4] = False
default_value.set(value2 + 1)
entry1.config(textvariable=default_value) # 更新entry1的默认值
break
else:
tk.messagebox.askokcancel(title='提示', message='未进行路径建设!')
for i in range(len(d.Edges)):
d.Edges[i][4] = False
G.draw()
self.user_path_display()
except ValueError:
tk.messagebox.askokcancel(title='错误', message='请输入正确的城市编号!')
def greedy_path(self):
g = self.check_user_greey()
if g == -2:
# cvx.create_text(0, 0, text="不存在贪心路径")
lab1 = tk.Label(frame_left, text="不存在贪心路径", bg='white', fg=BLACK, font=('微软雅黑', 12))
lab1.place(relx=0.3, rely=0.8, width=180, height=30)
elif g == -1: # 如果用户路径是贪心路径则显示
# cvx.create_text(0, 0, text="用户路径是贪心路径")
lab1 = tk.Label(frame_left, text="用户路径是贪心路径", bg='white', fg=BLACK, font=('微软雅黑', 12))
lab1.place(relx=0.3, rely=0.8, width=180, height=30)
# elif g:
# self.servey_greedy_path()
else:
# print(self.user_way)
# print(self.user_way[0:g])
# cvx.create_text(0, 0, text="用户路径不是是贪心路径")
lab1 = tk.Label(frame_left, text="用户路径不是是贪心路径", bg='white', fg=BLACK, font=('微软雅黑', 12))
lab1.place(relx=0.3, rely=0.8, width=180, height=30)
def servey_greedy_path(self):
lab1 = tk.Label(frame_left, text="最短路径:", bg=RED, fg=BLACK, font=('微软雅黑', 12))
lab1.place(relx=0.1, rely=0.75, width=80, height=30)
# self.auto_ways()
g_way = ''
for a in s.ans:
g_way += d.Nodes[a][1]
g_way += "->"
g_way = g_way[:-2]
lab2 = tk.Label(frame_left, text=f"{g_way}", bg=BLUE, fg=BLACK, font=('微软雅黑', 12), wraplength=200,
justify='left')
lab2.place(relx=0.1, rely=0.8, width=200, height=100)
def shortest_path(self):
best, user = self.check_user_best() # 计算最优路径与用户路径的总花费
if not best:
lab1 = tk.Label(frame_left, text="不存在最短路径", bg='white', fg=BLACK, font=('微软雅黑', 12))
lab1.place(relx=0.33, rely=0.8, width=180, height=30)
elif best == user: # 如果最优路径与用户路径的总花费相等,则说明用户路径是最短路径
lab1 = tk.Label(frame_left, text="用户路径是最短路径", bg='white', fg=BLACK, font=('微软雅黑', 12))
lab1.place(relx=0.33, rely=0.8, width=180, height=30)
else:
lab1 = tk.Label(frame_left, text="用户路径是不最短路径", bg='white', fg=BLACK, font=('微软雅黑', 12))
lab1.place(relx=0.33, rely=0.8, width=180, height=30)
# if best == user: # 如果最优路径与用户路径的总花费相等,则说明用户路径是最短路径
# lab1 = tk.Label(frame_left, text="用户路径是最短路径", bg=RED, fg=BLACK, font=('微软雅黑', 12))
# lab1.place(relx=0.1, rely=0.8, width=180, height=30)
# else: # 否则说明用户路径不是最短路径
# self.servey_path_display()
# 用户路径展示
def user_path_display(self):
lab1 = tk.Label(frame_left, text="用户路径:", bg='white', fg=BLACK, font=('微软雅黑', 15))
lab1.place(relx=0.15, rely=0.8, width=80, height=30)
# self.auto_ways()
way = ""
# d_way = []
# for i in d.Nodes:
# d_way.append(i[1])
for i in range(len(self.user_way)):
if len(self.user_way)-i > 1:
way += str(self.user_way[i]+1)
way += "->"
else:
way += str(self.user_way[i]+1)
lab2 = tk.Label(frame_left, text=f"{way}", bg='#F8F8FF', fg=BLACK, font=('微软雅黑', 12), wraplength=200,
justify='left')
lab2.place(relx=0.18, rely=0.84, width=300, height=100)
def way_operate(self, event):
global MARK_NODE
for i in range(len(d.Edges)):
d.Edges[i][4] = False
MARK_NODE = 5
# E.newedge_del_all(1)
for w in frame_left.winfo_children():
w.destroy()
# 清除左侧组件
left_window()
self.user_way = [0]
y0 = 0.1
label1 = tk.Label(frame_left, text="节点1:", bg='white', fg=BLACK, font=('微软雅黑', 15))
label1.place(relx=0.15, rely=0.32 + y0, width=100, height=30)
global entry3, entry4
# 创建第一个输入框
entry3 = tk.Entry(frame_left, bg='#F5F5F5')
entry3.place(relx=0.6, rely=0.32 + y0, width=50, height=30)
label2 = tk.Label(frame_left, text="节点2:", bg='white', fg=BLACK, font=('微软雅黑', 15))
label2.place(relx=0.15, rely=0.37 + y0, width=100, height=30)
# 创建第二个输入框
entry4 = tk.Entry(frame_left, bg='#F5F5F5')
entry4.place(relx=0.6, rely=0.37 + y0, width=50, height=30)
button1 = tk.Button(frame_left, image=List_Image[45], relief="flat", bg='white',
command=lambda: self.complete_graph()) # 完全图
button1.place(relx=0.15, rely=0.43 + y0)
button2 = tk.Button(frame_left, image=List_Image[46], relief="flat", bg='white',
command=lambda: self.reachable_judgment()) # 节点可达性判断
button2.place(relx=0.5, rely=0.43 + y0)
label3 = tk.Label(frame_left, text="是否进行两节点之间操作:", bg='white', fg=BLACK, font=('微软雅黑', 14))
label3.place(relx=0.15, rely=0.5 + y0, width=240, height=30)
# 按钮
button3 = tk.Button(frame_left, relief="flat", image=List_Image[33], bg='white',
command=lambda: self.way_add()) # 线路增加
button3.place(relx=0.2, rely=0.56 + y0)
button4 = tk.Button(frame_left, relief="flat", image=List_Image[34], bg='white',
command=lambda: self.way_change()) # 线路修改
button4.place(relx=0.2, rely=0.66 + y0)
button5 = tk.Button(frame_left, relief="flat", image=List_Image[35], bg='white',
command=lambda: self.way_delete()) # 线路删除
button5.place(relx=0.2, rely=0.76 + y0)
def is_reachable(self, graph, start, target, visited):
if start == target:
return True
visited.add(start)
for neighbor in graph[start]:
if neighbor not in visited:
if self.is_reachable(graph, neighbor, target, visited):
return True
return False
def complete_graph(self):
l = len(d.Nodes)
result = d.node_coordinate(l)
for index in range(len(result)):
if d.Edges[index][3] == 0:
dist_c = random.randint(10, 99) # 随机生成距离成本
time_c = random.randint(10, 99) # 随机生成链接的时间
d.Edges[index][3] = 1
d.Edges[index][6] = dist_c
d.Edges[index][7] = time_c
G.draw()
def reachable_judgment(self):
try:
a = int(entry3.get()) # 获取第一个输入框的值
b = int(entry4.get()) # 获取第二个输入框的值
l = len(d.Nodes)
index_list = []
for i in d.Nodes:
index_list.append(i[1])
dictionary = {i: [] for i in range(1, l + 1)}
result = d.node_coordinate(l)
# 生成无向图
for i in range(len(result)):
if d.Edges[i][3] == 1:
x = d.Edges[i][5].split('-')[0]
y = d.Edges[i][5].split('-')[-1]
dictionary[index_list.index(x)+1].append(index_list.index(y)+1)
dictionary[index_list.index(y)+1].append(index_list.index(x)+1)
start_node = index_list.index(str(a))+1
target_node = index_list.index(str(b))+1
visited_nodes = set()
print(dictionary)
flag = self.is_reachable(dictionary, start_node, target_node, visited_nodes)
if flag:
tk.messagebox.askokcancel(title='结果', message=f'{a}{b}可达')
else:
tk.messagebox.askokcancel(title='结果', message=f'{a}{b}不可达')
except ValueError:
tk.messagebox.askokcancel(title='结果', message='请输入节点')
def way_add(self):
global entry3, entry4
value1 = entry3.get() # 获取第一个输入框的值
value2 = entry4.get() # 获取第二个输入框的值
try:
value1 = int(value1) # 将值转换为整数
value2 = int(value2) # 将值转换为整数
print("值1:", value1)
print("值2:", value2)
ways_list = [int(node[1]) for node in d.Nodes]
# 判断两点是否连接
if abs(value2) > 10 and abs(value1) > 10:
tk.messagebox.askokcancel(title='错误', message='请重新输入正确的编号!')
return
else:
if value1 < value2:
str_route = f'{value1}-{value2}'
else:
str_route = f'{value2}-{value1}'
if d.drop.count(ways_list.index(value1)) > 4 or d.drop.count(ways_list.index(value2)) > 4:
print(d.drop)
tk.messagebox.askokcancel(title='提醒', message='当前节点已经到达最大连接数!')
return
no = 0
for item in d.Edges:
if str_route == item[5]:
no = G.node_edge(value1 - 1, value2 - 1)
if d.Edges[no][3] == 1:
tk.messagebox.askokcancel(title='提醒', message='当前路线已存在!')
return
top = creat_window(str_route+'路径参数配置') # 创建弹出窗口
distance_entry = create_input_box(top, "路程/公里1-99", 101) # 创建一个输入框,获取卷积核大小
time_entry = create_input_box(top, "时间/h1-99", 101) # 创建一个输入框,获取卷积步长
def get_input(): # 创建
# 获取输入框的内容
try:
result1 = int(distance_entry.get())
result2 = int(time_entry.get())
print(result1,result2)
if 100 > result1 > 0 and 0 < result2 < 100:
d.Edges[no][3] = 1
d.Edges[no][6] = result1
d.Edges[no][7] = result2
top.destroy() # 关闭窗口
G.draw()
else:
tk.messagebox.askokcancel(title='错误', message='路程与时间范围错误!')
except ValueError:
tk.messagebox.askokcancel(title='错误', message='请输入正确的路程与时间!')
button = tk.Button(top, text="获取信息", command=get_input)
button.pack()
except ValueError:
tk.messagebox.askokcancel(title='错误', message='请输入正确的城市编号!')
def way_change(self):
global entry3, entry4
value1 = entry3.get() # 获取第一个输入框的值
value2 = entry4.get() # 获取第二个输入框的值
try:
value1 = int(value1) # 将值转换为整数
value2 = int(value2) # 将值转换为整数
print("值1:", value1)
print("值2:", value2)
ways_list = [int(node[1]) for node in d.Nodes]
# 判断两点是否连接
if abs(value2) > 10 and abs(value1) > 10:
tk.messagebox.askokcancel(title='错误', message='请重新输入正确的编号!')
return
else:
if value1 < value2:
str_route = f'{value1}-{value2}'
else:
str_route = f'{value2}-{value1}'
no = 0
for item in d.Edges:
if str_route == item[5]:
no = G.node_edge(value1 - 1, value2 - 1)
if d.Edges[no][3] == 0:
tk.messagebox.askokcancel(title='提醒', message='当前路线不存在!')
return
top = creat_window(str_route + '路径参数修改') # 创建弹出窗口
distance_entry = create_input_box(top, "路程/公里1-99", d.Edges[no][6]) # 创建一个输入框,获取卷积核大小
time_entry = create_input_box(top, "时间/h1-99", d.Edges[no][7]) # 创建一个输入框,获取卷积步长
def get_input(): # 创建
# 获取输入框的内容
try:
result1 = int(distance_entry.get())
result2 = int(time_entry.get())
print(result1, result2)
if 100 > result1 > 0 and 0 < result2 < 100:
d.Edges[no][6] = result1
d.Edges[no][7] = result2
top.destroy() # 关闭窗口
G.draw()
else:
tk.messagebox.askokcancel(title='错误', message='路程与时间范围错误!')
except ValueError:
tk.messagebox.askokcancel(title='错误', message='请输入正确的路程与时间!')
button = tk.Button(top, text="获取信息", command=get_input)
button.pack()
except ValueError:
tk.messagebox.askokcancel(title='错误', message='请输入正确的城市编号!')
def way_delete(self):
global entry3, entry4
value1 = entry3.get() # 获取第一个输入框的值
value2 = entry4.get() # 获取第二个输入框的值
try:
value1 = int(value1) # 将值转换为整数
value2 = int(value2) # 将值转换为整数
print("值1:", value1)
print("值2:", value2)
ways_list = [int(node[1]) for node in d.Nodes]
# 判断两点是否连接
if abs(value2) > 10 and abs(value1) > 10:
tk.messagebox.askokcancel(title='错误', message='请重新输入正确的编号!')
return
else:
if value1 < value2:
str_route = f'{value1}-{value2}'
else:
str_route = f'{value2}-{value1}'
no = 0
for item in d.Edges:
if str_route == item[5]:
no = G.node_edge(value1 - 1, value2 - 1)
if d.Edges[no][3] == 0:
tk.messagebox.askokcancel(title='提醒', message='当前路线不存在!')
return
d.Edges[no][3] = 0
d.Edges[no][6] = 111
d.Edges[no][7] = 111
G.draw()
except ValueError:
tk.messagebox.askokcancel(title='错误', message='请输入正确的城市编号!')
class Node:
# 标记节点按钮
def node_mark_display(self, event):
global MARK_NODE, Specify_Nodes
W.user_way = [0]
MARK_NODE = 1
for w in frame_left.winfo_children():
w.destroy()
# 清除右侧组件
left_window()
curx = -1
y0 = 0.1
for i in d.Nodes:
if i[2] == 1:
curx = i[0]
# lab = tk.Label(frame_left, text="节点操作", bg=RED, fg=BLACK, font=('微软雅黑', 20))
# lab.place(relx=0.18, rely=0.32, width=200, height=30)
labx1 = tk.Label(frame_left, text=f"当前共有{len(d.Nodes)}个节点", bg='white', fg=BLACK, font=('微软雅黑', 20))
labx1.place(relx=0.15, rely=0.3 + y0, width=260, height=30)
lab1 = tk.Label(frame_left, text="当前节点:", bg='white', fg=BLACK, font=('微软雅黑', 20))
if curx == -1:
text = "未选择"
else:
text = f"{d.Nodes[curx][1]}"
Specify_Nodes = str(d.Nodes[curx][1])
lab2 = tk.Label(frame_left, text=text, bg='#F8F8FF', fg=BLACK, font=('微软雅黑', 20))
lab1.place(relx=0.15, rely=0.4 + y0, width=160, height=50)
lab2.place(relx=0.6, rely=0.4 + y0, width=100, height=50)
# 按钮
button = tk.Button(frame_left, relief="flat", image=List_Image[30], bg='white',
command=lambda: self.node_del_exact()) # 删除当前节点
button.place(relx=0.2, rely=0.5 + y0)
button2 = tk.Button(frame_left, relief="flat", image=List_Image[32], bg='white',
command=lambda: self.node_add()) # 增加一个节点
button2.place(relx=0.2, rely=0.6 + y0)
button3 = tk.Button(frame_left, relief="flat", image=List_Image[31], bg='white',
command=lambda: self.node_del()) # 减少一个节点
button3.place(relx=0.2, rely=0.7 + y0)
def node_add(self):
global d,Edges_list
for i in range(len(d.Edges)):
d.Edges[i][4] = False
Edges_list = []
Edges_list = d.Edges
# print(len(Edges_list))
# 增加一个节点
nodes = len(d.Nodes) + 1
# 如果节点数目大于五,则将连接详细信息改为不显示
if nodes > 5:
d.edgeinfo = 0
if nodes > 10:
tk.messagebox.askokcancel(title='错误', message='已达到最大节点')
return
d = Data(1, nodes, d.styple)
# for i in d.Edges:
# print(i)
G.draw()
self.node_mark_display(1)
def node_del(self):
global d,Edges_list
for i in range(len(d.Edges)):
d.Edges[i][4] = False
Edges_list = []
Edges_list = d.Edges
# print(len(Edges_list))
if d.nodes_num <= 2:
tk.messagebox.askokcancel(title='错误', message='节点过少')
return
nodes = len(d.Nodes) - 1
if nodes < 6:
d.edgeinfo = 1
d = Data(2, nodes, d.styple)
# for i in d.Edges:
# print(i)
G.draw()
self.node_mark_display(1)
def node_del_exact(self):
global d,Edges_list
for i in range(len(d.Edges)):
d.Edges[i][4] = False
Edges_list = d.Edges
if len(Specify_Nodes) == 3:
tk.messagebox.askokcancel(title='错误', message='未选择节点')
return
if d.nodes_num <= 2:# 若删除节点以后节点过少,则直接返回并警告
tk.messagebox.askokcancel(title='错误', message='节点过少')
return
nodes = len(d.Nodes) - 1
if nodes < 6:
d.edgeinfo = 1
d = Data(3, nodes, d.styple)
# for i in d.Edges:
# print(i)
G.draw()
self.node_mark_display(1)
def refresh():
top = creat_window('路径生成') # 创建弹出窗口
distance_entry = create_input_box(top, "节点个数1-10", 6) # 创建一个输入框,获取节点个数
# 创建一个下拉框,用于选择生成图类型
mode_combobox = create_dropdown_box(top, "生成图类型", ["TSP", "完全图"]) # 创建一个下拉框,用于选择生成图类型
def get_input(): # 创建
# 获取输入框的内容
try:
result1 = int(distance_entry.get())
# 从下拉框中获取池化类型
result2 = mode_combobox.get()
print(result1, result2)
flag = 0
if result2!=None:
if result2 == '完全图':
flag = 1
if 10 > result1 > 0:
global d
if flag:
d = Data(0, result1, 1)
else:
d = Data(0, result1, 0)
top.destroy() # 关闭窗口
G.draw()
# for i in d.Edges:
# print(i)
else:
tk.messagebox.askokcancel(title='错误', message='节点数范围错误!')
except ValueError:
tk.messagebox.askokcancel(title='错误', message='请输入正确的节点数')
button = tk.Button(top, text="获取信息", command=get_input)
button.pack()
def graph_init():
node = random.randint(4, 10)
global d, MODE, MARK_NODE
MARK_NODE = 0
MODE = 0
d = Data(0, node,0)
print(node)
W.user_way = [0]
G.draw()
window(2)
def empty():
global MARK_NODE
MARK_NODE = 0
G.delete_all()
def auto_opera_mu(menu: tk.Menu):
"""TSP问题自动生成与自动求解相关"""
menu_node = Menu(menu, tearoff=False)
menu_node.add_command(label="自动随机产生一个TSP问题", command=lambda: graph_init())
menu_node.add_command(label="重新生成", command=lambda: refresh())
menu_node.add_command(label="清空", command=lambda: empty())
menu_node.add_command(label="自动求解最优路径-dfs遍历", command=lambda: s.auto_solve(3))
menu_node.add_command(label="自动求解优化路径-贪心", command=lambda: s.auto_solve(2))
menu_node.add_command(label="自动求解优化路径-回溯", command=lambda: s.auto_solve(1))
menu_node.add_command(label="检查是否存在路径", command=lambda: s.check())
# 在主目录菜单上新增"文件"选项并通过menu参数与下拉菜单绑定
menu.add_cascade(label="TSP问题自动生成与自动求解相关", menu=menu_node)
# 创建弹出窗口
def creat_window(title):
top = tk.Toplevel(root)
top.geometry("300x350")
top.title(title)
return top
# 输入框
def create_input_box(top, text, value):
box_label = tk.Label(top, text=text)
box_label.pack(padx=10, pady=10)
box_size = tk.IntVar(top, value=value) # 创建一个IntVar对象并设置默认值为3
box_size_entry = tk.Entry(top, textvariable=box_size) # 关联IntVar对象
box_size_entry.pack(padx=20, pady=20)
return box_size_entry
# 下拉框
def create_dropdown_box(top, text, listvalues):
# 创建一个下拉框,用于选择
box_mode_label = tk.Label(top, text=text)
box_mode_label.pack(padx=10, pady=10)
box_mode_combobox = ttk.Combobox(top)
box_mode_combobox["values"] = listvalues
box_mode_combobox.pack(padx=20, pady=15)
return box_mode_combobox
def dir2(x, y): # 计算两点间距离
return (x[0] - y[0]) * (x[0] - y[0]) + (x[1] - y[1]) * (x[1] - y[1])
def left1(event):
global entry1, entry2, default_value, CLICK, entry3, entry4
# 查找鼠标左键按下时位置是否在某个节点内
n = -1
for node in d.Nodes:
if dir2([event.x, event.y], [node[3][0], node[3][1]]) < 20 * 20:
n = node[0]
# n为点击的节点若没有则为-1
m = mode.get() # 查看现在操作类型
if m == 1 and not n == -1:
# 节点操作
# N.node_mark(n)
if n >= len(d.Nodes):
# 弹出对话框
tk.messagebox.askokcancel(title='错误', message='该节点不存在')
return
for node in d.Nodes:
if node[0] == n:
node[2] = 1
else:
node[2] = 0
G.draw()
if MARK_NODE == 1: # 限制节点操作
N.node_mark_display(1)
elif MARK_NODE == 2: # 若是MARK_NODE==2则可以进行路线画图操作
if CLICK[2] == 0:
CLICK[0] = n
default_value.set(d.Nodes[n][1])
entry1.config(textvariable=default_value) # 更新entry1的默认值
elif CLICK[2] == 1:
CLICK[1] = n
W.extract_ligature(CLICK[0], CLICK[1])
CLICK[2] = -1
CLICK[2] += 1
elif MARK_NODE == 5: # 若是MARK_NODE==5则可以进行节点连接操作
if CLICK[2] == 0:
CLICK[0] = n
entry3.delete(0, tk.END)
# 设置输入框的值为 节点
entry3.insert(0, d.Nodes[n][1])
elif CLICK[2] == 1:
CLICK[1] = n
# W.extract_ligature(CLICK[0], CLICK[1])
entry4.delete(0, tk.END)
# 设置输入框的值为 节点
entry4.insert(0, d.Nodes[n][1])
CLICK[2] = -1
CLICK[2] += 1
print(n)
def element_reading():
global List_Image
folder_path = 'K:/work/traveler/background/'
image_path = ['1_红.png', '1_绿.png', '1_蓝.png', '2_红.png', '2_绿.png', '2_蓝.png', '3_红.png', '3_绿.png', '3_蓝.png',
'4_红.png', '4_绿.png', '4_蓝.png', '5_红.png', '5_绿.png', '5_蓝.png', '6_红.png', '6_绿.png', '6_蓝.png',
'7_红.png', '7_绿.png', '7_蓝.png', '8_红.png', '8_绿.png', '8_蓝.png', '9_红.png', '9_绿.png', '9_蓝.png',
'10_红.png', '10_绿.png', '10_蓝.png', '删除节点.png', '减少节点.png', '增加节点.png', '线路增加.png',
'线路修改.png', '线路删除.png', '撤销.png', '清除路径.png', '确认.png', '编组9.png', '背景.png',
'矩形.png', '判断路径是否为贪心路径.png', '判断路径是否为最短路径.png', '矩形2.png', '完全图.png', '可达判断.png']
for path in image_path[:30]: # 1-10球加载
List_Image.append(element(folder_path+path, 50, 50))
for path in image_path[30:36]: # 节点操作与连线操作功能按钮
List_Image.append(element(folder_path + path, 280, 50))
List_Image.append(element(folder_path + image_path[36], 140, 50)) # 撤销
List_Image.append(element(folder_path + image_path[37], 140, 50)) # 清除路径
List_Image.append(element(folder_path + image_path[38], 120, 50)) # 确认
List_Image.append(element(folder_path + image_path[39], 380, 100)) # 编组_9
List_Image.append(element(folder_path + image_path[40], 900, 900)) # 背景
List_Image.append(element(folder_path + image_path[41], 380, 140)) # 小矩形
List_Image.append(element(folder_path + image_path[42], 300, 50)) # 判断路径是否为贪心路径
List_Image.append(element(folder_path + image_path[43], 300, 50)) # 判断路径是否为最短路径
List_Image.append(element(folder_path + image_path[44], 380, 530)) # 大矩形
List_Image.append(element(folder_path + image_path[45], 140, 50)) # 完全图
List_Image.append(element(folder_path + image_path[46], 140, 50)) # 可达判断
def element(path, width, height):
# 加载图元对应的图片文件
img = Image.open(path)
# 使用resize方法调整图片
img = img.resize((width, height))
# 把Image对象转换成PhotoImage对象
img = ImageTk.PhotoImage(img)
# 保存图片的引用,防止被垃圾回收
root.img = img
return img
def window(event):
global MARK_NODE
MARK_NODE = 0
for w in frame_left.winfo_children():
w.destroy()
# 清除右侧组件
left_window()
Label_1 = tk.Label(frame_left, text="遍历节点数目", bg='white', fg=BLACK, font=('微软雅黑', 16))
Label_1.place(x=50, y=330, width=180, height=40)
Label_2 = tk.Label(frame_left, text="求解花费时间", bg='white', fg=BLACK, font=('微软雅黑', 16))
Label_2.place(x=50, y=450, width=180, height=40)
Label_3 = tk.Label(frame_left, text="最短路径", bg='white', fg=BLACK, font=('微软雅黑', 16))
Label_3.place(x=50, y=570, width=140, height=40)
if event == 2:
lab1 = tk.Label(frame_left, text=f"{0}", bg='#F8F8FF', fg=BLACK, font=('微软雅黑', 12), wraplength=200,
justify='left')
lab1.place(x=80, y=370, width=300, height=60)
lab2 = tk.Label(frame_left, text=f"{0}", bg='#F8F8FF', fg=BLACK, font=('微软雅黑', 12), wraplength=200,
justify='left')
lab2.place(x=80, y=490, width=300, height=60)
lab3 = tk.Label(frame_left, text=f"{1}", bg='#F8F8FF', fg=BLACK, font=('微软雅黑', 12), wraplength=200,
justify='left')
lab3.place(x=80, y=610, width=300, height=140)
else:
lab1 = tk.Label(frame_left, text=f"{s.way_sum}", bg='#F8F8FF', fg=BLACK, font=('微软雅黑', 12), wraplength=200,
justify='left')
lab1.place(x=80, y=370, width=300, height=60)
t = round(s.end - s.start, 4)
lab2 = tk.Label(frame_left, text=f"{t}", bg='#F8F8FF', fg=BLACK, font=('微软雅黑', 12), wraplength=200,
justify='left')
lab2.place(x=80, y=490, width=300, height=60)
g_way = ''
if len(s.ans) > 0:
for a in s.ans:
g_way += d.Nodes[a][1]
g_way += "->"
g_way = g_way[:-2]
else:
g_way='1'
lab3 = tk.Label(frame_left, text=f"{g_way}", bg='#F8F8FF', fg=BLACK, font=('微软雅黑', 12), wraplength=200,
justify='left')
lab3.place(x=80, y=610, width=300, height=140)
def left_window():
background_button = tk.Label(frame_left, image=List_Image[39], bg='#FFFAF0') # 编组
background_button.place(x=50, y=20, width=380, height=100)
background_button.bind("<Button-1>", window)
# # 创建 Label 组件来显示背景图片
background_on = tk.Label(frame_left, bg='#FFFAF0', image=List_Image[41]) # 小矩形背景
background_on.place(x=50, y=140, width=380, height=140)
if MARK_NODE == 1:
background_color = [BLACK, '#DCDCDC', '#DCDCDC']
elif MARK_NODE == 5:
background_color = ['#DCDCDC', BLACK, '#DCDCDC']
elif MARK_NODE == 2:
background_color = ['#DCDCDC', '#DCDCDC', BLACK]
else:
background_color = ['#DCDCDC', '#DCDCDC', '#DCDCDC']
Label_button1 = tk.Label(frame_left, text="节点操作", bg='white', fg=background_color[0], font=('微软雅黑', 15))
Label_button1.place(x=60, y=180, width=80, height=50)
Label_button1.bind("<Button-1>", N.node_mark_display)
Label_button2 = tk.Label(frame_left, text="连接操作", bg='white', fg=background_color[1], font=('微软雅黑', 15))
Label_button2.place(x=200, y=180, width=80, height=50)
Label_button2.bind("<Button-1>", W.way_operate)
Label_button3 = tk.Label(frame_left, text="路径操作", bg='white', fg=background_color[2], font=('微软雅黑', 15))
Label_button3.place(x=340, y=180, width=80, height=50)
Label_button3.bind("<Button-1>", W.way_route_markings)
background_below = tk.Label(frame_left, bg='#FFFAF0', image=List_Image[44]) # 大矩形背景
background_below.place(x=50, y=300, width=380, height=530)
if __name__ == '__main__':
root = Tk() # 设置主界面
root.title("旅行商问题") # 设置标题
root.geometry("1400x900") # 设置大小
root.resizable(0, 0) # 设置不能调整显示边框
element_reading()
frame_top = tk.Frame(root, bg='white', width=1400, height=50, highlightthickness=0)
frame_top.place(x=0, y=0) # 绘制信息输出栏
frame_left = tk.Frame(root, bg='#FFFAF0', highlightthickness=0, width=500, height=850) # 左边栏
frame_left.place(x=0, y=50) # 左边栏
cv = tk.Canvas(root, bg='#FFFAF0', bd=0, relief="flat", width=900, height=850, highlightthickness=0)
cv.place(x=500, y=50) # 放置绘图Canvas画布
cv.create_image(0, 0, image=List_Image[40], anchor=tk.NW) # 背景
G = Graph()
mode = tk.IntVar(value=1)
main_menu = tk.Menu(root)
root.config(menu=main_menu)
cv.bind('<Button-1>', left1)
E = Edge()
W = Ways()
s = Solve()
N = Node()
auto_opera_mu(main_menu)
window(2)
G.lab_show(frame_top, 0, 0, 0) # 显示边信息
root.mainloop()