算法[5] 并查集、图

并查集、图

并查集

  1. 有若干个样本a、b、C、d类型假设是V
    2)在并查集中一开始认为每个样本都在单独的集合里
    3)用户可以在任何时候调用如下两个方法:
    boolean isSameSet(Vx, V y):查询样本x和样本y是否属于一个集合
    void union(V x, V y):把x和y各自所在集合的所有样本合并成- -个集合
  2. isSameSet和union方法的代价越低越好

步骤:

建立map,k为节点,v为节点的指向节点

1.首先第一步将所有各自的数目整成一个集合(每个节点单独形成一个集合)
2.在一个集合中每个节点都是自己集合中的代表节点,当集合合并的时候,如果决定将2挂到1的底下,此时2所在集合中,只有1节点指向自己,所以1为这个集合的代表节点。
3.由于每一个集合中都有一个集合的代表节点,所以可以通过代表节点来进行是否在同一个集合的判断,也可以将另外一个集合的代表节点连接到本集合中,此时整个集合也只有一个代表节点,实现集合的整合。(首先进行元素数目的判断,将少元素的集合挂到多元素的底下)
优化:在向上查找的过程中,将路径上的所有元素直接连接到代表节点上。

import java.util.HashMap;
import java.util.List;
import java.util.Stack;
 
public class Code04_UnionFind {
 
	public static class Element<V> {//加了个封装
		public V value;
 
		public Element(V value) {
			this.value = value;
		}
 
	}
 
	public static class UnionFindSet<V> {
		public HashMap<V, Element<V>> elementMap;//v为值,Element为元素
		public HashMap<Element<V>, Element<V>> fatherMap;//父节点
		public HashMap<Element<V>, Integer> sizeMap;//代表节点的集合有多少个节点
 
		public UnionFindSet(List<V> list) {
			elementMap = new HashMap<>();
			fatherMap = new HashMap<>();
			sizeMap = new HashMap<>();
			for (V value : list) {
				//初始化
				Element<V> element = new Element<V>(value);
				elementMap.put(value, element);
				fatherMap.put(element, element);//每个节点是自己的父
				sizeMap.put(element, 1);//每个节点是自己代表节点
			}
		}
 
		private Element<V> findHead(Element<V> element) {
			Stack<Element<V>> path = new Stack<>();//沿途的路
			while (element != fatherMap.get(element)) {
				path.push(element);//一直走到代表节点
				element = fatherMap.get(element);
			}
			while (!path.isEmpty()) {
                //扁平化,所有的节点都是直接指向代表节点的
				fatherMap.put(path.pop(), element);
			}
			return element;
		}
 
		public boolean isSameSet(V a, V b) {
			//看a有没有注册过,一个值都有对应的element
			if (elementMap.containsKey(a) && elementMap.containsKey(b)) {
				return findHead(elementMap.get(a)) == findHead(elementMap.get(b));
			}
            // 如果有一个点没有登记过则不进行查找
			return false;
		}
 
		public void union(V a, V b) {
			if (elementMap.containsKey(a) && elementMap.containsKey(b)) {
				//先找代表节点【头结点】
				Element<V> aF = findHead(elementMap.get(a));//找a对应的元素的代表节点
				Element<V> bF = findHead(elementMap.get(b));//代表节点里面有这个结合的大小
				if (aF != bF) {
					int aSetsize = sizeMap. get(aHead);
                    int bSetSize = sizeMap . get(bHead);
                    // bf 小,将小的节点挂在 大的节点上,直接副高parents中的记录
                    if (aSetSize >= bSetSize) {
                        parents . put(bF, aF);
                        sizeMap. put(aF, aSetSize + bSetSize); .
                        sizeMap . remove(bF);
                    } else {
                        parents . put( aF, bF);
                        sizeMap . put(bHead, aSetSize + bSetSize);
                        sizeMap . remove(bF);
                }
				
			}
		}
 
	}
 
}

岛屿问题

一个矩阵中只有0和1两种值,每个位置都可以和自己的上、下、左、右

