java
复制代码
package com.alvin.datastruct;
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.util.*;
import java.util.List;
// 顶点类
class Vertex {
private int id;
private int x, y;
public static final int DIAMETER = 40; // 改为public static final
private Color color = Color.LIGHT_GRAY;
public Vertex(int id, int x, int y) {
this.id = id;
this.x = x;
this.y = y;
}
public int getId() { return id; }
public int getX() { return x; }
public int getY() { return y; }
public void setPosition(int x, int y) { this.x = x; this.y = y; }
public Color getColor() { return color; }
public void setColor(Color color) { this.color = color; }
public boolean contains(int x, int y) {
int centerX = this.x + DIAMETER/2;
int centerY = this.y + DIAMETER/2;
return Math.sqrt(Math.pow(x - centerX, 2) + Math.pow(y - centerY, 2)) <= DIAMETER/2;
}
public void draw(Graphics g) {
g.setColor(color);
g.fillOval(x, y, DIAMETER, DIAMETER);
g.setColor(Color.BLACK);
g.drawOval(x, y, DIAMETER, DIAMETER);
g.setFont(new Font("Arial", Font.BOLD, 16));
FontMetrics fm = g.getFontMetrics();
String text = String.valueOf(id);
int textX = x + (DIAMETER - fm.stringWidth(text)) / 2;
int textY = y + (DIAMETER - fm.getHeight()) / 2 + fm.getAscent();
g.drawString(text, textX, textY);
}
}
// 边类
class Edge {
private Vertex from, to;
private int weight;
private Color color = Color.BLACK;
public Edge(Vertex from, Vertex to, int weight) {
this.from = from;
this.to = to;
this.weight = weight;
}
public Vertex getFrom() { return from; }
public Vertex getTo() { return to; }
public int getWeight() { return weight; }
public void setWeight(int weight) { this.weight = weight; }
public Color getColor() { return color; }
public void setColor(Color color) { this.color = color; }
public void draw(Graphics g) {
int x1 = from.getX() + Vertex.DIAMETER/2;
int y1 = from.getY() + Vertex.DIAMETER/2;
int x2 = to.getX() + Vertex.DIAMETER/2;
int y2 = to.getY() + Vertex.DIAMETER/2;
g.setColor(color);
g.drawLine(x1, y1, x2, y2);
// 绘制权重
int midX = (x1 + x2) / 2;
int midY = (y1 + y2) / 2;
g.setFont(new Font("Arial", Font.PLAIN, 12));
g.drawString(String.valueOf(weight), midX, midY);
// 绘制箭头
drawArrow(g, x1, y1, x2, y2);
}
private void drawArrow(Graphics g, int x1, int y1, int x2, int y2) {
double angle = Math.atan2(y2 - y1, x2 - x1);
int arrowSize = 10;
int x3 = (int) (x2 - arrowSize * Math.cos(angle - Math.PI / 6));
int y3 = (int) (y2 - arrowSize * Math.sin(angle - Math.PI / 6));
int x4 = (int) (x2 - arrowSize * Math.cos(angle + Math.PI / 6));
int y4 = (int) (y2 - arrowSize * Math.sin(angle + Math.PI / 6));
g.drawLine(x2, y2, x3, y3);
g.drawLine(x2, y2, x4, y4);
}
}
// 图类
class Graph {
private Map<Integer, Vertex> vertices = new HashMap<>();
private List<Edge> edges = new ArrayList<>();
private boolean directed = false;
public void addVertex(int id, int x, int y) {
vertices.put(id, new Vertex(id, x, y));
}
public void removeVertex(int id) {
Vertex v = vertices.remove(id);
if (v != null) {
edges.removeIf(edge -> edge.getFrom().getId() == id || edge.getTo().getId() == id);
}
}
public void addEdge(int fromId, int toId, int weight) {
Vertex from = vertices.get(fromId);
Vertex to = vertices.get(toId);
if (from != null && to != null) {
edges.add(new Edge(from, to, weight));
if (!directed) {
edges.add(new Edge(to, from, weight));
}
}
}
public void removeEdge(int fromId, int toId) {
edges.removeIf(edge ->
(edge.getFrom().getId() == fromId && edge.getTo().getId() == toId) ||
(!directed && edge.getFrom().getId() == toId && edge.getTo().getId() == fromId)
);
}
public Vertex getVertexAt(int x, int y) {
for (Vertex v : vertices.values()) {
if (v.contains(x, y)) {
return v;
}
}
return null;
}
public void draw(Graphics g) {
// 先绘制边
for (Edge edge : edges) {
edge.draw(g);
}
// 再绘制顶点
for (Vertex vertex : vertices.values()) {
vertex.draw(g);
}
}
public void clearColors() {
for (Vertex v : vertices.values()) {
v.setColor(Color.LIGHT_GRAY);
}
for (Edge e : edges) {
e.setColor(Color.BLACK);
}
}
public void setDirected(boolean directed) {
this.directed = directed;
}
public boolean isDirected() {
return directed;
}
public Map<Integer, Vertex> getVertices() {
return vertices;
}
public List<Edge> getEdges() {
return edges;
}
public int getNextAvailableId() {
int maxId = 0;
for (int id : vertices.keySet()) {
if (id > maxId) {
maxId = id;
}
}
return maxId + 1;
}
// 深度优先遍历
public List<Integer> dfs(int startId) {
List<Integer> result = new ArrayList<>();
Set<Integer> visited = new HashSet<>();
Vertex start = vertices.get(startId);
if (start != null) {
dfsHelper(start, visited, result);
}
return result;
}
private void dfsHelper(Vertex current, Set<Integer> visited, List<Integer> result) {
if (visited.contains(current.getId())) return;
visited.add(current.getId());
result.add(current.getId());
for (Edge edge : edges) {
if (edge.getFrom().getId() == current.getId()) {
dfsHelper(edge.getTo(), visited, result);
}
}
}
// 广度优先遍历
public List<Integer> bfs(int startId) {
List<Integer> result = new ArrayList<>();
Set<Integer> visited = new HashSet<>();
Queue<Vertex> queue = new LinkedList<>();
Vertex start = vertices.get(startId);
if (start == null) return result;
queue.add(start);
visited.add(startId);
while (!queue.isEmpty()) {
Vertex current = queue.poll();
result.add(current.getId());
for (Edge edge : edges) {
Vertex neighbor = edge.getTo();
if (!visited.contains(neighbor.getId())) {
visited.add(neighbor.getId());
queue.add(neighbor);
}
}
}
return result;
}
// 查找顶点
public boolean containsVertex(int id) {
return vertices.containsKey(id);
}
// 查找边
public boolean containsEdge(int fromId, int toId) {
for (Edge edge : edges) {
if (edge.getFrom().getId() == fromId && edge.getTo().getId() == toId) {
return true;
}
}
return false;
}
// 获取边的权重
public int getEdgeWeight(int fromId, int toId) {
for (Edge edge : edges) {
if (edge.getFrom().getId() == fromId && edge.getTo().getId() == toId) {
return edge.getWeight();
}
}
return -1; // 表示边不存在
}
// 更新边的权重
public void updateEdgeWeight(int fromId, int toId, int newWeight) {
for (Edge edge : edges) {
if (edge.getFrom().getId() == fromId && edge.getTo().getId() == toId) {
edge.setWeight(newWeight);
// 如果是无向图,还需要更新反向边
if (!directed) {
for (Edge reverseEdge : edges) {
if (reverseEdge.getFrom().getId() == toId && reverseEdge.getTo().getId() == fromId) {
reverseEdge.setWeight(newWeight);
break;
}
}
}
break;
}
}
}
}
// 主界面
public class GraphVisualization extends JFrame {
private Graph graph = new Graph();
private JPanel canvas;
private JTextField vertexIdField, fromField, toField, weightField;
private JButton addVertexBtn, removeVertexBtn, addEdgeBtn, removeEdgeBtn;
private JButton dfsBtn, bfsBtn, clearBtn, updateWeightBtn, generateRandomBtn;
private JCheckBox directedCheckbox;
private Vertex selectedVertex = null;
private Random random = new Random();
public GraphVisualization() {
setTitle("图数据结构可视化");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setSize(1000, 700);
setLayout(new BorderLayout());
// 创建画布
canvas = new JPanel() {
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
graph.draw(g);
}
};
canvas.setBackground(Color.WHITE);
canvas.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
if (SwingUtilities.isRightMouseButton(e)) {
Vertex vertex = graph.getVertexAt(e.getX(), e.getY());
if (vertex != null) {
graph.removeVertex(vertex.getId());
canvas.repaint();
}
} else {
if (selectedVertex == null) {
int nextId = graph.getNextAvailableId();
graph.addVertex(nextId, e.getX() - Vertex.DIAMETER/2, e.getY() - Vertex.DIAMETER/2);
canvas.repaint();
} else {
Vertex toVertex = graph.getVertexAt(e.getX(), e.getY());
if (toVertex != null && toVertex != selectedVertex) {
String weightStr = JOptionPane.showInputDialog("请输入边的权重:", "1");
try {
int weight = Integer.parseInt(weightStr);
graph.addEdge(selectedVertex.getId(), toVertex.getId(), weight);
canvas.repaint();
} catch (NumberFormatException ex) {
JOptionPane.showMessageDialog(GraphVisualization.this,
"权重必须是整数", "错误", JOptionPane.ERROR_MESSAGE);
}
}
}
}
}
@Override
public void mousePressed(MouseEvent e) {
selectedVertex = graph.getVertexAt(e.getX(), e.getY());
}
@Override
public void mouseReleased(MouseEvent e) {
selectedVertex = null;
}
});
canvas.addMouseMotionListener(new MouseMotionAdapter() {
@Override
public void mouseDragged(MouseEvent e) {
if (selectedVertex != null) {
selectedVertex.setPosition(e.getX() - Vertex.DIAMETER/2, e.getY() - Vertex.DIAMETER/2);
canvas.repaint();
}
}
});
add(canvas, BorderLayout.CENTER);
// 创建控制面板
JPanel controlPanel = new JPanel();
controlPanel.setLayout(new BoxLayout(controlPanel, BoxLayout.Y_AXIS));
// 顶点操作面板
JPanel vertexPanel = new JPanel();
vertexPanel.setBorder(BorderFactory.createTitledBorder("顶点操作"));
vertexIdField = new JTextField(5);
addVertexBtn = new JButton("添加顶点");
removeVertexBtn = new JButton("删除顶点");
generateRandomBtn = new JButton("随机生成10个顶点");
addVertexBtn.addActionListener(e -> {
try {
int id = Integer.parseInt(vertexIdField.getText());
if (graph.containsVertex(id)) {
JOptionPane.showMessageDialog(this, "顶点ID已存在", "错误", JOptionPane.ERROR_MESSAGE);
} else {
graph.addVertex(id, 100, 100);
canvas.repaint();
}
} catch (NumberFormatException ex) {
JOptionPane.showMessageDialog(this, "请输入有效的顶点ID", "错误", JOptionPane.ERROR_MESSAGE);
}
});
removeVertexBtn.addActionListener(e -> {
try {
int id = Integer.parseInt(vertexIdField.getText());
if (!graph.containsVertex(id)) {
JOptionPane.showMessageDialog(this, "顶点不存在", "错误", JOptionPane.ERROR_MESSAGE);
} else {
graph.removeVertex(id);
canvas.repaint();
}
} catch (NumberFormatException ex) {
JOptionPane.showMessageDialog(this, "请输入有效的顶点ID", "错误", JOptionPane.ERROR_MESSAGE);
}
});
generateRandomBtn.addActionListener(e -> {
generateRandomVertices(10);
canvas.repaint();
});
vertexPanel.add(new JLabel("顶点ID:"));
vertexPanel.add(vertexIdField);
vertexPanel.add(addVertexBtn);
vertexPanel.add(removeVertexBtn);
vertexPanel.add(generateRandomBtn);
// 边操作面板
JPanel edgePanel = new JPanel();
edgePanel.setBorder(BorderFactory.createTitledBorder("边操作"));
fromField = new JTextField(3);
toField = new JTextField(3);
weightField = new JTextField(3);
addEdgeBtn = new JButton("添加边");
removeEdgeBtn = new JButton("删除边");
updateWeightBtn = new JButton("更新权重");
directedCheckbox = new JCheckBox("有向图");
addEdgeBtn.addActionListener(e -> {
try {
int from = Integer.parseInt(fromField.getText());
int to = Integer.parseInt(toField.getText());
int weight = Integer.parseInt(weightField.getText());
if (!graph.containsVertex(from) || !graph.containsVertex(to)) {
JOptionPane.showMessageDialog(this, "顶点不存在", "错误", JOptionPane.ERROR_MESSAGE);
return;
}
if (graph.containsEdge(from, to)) {
JOptionPane.showMessageDialog(this, "边已存在", "错误", JOptionPane.ERROR_MESSAGE);
return;
}
graph.addEdge(from, to, weight);
canvas.repaint();
} catch (NumberFormatException ex) {
JOptionPane.showMessageDialog(this, "请输入有效的顶点ID和权重", "错误", JOptionPane.ERROR_MESSAGE);
}
});
removeEdgeBtn.addActionListener(e -> {
try {
int from = Integer.parseInt(fromField.getText());
int to = Integer.parseInt(toField.getText());
if (!graph.containsEdge(from, to)) {
JOptionPane.showMessageDialog(this, "边不存在", "错误", JOptionPane.ERROR_MESSAGE);
return;
}
graph.removeEdge(from, to);
canvas.repaint();
} catch (NumberFormatException ex) {
JOptionPane.showMessageDialog(this, "请输入有效的顶点ID", "错误", JOptionPane.ERROR_MESSAGE);
}
});
updateWeightBtn.addActionListener(e -> {
try {
int from = Integer.parseInt(fromField.getText());
int to = Integer.parseInt(toField.getText());
int weight = Integer.parseInt(weightField.getText());
if (!graph.containsEdge(from, to)) {
JOptionPane.showMessageDialog(this, "边不存在", "错误", JOptionPane.ERROR_MESSAGE);
return;
}
graph.updateEdgeWeight(from, to, weight);
canvas.repaint();
} catch (NumberFormatException ex) {
JOptionPane.showMessageDialog(this, "请输入有效的顶点ID和权重", "错误", JOptionPane.ERROR_MESSAGE);
}
});
directedCheckbox.addActionListener(e -> {
graph.setDirected(directedCheckbox.isSelected());
JOptionPane.showMessageDialog(this,
"图类型已设置为: " + (directedCheckbox.isSelected() ? "有向图" : "无向图"),
"提示", JOptionPane.INFORMATION_MESSAGE);
});
edgePanel.add(new JLabel("从:"));
edgePanel.add(fromField);
edgePanel.add(new JLabel("到:"));
edgePanel.add(toField);
edgePanel.add(new JLabel("权重:"));
edgePanel.add(weightField);
edgePanel.add(addEdgeBtn);
edgePanel.add(removeEdgeBtn);
edgePanel.add(updateWeightBtn);
edgePanel.add(directedCheckbox);
// 遍历操作面板
JPanel traversalPanel = new JPanel();
traversalPanel.setBorder(BorderFactory.createTitledBorder("遍历操作"));
dfsBtn = new JButton("深度优先遍历");
bfsBtn = new JButton("广度优先遍历");
clearBtn = new JButton("清除颜色");
dfsBtn.addActionListener(e -> {
try {
int startId = Integer.parseInt(vertexIdField.getText());
if (!graph.containsVertex(startId)) {
JOptionPane.showMessageDialog(this, "顶点不存在", "错误", JOptionPane.ERROR_MESSAGE);
return;
}
List<Integer> result = graph.dfs(startId);
graph.clearColors();
highlightTraversal(result, Color.RED);
JOptionPane.showMessageDialog(this, "DFS遍历结果: " + result);
} catch (NumberFormatException ex) {
JOptionPane.showMessageDialog(this, "请输入有效的起始顶点ID", "错误", JOptionPane.ERROR_MESSAGE);
}
});
bfsBtn.addActionListener(e -> {
try {
int startId = Integer.parseInt(vertexIdField.getText());
if (!graph.containsVertex(startId)) {
JOptionPane.showMessageDialog(this, "顶点不存在", "错误", JOptionPane.ERROR_MESSAGE);
return;
}
List<Integer> result = graph.bfs(startId);
graph.clearColors();
highlightTraversal(result, Color.BLUE);
JOptionPane.showMessageDialog(this, "BFS遍历结果: " + result);
} catch (NumberFormatException ex) {
JOptionPane.showMessageDialog(this, "请输入有效的起始顶点ID", "错误", JOptionPane.ERROR_MESSAGE);
}
});
clearBtn.addActionListener(e -> {
graph.clearColors();
canvas.repaint();
});
traversalPanel.add(dfsBtn);
traversalPanel.add(bfsBtn);
traversalPanel.add(clearBtn);
// 添加所有面板到控制面板
controlPanel.add(vertexPanel);
controlPanel.add(edgePanel);
controlPanel.add(traversalPanel);
add(controlPanel, BorderLayout.SOUTH);
setVisible(true);
}
private void generateRandomVertices(int count) {
int width = canvas.getWidth();
int height = canvas.getHeight();
for (int i = 0; i < count; i++) {
int id = graph.getNextAvailableId();
int x = random.nextInt(width - Vertex.DIAMETER);
int y = random.nextInt(height - Vertex.DIAMETER);
graph.addVertex(id, x, y);
}
// 随机添加一些边
List<Integer> vertexIds = new ArrayList<>(graph.getVertices().keySet());
if (vertexIds.size() >= 2) {
for (int i = 0; i < count / 2; i++) {
int fromIdx = random.nextInt(vertexIds.size());
int toIdx = random.nextInt(vertexIds.size());
if (fromIdx != toIdx) {
int fromId = vertexIds.get(fromIdx);
int toId = vertexIds.get(toIdx);
int weight = random.nextInt(10) + 1; // 权重1-10
if (!graph.containsEdge(fromId, toId)) {
graph.addEdge(fromId, toId, weight);
}
}
}
}
}
private void highlightTraversal(List<Integer> traversalOrder, Color color) {
if (traversalOrder.isEmpty()) return;
// 使用javax.swing.Timer而不是java.util.Timer
javax.swing.Timer timer = new javax.swing.Timer(1000, null);
timer.setRepeats(true);
final int[] index = {0};
timer.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
if (index[0] < traversalOrder.size()) {
int vertexId = traversalOrder.get(index[0]);
for (Vertex v : graph.getVertices().values()) {
if (v.getId() == vertexId) {
v.setColor(color);
break;
}
}
canvas.repaint();
index[0]++;
} else {
((javax.swing.Timer) e.getSource()).stop();
}
}
});
timer.start();
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> new GraphVisualization());
}
}