算法-Java

算法

一、图

1.1.基础知识:

  • 一个图G=<V,E>由两个集合来 定义:一个有限集合V,它的元素称为顶点(vertex);另一个有限集合E,它的元素是一对顶点,称为边(edge)。

  • G中的所有边都是无向的,我们称之为 无向图,若本身是有向的,则称为 有向图

  • 对图的定义没有禁止圈(loop),即连接顶点自身的边,对于|V|个顶点的无圈无向图,它可能包含的边的数量|E|可以用这个不等式表示:

    0≤|E|≤|V|(|V|–1)/2

    (如果每个顶点|V|和所有其他|V|–1个顶点之间都有边相连,图中边的数量就会达到最大。但是,我们必须对|V|(|V|–1)的积除以2,因为每条边被包含了两次。)

  • 任意两个顶点之间都有边相连的图称为 完全(complete)图。如果图中所缺的边数量相对较少,我们称它为 稠密(dense)图;如果图中的边相对顶点来说数量较少,我们称它为稀疏(sparse)图

  • 矩阵元素A[i, j]可以简单地包含这条边的权重;当不存在这样一条边时,则包含一个特殊符号,例如∞。这种矩阵称为权重矩阵(weight matrix)或成本矩阵(cost matrix)。

2.2 .图的表示方法:

2.21.基础

  • 图处理问题就是用哪种方式(数据结构)来表示图并实现这份API,这包含以下两个要求
    ❏它必须为可能在应用中碰到的各种类型的图预留出足够的空间;
    ❏ Graph的实例方法的实现一定要快——它们是开发处理图的各种用例的基础。
  • 表示方法:邻接矩阵与邻接表
    邻接矩阵。我们可以使用一个V乘V的布尔矩阵。当顶点v和顶点w之间有相连接的边时,定义v行w列的元素值为true,否则为false。这种表示方法不符合第一个条件——含有上百万个顶点的图是很常见的,V2个布尔值所需的空间是不能满足的。
    邻接表数组。我们可以使用一个以顶点为索引的列表数组,其中的每个元素都是和该顶点相邻的顶点列表,参见图4.1.9。这种数据结构能够同时满足典型应用所需的以上两个条件

2.22.邻接矩阵数据结构:

性能特点:

❏使用的空间和V+E成正比;
❏添加一条边所需的时间为常数;
❏遍历顶点v的所有相邻顶点所需的时间和v的度数成正比(处理每个相邻顶点所需的时间为常数)。

Java代码实现:
public class Graph
{
    private final int V;           // 顶点数目
    private int E;                  // 边的数目
    private Bag<Integer>[] adj;   // 邻接表
    public Graph(int V)
    {
      this.V = V; this.E = 0;
      adj = (Bag<Integer>[]) new Bag[V];       // 创建邻接表
      for (int v = 0; v < V; v++)               // 将所有链表初始化为空
          adj[v] = new Bag<Integer>();
    }
    public Graph(In in)
    {
      this(in.readInt());           // 读取V并将图初始化
      int E = in.readInt();         // 读取E
      for (int i = 0; i < E; i++)
      {  // 添加一条边
          int v = in.readInt();         // 读取一个顶点
          int w = in.readInt();         // 读取另一个顶点
          addEdge(v, w);                 // 添加一条连接它们的边
      }
    }
    public int V()  {  return V;  }
    public int E()  {  return E;  }
    public void addEdge(int v, int w)
    {
      adj[v].add(w);                             // 将w添加到v的链表中
      adj[w].add(v);                             // 将v添加到w的链表中
      E++;
    }
    public Iterable<Integer> adj(int v)
    {  return adj[v];  }
}

这份Graph的实现使用了一个由顶点索引的整型链表数组。每条边都会出现两次,即当存在一条连接v与w的边时,w会出现在v的链表中,v也会出现在w的链表中。常见的应用场景都需要处理庞大的稀疏图,因此我们会一直使用邻接表。

2.23.深度优先与广度优先遍历:

图处理算法api:

image-20230403201113049

深度优先搜索dfs:
  • 深度优先和广度优先均为暴力法

  • 在访问其中一个顶点时:
    ❏将它标记为已访问;

    ❏递归地访问它的所有没有被标记过的邻居顶点。

  • 在图中会路过每条边两次(在它的两个端点各一次)

  • 伪码表示:
    image-20230403195212709

  • Java代码实现:
    image-20230403201546069

寻找路径:
  • 路径的API:

image-20230403202114800

  • 使用深度优先搜索查找图中的路径

    public class DepthFirstPaths
    {
        private boolean[] marked; // 这个顶点上调用过dfs()了吗?
        private int[] edgeTo;      // 从起点到一个顶点的已知路径上的最后一个顶点
        private final int s;       // 起点
        public DepthFirstPaths(Graph G, int s)
        {
          marked = new boolean[G.V()];
          edgeTo = new int[G.V()];
          this.s = s;
          dfs(G, s);
        }
        private void dfs(Graph G, int v)
        {
          marked[v] = true;
          for (int w : G.adj(v))
              if (! marked[w])
              {
                edgeTo[w] = v;
                dfs(G, w);
              }
        }
        public boolean hasPathTo(int v)
        {  return marked[v];  }
        public Iterable<Integer> pathTo(int v)
        {
          if (! hasPathTo(v)) return null;
          Stack<Integer> path = new Stack<Integer>();
          for (int x = v; x ! = s; x = edgeTo[x])
              path.push(x);
          path.push(s);
          return path;
        }
    }
    
posted @ 2023-04-03 20:32  superJxt  阅读(75)  评论(0)    收藏  举报