  • 四个位置相连,如果有一片1连在一起,这个部分叫做一个岛,求一个矩阵中有多少个岛?
  • 举例:
    0 0 1 0 1 0
    1 1 1 0 1 0
    1 0 0 1 0 0
    0 0 0 0 0 0
    这个矩阵中有三个岛。
	public static int countIslands(Int[][] m){
	if(m == null || m[0] == null){
		return 0;
	}
	int N = m.length;
	int M = m[0].length;
	int res = 0;
	for(int i = 0;i<N;i++){
		for(int j = 0;j<M;j++{
			if(m[i][j] == 1){
				res++;
				infect(m,i,j,N,M);
			}
		}
	}
	return res;
}


public static void infect(int[][]m,int i,int j,int N,int M){
	if(i<0 || i>=N||j<0||j>=M || m[i][j] != 1){
		return ;
	}
	m[i][j] =2;
	infect(m,i+1,j,N,M);
	infect(m,i,j+1,N,M);
	infect(m,i-1,j,N,M);
	infect(m,i,j-1,N,M);
}

切分矩阵,并行

核心逻辑:切割矩阵。分成n块,记录边界的信息

我们把刀附近的2标记为属于a,之后合并的时候看一下,ac是不是一个集合,不是,但是刀左右,所以合并为一个岛屿。

记录边界信息,建立节点,然后用并查集来判断到底有几个集合

1).由点的集合和边的集合构成

2)虽然存在有向图和无向图的概念,但实际上都可以用有向图来表达

3)边上可能带有权值

图:

public class Graph {
	public HashMap<Integer,Node> nodes;//点的集合
	public HashSet<Edge> edges;//边的集合

	public Graph() {
		nodes = new HashMap<>();
		edges = new HashSet<>();
	}
}

点:

public class Node {
	public int value;//节点的数值
	public int in;//入度(有多少个节点指向我)
	public int out;//出度(我指向多少个节点)
	public ArrayList<Node> nexts;//从我出发能到达的下一级节点,邻居节点
	public ArrayList<Edge> edges;//从我出发发散出的边的集合

	public Node(int value) {
		this.value = value;
		in = 0;
		out = 0;
		nexts = new ArrayList<>();
		edges = new ArrayList<>();
	}
}

边:

public class Edge {
	public int weight;//这个边的权重是多少
	public Node from;//这个边从哪里出发
	public Node to;//这个边到达哪里的

	public Edge(int weight, Node from, Node to) {
		this.weight = weight;
		this.from = from;
		this.to = to;
	}

}

图生成器:

public class GraphGenerator {

	public static Graph createGraph(Integer[][] matrix) {//输入一个矩阵
		Graph graph = new Graph();//初始化自定义的图
		for (int i = 0; i < matrix.length; i++) {
			Integer weight = matrix[i][0];//边的权重
			Integer from = matrix[i][1];//from节点的序列
			Integer to = matrix[i][2];//to节点的序列
			if (!graph.nodes.containsKey(from)) {//先检查from节点存在否,不存在就建
				graph.nodes.put(from, new Node(from));
			}
			if (!graph.nodes.containsKey(to)) {//再检查to节点存在否,不存在就建立
				graph.nodes.put(to, new Node(to));
			}
			Node fromNode = graph.nodes.get(from);//拿出from点
			Node toNode = graph.nodes.get(to);//拿出to点
			Edge newEdge = new Edge(weight, fromNode, toNode);//建立新的边
			fromNode.nexts.add(toNode);//from的邻接点增加了一个to节点
			fromNode.out++;//from的出度加1
			toNode.in++;//to节点的入度加1
			fromNode.edges.add(newEdge);//from节点的边集增加
			graph.edges.add(newEdge);//加到整个图的边集里
		}
		return graph;
	}

}

广(宽)度优先遍历

1,利用队列实现
2,从源节点开始依次按照宽度进队列,然后弹出
3,每弹出一个点,把该节点所有没有进过队列的邻接点放入队 列
4,直到队列变空

public class Code_01_BFS {

	public static void bfs(Node node) {//广(宽)度优先遍历
		if (node == null) {
			return;
		}
		Queue<Node> queue = new LinkedList<>();
		HashSet<Node> map = new HashSet<>();
		queue.add(node);
		map.add(node);
		while (!queue.isEmpty()) {
			Node cur = queue.poll();//将当前节点拿出来
			System.out.println(cur.value);//打印当前的节点
			for (Node next : cur.nexts) {//遍历当前节点的所有邻接点
				if (!map.contains(next)) {//如果有某个邻接点已经在set里边了,就不再需要了
					map.add(next);
					queue.add(next);
				}
			}
		}
	}

}

