
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; 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() {@Overrideprotected void paintComponent(Graphics g) {super.paintComponent(g);graph.draw(g);}};canvas.setBackground(Color.WHITE);canvas.addMouseListener(new MouseAdapter() {@Overridepublic 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);}}}}}@Overridepublic void mousePressed(MouseEvent e) {selectedVertex = graph.getVertexAt(e.getX(), e.getY());}@Overridepublic void mouseReleased(MouseEvent e) {selectedVertex = null;}});canvas.addMouseMotionListener(new MouseMotionAdapter() {@Overridepublic 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; 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 timer = new javax.swing.Timer(1000, null);timer.setRepeats(true);final int[] index = {0};timer.addActionListener(new ActionListener() {@Overridepublic 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());}
}