数据结构与算法-图

152 阅读4分钟

1,什么是图?

  • 图是由顶点(vertex)和边(edge)组成,通常表示为G = (V , E)
  • G表示一个图,V是定点集,E是边集
  • 顶点集V,可以是有穷且非空
  • 任意两点之间都可以用边来表示他们之间的关系,边集E可以是空的

1.1,有向图

  • 有向图的边是有明确方向的

1.2,有向无环图

  • 如果一个有向图,从任意顶点出发,无法经过若干条边回到该顶点,那么就是一个有向无环图

1.3,无向完全图

  • 无向完全图的任意两个顶点之间都存在边
  • n个顶点的无向完全图有n(n-1)/2条边

1.4,有向完全图

  • 有向完全图的任意两个顶点之间都存在方向相反的两条边
  • n个顶点的有向完全图有n(n - 1)条边

1.5,有权图

  • 有权图的边可以有权值

2,图的出度,入度

  • 出度,入度适用于有向图

出度:一个顶点的出度为x,是指有x条边以该顶点为起点
例如:顶点11的出度为3

入度:一个顶点的入度为x,是指有x条边以该顶点为终点
例如:顶点11的入度为2

3,图的实现方案

图有两种实现方案:邻接矩阵,邻接表

3.1,邻接矩阵

邻接矩阵

  • 一维数组存放顶点信息
  • 二维数组存放边的信息

有向图和无向图的数据都可以存储,但是会比较浪费内存,适合稠密图的存储

3.2,邻接表

  • 顶点的数据通过数组或者链表的形式存储
  • 每个顶点,与之两连接的顶点可以通过链表形式存储

4,邻接表的代码实现

4.1,图的基础接口

//获取点的数量
int verticesSize();
//获取边的数量
int edgeSize();
//添加顶点
void addVertex(V v);
//删除顶点
void removeVertex(V v);
//从起点到终点,添加边和权值
void addEdge(V fromV, V toV);
void addEdge(V fromV, V toV, E weight);
//删除边
void removeEdge(V fromV, V toV);

4.2,顶点的定义

private static class Vertex<V,E> {
    //顶点的value值
    V value;
    //创建入度集合
    Set<Edge<V,E>> inEdges = new HashSet<>();
    //创建出度集合
    Set<Edge<V,E>> outEdges = new HashSet<>();

    Vertex(V value) {
        this.value = value;
    }

    //判断顶点的值是否相等
    public boolean equals(Object obj) {
        return Objects.equals(value, ((Vertex<V,E>) obj).value);
    }
    //获取value的哈希值
    public int hashCode(){
        return value == null ? 0 : value.hashCode();
    }

}

4.3,边的定义

private static class Edge<V,E> {
    //起点
    Vertex<V, E> from;
    //终点
    Vertex<V, E> to;
    //权值
    E weight;
    //判断 起点 到 终点的边,是否一样
    public boolean equals(Object obj) {
        //转化为边
        Edge<V, E> edge = (Edge<V, E>) obj;
        //判断边的 起点 到 终点,是否一样
        return from.equals(edge.from) && to.equals(edge.to);
    }
    //获取哈希值
    public int hashCode() {
        //计算哈希值
        return from.hashCode() * 31 + to.hashCode();
    }

}

4.4,实现代码

//存储每次添加的。顶点的哈希值的链表
private Map<V, Vertex<V, E>> vertices = new HashMap<>();//存储每条边的集合
private Set<Edge<V, E>> edges = new HashSet<>();
//获取边的数量
public int edgesSize() {
    return edges.size();
}

//获取顶点的数量
public int verticesSize() {
    return verticesSize;
}

//添加顶点
public void addVertex(V v) {
    //如果包含,就return
    if (vertices.containKey(v)) return;
    /添加到链表的 key和value
    vertices.put(v,new Vertex<>(v));
}

//添加边
public void addEdge(V from, V to) {
    addEdge(from, to, null);
}

//添加边,起点,终点,权值
public void addEdge(V from, V to, E weight) {
    //获取起点
    Vertex<V, E> fromVertex = vertives.get(from);
    //如果为空,创建一个,添加到链表
    if (fromVertex == null) {       
       fromVertex = new Vertex<>(from);       
       vertices.put(from, fromVertex);    
    }      
    //获取终点      
    Vertex<V, E> toVertex = vertices.get(to);
      //如果为空,创建一个,添加到链表
        if (toVertex == null) {
            toVertex = new Vertex<>(to);            
            vertices.put(to, toVertex);        
        }        
        //通过 起点 和 终点,创建一条边
        Edge<V, E> edge = new Edge<>(fromVertex, toVertex);
        //添加 权值        
        edge.weight = weight;
        //删除起点的边存在,说明原来边存在。如果存在:先删除,后添加        
        if (fromVertex.outEdges.remove(edge)) {
            //终点的入度要删除            
            toVertex.inEdges.remove(edge);
            //边的集合要删除            
            edges.remove(edge);        
        }
        //起点的出度添加        
        fromVertex.outEdges.add(edge);
        //终点的入度添加        
        toVertex.inEdges.add(edge);
        //边的集合添加        
        edges.add(edge);
}


