一些有关FF算法的问题
一个特例:每一个边的容量都是1到U之间的整数
一些分析
扩展方式 | 路径数量 | 实现 |
---|---|---|
最短路 | ≤ 1 2 E V \le \frac12EV ≤21EV | 队列(BFS) |
最宽路 | ≤ E ln ( E U ) \le E \ln (EU) ≤Eln(EU) | 优先队列 |
随机路 | ≤ E U \le EU ≤EU | 随机队列 |
DFS路 | ≤ E U \le EU ≤EU | 栈(DFS) |
流网络表示
public class FlowEdge
{
FlowEdge(int v, int w, double capacity);// create a flow edge v→w
int from(); // vertex this edge points from
int to(); // vertex this edge points to
int other(int v); // other endpoint
double capacity(); // capacity of this edge
double flow(); // flow in this edge
double residualCapacityTo(int v); // residual capacity toward v
void addResidualFlowTo(int v, double delta); // add delta flow toward v
String toString(); // string representation
}
public class FlowEdge
{
private final int v, w; // from and to
private final double capacity; // capacity
private double flow; // flow
public FlowEdge(int v, int w, double capacity)
{
this.v = v;
this.w = w;
this.capacity = capacity;
}
public int from() {
return v; }
public int to() {
return w; }
public double capacity() {
return capacity; }
public double flow() {
return flow; }
public int other(int vertex)
{
if (vertex == v) return w;
else if (vertex == w) return v;
else throw new RuntimeException("Illegal endpoint");
}
public double residualCapacityTo(int vertex)
{
if (vertex == v) return flow;
else if (vertex == w) return capacity - flow;
else throw new IllegalArgumentException();
}
public void addResidualFlowTo(int vertex, double delta)
{
if (vertex == v) flow -= delta; // backward
else if (vertex == w) flow += delta; //forward
else throw new IllegalArgumentException();
}
}
public class FlowNetwork
{
FlowNetwork(int V); // create an empty flow network with V vertices
FlowNetwork(In in); // construct flow network input stream
void addEdge(FlowEdge e); // add flow edge e to this flow network
Iterable<FlowEdge> adj(int v); // forward and backward edges incident to v
Iterable<FlowEdge> edges(); // all edges in this flow network
int V(); // number of vertices
int E(); // number of edges
String toString(); // string representation
}
public class FlowNetwork
{
private final int V;
private Bag<FlowEdge>[] adj;
public FlowNetwork(int V)
{
this.V = V;
adj = (Bag<FlowEdge>[]) new Bag[V];
for (int v = 0; v < V; v++)
adj[v] = new Bag<FlowEdge>();
}
public void addEdge(FlowEdge e)
{
int v = e.from();
int w = e.to();
// add to both vertices
adj[v].add(e);
adj[w].add(e);
}
public Iterable<FlowEdge> adj(int v)
{
return adj[v]; }
}
public class FordFulkerson
{
private boolean[] marked; // true if s->v path in residual network
private FlowEdge[] edgeTo; // last edge on s->v path
private double value; // value of flow
public FordFulkerson(FlowNetwork G, int s, int t)
{
value = 0.0;
while (hasAugmentingPath(G, s, t))
{
double bottle = Double.POSITIVE_INFINITY;
// conpute bottleneck capacity
for (int v = t; v != s; v = edgeTo[v].other(v))
bottle = Math.min(bottle, edgeTo[v].residualCapacityTo(v));
// augment flow
for (int v = t; v != s; v = edgeTo[v].other(v))
edgeTo[v].addResidualFlowTo(v, bottle);
// update flow
value += bottle;
}
}
// BFS
private boolean hasAugmentingPath(FlowNetwork G, int s, int t)
{
edgeTo = new FlowEdge[G.V()];
marked = new boolean[G.V()];
Queue<Integer> queue = new Queue<Integer>();
queue.enqueue(s);
marked[s] = true;
while (!queue.isEmpty())
{
int v = queue.dequeue();
for (FlowEdge e : G.adj(v))
{
int w = e.other(v);
// a path from s to w
if (e.residualCapacityTo(w) > 0 && !marked[w])
{
edgeTo[w] = e;
marked[w] = true;
queue.enqueue(w);
}
}
}
return marked[t];
}
public double value()
{
return value; }
// is v in the cut containing s? or reachable from s in the residual network
public boolean inCut(int v)
{
return marked[v]; }
}
public final class String implements Comparable<String>
{
private char[] value; // characters
private int offset; // index of first char in array
private int length; // length of string
private int hash; // cache of hashCode()
public int length()
{
return length; }
public char charAt(int i)
{
return value[i + offset]; }
private String(int offset, int length, char[] value)
{
this.offset = offset;
this.length = length;
this.value = value;
}
public String substring(int from, int to)
{
return new String(offset + from, to - from, value); }
...
StringBuilder
:可异变的字符序列
StringBuffer
十分相似,但是线程安全的(更慢)在不依赖键的比较的情况下,性能下界可以优于 N log N N \log N NlogN
键索引计数有关键的假设
目标:对一个保存着N的 0 0 0到 R − 1 R-1 R−1之间的整数的数组排序
步骤:
int N = a.length;
int[] count = new int[R+1];
for (int i = 0; i < N; i++)
count[a[i]+1]++;
for (int r = 0; r < R; r++)
count[r+1] += count[r];
for (int i = 0; i < N; i++)
aux[count[a[i]]++] = a[i];
for (int i = 0; i < N; i++)
a[i] = aux[i];
性质:使用大约 ∼ 11 N + 4 R \sim11N+4R ∼11N+4R次数组访问,空间占用正比于 N + R N+R N+R
该排序方法稳定,因为在移动时,仍然保持着元素的相对顺序
public class LSD
{
public static void sort(String[] a, int W)
{
int R = 256;
int N = a.length;
String[] aux = new String[N];
for (int d = W-1; d >= 0; d--)
{
int[] count = new int[R+1];
for (int i = 0; i < N; i++)
count[a[i].charAt(d) + 1]++;
for (int r = 0; r < R; r++)
count[r+1] += count[r];
for (int i = 0; i < N; i++)
aux[count[a[i].charAt(d)]++] = a[i];
for (int i = 0; i < N; i++)
a[i] = aux[i];
}
}
}
最大有效数字优先(most significant)排序:
边长字符串的处理:每个字符串处理成最后都有一个-1(比任何字符都小)
\0
private static int charAt(String s, int d)
{
if (d < s.length()) return s.charAt(d);
else return -1;
}
java实现
public static void sort(String[] a)
{
aux = new String[a.length];
sort(a, aux, 0, a.length-1, 0);
}
private static void sort(String[] a, String[] aux, int lo, int hi, int d)
{
// 查到只剩一个,就退出
if (hi <= lo) return;
// 避免了count的递归,节省空间
int[] count = new int[R+2];
// 计数算法
for (int i = lo; i <= hi; i++)
count[charAt(a[i], d) + 2]++; //多挪了一位,因为要处理-1的情况
for (int r = 0; r < R+1; r++)
count[r+1] += count[r];
for (int i = lo; i <= hi; i++)
aux[count[charAt(a[i], d) + 1]++] = a[i];
for (int i = lo; i <= hi; i++)
a[i] = aux[i - lo];
// 递归往后查,注意只查一个组
for (int r = 0; r < R; r++)
sort(a, aux, lo + count[r], lo + count[r+1] - 1, d+1);
}
less()
比较器private static void sort(String[] a)
{
sort(a, 0, a.length - 1, 0); }
// 3-way partitioning (using d th character)
private static void sort(String[] a, int lo, int hi, int d)
{
if (hi <= lo) return;
int lt = lo, gt = hi;
int v = charAt(a[lo], d); // to handle variable-length strings
int i = lo + 1;
while (i <= gt)
{
int t = charAt(a[i], d);
if
(t < v) exch(a, lt++, i++);
else if (t > v) exch(a, i, gt--);
else
i++;
}
// 分别对三个部分递归,注意,中间部分是查看下一个字符,而上下两部分仍然按照当前字符划分
sort(a, lo, lt-1, d);
if (v >= 0) sort(a, lt, gt, d+1); // sort 3 subarrays recursively
sort(a, gt+1, hi, d);
}
上下文关键字搜索(keyword-in-context):
后缀搜索:
最长重复子串:
暴力方法找LRS:
后缀搜索方法:
public String lrs(String s)
{
int N = s.length();
// create suffixes
String[] suffixes = new String[N];
for (int i = 0; i < N; i++)
suffixes[i] = s.substring(i, N);
// sort them
Arrays.sort(suffixes);
// find LCP between adjacent suffixes in sorted order
String lrs = "";
for (int i = 0; i < N-1; i++)
{
int len = lcp(suffixes[i], suffixes[i+1]);
if (len > lrs.length())
lrs = suffixes[i].substring(0, len);
}
return lrs;
}
简单的解决方案:后缀搜索+3路基数快排+LCP查找(前提是LRS不是很长)
如果LRS过长,上述解决方案并不适用
最坏情况下对数线性时间复杂度的方案:Manber-Myers MSD算法
最坏情形运行时间: N lg N N \lg N NlgN
常数时间的比较: