数据结构第12节 有向图

news/2024/10/6 9:02:33/

有向图(Directed Graph 或 Digraph)是一种图数据结构,其中的边具有方向性。这意味着在一个有向图中,边可以被视为箭头,箭头从一个顶点指向另一个顶点,表示从起点到终点的单向关系。有向图的这种特性使得它在许多现实问题的建模中非常有用,例如网络流分析、计算机网络、基因调控网络、网页链接结构等。

有向图的基本概念:

  1. 顶点(Vertex):有向图中的基本单元,可以表示实体或状态。
  2. 边(Edge)或弧(Arc):连接两个顶点的有向线段,表示从一个顶点到另一个顶点的关系。边由一对顶点组成,通常写作 <u, v>(u, v),其中 u 是边的起点(尾部)而 v 是终点(头部)。
  3. 邻接:如果存在一条从顶点 u 指向顶点 v 的边,则称 vu 的邻接顶点,同时 uv 的前驱顶点。
  4. 出度(Out-degree):一个顶点的出度是所有以该顶点为起点的边的数量。
  5. 入度(In-degree):一个顶点的入度是所有以该顶点为终点的边的数量。
  6. 路径:在有向图中,从顶点 u 到顶点 v 的路径是由一系列顶点组成的序列 u, v1, v2, …, v,其中每对连续顶点之间都存在一条指向序列前进方向的边。
  7. 环(Cycle):如果存在一条路径从顶点 u 开始,经过一系列顶点后又回到顶点 u,则称有向图中存在环。
  8. 强连通图:如果一个有向图中任意两个顶点都可以相互到达,则称这个有向图为强连通的。
  9. 有向无环图(DAG):如果一个有向图中不存在环,则称这个有向图为有向无环图。

有向图的表示:

  1. 邻接矩阵:一个二维数组,其中 adj[u][v] 表示是否存在从顶点 u 到顶点 v 的边。对于有向图,邻接矩阵通常不是对称的。
  2. 邻接表:对于每个顶点,邻接表是一个链表或数组,其中包含所有与该顶点直接相连的顶点。邻接表对于稀疏图(边的数量远小于顶点数量的平方)更节省空间。

有向图的应用算法:

  1. 拓扑排序:在有向无环图(DAG)中,对顶点进行排序,使得对于每条边 (u, v),顶点 u 在排序中出现在顶点 v 之前。
  2. 最短路径算法:例如 Dijkstra 算法和 Bellman-Ford 算法,用于寻找图中两点间最短路径。
  3. 强连通分量算法:例如 Kosaraju’s 算法和 Tarjan’s 算法,用于识别有向图中的强连通分量。
  4. 流网络算法:例如 Ford-Fulkerson 算法,用于求解网络的最大流问题。

有向图在计算机科学和其他领域中有广泛的应用,掌握其基本概念和相关算法对于解决实际问题是至关重要的。

有向图在游戏开发中扮演着重要的角色,特别是在涉及路径寻找、任务规划、NPC行为、游戏地图生成等方面。有向图是由顶点(节点)和边组成的数据结构,其中边是有方向的,这意味着从一个顶点到另一个顶点的路径可能与反方向的路径不同。

在Java中,我们可以使用邻接表或者邻接矩阵的方式来表示有向图。下面我将使用邻接表的方式,结合一个简单的游戏场景来讲解有向图的应用。

游戏场景描述

想象一下,你正在开发一个角色扮演游戏,游戏中有一个由村庄、森林、山脉、城堡等组成的大陆。玩家可以从村庄出发,探索这个世界,完成各种任务。我们的目标是使用有向图来表示游戏世界中的各个地点以及它们之间的连接关系。

Java实现

java">import java.util.*;public class GameWorld {private Map<String, List<String>> graph;public GameWorld() {graph = new HashMap<>();}// 添加地点public void addLocation(String location) {graph.put(location, new ArrayList<>());}// 添加边,即两个地点之间的连接public void addConnection(String from, String to) {List<String> connections = graph.get(from);if (!connections.contains(to)) {connections.add(to);}}// 获取地点的所有可前往的地点public List<String> getConnections(String location) {return graph.get(location);}// 深度优先遍历示例,可以用于探索游戏世界public void dfs(String start) {Set<String> visited = new HashSet<>();dfsHelper(start, visited);}private void dfsHelper(String current, Set<String> visited) {System.out.println("Visited " + current);visited.add(current);for (String neighbor : graph.get(current)) {if (!visited.contains(neighbor)) {dfsHelper(neighbor, visited);}}}// 测试代码public static void main(String[] args) {GameWorld world = new GameWorld();world.addLocation("Village");world.addLocation("Forest");world.addLocation("Mountain");world.addLocation("Castle");world.addConnection("Village", "Forest");world.addConnection("Forest", "Mountain");world.addConnection("Mountain", "Castle");world.addConnection("Castle", "Village");world.dfs("Village");}
}