//删除边
public void removeEdge(V from, V to) {
        //链表获取起点,不存在return        
        Vertex<V, E> fromVertex = vertices.get(from);        
        if (fromVertex == null) return;        
        //链表获取终点,不存在return        
        Vertex<V, E> toVertex = vertices.get(to);        
        if (toVertex == null) return;        
        //根据 起点 和 终点,创建边        
        Edge<V, E> edge = new Edge<>(fromVertex, toVertex);
        //如果边之前存在,就删除边        
        if (fromVertex.outEdges.remove(edge)) {            
            toVertex.inEdges.remove(edge);            
            edges.remove(edge);        
        }    
}
//删除顶点
public void removeVertex(V v) {
        //判断要删除的顶点是否存在,如果存在且不为空,就遍历起点和终点删除       
         Vertex<V, E> vertex = vertices.remove(v);        
        if (vertex == null) return;        
        //适代器,遍历删除
        for (Iterator<Edge<V, E>> iterator = vertex.outEdges.iterator(); iterator.hasNext();) {            
             Edge<V, E> edge = iterator.next();            
            edge.to.inEdges.remove(edge);            
            // 将当前遍历到的元素edge从集合vertex.outEdges中删掉            
            iterator.remove();             
            edges.remove(edge);        
        }
        for (Iterator<Edge<V, E>> iterator = vertex.inEdges.iterator(); iterator.hasNext();) {            
            Edge<V, E> edge = iterator.next();            
            edge.from.outEdges.remove(edge);            
            // 将当前遍历到的元素edge从集合vertex.inEdges中删掉            
            iterator.remove();             
            edges.remove(edge);        
        }    
}

5,图的遍历

从图中的某一顶点出发,访问图中其余顶点,且每隔顶点仅被访问一次

图的遍历有两种实现方式:广度优先搜索 和 深度优先搜索

5.1,广度优先搜索

  • 之前所学的二叉树层序遍历就是一种广度优先搜索

思路

  • 假如从A开始遍历
  • 第一层就是A,第二层就是A的相连顶点,B,F
  • 第三层就是B,F的相连顶点F,C,I,G
  • 如图,依次向下层遍历,最后遍历完每一层的节点

我们打印出来的每层节点,不会重复的打印

代码实现

public void bfs(V begin, VertexVisitor<V> visitor) {
        //存储顶点链表为null,return        
        if (visitor == null) return;
        //获取广度优先搜索的开始顶点        
        Vertex<V, E> beginVertex = vertices.get(begin);        
        if (beginVertex == null) return;        
        // 创建遍历过的顶点的集合        
        Set<Vertex<V, E>> visitedVertices = new HashSet<>();
        //创建队列        
        Queue<Vertex<V, E>> queue = new LinkedList<>();
        //开始顶点入队        
        queue.offer(beginVertex);
        //存储,遍历过的顶点        
        visitedVertices.add(beginVertex);        
        //循环遍历        
        while (!queue.isEmpty()) {
            //顶点出队            
            Vertex<V, E> vertex = queue.poll();            
            if (visitor.visit(vertex.value)) return;            
            //遍历顶点的所有 边的出度            
            for (Edge<V, E> edge : vertex.outEdges) {
                //如果遍历过,继续向下遍历                
                if (visitedVertices.contains(edge.to)) continue;
                //没有遍历过,入队                
                queue.offer(edge.to);
                //记录遍历过的 顶点                
                visitedVertices.add(edge.to);            
            }        
      }    
}

5.2,深度优先搜索

因为不确定,先遍历哪一条路径,所以遍历出来的结果会不一样

1,递归实现

    private void dfs2(Vertex<V, E> vertex, Set<Vertex<V, E>> visitedVertices) {
        //打印顶点的值        
        System.out.println(vertex.value);
        //记录 遍历过的顶点        
        visitedVertices.add(vertex);        
        //遍历顶点的 所有边的出度
        for (Edge<V, E> edge : vertex.outEdges) {
            //如果遍历过,继续走下一条出度            
            if (visitedVertices.contains(edge.to)) continue;
            //递归遍历            
            dfs2(edge.to, visitedVertices);        
        }    
}

2,非递归实现

  • 用栈来实现

    public void dfs(V begin) {        
        Vertex<V, E> beginVertex = vertices.get(begin);        
        if (beginVertex == null) return;
        Set<Vertex<V, E>> visitedVertices = new HashSet<>();        
        Stack<Vertex<V, E>> stack = new Stack<>();                
        // 先访问起点        
        stack.push(beginVertex);        
        visitedVertices.add(beginVertex);        
        System.out.println(beginVertex.value);                
        while (!stack.isEmpty()) {            
            Vertex<V, E> vertex = stack.pop();                        
            for (Edge<V, E> edge : vertex.outEdges) {                
                if (visitedVertices.contains(edge.to)) continue;                                
                    stack.push(edge.from);                
                    stack.push(edge.to);                
                    visitedVertices.add(edge.to);                
                    System.out.println(edge.to.value);                                
                    break;            
                }        
            }    
    

    }