之前写过一篇边界标识内存管理算法,在分配内存端,分配算法没问题,有很多种,首次适应、最佳适应、最坏适应法等,分配的时机,看用户什么时候申请。在回收端,回收情况分很四种:左/右临界区为空闲块、左右临界区均为空闲块和左右临界区均不为空,根据不同的情况,空闲块回收的方法也不同。那么回收的时机呢?在C语言中,我们要显式地使用free()来回收不再使用的内存,在Java里,我们不需要这么做,自动回收机制会帮我们这么做,它又是怎么实现的呢?它怎么知道当前哪些对象不被使用,哪些对象可以被回收了呢?
在程序执行过程中,很多对象的引用和变量的使用,我们随时都可以访问到这些对象,而对于那些不能被直接访问到的对象,就应该被回收,以释放内存给新的对象使用。那么怎么知道哪些对象能被直接访问到,哪些对象不能被访问?我们看,对象的引用就像指针,把对象看做一个顶点,一个对象对另一个对象的应用看作一个箭头相连,那么多个对象之间的引用关系就构成了一个有向图。
在有向图中,一个顶点可以到达另一个顶点,那么就认为这两个顶点是连通的,就像一个对象可以直接或间接地访问到另一个对象。遍历有向图,找出所有不能被访问的顶点,然后把它们回收,实际的内存管理中回收机制也是这样的,一个顶点表示一个对象,对象之间的引用表示一条边,至于寻找图中无法被访问的顶点,那就是用到有向图中顶点可达性的寻找方法。给定的某一个顶点或是一组顶点,在图中遍历寻找所有从这些顶点开始,可到达的顶点,至于不能到达的顶点,则认为是不再使用的垃圾块或空闲块,将其回收。
有向图用背包数据结构来存储邻接表的方式,背包是一种不能做删除操作得集合数据类型,使用背包的目的是:收集元素并迭代遍历所有收集到的元素。由于我们的有向图无论是深度优先还是广度优先搜索,都要遍历图,所以用背包很合适。
package directeddfs;
import java.util.Iterator;
public class Bag<Item> implements Iterable<Item> {
private Node first; //链表头结点
private int N;
//链表结点的数据结构
private class Node {
Item item;
Node next;
}
public boolean isEmpty() {
return first == null;// or N == 0
}
public int size() {
return N;
}
public void add(Item item) {
Node oldfirst = first;
first = new Node();
first.item = item;
first.next = oldfirst;
}
@Override
public Iterator<Item> iterator() {
return new ListIterator();
}
private class ListIterator implements Iterator<Item> {
private Node current = first;
@Override
public boolean hasNext() {
return current != null;
}
@Override
public Item next() {
Item item = current.item;
current = current.next;
return item;
}
@Override
public void remove() {
}
}
}
背包的实现这里不详述,就是一个链表结构,可以添加元素和遍历,但没有删除元素的方法。来看看有向图的代码:
(有向图的初始化)
对于addEdge()方法,也就是向图中插入一条边,由于是有向图,所以只需要设置一次,v1指向v2就把Graph[ v1 ] = v2,否则相反。
//深度优先搜索
public void DFSSearch(GraphDemo Graph, int tag) {
mark[tag] = true; //从顶点tag开始
for(int v : Graph.returnGraph(tag)) {
if(mark[v] != true) {
DFSSearch(Graph, v);
}
}
}
遍历有向图的方法,可以使用深度优先搜索或广度优先搜索。
用一个boolean类型的mark数组来记录遍历图过程中可以访问到的顶点,在DFS中,每一次被访问到的顶点都把其mark[ v ]的值设置为true。
这里给出两种实现,第一种是单个顶点的可达性搜索:
//在图中查找顶点tag可到达的所有顶点
public dfsSearchDemo(GraphDemo Graph, int tag) {
mark = new boolean[Graph.returnV()];
DFSSearch(Graph, tag); //在图中查找tag顶点可达的所有顶点
}
它要做的事情很简单,先获得图中顶点的数量,根据顶点数实例化mark数组,然后在图Graph中寻找并标记与tag连通的顶点,每访问到一个顶点就把mark数组中对应的mark[ v ]设置为true。最后完成一次遍历下来,找出Graph中所有顶点tag可到达的顶点。
对于多个顶点来说,方法一模一样,例如下面这个例子:
//在图中查找集合graph中的多个顶点可到达的所有顶点
public dfsSearchDemo(GraphDemo Graph, Iterable<Integer> graph) {
mark = new boolean[Graph.returnV()];
for(int v : graph) {
if(mark[v] != true) {
DFSSearch(Graph, v);
}
}
}
我们把存放了多个顶点的集合,每次拿出一个顶点来做DFS,最后能获得集合中所有顶点在Graph中可到达的所有顶点(表述得有点拗口- -、)。
看看运行结果:
在典型的内存管理中,系统会定时地运行一个类似于这样有向图的多点可达性算法,来检测程序中的对象引用,然后清除那些不再被引用的对象,释放其内存。
完整实现代码已上传:
https://github.com/justinzengtm/Algorithms/tree/master/DepthFirstSearch/MultipointDirectedDFS