游戏应用实例

在这个游戏中,我们创建了一个有向图表示游戏世界。我们首先添加了四个地点:“Village”、“Forest”、“Mountain”和“Castle”。然后,我们添加了连接,表示从一个地点到另一个地点的路径。最后,我们使用深度优先搜索算法来遍历整个游戏世界,模拟玩家的探索过程。

通过这种方式,我们可以在游戏中实现复杂的路径寻找和任务规划。例如,我们可以使用图算法来确定从一个地点到另一个地点的最短路径,或者规划一系列任务的最优顺序。有向图为我们提供了一种强大的工具,可以用来表示和处理游戏世界中的复杂关系。

为了进一步扩展游戏并深入利用有向图的概念,我们可以添加更多的功能,比如:

  1. 双向边:允许玩家从一个地点往返另一个地点。
  2. 边的权重:代表移动到下一个地点所需的时间或消耗的能量。
  3. 动态变化的图:游戏世界中的某些路径可能因为天气、时间或其他因素而暂时不可用。
  4. 任务系统:基于图的连通性,设计任务流程和奖励机制。

接下来,我们将通过更新之前的代码来实现这些功能。

java">import java.util.*;public class GameWorld {private Map<String, List<Edge>> graph;public GameWorld() {graph = new HashMap<>();}public void addLocation(String location) {graph.put(location, new ArrayList<>());}public void addConnection(String from, String to, int weight) {Edge edge = new Edge(to, weight);List<Edge> connections = graph.get(from);if (!connections.contains(edge)) {connections.add(edge);}}public List<Edge> getConnections(String location) {return graph.get(location);}public void dfs(String start) {Set<String> visited = new HashSet<>();dfsHelper(start, visited);}private void dfsHelper(String current, Set<String> visited) {System.out.println("Visited " + current);visited.add(current);for (Edge edge : graph.get(current)) {if (!visited.contains(edge.to)) {dfsHelper(edge.to, visited);}}}// 新增方法:获取到达目的地的总权重public int getTotalWeight(String start, String end) {return getTotalWeightHelper(start, end, 0, new HashSet<>());}private int getTotalWeightHelper(String current, String end, int weight, Set<String> visited) {if (current.equals(end)) {return weight;}visited.add(current);int minWeight = Integer.MAX_VALUE;for (Edge edge : graph.get(current)) {if (!visited.contains(edge.to)) {int newWeight = getTotalWeightHelper(edge.to, end, weight + edge.weight, visited);if (newWeight != Integer.MAX_VALUE) {minWeight = Math.min(minWeight, newWeight);}}}visited.remove(current);return minWeight;}// 边类private static class Edge {String to;int weight;Edge(String to, int weight) {this.to = to;this.weight = weight;}@Overridepublic boolean equals(Object o) {if (this == o) return true;if (o == null || getClass() != o.getClass()) return false;Edge edge = (Edge) o;return weight == edge.weight && Objects.equals(to, edge.to);}@Overridepublic int hashCode() {return Objects.hash(to, weight);}}// 测试代码public static void main(String[] args) {GameWorld world = new GameWorld();world.addLocation("Village");world.addLocation("Forest");world.addLocation("Mountain");world.addLocation("Castle");world.addLocation("Lake");world.addConnection("Village", "Forest", 2);world.addConnection("Forest", "Mountain", 3);world.addConnection("Mountain", "Castle", 4);world.addConnection("Castle", "Village", 5);world.addConnection("Village", "Lake", 1);world.dfs("Village");System.out.println("Total Weight from Village to Castle: " + world.getTotalWeight("Village", "Castle"));}
}

扩展说明

在这个扩展版本中,我们增加了Edge类来表示带权重的边,这允许我们计算从一个地点到另一个地点的总消耗。我们还添加了一个getTotalWeight方法,它使用递归深度优先搜索来找到从起点到终点的最短路径,并返回这条路径上的总权重。

