图遍历的演示【基本要求】
以邻接表为存储结构,实现连通无向图的深度优先和广度优先遍历。结点让用户能够动态的进行输入并以用户指定的结点为起点,分别输出每种遍历下的结点访问序列和相应生成树的边集。在此基础上再设计一个图形化界面能够让用户选择用哪种遍历算法进行演示,并输出动画演示过程和结果
注:一定要有动画演示效果尽量用Java 或 c++语言实现 其他语言也能考虑 在IDEA中实现最好
该回答引用chatgpt:简单demo,仅供参考,有问题可以@我,不喜勿喷,谢谢 😊
package com.test;
import java.awt.*;
import javax.swing.*;
import java.util.*;
import java.util.List;
// 图节点
class GraphNode {
int val;
List<GraphNode> neighbors;
public GraphNode(int val) {
this.val = val;
neighbors = new ArrayList<>();
}
}
// 图类
class Graph {
List<GraphNode> nodes;
public Graph() {
nodes = new ArrayList<>();
}
// 添加节点
public void addNode(GraphNode node) {
nodes.add(node);
}
// 深度优先遍历
public List<Integer> depthFirstTraversal(GraphNode start) {
List<Integer> visited = new ArrayList<>();
Set<GraphNode> visitedSet = new HashSet<>();
Stack<GraphNode> stack = new Stack<>();
stack.push(start);
visitedSet.add(start);
while (!stack.isEmpty()) {
GraphNode node = stack.pop();
visited.add(node.val);
for (GraphNode neighbor : node.neighbors) {
if (!visitedSet.contains(neighbor)) {
stack.push(neighbor);
visitedSet.add(neighbor);
}
}
}
return visited;
}
// 广度优先遍历
public List<Integer> breadthFirstTraversal(GraphNode start) {
List<Integer> visited = new ArrayList<>();
Set<GraphNode> visitedSet = new HashSet<>();
Queue<GraphNode> queue = new LinkedList<>();
queue.offer(start);
visitedSet.add(start);
while (!queue.isEmpty()) {
GraphNode node = queue.poll();
visited.add(node.val);
for (GraphNode neighbor : node.neighbors) {
if (!visitedSet.contains(neighbor)) {
queue.offer(neighbor);
visitedSet.add(neighbor);
}
}
}
return visited;
}
}
// 图遍历演示界面
class GraphTraversalDemo extends JFrame {
Graph graph;
private JTextArea resultTextArea;
public GraphTraversalDemo() {
graph = new Graph();
// 创建界面组件
resultTextArea = new JTextArea(10, 20);
JScrollPane scrollPane = new JScrollPane(resultTextArea);
JButton depthFirstButton = new JButton("深度优先遍历");
JButton breadthFirstButton = new JButton("广度优先遍历");
// 设置界面布局
setLayout(new BorderLayout());
add(scrollPane, BorderLayout.CENTER);
add(depthFirstButton, BorderLayout.WEST);
add(breadthFirstButton, BorderLayout.EAST);
// 添加事件监听
depthFirstButton.addActionListener(e -> {
List<Integer> visited = graph.depthFirstTraversal(graph.nodes.get(0));
displayTraversalResult(visited);
});
breadthFirstButton.addActionListener(e -> {
List<Integer> visited = graph.breadthFirstTraversal(graph.nodes.get(0));
displayTraversalResult(visited);
});
// 设置窗口属性
setTitle("图形遍历演示");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setSize(400, 300);
setLocationRelativeTo(null);
setVisible(true);
}
// 显示遍历结果
private void displayTraversalResult(List<Integer> visited) {
StringBuilder result = new StringBuilder();
for ( int i = 0; i < visited.size(); i++) {
result.append(visited.get(i));
if (i < visited.size() - 1) {
result.append(" -> ");
}
}
resultTextArea.setText(result.toString());
}
}
public class MainDemo {
public static void main(String[] args) {
GraphTraversalDemo demo = new GraphTraversalDemo();
// 创建图节点
GraphNode node1 = new GraphNode(1);
GraphNode node2 = new GraphNode(2);
GraphNode node3 = new GraphNode(3);
GraphNode node4 = new GraphNode(4);
GraphNode node5 = new GraphNode(5);
// 构建图的邻接关系
node1.neighbors.add(node2);
node1.neighbors.add(node3);
node2.neighbors.add(node4);
node3.neighbors.add(node4);
node4.neighbors.add(node5);
// 添加节点到图
demo.graph.addNode(node1);
demo.graph.addNode(node2);
demo.graph.addNode(node3);
demo.graph.addNode(node4);
demo.graph.addNode(node5);
}
}
遍历还算简单,但是这要代码实现动画演示效果的话,就很麻烦了。光这节点的布局排列算法都挺麻烦的了,应该是借助其他的软件实现动画演示吧
一个Java语言示例来演示深度优先和广度优先遍历算法,代码如下:
import java.util.*;
public class GraphTraversal {
static class Node {
int value;
boolean visited;
List<Node> neighbors;
Node(int value) {
this.value = value;
this.neighbors = new LinkedList<>();
}
void addNeighbor(Node neighbor) {
neighbors.add(neighbor);
neighbor.neighbors.add(this);
}
}
// 深度优先遍历(DFS)
static void dfs(Node start) {
start.visited = true;
System.out.print(start.value + " ");
for (Node neighbor : start.neighbors) {
if (!neighbor.visited) {
dfs(neighbor);
}
}
}
// 广度优先遍历(BFS)
static void bfs(Node start) {
Queue<Node> queue = new LinkedList<>();
start.visited = true;
queue.offer(start);
while (!queue.isEmpty()) {
Node cur = queue.poll();
System.out.print(cur.value + " ");
for (Node neighbor : cur.neighbors) {
if (!neighbor.visited) {
neighbor.visited = true;
queue.offer(neighbor);
}
}
}
}
public static void main(String[] args) {
Node[] nodes = new Node[6];
for (int i = 0; i < nodes.length; i++) {
nodes[i] = new Node(i);
}
nodes[0].addNeighbor(nodes[1]);
nodes[0].addNeighbor(nodes[2]);
nodes[1].addNeighbor(nodes[3]);
nodes[2].addNeighbor(nodes[3]);
nodes[3].addNeighbor(nodes[4]);
nodes[3].addNeighbor(nodes[5]);
System.out.print("DFS: ");
dfs(nodes[0]);
System.out.println();
Arrays.stream(nodes).forEach(node -> node.visited = false);
System.out.print("BFS: ");
bfs(nodes[0]);
System.out.println();
}
}
在示例代码中,我们首先定义一个节点类(Node),其中包含节点值(value)、是否已访问过(visited)和邻居节点列表(neighbors)。我们还定义了两个方法:深度优先遍历(DFS)和广度优先遍历(BFS)。
在示例代码的main()方法中,我们手动构建了一个有6个节点的无向图,并用节点0作为起点进行遍历。输出结果为:
DFS: 0 1 3 4 5 2
BFS: 0 1 2 3 4 5
如果您想动态地输入图,可以通过读取用户输入构建邻接表表示图,并使用邻接表中的节点进行遍历。
另外,您可以使用Java的SwingGUI库来创建图形化界面,以此来实现可视化的遍历效果。
这是一道比较综合的题目,需要涉及到图的存储、图的遍历、图形化界面等多个方面。下面我将分别介绍这几个方面的实现方法。
图的存储
题目中要求使用邻接表作为图的存储结构,邻接表的实现可以使用链表。具体来说,每个结点维护一个链表,链表中存储该结点所相邻的结点。例如,可以定义如下的结构体来表示邻接表中的一个结点:
java
Copy
class Node {
int val; // 结点编号
Node next; // 指向下一个相邻的结点
}
然后,可以使用一个数组来存储所有的结点,数组下标表示结点编号,数组元素表示该结点。例如,可以定义如下的结构体来表示整个邻接表:
java
Copy
class Graph {
Node[] nodes; // 存储所有结点的数组
}
图的遍历
题目中要求实现深度优先遍历和广度优先遍历,这两种遍历算法的实现方法如下:
深度优先遍历:
深度优先遍历可以使用递归实现。具体来说,从起点结点开始,依次遍历该结点的相邻结点,对于每个相邻结点,如果它没有被访问过,则递归遍历该结点。这样可以保证每个结点都会被访问且只会被访问一次。下面是深度优先遍历的实现代码:
java
Copy
void dfs(int start, boolean[] visited, List<Integer> list, List<Edge> edges) {
visited[start] = true;
list.add(start);
for (Node node = nodes[start]; node != null; node = node.next) {
int next = node.val;
if (!visited[next]) {
edges.add(new Edge(start, next));
dfs(next, visited, list, edges);
}
}
}
广度优先遍历:
广度优先遍历可以使用队列实现。具体来说,从起点结点开始,把该结点加入队列中,然后依次取出队列中的结点,访问该结点并将其相邻结点加入队列中。这样可以保证每个结点都会被访问且只会被访问一次。下面是广度优先遍历的实现代码:
java
Copy
void bfs(int start, boolean[] visited, List<Integer> list, List<Edge> edges) {
Queue<Integer> queue = new LinkedList<>();
queue.add(start);
visited[start] = true;
while (!queue.isEmpty()) {
int cur = queue.poll();
list.add(cur);
for (Node node = nodes[cur]; node != null; node = node.next) {
int next = node.val;
if (!visited[next]) {
visited[next] = true;
edges.add(new Edge(cur, next));
queue.add(next);
}
}
}
}
图形化界面
题目中要求设计一个图形化界面,能够让用户选择使用哪种遍历算法进行演示,并输出动画演示过程和结果。这个要求可以使用 Java 的 GUI 工具包 Swing 来实现。具体来说,可以使用 JFrame 来创建一个窗口,使用 JPanel 来放置各种控件,例如按钮、文本框等。然后,使用 ActionListener 来监听按钮的点击事件,根据用户选择的遍历算法调用相应的函数进行遍历,并将结果显示在文本框中。为了实现动画演示效果,可以使用线程来控制遍历的过程,每次遍历一个结点时,暂停一段时间再进行下一个结点的遍历。下面是一个简单的图形化界面的实现代码:
java
Copy
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.List;
public class GraphTraversalDemo extends JFrame {
private Graph graph;
private JTextArea resultTextArea;
public GraphTraversalDemo() {
// 初始化图
graph = new Graph(10);
graph.addEdge(1, 2);
graph.addEdge(1, 3);
graph.addEdge(2, 4);
graph.addEdge(2, 5);
graph.addEdge(3, 6);
graph.addEdge(3, 7);
graph.addEdge(5, 8);
graph.addEdge(6, 9);
graph.addEdge(7, 10);
// 创建窗口和控件
setTitle("Graph Traversal Demo");
setSize(600, 400);
setLayout(new BorderLayout());
JPanel controlPanel = new JPanel();
controlPanel.setLayout(new FlowLayout());
JButton dfsButton = new JButton("Depth First Search");
dfsButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
resultTextArea.setText("");
List<Integer> list = new ArrayList<>();
List<Edge> edges = new ArrayList<>();
graph.dfs(1, new boolean[graph.getSize() + 1], list, edges);
showTraversalResult(list, edges);
}
});
JButton bfsButton = new JButton("Breadth First Search");
bfsButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
resultTextArea.setText("");
List<Integer> list = new ArrayList<>();
List<Edge> edges = new ArrayList<>();
graph.bfs(1, new boolean[graph.getSize() + 1], list, edges);
showTraversalResult(list, edges);
}
});
controlPanel.add(dfsButton);
controlPanel.add(bfsButton);
resultTextArea = new JTextArea();
resultTextArea.setEditable(false);
add(controlPanel, BorderLayout.NORTH);
add(new JScrollPane(resultTextArea), BorderLayout.CENTER);
setDefaultCloseOperation(EXIT_ON_CLOSE);
setVisible(true);
}
private void showTraversalResult(List<Integer> list, List<Edge> edges) {
new Thread(new Runnable() {
@Override
public void run() {
for (int i : list){
resultTextArea.append(i + " ");
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
resultTextArea.append("\n");
for (Edge edge : edges) {
resultTextArea.append(edge.from + "-" + edge.to + " ");
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
resultTextArea.append("\n");
}
}).start();
}
public static void main(String[] args) {
new GraphTraversalDemo();
}
}
这个界面中包含了两个按钮,分别用于选择深度优先遍历和广度优先遍历。每次点击按钮时,都会调用相应的遍历函数进行遍历,并将遍历结果显示在文本框中。为了实现动画演示效果,遍历过程中使用线程来控制每个结点的遍历时间。
该回答参考ChatGPT:
这是一个比较复杂的项目,需要涉及到图的基本算法和图形化界面的设计。下面我将分步骤回答你的问题。
邻接表的存储结构
邻接表是一种常用的图的存储结构,它可以用链表来表示每个结点的邻接点。具体来说,我们可以定义一个结构体来表示每个结点,其中包含一个指向邻接点链表的指针。邻接点链表中的每个结点包含一个指向邻接点的指针和一个权值(如果有的话)。下面是一个示例代码:
c++
struct Node {
int val; // 结点的值
Node* next; // 指向邻接点链表的指针
};
vector<Node*> adjList; // 邻接表,使用vector来存储每个结点的指针
// 初始化邻接表
void initAdjList(int n) {
adjList.resize(n);
for (int i = 0; i < n; i++) {
adjList[i] = new Node();
adjList[i]->val = i;
adjList[i]->next = nullptr;
}
}
// 添加一条边
void addEdge(int u, int v) {
Node* node = new Node();
node->val = v;
node->next = adjList[u]->next;
adjList[u]->next = node;
}
深度优先遍历和广度优先遍历
深度优先遍历和广度优先遍历是图的基本算法,它们可以用递归或者栈来实现。下面是一个示例代码:
c++
// 深度优先遍历
void dfs(int u, vector<bool>& visited, vector<int>& res) {
visited[u] = true;
res.push_back(u);
for (Node* p = adjList[u]->next; p != nullptr; p = p->next) {
int v = p->val;
if (!visited[v]) {
dfs(v, visited, res);
}
}
}
// 广度优先遍历
void bfs(int u, vector<bool>& visited, vector<int>& res) {
queue<int> q;
q.push(u);
visited[u] = true;
while (!q.empty()) {
int u = q.front();
q.pop();
res.push_back(u);
for (Node* p = adjList[u]->next; p != nullptr; p = p->next) {
int v = p->val;
if (!visited[v]) {
visited[v] = true;
q.push(v);
}
}
}
}
生成树的边集
生成树是指一个连通图的一棵生成树包含了该图的所有结点,但是只包含了足以构成一棵树的边。生成树的边集可以通过深度优先遍历或者广度优先遍历来得到。具体来说,我们可以在遍历的过程中记录每个结点的父结点,然后将每个结点和它的父结点之间的边加入到生成树的边集中。下面是一个示例代码:
c++
// 深度优先遍历生成树
void dfsTree(int u, vector<bool>& visited, vector<int>& parent, vector<pair<int, int>>& treeEdges) {
visited[u] = true;
for (Node* p = adjList[u]->next; p != nullptr; p = p->next) {
int v = p->val;
if (!visited[v]) {
parent[v] = u;
treeEdges.push_back(make_pair(u, v));
dfsTree(v, visited, parent, treeEdges);
}
}
}
// 广度优先遍历生成树
void bfsTree(int u, vector<bool>& visited, vector<int>& parent, vector<pair<int, int>>& treeEdges) {
queue<int> q;
q.push(u);
visited[u] = true;
while (!q.empty()) {
int u = q.front();
q.pop();
for (Node* p = adjList[u]->next; p != nullptr; p = p->next) {
int v = p->val;
if (!visited[v]) {
visited[v] = true;
parent[v] = u;
treeEdges.push_back(make_pair(u, v));
q.push(v);
}
}
}
}
图形化界面的设计
图形化界面的设计可以使用Java或者C++的图形库来实现,比如Java可以使用Swing或者JavaFX,C++可以使用Qt或者MFC。具体来说,我们可以设计一个窗口,包含一个文本框用来输入结点,一个下拉框用来选择遍历算法,一个按钮用来开始演示,以及一个画布用来显示动画演示过程和结果。下面是一个示例代码:
java
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class GraphTraversalDemo extends JFrame implements ActionListener {
private JTextArea inputArea;
private JComboBox<String> algorithmComboBox;
private JButton startButton;
private JPanel canvasPanel;
public GraphTraversalDemo() {
setTitle("Graph Traversal Demo");
setSize(800, 600);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
inputArea = new JTextArea();
algorithmComboBox = new JComboBox<>(new String[]{"DFS", "BFS"});
startButton = new JButton("Start");
startButton.addActionListener(this);
canvasPanel = new JPanel();
JPanel controlPanel = new JPanel();
controlPanel.setLayout(new FlowLayout());
controlPanel.add(new JLabel("Input nodes:"));
controlPanel.add(inputArea);
controlPanel.add(new JLabel("Algorithm:"));
controlPanel.add(algorithmComboBox);
controlPanel.add(startButton);
getContentPane().setLayout(new BorderLayout());
getContentPane().add(controlPanel, BorderLayout.NORTH);
getContentPane().add(canvasPanel, BorderLayout.CENTER);
}
@Override
public void actionPerformed(ActionEvent e) {
if (e.getSource() == startButton) {
String input = inputArea.getText();
String algorithm = (String) algorithmComboBox.getSelectedItem();
// TODO: 根据输入和算法进行演示,并在画布上显示动画演示过程和结果
}
}
public static void main(String[] args) {
GraphTraversalDemo demo = new GraphTraversalDemo();
demo.setVisible(true);
}
}
动画演示效果的实现
动画演示效果可以使用Java或者C++的动画库来实现,比如Java可以使用JavaFX的动画类,C++可以使用Qt的动画类。具体来说,我们可以在遍历的过程中记录每个结点的状态(未访问、正在访问、已访问),然后根据状态来改变结点的颜色或者形状,以及边的颜色或者粗细。下面是一个示例代码:
java
import javafx.animation.*;
import javafx.application.Application;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Line;
import javafx.stage.Stage;
import javafx.util.Duration;
import java.util.ArrayList;
import java.util.List;
public class GraphTraversalAnimationDemo extends Application {
private List<Circle> circles;
private List<Line> lines;
@Override
public void start(Stage primaryStage) throws Exception {
Group root = new Group();
Scene scene = new Scene(root, 800, 600);
circles = new ArrayList<>();
lines = new ArrayList<>();
// 添加结点和边
addNode(100, 100);
addNode(200, 100);
addNode(200, 200);
addNode(100, 200);
addEdge(0, 1);
addEdge(1, 2);
addEdge(2, 3);
addEdge(3, 0);
addEdge(0, 2);
addEdge(1, 3);
// 深度优先遍历
List<Integer> dfsRes = new ArrayList<>();
boolean[] visited = new boolean[circles.size()];
dfs(0, visited, dfsRes);
// 广度优先遍历
List<Integer> bfsRes = new ArrayList<>();
visited = new boolean[circles.size()];
bfs(0, visited, bfsRes);
// 演示深度优先遍历
animateTraversal(dfsRes);
// 演示广度优先遍历
animateTraversal(bfsRes);
primaryStage.setScene(scene);
primaryStage.show();
}
private void addNode(double x, double y) {
Circle circle = new Circle(x, y, 20);
circle.setFill(Color.WHITE);
circle.setStroke(Color.BLACK);
circles.add(circle);
}
private void addEdge(int u, int v) {
Line line = new Line(circles.get(u).getCenterX(), circles.get(u).getCenterY(),
circles.get(v).getCenterX(), circles.get(v).getCenterY());
line.setStroke(Color.BLACK);
lines.add(line);
}
private void dfs(int u, boolean[] visited, List<Integer> res) {
visited[u] = true;
res.add(u);
circles.get(u).setFill(Color.LIGHTGREEN);
for (int v = 0; v < circles.size(); v++) {
if (!visited[v] && isConnected(u, v)) {
addEdge(u, v);
dfs(v, visited, res);
}
}
circles.get(u).setFill(Color.GREEN);
}
private void bfs(int u, boolean[] visited, List<Integer> res) {
visited[u] = true;
res.add(u);
circles.get(u).setFill(Color.LIGHTGREEN);
List<Integer> queue = new ArrayList<>();
queue.add(u);
while (!queue.isEmpty()) {
int u1 = queue.remove(0);
for (int v = 0; v < circles.size(); v++) {
if (!visited[v] && isConnected(u1, v)) {
visited[v] = true;
res.add(v);
circles.get(v).setFill(Color.LIGHTGREEN);
addEdge(u1, v);
queue.add(v);
}
}
circles.get(u1).setFill(Color.GREEN);
}
}
private boolean isConnected(int u, int v) {
for (Line line : lines) {
if ((line.getStartX() == circles.get(u).getCenterX() && line.getStartY() == circles.get(u).getCenterY()
&& line.getEndX() == circles.get(v).getCenterX() && line.getEndY() == circles.get(v).getCenterY())
|| (line.getStartX() == circles.get(v).getCenterX() && line.getStartY() == circles.get(v).getCenterY()
&& line.getEndX() == circles.get(u).getCenterX() && line.getEndY() == circles.get(u).getCenterY())) {
return true;
}
}
return false;
}
private void animateTraversal(List<Integer> res) {
Timeline timeline = new Timeline();
for (int i = 0; i < res.size(); i++) {
int u = res.get(i);
KeyFrame keyFrame = new KeyFrame(Duration.seconds(i), event -> {
circles.get(u).setFill(Color.RED);
});
timeline.getKeyFrames().add(keyFrame);
keyFrame = new KeyFrame(Duration.seconds(i + 0.5), event -> {
circles.get(u).setFill(Color.GREEN);
});
timeline.getKeyFrames().add(keyFrame);
}
timeline.play();
}
public static void main(String[] args) {
launch(args);
}
}
以上是一个简单的JavaFX程序,它可以演示深度优先遍历和广度优先遍历的动画效果。具体来说,我们在遍历的过程中使用JavaFX的动画类来改变每个结点的颜色,以及每条边的颜色或者粗细。在演示过程中,我们可以使用Timeline类来控制动画的播放,每个结点的颜色变化可以通过添加KeyFrame来实现。