实验九 图及其应用
实验目的
理解描述图中的术语:顶点、边、简单图、加权/非加权图以及有向/无向图。
使用线性表、边数组、边对象、邻接矩阵和邻接线性表来表示顶点和边。
使用Graph接口和UnweightedGraph类来对图进行建模。
使用UnweightedGraph.SearchTree类来表示对图的遍历。
设计并且实现深度优先搜索。
设计并实现广度优先搜索。
使用广度优先搜索解决9枚硬币反面的问题。
实验内容
9枚硬币的反面问题:将9枚硬币放在一个3*3矩阵中,其中一些正面朝上(H),另一些正面朝下(T)。一个合法的移动是指翻转任何一个正面朝上的硬币以及与它相邻的硬币(不包括对角线相邻的)。任务就是找到最少次数的移动,使得所有的硬币正面朝下。例子如下,当翻动图1最后一行的第二个硬币之后,9枚硬币如图2;当翻动图2第一行的第二个硬币之后,9枚硬币都将正面朝下如图3。
H | H | H |
T | T | T |
H | H | H |
H | H | H |
T | H | T |
T | T | T |
T | T | T |
T | T | T |
T | T | T |
该问题的本质可以简化为无权图中寻找最短路径的问题。根据实验九给出的代码,实现相应方法,编写一个程序,提示用户输入9枚硬币的一个初始状态,然后显示解决方法,如下面的运行示例所示:
代码的具体要求如下:
- Edge类定义了图的边。
- Gragh接口定义了图的常用操作。比如得到图中顶点的个数,得到图中所有的顶点,得到指定下表的顶点对象等等,具体可见代码。
- UnweightedGraph类定义无权重的图,实现Graph接口并实现了其中的方法。并给出了查找深度优先搜索树和广度优先搜索树的方法。其中定义了内部类SearchTree为搜索树。
- NineTailModel类使用无权重的图实现解决实验问题的相关方法。
- NineTail类只含有main方法,输入硬币初始状态,提供解决方法。
实验代码框架
代码框架来源于参考书《Java语言程序设计与数据结构(进阶篇)》,其中学生需要实现的是NineTailModel、NineTail两个类。
Edge类
public class Edge {
int u;
int v;
public Edge(int u,int v){
this.u=u;
this.v=v;
}
public boolean equals(Object o){
return u==((Edge)o).u&&v==((Edge)o).v;
}
}
Graph接口
import java.util.List;
public interface Gragh<V> {
public int getSize();
public List<V> getVertices();
public V getVertex(int index);
public int getIndex(V v);
public List<Integer> getNeighbors(int index);
public int getDegree(int v);
public void printEdges();
public void clear();
public boolean addVertex(V vertex);
public boolean addEdge(int u,int v);
public boolean addEdge(Edge e);
public boolean remove(V v);
public boolean remove(int u,int v);
public UnweightedGraph<V>.SearchTree dfs(int v);
public UnweightedGraph<V>.SearchTree bfs(int v);
}
UnweightedGraph类
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
public class UnweightedGraph<V> implements Gragh<V> {
protected List<V> vertices=new ArrayList<>();
protected List<List<Edge>> neighbors=new ArrayList<>();
protected UnweightedGraph() {
}
protected UnweightedGraph(V[] vertices,int [][] edges){
for (int i=0;i<vertices.length;i++){
addVertex(vertices[i]);
}
createAdjacencyLists(edges,vertices.length);
}
protected UnweightedGraph(List<V> vertices,List<Edge> edges){
for (int i=0;i<vertices.size();i++){
addVertex(vertices.get(i));
}
createAdjacencyLists(edges,vertices.size());
}
protected UnweightedGraph(List<Edge> edges,int numberOfVertices){
for (int i=0;i<numberOfVertices;i++){
addVertex((V)(new Integer(i)));
}
createAdjacencyLists(edges,numberOfVertices);
}
protected UnweightedGraph(int[][] edges,int numberOfVertices){
for (int i=0;i<numberOfVertices;i++){
addVertex((V)(new Integer(i)));
}
createAdjacencyLists(edges,numberOfVertices);
}
private void createAdjacencyLists(int[][] edges,int numberOfVertices){
for (int i=0;i<edges.length;i++){
addEdge(edges[i][0],edges[i][1]);
}
}
private void createAdjacencyLists(List<Edge> edges,int numberOfVertices){
for (Edge edge:edges){
addEdge(edge.u,edge.v);
}
}
@Override
public int getSize() {
return vertices.size();
}
@Override
public List<V> getVertices() {
return vertices;
}
@Override
public V getVertex(int index) {
return vertices.get(index);
}
@Override
public int getIndex(V v) {
return vertices.indexOf(v);
}
@Override
public List<Integer> getNeighbors(int index) {
List<Integer> result=new ArrayList<>();
for (Edge e:neighbors.get(index))
result.add(e.v);
return result;
}
@Override
public int getDegree(int v) {
return neighbors.get(v).size();
}
@Override
public void printEdges() {
for (int u=0;u<neighbors.size();u++){
System.out.print(getVertex(u)+" ("+u+"): ");
for (Edge e:neighbors.get(u)){
System.out.print("("+getVertex(e.u)+", "+getVertex(e.v)+") ");
}
System.out.println();
}
}
@Override
public void clear() {
vertices.clear();
neighbors.clear();
}
@Override
public boolean addVertex(V vertex) {
if(!vertices.contains(vertex)){
vertices.add(vertex);
neighbors.add(new ArrayList<Edge>());
return true;
}else {
return false;
}
}
@Override
public boolean addEdge(int u, int v) {
return addEdge(new Edge(u,v));
}
@Override
public boolean addEdge(Edge e) {
if(e.u<0||e.u>getSize()-1){
throw new IllegalArgumentException("No such index:"+e.u);
}
if(e.v<0||e.v>getSize()-1){
throw new IllegalArgumentException("No such index:"+e.v);
}
if(!neighbors.get(e.u).contains(e)){
neighbors.get(e.u).add(e);
return true;
}else {
return false;
}
}
@Override
public SearchTree dfs(int v) {
List<Integer> searchOrder=new ArrayList<>();
int[] parent=new int[vertices.size()];
for (int i=0;i<parent.length;i++)
parent[i]=-1;
boolean[] isVisited=new boolean[vertices.size()];
dfs(v,parent,searchOrder,isVisited);
return new SearchTree(v,parent,searchOrder);
}
private void dfs(int v,int[] parent,List<Integer> searchOrder,boolean[] isVisited){
searchOrder.add(v);
isVisited[v]=true;
for (Edge e:neighbors.get(v)){
if(!isVisited[e.v]){
parent[e.v]=v;
dfs(e.v,parent,searchOrder,isVisited);
}
}
}
@Override
public SearchTree bfs(int v) {
List<Integer> searchOrder=new ArrayList<>();
int[] parent=new int[vertices.size()];
for (int i=0;i<parent.length;i++)
parent[i]=-1;
LinkedList<Integer> queue=new LinkedList<>();
boolean[] isVisited=new boolean[vertices.size()];
queue.offer(v);
isVisited[v]=true;
while (!queue.isEmpty()){
int u=queue.poll();
searchOrder.add(u);
for (Edge e:neighbors.get(u)){
if(!isVisited[e.v]){
queue.offer(e.v);
parent[e.v]=u;
isVisited[e.v]=true;
}
}
}
return new SearchTree(v,parent,searchOrder);
}
public class SearchTree{
private int root;
private int[] parent;
private List<Integer> searchOrder;
public SearchTree(int root, int[] parent, List<Integer> searchOrder) {
this.root = root;
this.parent = parent;
this.searchOrder = searchOrder;
}
//返回树的根
public int getRoot() {
return root;
}
//找出顶点v在这个搜索中的父节点
public int getParent(int v) {
return parent[v];
}
//获取被搜索顶点的顺序
public List<Integer> getSearchOrder() {
return searchOrder;
}
//返回搜索到的顶点的个数
public int getNumberOfVerticesFound(){
return searchOrder.size();
}
//返回一个从指定下标的顶点到根节点的顶点线性表
public List<V> getPath(int index){
ArrayList<V> path=new ArrayList<>();
do{
path.add(vertices.get(index));
index=parent[index];
}
while (index!=-1);
return path;
}
//显示一条从根节点到顶点的路径
public void printPath(int index){
List<V> path=getPath(index);
System.out.print("A path from "+vertices.get(root)+" to "+vertices.get(index)+": ");
for (int i=path.size()-1;i>=0;i--){
System.out.print(path.get(i)+" ");
}
}
//显示树中所有的边
public void printTree(){
System.out.println("Root is: "+vertices.get(root));
System.out.print("Edges: ");
for(int i=0;i<parent.length;i++){
if(parent[i]!=-1){
System.out.print("("+vertices.get(parent[i])+", "+vertices.get(i)+") ");
}
}
System.out.println();
}
}
@Override
public boolean remove(V v) {
return true;
}
@Override
public boolean remove(int u, int v) {
return true;
}
}
NineTailModel类
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
public class NineTailModel {
public final static int NUMBER_OF_NODES=512;
protected UnweightedGraph<Integer>.SearchTree tree;
public NineTailModel(){
List<Edge> edges=getEdges();
UnweightedGraph<Integer> graph=new UnweightedGraph<>(edges,NUMBER_OF_NODES);
tree=graph.bfs(511);
}
}
NineTail类
import java.util.List;
import java.util.Scanner;
public class NineTail {
public static void main(String[] args) {
}
}
}
实验设计的思路与考量
表示图
表示一个图是在程序中存储它的顶点和边。存储图的数据结构是数组或者线性表。
图的遍历
图的遍历是指访问图中的每一个顶点,且之访问一次的过程,存在两种流行的遍历图的方法:深度优先遍历和广度优先遍历。这两种遍历方法都会产生一个生成树,它可以用类来建模。比如UnweightedGraph<V>.SearchTree,他是一个特定的类,描述节点的父子关系。在代码中定义为UnweightedGraph类中的一个内部类,方法的作用见代码框架。
深度优先搜索
树的深度优先搜索首先访问根节点,然后递归地访问根节点的子树。图中,搜索从某个顶点v开始,然后访问顶点v的第一个未被访问的邻居。如果顶点v没有未被访问的邻居,返回到到达顶点v的那个顶点。若图是连通的,则从任意节点开始的搜索可以到达所有的节点。
广度优先搜索
对于树的广度优先遍历而言,将逐层访问节点。首先访问根节点,然后是根节点的所有子节点,接着是根节点的孙子节点,依此类推。同样,图的广度优先搜索首先访问一个顶点,然后是所有与其邻接的顶点,最后是所有与这些顶点邻接的顶点,依此类推。