通过这种方式,我们可以为游戏增加更多真实感和策略性,例如,玩家需要权衡时间成本和资源消耗,选择最佳的旅行路线。此外,我们还可以在此基础上添加更多复杂的功能,如动态障碍物、动态边权重(例如,随天气变化的路径难度),以及更高级的任务和奖励系统。


这种设计不仅增强了游戏的可玩性和沉浸感,同时也展示了有向图在游戏开发中的强大应用潜力。通过不断迭代和改进,我们能够构建出更加丰富和多变的游戏世界。

为了进一步扩展这个游戏,我们可以考虑引入更复杂的元素,如角色状态、动态事件、NPC交互和物品系统。以下是针对这些新增功能的代码实现:

java">import java.util.*;public class GameWorld {private Map<String, List<Edge>> graph;private Map<String, Location> locations;private Player player;public GameWorld() {graph = new HashMap<>();locations = new HashMap<>();player = new Player();}public void addLocation(String name, String description) {Location location = new Location(name, description);locations.put(name, location);graph.put(name, new ArrayList<>());}public void addConnection(String from, String to, int distance) {Edge edge = new Edge(to, distance);List<Edge> connections = graph.get(from);if (!connections.contains(edge)) {connections.add(edge);}}public List<Edge> getConnections(String location) {return graph.get(location);}public void movePlayer(String destination) {player.setCurrentLocation(destination);}public Location getPlayerLocation() {return player.getCurrentLocation();}public void interactWithNPC(String npcName) {Location currentLocation = player.getCurrentLocation();NPC npc = currentLocation.getNPC(npcName);if (npc != null) {npc.interact(player);} else {System.out.println("No such NPC in the current location.");}}public void addItemToInventory(String itemName) {Item item = new Item(itemName);player.addItemToInventory(item);}public void removeItemFromInventory(String itemName) {player.removeItemFromInventory(itemName);}public void printInventory() {player.printInventory();}public static void main(String[] args) {GameWorld world = new GameWorld();world.addLocation("Village", "A small village surrounded by dense forests.");world.addLocation("Forest", "A dark and mysterious forest with hidden paths.");world.addLocation("Mountain", "A steep mountain with breathtaking views.");world.addLocation("Castle", "An ancient castle with a rich history.");world.addConnection("Village", "Forest", 2);world.addConnection("Forest", "Mountain", 3);world.addConnection("Mountain", "Castle", 4);world.addConnection("Castle", "Village", 5);world.player.setCurrentLocation("Village");world.addItemToInventory("Map");world.addItemToInventory("Compass");world.movePlayer("Forest");world.interactWithNPC("Old Hermit");world.movePlayer("Mountain");world.interactWithNPC("Mountain Guide");world.movePlayer("Castle");world.interactWithNPC("King");world.printInventory();}
}class Player {private Location currentLocation;private List<Item> inventory;public Player() {inventory = new ArrayList<>();}public void setCurrentLocation(String locationName) {currentLocation = GameWorld.this.locations.get(locationName);}public Location getCurrentLocation() {return currentLocation;}public void addItemToInventory(Item item) {inventory.add(item);}public void removeItemFromInventory(String itemName) {inventory.removeIf(item -> item.getName().equals(itemName));}public void printInventory() {System.out.println("Inventory:");for (Item item : inventory) {System.out.println("- " + item.getName());}}
}class Location {private String name;private String description;private List<NPC> npcs;public Location(String name, String description) {this.name = name;this.description = description;npcs = new ArrayList<>();}public void addNPC(NPC npc) {npcs.add(npc);}public NPC getNPC(String name) {for (NPC npc : npcs) {if (npc.getName().equals(name)) {return npc;}}return null;}
}class NPC {private String name;private String greeting;public NPC(String name, String greeting) {this.name = name;this.greeting = greeting;}public void interact(Player player) {System.out.println(greeting);// Additional interaction logic can be added here.}public String getName() {return name;}
}class Item {private String name;public Item(String name) {this.name = name;}public String getName() {return name;}
}class Edge {private String to;private int distance;public Edge(String to, int distance) {this.to = to;this.distance = distance;}public String getTo() {return to;}public int getDistance() {return distance;}
}

扩展功能说明:

  1. 角色状态:我们引入了Player类,用于管理玩家的状态,如当前位置和背包物品。
  2. 动态事件:虽然在这个简单示例中没有具体实现,但我们可以通过在NPC类中添加更多交互逻辑,以及在Location类中添加随机事件来实现动态事件。
  3. NPC交互:我们添加了NPC类,玩家可以与之互动。目前的交互仅限于打印问候语,但可以进一步扩展,如交易、接受任务等。
  4. 物品系统Item类用于表示玩家可以收集的物品。Player类中的inventory属性用于存储这些物品。

通过这些扩展,游戏变得更加丰富和有趣。玩家可以在游戏世界中探索、与NPC互动、收集物品,并根据自己的选择影响游戏进程。这不仅提高了游戏的可玩性,也为开发者提供了更多创造性的空间,以构建更加复杂和引人入胜的游戏体验。

为了进一步扩展游戏,我们可以增加一些更复杂的功能,如任务系统、战斗机制、技能树和角色成长。以下是在现有游戏框架基础上的扩展代码:

java">import java.util.*;public class GameWorld {private Map<String, List<Edge>> graph;private Map<String, Location> locations;private Player player;public GameWorld() {graph = new HashMap<>();locations = new HashMap<>();player = new Player();}// ... 保留之前的方法 ...public void acceptQuest(Quest quest) {player.acceptQuest(quest);}public void completeQuest(Quest quest) {player.completeQuest(quest);}public void attackMonster(Monster monster) {player.attack(monster);}public void levelUp() {player.levelUp();}// ... 其他方法 ...public static void main(String[] args) {GameWorld world = new GameWorld();// ... 地图初始化代码 ...// 创建NPC并分配任务NPC oldHermit = new NPC("Old Hermit", "Greetings, young traveler!");Quest hermitQuest = new Quest("FindHerbs", "Collect 5 herbs", 10);oldHermit.setQuest(hermitQuest);world.locations.get("Forest").addNPC(oldHermit);// 创建怪物Monster wolf = new Monster("Wolf", 5, 2);world.locations.get("Forest").addMonster(wolf);// 主循环while (true) {Location currentLocation = world.getPlayerLocation();System.out.println("You are in " + currentLocation.getName() + ".");System.out.println(currentLocation.getDescription());// 处理NPC交互for (NPC npc : currentLocation.getNPCs()) {System.out.println("NPC: " + npc.getName());world.interactWithNPC(npc.getName());}// 处理怪物战斗for (Monster monster : currentLocation.getMonsters()) {System.out.println("Encountered a " + monster.getName() + "!");world.attackMonster(monster);}// 更新玩家状态world.player.update();// 休息和恢复System.out.println("Resting...");player.rest();// 接受和完成任务for (Quest quest : player.getQuests()) {if (quest.isCompleted()) {System.out.println("Quest completed: " + quest.getName());world.completeQuest(quest);}}// 级别提升if (player.canLevelUp()) {world.levelUp();}// 移动到下一个地点List<Edge> connections = world.getConnections(currentLocation.getName());if (!connections.isEmpty()) {Edge nextEdge = connections.get(new Random().nextInt(connections.size()));world.movePlayer(nextEdge.getTo());}}}
}class Player {private Location currentLocation;private List<Item> inventory;private List<Quest> quests;private int level;private int experience;private int health;private int maxHealth;public Player() {inventory = new ArrayList<>();quests = new ArrayList<>();level = 1;experience = 0;health = maxHealth = 100;}// ... 保留之前的方法 ...public void acceptQuest(Quest quest) {quests.add(quest);}public void completeQuest(Quest quest) {quests.remove(quest);experience += quest.getExperienceReward();}public void attack(Monster monster) {// 攻击逻辑int damage = 10; // 假定固定伤害monster.takeDamage(damage);}public void levelUp() {level++;maxHealth += 10;health = maxHealth;}public boolean canLevelUp() {return experience >= level * 100;}public void rest() {health = maxHealth;}public void update() {// 更新玩家状态}// ... 其他方法 ...
}// ... 其他类 ...class Quest {private String name;private String description;private int experienceReward;public Quest(String name, String description, int experienceReward) {this.name = name;this.description = description;this.experienceReward = experienceReward;}public boolean isCompleted() {// 判断任务是否完成return false;}
}class Monster {private String name;private int health;private int damage;public Monster(String name, int health, int damage) {this.name = name;this.health = health;this.damage = damage;}public void takeDamage(int damage) {this.health -= damage;}
}// ... 其他类 ...

