1)每个节点都有一条往上指的指针。
2)节点a往上找到的头节点,叫做a所在集合的代表节点。
3)查询x和y是否属于同一个集合,就是看看找到的代表节点是不是一个。
4)把x和y各自所在集合的所有点合并成一个集合,只需要小集合的代表点挂在大集合的代表点的下方即可。
1)节点往上找代表点的过程,把沿途的链变成扁平的。
2)小集合挂在大集合的下面。
3)如果方法调用很频繁,那么单次调用的代价为O(1),两个方法都如此。
/**
* 并查集
* 提供两个方法:
* 1、查询两个元素是否在同一个集合中 isSameSet
* 2、把两个元素所在的集合合并成一个 union
*
* 假定用户一次直接给出所有集合
*/
public class UnionSearch<V> {
// 节点位置表
Map<V,Node> nodes;
// 查找当前节点对应的父节点表
Map<Node,Node> parents;
// 作为代表元素表 k代表元素 v 集合中有几个元素
Map<Node,Integer> size;
public UnionSearch(List<V> list){
nodes = new HashMap<>();
parents = new HashMap<>();
size = new HashMap<>();
for(V v : list){
// 出事时每个元素单独作为一个结合
Node node = new Node(v);
nodes.put(v,node);
parents.put(node,node);
size.put(node,1);
}
}
public boolean isSameSet(V v1,V v2){
if(!nodes.containsKey(v1)
|| !nodes.containsKey(v2)){
return false;
}
Node node1 = findFather(v1);
Node node2 = findFather(v2);
if(node1 == node2){
return true;
}
return false;
}
public void union(V v1,V v2){
if(!nodes.containsKey(v1)
|| !nodes.containsKey(v2)){
return;
}
Node node1 = findFather(v1);
Node node2 = findFather(v2);
// 两个元素不在一个集合中才合并
if(node1 != node2){
// 看谁的元素多
Node big = size.get(node1) > size.get(node2) ? node1 : node2;
Node small = size.get(node1) <= size.get(node2) ? node1 : node2;
// 小的挂在大的上面
parents.put(small,big);
size.put(big,size.get(big) + size.get(small));
size.remove(small);
}
}
// 调用此方法是一定保证集合中有此元素
private Node findFather(V node){
// 做一个扁平化优化,把从当前节点网上找的节点都加入容器中
Stack<Node> stack = new Stack<>();
Node cur = nodes.get(node);
while (parents.get(cur) != cur){
// 路径上经过的节点加入容器
stack.push(cur);
// 只要此时节点不等于父节点,说明不是代表节点就一直往上找
cur = parents.get(cur);
}
// 把所有经过节点直接和代表节点相连,下次查找是O(1)
while (!stack.isEmpty()){
parents.put(stack.pop(),cur);
}
//跳出来说明已经找到了
return cur;
}
public static void main(String[] args) {
List<String> list = new ArrayList();
list.add("a");
list.add("b");
list.add("c");
list.add("d");
list.add("e");
list.add("f");
list.add("g");
UnionSearch<String> unionSearch = new UnionSearch<>(list);
unionSearch.union("a","b");
unionSearch.union("c","b");
System.out.println(unionSearch.isSameSet("a", "g"));
}
}
图的算法都不算难,只不过coding的代价比较高
// 点
public class Node {
// 结点值
public int value;
// 入度
public int in;
// 出度
public int out;
// 邻居
List<Node> nexts;
// 存一份边
List<Edge> edges;
public Node(int value){
this.value = value;
// 新结点入度为0
this.in = 0;
// 新结点出度为0
this.out = 0;
// 新结点邻居为空
this.nexts = new ArrayList<>();
this.edges = new ArrayList<>();
}
}
// 边
public class Edge {
// 权重
public int weight;
// 起点边
public Node from;
// 终点边
public Node to;
public Edge(Node from,Node to,int weight){
this.from = from;
this.to = to;
this.weight = weight;
}
}
// 图
public class Graph {
// 点的集合
HashMap<Integer,Node> nodes;
// 边的集合
HashSet<Edge> edges;
public Graph(){
this.nodes = new HashMap<>();
this.edges = new HashSet<>();
}
}
public class GraphGenerator {
// matrix 所有的边
// N*3 的矩阵
// [weight, from节点上面的值,to节点上面的值]
public static Graph graphGenerator(Integer[][] matrix){
Graph graph = new Graph();
for (int i = 0;i < matrix.length;i++){
// 每一行代表一个点
// 权重
int weight = matrix[i][0];
// 起始点
int from = matrix[i][1];
// 终点
int to = matrix[i][2];
// 如果没有起始点就新建
if(graph.nodes.containsKey(from)){
graph.nodes.put(from,new Node(from));
}
if(graph.nodes.containsKey(to)){
graph.nodes.put(to,new Node(to));
}
// 取出点构建图
Node fromNode = graph.nodes.get(from);
Node toNode = graph.nodes.get(to);
// 构建边
Edge edge = new Edge(fromNode,toNode,weight);
// fromNode 几点出度加一,邻居加一
fromNode.out ++;
fromNode.nexts.add(toNode);
fromNode.edges.add(edge);
// toNode入度加一
toNode.in ++;
// 图中边集加一
graph.edges.add(edge);
}
return graph;
}
}
注意要点,不能重复遍历,遍历过的点应该排除。
package class16;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.Queue;
public class Code01_BFS {
// 从node出发,进行宽度优先遍历
public static void bfs(Node start) {
if (start == null) {
return;
}
Queue<Node> queue = new LinkedList<>();
HashSet<Node> set = new HashSet<>();
queue.add(start);
set.add(start);
while (!queue.isEmpty()) {
Node cur = queue.poll();
System.out.println(cur.value);
for (Node next : cur.nexts) {
if (!set.contains(next)) {
set.add(next);
queue.add(next);
}
}
}
}
}
package class16;
import java.util.HashSet;
import java.util.Stack;
public class Code02_DFS {
public static void dfs(Node node) {
if (node == null) {
return;
}
Stack<Node> stack = new Stack<>();
HashSet<Node> set = new HashSet<>();
stack.add(node);
set.add(node);
System.out.println(node.value);
while (!stack.isEmpty()) {
Node cur = stack.pop();
for (Node next : cur.nexts) {
if (!set.contains(next)) {
stack.push(cur);
stack.push(next);
set.add(next);
System.out.println(next.value);
break;
}
}
}
}
}
要求:有向图且其中没有环
应用:事件安排、编译顺序
package class16;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
public class Code03_TopologySort {
// directed graph and no loop
public static List<Node> sortedTopology(Graph graph) {
// key 某个节点 value 剩余的入度
HashMap<Node, Integer> inMap = new HashMap<>();
// 只有剩余入度为0的点,才进入这个队列
Queue<Node> zeroInQueue = new LinkedList<>();
for (Node node : graph.nodes.values()) {
inMap.put(node, node.in);
if (node.in == 0) {
zeroInQueue.add(node);
}
}
List<Node> result = new ArrayList<>();
while (!zeroInQueue.isEmpty()) {
Node cur = zeroInQueue.poll();
result.add(cur);
for (Node next : cur.nexts) {
inMap.put(next, inMap.get(next) - 1);
if (inMap.get(next) == 0) {
zeroInQueue.add(next);
}
}
}
return result;
}
}
/**
* 最小生成树算法之Kruskal
* 1)总是从权值最小的边开始考虑,依次考察权值依次变大的边(优先级队列)
* 2)当前的边要么进入最小生成树的集合,要么丢弃
* 3)如果当前的边进入最小生成树的集合中不会形成环,就要当前边
* 4)如果当前的边进入最小生成树的集合中会形成环,就不要当前边
* 5)考察完所有边之后,最小生成树的集合也得到了
*/
public class Kruskal {
public static Set<Edge> kruskal(Graph graph){
if(graph == null){
return null;
}
Set<Edge> res = new HashSet<>();
// 先把所有点都加入并查集
List<Node> nodeList = (List<Node>)graph.nodes.values();
UnionSearch<Node> unionSearch = new UnionSearch<>(nodeList);
// 获取所有边
PriorityQueue<Edge> queue = new PriorityQueue<>(new MyComparator());
for(Edge item : graph.edges){
queue.offer(item);
}
while (!queue.isEmpty()){
Edge cur = queue.poll();
Node from = cur.from;
Node to = cur.to;
if(!unionSearch.isSameSet(from,to)){
res.add(cur);
// 加入并查集
unionSearch.union(from,to);
}
}
return res;
}
private void makeUnion(List<Node> list,UnionSearch<Node> unionSearch){
for(Node item : list){
}
}
static class MyComparator implements Comparator<Edge>{
@Override
public int compare(Edge o1, Edge o2) {
return o1.weight - o2.weight;
}
}
static class UnionSearch<V> {
// 节点位置表
Map<V,Node2> nodes;
// 查找当前节点对应的父节点表
Map<Node2,Node2> parents;
// 作为代表元素表 k代表元素 v 集合中有几个元素
Map<Node2,Integer> size;
public UnionSearch(List<V> list){
nodes = new HashMap<>();
parents = new HashMap<>();
size = new HashMap<>();
for(V v : list){
// 出事时每个元素单独作为一个结合
Node2 node = new Node2(v);
nodes.put(v,node);
parents.put(node,node);
size.put(node,1);
}
}
public boolean isSameSet(V v1,V v2){
if(!nodes.containsKey(v1)
|| !nodes.containsKey(v2)){
return false;
}
Node2 node1 = findFather(v1);
Node2 node2 = findFather(v2);
if(node1 == node2){
return true;
}
return false;
}
public void union(V v1,V v2){
if(!nodes.containsKey(v1)
|| !nodes.containsKey(v2)){
return;
}
Node2 node1 = findFather(v1);
Node2 node2 = findFather(v2);
// 两个元素不在一个集合中才合并
if(node1 != node2){
// 看谁的元素多
Node2 big = size.get(node1) > size.get(node2) ? node1 : node2;
Node2 small = size.get(node1) <= size.get(node2) ? node1 : node2;
// 小的挂在大的上面
parents.put(small,big);
size.put(big,size.get(big) + size.get(small));
size.remove(small);
}
}
// 调用此方法是一定保证集合中有此元素
private Node2 findFather(V node){
// 做一个扁平化优化,把从当前节点网上找的节点都加入容器中
Stack<Node2> stack = new Stack<>();
Node2 cur = nodes.get(node);
while (parents.get(cur) != cur){
// 路径上经过的节点加入容器
stack.push(cur);
// 只要此时节点不等于父节点,说明不是代表节点就一直往上找
cur = parents.get(cur);
}
// 把所有经过节点直接和代表节点相连,下次查找是O(1)
while (!stack.isEmpty()){
parents.put(stack.pop(),cur);
}
//跳出来说明已经找到了
return cur;
}
}
}
/**
* 最小生成树,p算法
* 1)可以从任意节点出发来寻找最小生成树
* 2)某个点加入到被选取的点中后,解锁这个点出发的所有新的边
* 3)在所有解锁的边中选最小的边,然后看看这个边会不会形成环
* 4)如果会,不要当前边,继续考察剩下解锁的边中最小的边,重复3)
* 5)如果不会,要当前边,将该边的指向点加入到被选取的点中,重复2)
* 6)当所有点都被选取,最小生成树就得到了
*/
public class Prim {
public static Set<Edge> prim(Graph graph){
if(graph == null){
return null;
}
// 生成优先级队列
PriorityQueue<Edge> queue = new PriorityQueue<>(new MyComparator());
Set<Edge> edgeSet = new HashSet<>();
Set<Node> nodeSet = new HashSet<>();
Set<Edge> res = new HashSet<>();
List<Node> nodeList = (List<Node>)graph.nodes.values();
for(Node node : nodeList){ // 防止森林,如果明确没有森林可不要此循环。
if(!nodeSet.contains(node)){
// 解锁点的所有边
nodeSet.add(node);
for(Edge temp : node.edges){
if(!edgeSet.contains(temp)){
queue.offer(temp);
edgeSet.add(temp);
}
}
while (!queue.isEmpty()){
// 权重最小的边
Edge cur = queue.poll();
// 此时from节点已经在nodeSet结合中解锁出来了,只需判断to节点是否解锁了
if(!nodeSet.contains(cur.to)){
// 要这条边
res.add(cur);
// 加入点的集合
nodeSet.add(cur.to);
// 解锁cur.to的所有边
for (Edge e : cur.to.edges){
if(!edgeSet.contains(e)){
queue.offer(e);
edgeSet.add(e);
}
}
}
}
}
}
return res;
}
static class MyComparator implements Comparator<Edge>{
@Override
public int compare(Edge o1, Edge o2) {
return o1.weight - o2.weight;
}
}
}
/**
* 1)Dijkstra算法必须指定一个源点
* 2)生成一个源点到各个点的最小距离表,一开始只有一条记录,即原点到自己的最小距离为0,源点到其他所有点的最小距离都为正无穷大
* 3)从距离表中拿出没拿过记录里的最小记录,通过这个点发出的边,更新源点到各个点的最小距离表,不断重复这一步
* 4)源点到所有的点记录如果都被拿过一遍,过程停止,最小距离表得到了
*/
public class Dijkstra {
public static Map<Node2,Integer> dijkstra1(Node2 node){
if(node == null){
return null;
}
// 距离表,一开始只有出发点到出发点的距离为0
Map<Node2,Integer> distanceMap = new HashMap<>();
distanceMap.put(node,0);
// 去重黑名单,每次选举最小记录时排除此表中的数据
Set<Node2> noSelect = new HashSet<>();
// 获取最小记录点(出发点到某一点的最短距离)
Node2 minDistance = getMinAndNoSelect(distanceMap,noSelect);
while (minDistance != null){ // 把表中数据都遍历完
for (Edge temp : minDistance.edges){
// 这个点往能出去的所有边能不能更新表中的距离
Node2 to = temp.to;
if(!distanceMap.containsKey(to)){
// 如果表中不包含这个点,说明还没找到过重from点到这个距离,直接加入表中
distanceMap.put(to,distanceMap.get(minDistance) + temp.weight);
}else{
distanceMap.put(to,Math.min(distanceMap.get(to),distanceMap.get(minDistance) + temp.weight));
}
}
minDistance = getMinAndNoSelect(distanceMap,noSelect);
}
return distanceMap;
}
private static Node2 getMinAndNoSelect(Map<Node2,Integer> distanceMap,Set<Node2> noSelcet){
// 由于Dijkstra算法中边都是非负的用0就可以了
int min = 0;
Node2 res = null;
// 遍历距离表
for (Map.Entry<Node2, Integer> item : distanceMap.entrySet()){
Node2 key = item.getKey();
if(noSelcet.contains(key)){
continue;
}
// 比较大小
if(item.getValue() < min){
min = item.getValue();
res = item.getKey();
}
}
noSelcet.add(res);
return res;
}
}