深度优先遍历

1,利用栈实现
2,从源节点开始把节点按照深 度放入栈,然后弹出
3,每弹出一个点,把该节点下一个没有 进过栈的邻接点放入栈
4,直到栈变空

public class Code_02_DFS {

    public 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)){//如果set中没有下一个的节点就继续
                    stack.push(cur);//先把当前的节点重新放进去栈中
                    stack.push(next);//再把下一个节点放进去栈中
                    set.add(next);//set集合中添加下一个节点
                    System.out.println(next.value);//打印下一个节点的值
                    break;//不再继续进行当前的这个循环了,重新回到while循环中,继续下一个节点的遍历先
                }
            }
        }

    }
}

拓扑排序算法

适用范围:要求有向图,且有入度为0的节点,且没有环

实现的逻辑就是:先找出入度为0的节点,然后把他们打印,并且打印完成之后就马上删除掉,然后继续搜索图,找出新的入度为0的节点,继续前边的操作,直到图被完全遍历。

public class Code_03_TopologySort {

	// directed graph and no loop
	public static List<Node> sortedTopology(Graph graph) {
		HashMap<Node, Integer> inMap = new HashMap<>();//记录所有入点
		Queue<Node> zeroInQueue = new LinkedList<>();//记录所有入度为0的节点
		for (Node node : graph.nodes.values()) {//values就是当前所有点的意思
			inMap.put(node, node.in);//将入点全部登记
			if (node.in == 0) {
				zeroInQueue.add(node);//将所有入度为0的节点登记
				}
		}
		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);//找出新的入度为0的点
				if (inMap.get(next) == 0) {
					zeroInQueue.add(next);
				}
			}
		}
		return result;
	}
}

最小生成树以及相关算法

关于图的几个概念定义:

连通图:在无向图中,若任意两个顶点vivi与vjvj都有路径相通,则称该无向图为连通图。
强连通图:在有向图中,若任意两个顶点vivi与vjvj都有路径相通,则称该有向图为强连通图。
连通网:在连通图中,若图的边具有一定的意义,每一条边都对应着一个数,称为权;权代表着连接连个顶点的代价,称这种连通图叫做连通网。
生成树:一个连通图的生成树是指一个连通子图,它含有图中全部n个顶点,但只有足以构成一棵树的n-1条边。一颗有n个顶点的生成树有且仅有n-1条边,如果生成树中再添加一条边,则必定成环。
最小生成树:在连通网的所有生成树中,所有边的代价和最小的生成树,称为最小生成树。

kruskal算法(K算法) 适用范围:要求无向图

1)总是从权值最小的边开始考虑,依次考察权值依次变大的边
2)当前的边要么进入最小生成树的集合,要么丢弃
3)如果当前的边进入最小生成树的集合中不会形成环,就要当前边
4)如果当前的边进入最小生成树的集合中会形成环,就不要当前边
5)考察完所有边之后,最小生成树的集合也得到了

此算法可以称为“加边法”,初始最小生成树边数为0,每迭代一次就选择一条满足条件的最小代价边,加入到最小生成树的边集合里。

把图中的所有边按代价从小到大排序;
把图中的n个顶点看成独立的n棵树组成的森林;
按权值从小到大选择边,所选的边连接的两个顶点ui,viui,vi,应属于两颗不同的树,则成为最小生成树的一条边,并将这两颗树合并作为一颗树。
重复(3),直到所有顶点都在一颗树内或者有n-1条边为止。

//undirected graph only
public class Code_04_Kruskal {

	// Union-Find Set
	public static class UnionFind {
		private HashMap<Node, Node> fatherMap;
		private HashMap<Node, Integer> rankMap;

		public UnionFind() {
			fatherMap = new HashMap<Node, Node>();
			rankMap = new HashMap<Node, Integer>();
		}

		private Node findFather(Node n) {
			Node father = fatherMap.get(n);
			if (father != n) {
				father = findFather(father);
			}
			fatherMap.put(n, father);
			return father;
		}

		public void makeSets(Collection<Node> nodes) {
			fatherMap.clear();
			rankMap.clear();
			for (Node node : nodes) {
				fatherMap.put(node, node);
				rankMap.put(node, 1);
			}
		}