新增功能说明:

  1. 任务系统:我们引入了Quest类,NPC可以分配任务给玩家。在主循环中,玩家可以接受任务,并在完成后获得经验值奖励。
  2. 战斗机制Monster类表示游戏中的敌人,玩家可以攻击怪物。这里简化了战斗逻辑,但可以进一步扩展,如加入回合制战斗、技能释放等。
  3. 角色成长Player类中包含了级别、经验值、健康等属性,支持角色成长和升级。
  4. 主循环:游戏现在有了一个基本的主循环,处理玩家在不同地点之间的移动、与NPC的交互、战斗、任务完成和角色状态更新。

通过这些扩展,游戏变得更加复杂和吸引人。玩家可以享受探索、战斗、任务完成和角色成长的乐趣。当然,这只是一个基础框架,可以根据需要进一步完善和扩展,例如,增加更多的物品、技能、NPC对话、剧情分支等,以构建一个更加丰富多彩的游戏世界。


http://www.ppmy.cn/news/1473828.html

相关文章

Cookie的默认存储路径以及后端如何设置

问题场景 最近在写一个前后端分离的项目&#xff0c;需要跨域&#xff0c;前端开发同学遇到一个问题一直报错&#xff0c;本质上就是后端返回的cookie中的sessionID在前端发送http请求时无法被请求自动携带&#xff0c;每次htttpRequest都被后端识别为一个新的session&#xf…

