认识数据结构:图——无人机防空平台的“衍生品”

引言

想象一下这样的场景:一架无人机正在执行侦查任务,它需要扫描周围环境,记录各个入侵者的位置,并计算这些点之间的距离关系,便于攻击等。这正是我们项目要解决的问题,而图数据结构就是解决这类问题的完美选择。


什么是图?

1.图的基本概念

图是由顶点(Vertex) 和**边(Edge)**组成的非线性数据结构。在我们的雷达扫描项目中:

  • 顶点:每个雷达扫描到的目标点(如X, A, B, C等)

  • :两个目标点之间的连接,带有距离(权重)信息

数学上,一个图G可以表示为:G = (V, E),其中:

  • V 是顶点的集合

  • E 是边的集合

2.图的类型

在我们的项目中,我们构建的是无向加权图

  • 无向:边没有方向,A到B的距离等于B到A的距离

  • 加权:每条边都有一个权重值(距离)

复制代码
我们的图结构示意图:

如何构建图?

1.基础数据结构设计

首先,我们需要定义图的基本组成元素。在Graph类中,我们设计了两个核心类:

Edge类:表示图的边

复制代码
class Edge {
    String name;    // 目标节点的名称
    int distance;   // 到目标节点的距离(权重)

    public Edge(String name, int distance) {
        this.name = name;
        this.distance = distance;
    }
}
  • 每个边记录目标节点名称距离

  • 适合表示雷达扫描中"从当前点到目标点"的关系

MPoint类:表示图的顶点

复制代码
class MPoint {
    String name;    // 节点的名称
    int x, y;       // 节点的坐标位置

    public MPoint(String name, int x, int y) {
        this.name = name;
        this.x = x;
        this.y = y;
    }
}
  • 除了节点名称,还存储了坐标位置

  • 这反映了雷达扫描的实际需求:知道目标点的具体位置

  • 坐标信息可以用于计算两点之间的直线距离

2.图的核心数据结构

Graph类中,我们使用邻接表来表示图:

复制代码
public class Graph {
    Map<String, List<Edge>> graph = new HashMap<>();
    
    // 其他方法...
}

2.1 而什么是邻接表呢?

邻接表是图的一种链式存储结构,它通过为图中的每个顶点建立一个链表,来存储该顶点的所有邻接点(相邻顶点)。

在我们的代码中,邻接表通过以下方式实现:

复制代码
Map<String, List<Edge>> graph = new HashMap<>();

数据结构解析

  • 键(Key):顶点的名称(String类型)

  • 值(Value):该顶点的邻接边列表(List<Edge>类型)

  • Edge对象:存储目标顶点名称和距离权重

2.2 实际存储实例

假设我们有如下雷达扫描图:

  • 顶点:X, A, B, C

  • 边:X-A(150), X-B(200), X-C(180), A-B(100), A-C(120)

在内存中,邻接表的存储结构如下:

复制代码
graph = {
    "X": [Edge("A",150), Edge("B",200), Edge("C",180)],
    "A": [Edge("X",150), Edge("B",100), Edge("C",120)],
    "B": [Edge("X",200), Edge("A",100)],
    "C": [Edge("X",180), Edge("A",120)]
}

核心方法分析

1.add方法:添加顶点

复制代码
public void add(String name) {
    List<Edge> edgeList = new ArrayList<>();
    graph.put(name, edgeList);
}

功能:在图中添加一个新的顶点

  • 创建顶点的邻接边列表

  • 为后续添加边做准备

  • 确保每个顶点都有对应的数据结构

2.scan方法:添加边

复制代码
public void scan(String name, String to, int distance) {
    // 1. 检查两个顶点是否存在,不存在则创建
    if (!graph.containsKey(name)) {
        add(name);
    }
    if (!graph.containsKey(to)) {
        add(to);
    }
    
    // 2. 创建从name到to的边
    Edge edge = new Edge(to, distance);
    List<Edge> edgeList = graph.get(name);
    
    // 3. 检查边是否已存在(避免重复)
    for (int i = 0; i < edgeList.size(); i++) {
        Edge e1 = edgeList.get(i);
        if(e1.name.equals(edge.name)) {
            return;  // 边已存在,直接返回
        }
    }
    edgeList.add(edge);
    
    // 4. 创建反向边(无向图)
    Edge edge1 = new Edge(name, distance);
    List<Edge> edgeList1 = graph.get(to);
    
    // 5. 同样检查反向边是否已存在
    for (int i = 0; i < edgeList1.size(); i++) {
        Edge e1 = edgeList1.get(i);
        if(e1.name.equals(edge1.name)) {
            return;
        }
    }
    edgeList1.add(edge1);
}
  • 自动顶点创建:如果顶点不存在,自动创建。这简化了调用逻辑。

  • 重复边检查:避免图中出现重复的边,保证数据的一致性。

  • 双向边添加:因为我们构建的是无向图,所以需要添加两个方向的边。