		public boolean isSameSet(Node a, Node b) {
			return findFather(a) == findFather(b);
		}

		public void union(Node a, Node b) {
			if (a == null || b == null) {
				return;
			}
			Node aFather = findFather(a);
			Node bFather = findFather(b);
			if (aFather != bFather) {
				int aFrank = rankMap.get(aFather);
				int bFrank = rankMap.get(bFather);
				if (aFrank <= bFrank) {
					fatherMap.put(aFather, bFather);
					rankMap.put(bFather, aFrank + bFrank);
				} else {
					fatherMap.put(bFather, aFather);
					rankMap.put(aFather, aFrank + bFrank);
				}
			}
		}
	}

	public static class EdgeComparator implements Comparator<Edge> {

		@Override
		public int compare(Edge o1, Edge o2) {
			return o1.weight - o2.weight;
		}

	}

	public static Set<Edge> kruskalMST(Graph graph) {
		UnionFind unionFind = new UnionFind();
		unionFind.makeSets(graph.nodes.values());
		PriorityQueue<Edge> priorityQueue = new PriorityQueue<>(new EdgeComparator());
		for (Edge edge : graph.edges) {
			priorityQueue.add(edge);
		}
		Set<Edge> result = new HashSet<>();
		while (!priorityQueue.isEmpty()) {
			Edge edge = priorityQueue.poll();
			if (!unionFind.isSameSet(edge.from, edge.to)) {
				result.add(edge);
				unionFind.union(edge.from, edge.to);
			}
		}
		return result;
	}
}

Prim算法(P算法)

此算法可以称为“加点法”,每次迭代选择代价最小的边对应的点,加入到最小生成树中。算法从某一个顶点s开始,逐渐长大覆盖整个连通网的所有顶点。

图的所有顶点集合为VV;初始令集合u={s},v=V−uu={s},v=V−u;
在两个集合u,vu,v能够组成的边中,选择一条代价最小的边(u0,v0)(u0,v0),加入到最小生成树中,并把v0v0并入到集合u中。
重复上述步骤,直到最小生成树有n-1条边或者n个顶点为止。
由于不断向集合u中加点,所以最小代价边必须同步更新;需要建立一个辅助数组closedge,用来维护集合v中每个顶点与集合u中最小代价边信息,:

// undirected graph only
public class Code_05_Prim {

	public static class EdgeComparator implements Comparator<Edge> {

		@Override
		public int compare(Edge o1, Edge o2) {
			return o1.weight - o2.weight;
		}

	}

	public static Set<Edge> primMST(Graph graph) {
		PriorityQueue<Edge> priorityQueue = new PriorityQueue<>(
				new EdgeComparator());
		HashSet<Node> set = new HashSet<>();
		Set<Edge> result = new HashSet<>();
		for (Node node : graph.nodes.values()) {
			if (!set.contains(node)) {
				set.add(node);
				for (Edge edge : node.edges) {
					priorityQueue.add(edge);
				}
				while (!priorityQueue.isEmpty()) {
					Edge edge = priorityQueue.poll();//从优先级队列中弹出一个最小的边
					Node toNode = edge.to;
					if (!set.contains(toNode)) {
						set.add(toNode);
						result.add(edge);
						for (Edge nextEdge : toNode.edges) {
							priorityQueue.add(nextEdge);
						}
					}
				}
			}
		}
		return result;
	}

}

迪杰斯特拉算法

求解单元点的最短路径问题:给定带权有向图G和源点v,求v到G中其他顶点的最短路径

限制条件:图G中不存在负权值的边

迪杰斯特拉算法总共就干了两件事:

【1】不断运行广度优先算法找可见点,计算可见点到源点的距离长度

【2】从当前已知的路径中选择长度最短的将其顶点加入S作为确定找到的最短路径的顶点。

// no negative weight
public class Code_06_Dijkstra {

	public static HashMap<Node, Integer> dijkstra1(Node from) {
		HashMap<Node, Integer> distanceMap = new HashMap<>();
		distanceMap.put(from, 0);
        //已经选择过的结点
		HashSet<Node> selectedNodes = new HashSet<>();

		Node minNode = getMinDistanceAndUnselectedNode(distanceMap, selectedNodes);
		while (minNode != null) {
			int distance = distanceMap.get(minNode);
			for (Edge edge : minNode.edges) {
				Node toNode = edge.to;
				if (!distanceMap.containsKey(toNode)) {
					distanceMap.put(toNode, distance + edge.weight);
				}
				distanceMap.put(edge.to, Math.min(distanceMap.get(toNode), distance + edge.weight));
			}
			selectedNodes.add(minNode);
			minNode = getMinDistanceAndUnselectedNode(distanceMap, selectedNodes);
		}
		return distanceMap;
	}