go语言并发编程1-Gouroutine

参考文档&#xff1a;www.topgoer.com 使用方法 直接包装成函数&#xff0c;go关键字触发即可 注意事项 1 main方法结束后&#xff0c;main方法内启动的子协程会立即结束&#xff0c;无论是否执行完毕&#xff1b; 启动多个groutine 使用sync包的WaitGroup来控制&#xf…

解决Visual Studio 一直弹出管理员身份运行问题(win10/11解决办法)

不知道大家是否有遇到这个问题 解决办法也很简单 找到启动文件 如果是快捷方式就继续打开文件位置 找到这个程序启动项 右键 选择 兼容性疑难解答&#xff08;win11 则需要 按住 shift 右键&#xff09; win10 解决办法 这样操作完后就可以了 win11解决办法按以下选择就行

Perl的上下文之谜:深入理解上下文概念

&#x1f577;️ Perl的上下文之谜&#xff1a;深入理解上下文概念 Perl&#xff0c;这门被誉为“只需一条命令就能完成任务”的编程语言&#xff0c;以其强大的文本处理能力而闻名。在Perl中&#xff0c;上下文是一个核心概念&#xff0c;它决定了变量的解释方式以及操作符的…

15集终于编译成功了-了个球!编译TFLite Micro语音识别工程-《MCU嵌入式AI开发笔记》

15集终于编译成功了-个球&#xff01;编译TFLite Micro语音识别工程-《MCU嵌入式AI开发笔记》 还是参考这个官方文档&#xff1a; https://codelabs.developers.google.cn/codelabs/sparkfun-tensorflow#2 全是干货&#xff01; 这里面提到的这个Micro工程已经移开了&#xff1…

如何在Spring Boot中使用Quartz调度任务

如何在Spring Boot中使用Quartz调度任务 大家好&#xff0c;我是免费搭建查券返利机器人省钱赚佣金就用微赚淘客系统3.0的小编&#xff0c;也是冬天不穿秋裤&#xff0c;天冷也要风度的程序猿&#xff01;今天我们将深入探讨如何在Spring Boot应用程序中集成和使用Quartz调度任…

《算法笔记》总结No.3——排序

基础算法之一&#xff0c;相当重要。在普通的机试中如果没有数据类型和时空限制&#xff0c;基本上选择自己最熟悉的就好。本篇只总结选择排序和插入排序&#xff0c;侧重应用&#xff0c;408中要求的种类更加繁多&#xff0c;此处先不扩展难度~总结最常用的两种排序。 一.选择…

从测评到整改:一套完整的等保工作流程与实施路径

随着信息技术的飞速发展&#xff0c;网络安全问题日益凸显&#xff0c;信息安全等级保护&#xff08;简称“等保”&#xff09;成为保障信息系统安全的重要手段。等保工作通过系统定级、备案、安全建设/整改、等级测评及监督检查等五个阶段&#xff0c;旨在建立“可信、可控、可…