3.showInfo方法:显示图信息

复制代码
public void showInfo() {
    Set<String> keySet = graph.keySet();
    System.out.println("=== 雷达扫描图 ===");
    for (String key : keySet) {
        List<Edge> edges = graph.get(key);
        System.out.println("顶点 -> " + key);
        
        // 根据距离排序(冒泡排序)
        for (int i = 0; i < edges.size(); i++) {
            for (int j = 0; j < edges.size()-1; j++) {
                Edge e1 = edges.get(j);
                Edge e2 = edges.get(j+1);
                if(e1.distance > e2.distance) {
                    edges.set(j, e2);
                    edges.set(j+1, e1);
                }
            }
        }
        
        // 打印排序后的边
        for (Edge edge : edges) {
            System.out.print(edge.name + "(" + edge.distance + ") ");
        }
        System.out.println("---------");
    }
}
  • 信息展示:清晰地展示图的完整结构

  • 距离排序:使用冒泡排序将边按距离从小到大排列

  • 可读性:格式化输出,便于调试和理解图结构


交互系统设计

1.GraphRobot类:用户界面

复制代码
public class GraphRobot {
    public void showUI() {
        JFrame jf = new JFrame();
        jf.setTitle("图算法应用");
        jf.setSize(800, 1000);
        // ... 界面初始化代码
        
        // 创建起点标记
        g.fillRect(200, 700, 100, 100);
    }
}

2.RobotListener类:监听器

复制代码
public class RobotListener extends MouseAdapter implements MouseListener {
    Graphics g;
    final int x = 200;
    final int y = 700;
    char name = 'A';
    
    Graph scanGraph = new Graph();
    ArrayList<MPoint> pointList = new ArrayList<>();
    
    public RobotListener() {
        scanGraph.add("X");
        pointList.add(new MPoint("X", 200, 700));
    }
    
    @Override
    public void mousePressed(MouseEvent e) {
        int x1 = e.getX();
        int y1 = e.getY();
        
        // 绘制新点
        g.setColor(Color.GREEN);
        g.fillOval(x1, y1, 50, 50);
        
        // 计算到起点的距离
        int distance = (int) Math.sqrt(Math.pow(x1-x, 2) + Math.pow(y1-y, 2));
        scanGraph.scan("X", name + "", distance);
        
        // 计算与其他所有点的距离
        for (int i = 0; i < pointList.size(); i++) {
            MPoint p = pointList.get(i);
            int distance1 = (int) Math.sqrt(Math.pow(x1 - p.x, 2) + Math.pow(y1 - p.y, 2));
            scanGraph.scan(p.name, name + "", distance1);
        }
        
        // 记录新点
        MPoint p1 = new MPoint(name + "", x1, y1);
        pointList.add(p1);
        name++;
        
        // 显示图信息
        scanGraph.showInfo();
    }
}

交互系统展示

(可以看到这样的交互性和观感太差了,距离啥的只能通过输出框展现,数据冗杂且阅读性差)

因此我们进行以下两个方面的优化

  • 1.在UI界面右侧新增距离显示功能,将距离算法的结果用滑杆的方式直观表达。
  • 2.将距离黑色方块最短距离的圆的颜色由绿变红,直观迅速模拟雷达扫描。

项目优化

那么根据以上优化思路:

1.第一步:修改 GraphRobot类中的主界面(新增滑杆组件)

复制代码
 // 创建距离显示面板(滑杆+标签)
        JPanel sliderPanel = new JPanel();
        sliderPanel.setLayout(new BoxLayout(sliderPanel, BoxLayout.Y_AXIS));
        JLabel distanceLabel = new JLabel("当前距离:0");
        //设置字体大小
        distanceLabel.setFont(new Font("宋体", Font.PLAIN, 20));
        // 滑杆范围0-1000(适配窗口尺寸800x1000),初始值0
        JSlider distanceSlider = new JSlider(JSlider.VERTICAL, 0, 800, 0);
        distanceSlider.setMajorTickSpacing(100); // 主刻度间隔
        distanceSlider.setMinorTickSpacing(50);  // 副刻度间隔
        distanceSlider.setPaintTicks(true);      // 显示刻度
        distanceSlider.setPaintLabels(true);     // 显示刻度值
        sliderPanel.add(distanceLabel);
        sliderPanel.add(distanceSlider);
        // 将滑杆面板添加到窗口右侧
        jf.add(sliderPanel, BorderLayout.EAST);
  • GraphRobot中新增滑杆(JSlider)和标签组件,布局改为BorderLayout将滑杆放在右侧;
  • 同时将滑杆和标签传给RobotListener,在鼠标点击时更新滑杆值和标签文本,直观显示当前点击点到初始点 X 的距离。