	public static Node getMinDistanceAndUnselectedNode(HashMap<Node, Integer> distanceMap, 
			HashSet<Node> touchedNodes) {
		Node minNode = null;
		int minDistance = Integer.MAX_VALUE;
		for (Entry<Node, Integer> entry : distanceMap.entrySet()) {
			Node node = entry.getKey();
			int distance = entry.getValue();
			if (!touchedNodes.contains(node) && distance < minDistance) {
				minNode = node;
				minDistance = distance;
			}
		}
		return minNode;
	}

	public static class NodeRecord {
		public Node node;
		public int distance;

		public NodeRecord(Node node, int distance) {
			this.node = node;
			this.distance = distance;
		}
	}

	public static class NodeHeap {
		private Node[] nodes;
		private HashMap<Node, Integer> heapIndexMap;
		private HashMap<Node, Integer> distanceMap;
		private int size;

		public NodeHeap(int size) {
			nodes = new Node[size];
			heapIndexMap = new HashMap<>();
			distanceMap = new HashMap<>();
			this.size = 0;
		}

		public boolean isEmpty() {
			return size == 0;
		}

		public void addOrUpdateOrIgnore(Node node, int distance) {
			if (inHeap(node)) {
				distanceMap.put(node, Math.min(distanceMap.get(node), distance));
				insertHeapify(node, heapIndexMap.get(node));
			}
			if (!isEntered(node)) {
				nodes[size] = node;
				heapIndexMap.put(node, size);
				distanceMap.put(node, distance);
				insertHeapify(node, size++);
			}
		}

		public NodeRecord pop() {
			NodeRecord nodeRecord = new NodeRecord(nodes[0], distanceMap.get(nodes[0]));
			swap(0, size - 1);
			heapIndexMap.put(nodes[size - 1], -1);
			distanceMap.remove(nodes[size - 1]);
			nodes[size - 1] = null;
			heapify(0, --size);
			return nodeRecord;
		}

		private void insertHeapify(Node node, int index) {
			while (distanceMap.get(nodes[index]) < distanceMap.get(nodes[(index - 1) / 2])) {
				swap(index, (index - 1) / 2);
				index = (index - 1) / 2;
			}
		}

		private void heapify(int index, int size) {
			int left = index * 2 + 1;
			while (left < size) {
				int smallest = left + 1 < size && distanceMap.get(nodes[left + 1]) < distanceMap.get(nodes[left])
						? left + 1 : left;
				smallest = distanceMap.get(nodes[smallest]) < distanceMap.get(nodes[index]) ? smallest : index;
				if (smallest == index) {
					break;
				}
				swap(smallest, index);
				index = smallest;
				left = index * 2 + 1;
			}
		}

		private boolean isEntered(Node node) {
			return heapIndexMap.containsKey(node);
		}

		private boolean inHeap(Node node) {
			return isEntered(node) && heapIndexMap.get(node) != -1;
		}

		private void swap(int index1, int index2) {
			heapIndexMap.put(nodes[index1], index2);
			heapIndexMap.put(nodes[index2], index1);
			Node tmp = nodes[index1];
			nodes[index1] = nodes[index2];
			nodes[index2] = tmp;
		}
	}

	public static HashMap<Node, Integer> dijkstra2(Node head, int size) {
		NodeHeap nodeHeap = new NodeHeap(size);
		nodeHeap.addOrUpdateOrIgnore(head, 0);
		HashMap<Node, Integer> result = new HashMap<>();
		while (!nodeHeap.isEmpty()) {
			NodeRecord record = nodeHeap.pop();
			Node cur = record.node;
			int distance = record.distance;
			for (Edge edge : cur.edges) {
				nodeHeap.addOrUpdateOrIgnore(edge.to, edge.weight + distance);
			}
			result.put(cur, distance);
		}
		return result;
	}

}

借鉴1

借鉴2

借鉴3

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值