2.第二步:修改 RobotListener核心逻辑(让最短距离圆变红)

复制代码
//更新滑杆和距离标签
        distanceSlider.setValue(distance);
        distanceLabel.setText("距离:"+distance);

//其他方法......//

//重绘所有节点,最短距离变红
        repaintPoints();
    }

    private void repaintPoints() {
    // 1. 找到最短距离的节点(相对于初始点X)
        MPoint shortestPoint = findShortestDistancePoint();

        // 2. 重新绘制所有节点
        for (MPoint p : pointList) {
            if (p.equals(shortestPoint)) {
                // 最短距离节点:红色
                g.setColor(Color.RED);
            } else {
                // 其他节点:绿色
                g.setColor(Color.GREEN);
            }
            // 绘制圆形节点(尺寸50x50,和原有逻辑一致)
            g.fillOval(p.x, p.y, 50, 50);
        }
        // 重新绘制初始矩形(避免被覆盖)
        g.setColor(Color.BLACK);
        g.fillRect(200,700,100,100);
    }

    // 查找距离初始点X最短的节点
    private MPoint findShortestDistancePoint() {
        MPoint shortestPoint = pointList.get(0); // 初始化为X节点
        int minDistance = Integer.MAX_VALUE;

        for (MPoint p : pointList) {
            // 跳过X节点本身
            if (p.name.equals("X")) {
                continue;
            }
            // 计算当前节点到X的距离
            int distance = (int) Math.sqrt(Math.pow(p.x - x, 2) + Math.pow(p.y - y, 2));
            if (distance < minDistance) {
                minDistance = distance;
                shortestPoint = p;
            }
        }
        return shortestPoint;
    }
}
  • 新增findShortestDistancePoint()方法,遍历所有节点找到距离初始点 X 最短的节点(原有逻辑)
  • 新增repaintPoints()方法,重新绘制所有节点,最短距离节点用红色,其余保持绿色;
  • 在鼠标点击后调用repaintPoints(),确保每次新增节点后立即更新颜色。

3.保留原有逻辑的关键点

  • 未修改Graph类的核心图结构和算法;
  • 未改变原有鼠标点击绘制绿色圆的基础逻辑,仅新增了最短距离节点的颜色区分;
  • 滑杆组件为新增,未破坏原有窗口布局的核心逻辑。

项目优化后展示


结语

图数据结构是计算机科学中最美、最强大的数据结构之一。图结构就像一张智慧的网,连接着数据与现实世界,帮助我们洞察复杂的关系和模式。通过这个雷达扫描项目,我不仅学会了如何实现图,更重要的是理解了好的数据结构不是目的,而是解决问题的手段。当我们面对复杂的关系网络时,图数据结构就是我们最有力的工具之一。

相关推荐
huidu012 小时前
基于AQS实现的ReentrantLock
java
波波0072 小时前
Native AOT 能改变什么?.NET 预编译技术深度剖析
开发语言·.net
wkm9562 小时前
在arm64 ubuntu系统安装Qt后编译时找不到Qt3DExtras头文件
开发语言·arm开发·qt
冰敷逆向2 小时前
京东h5st纯算分析
java·前端·javascript·爬虫·安全·web
晚风吹长发2 小时前
初步了解Linux中的线程同步问题及线程安全和死锁与生产消费者模型
linux·运维·服务器·开发语言·数据结构·安全
一只专注api接口开发的技术猿2 小时前
淘宝商品详情API的流量控制与熔断机制:保障系统稳定性的后端设计
大数据·数据结构·数据库·架构·node.js
学嵌入式的小杨同学2 小时前
【Linux 封神之路】进程进阶实战:fork/vfork/exec 函数族 + 作业实现(含僵尸进程解决方案)
linux·开发语言·vscode·嵌入式硬件·vim·软件工程·ux
fengfuyao9852 小时前
基于MATLAB/Simulink的车辆自适应巡航控制(ACC)实现
开发语言·matlab