算法笔记第6章--图

图的题笔试爱出,今年也不多,考察就数组就行
图难的地方在于,你已经会了的算法,但是由于图的表达方式一变,你的算法也得改变,因此得有一个万能的格式来表达图。

图的表达方式

  1. 邻接表
    邻接表
    如果有路径长度,则在节点上添加一个数据项即可, 一行一个链表形状
  2. 邻接矩阵
    邻接矩阵
    自己到自己0,节点不通就是∞,节点相通就是路径长度
public class Graph {
	public HashMap<Integer,Node> nodes;//(编号,具体点)点集
	public HashSet<Edge> edges;//边集

	public Graph() {
		nodes = new HashMap<>();
		edges = new HashSet<>();
	}
}
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 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<>();
	}
}

广度优先遍历(BFS)

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

//从node出发,BFS整个图
public static void bfs(Node node) {
	if (node == null) {
		return;
	}
	Queue<Node> queue = new LinkedList<>();
	HashSet<Node> set = new HashSet<>();//防止重复进入队列
	queue.add(node);
	set.add(node);//进入一次队列,就得在set里注册一遍
	//可换成数组,1~1000值代表节点城市
	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);
			}
		}
	}
}

深度优先遍历(DFS)

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

	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)) {//未访问过连同cur都重新放回去,再在set里注册
					stack.push(cur);//重新压栈是为了等这条路走尽了,回来时看当前节点还有没有其他路
					stack.push(next);
					set.add(next);
					System.out.println(next.value);
					break;//先逮着一条路走到黑
				}
			}
		}
	}

拓扑排序算法

需要图了
要求:有向图,且有入度为0的节点,且没有环
(在config里常用,依赖包编译的顺序)
拓扑编译顺序
B依赖E,C,D; A依赖B,C,D.
所以,E,C,D编译完成才能编译B
分析:找入度为零的点,删除它及其它的影响,再找入度为零的点,循环

// directed graph and no loop
public static List<Node> sortedTopology(Graph graph) {
	//key:某一个node   value:其剩余的入度
	HashMap<Node, Integer> inMap = new HashMap<>();
	//当其入度为零,进入队列
	Queue<Node> zeroInQueue = new LinkedList<>();
	
	//信息录入
	for (Node node : graph.nodes.values()) {
		inMap.put(node, node.in);//原始的节点, 及其入度
		if (node.in == 0) {
			zeroInQueue.add(node);//原始入度为零 入队列
		}
	}
	
	//拓扑排序的结果,依次加入result
	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算法

图解:以边的角度来
并查集 结构实现算法(并查集后续再看,本文使用其他方法替代)
分析: 把最小的边加上,无环加上,有环放弃(重点是如何知到其有无环)
各自为一个集合,新加进来的看两个人在不在一个集合里,不在合并(无环),在就放弃(就会产生环)

public static class MySet{//并查集比这个快, 同样实现三个方法
	//当前节点, 它所在的集合
	public HashMap<Node, List<Node>> setMap;
	
	//构造函数,每个节点各自为一个集合
	public MySet(List<Node> nodes){
		for(Node cur : nodes){
			List<Node> set = new ArrayList<Node>();//创建它自己的集合
			set.add(cur);
			setMap.put(cur, set);//记录
		}
	}
	
	//是否在同一个集合
	public boolean isSameSet(Node from, Node to){
		List<Node> fromSet = setMap.get(from);
		List<Node> toSet = setMap.get(to);
		return fromSet == toSet;
	}
	
	//合并到同一个集合
	public void union(Node from, Node to){
		List<Node> fromSet = setMap.get(from);
		List<Node> toSet = setMap.get(to);
		for(Node toNode : toSet){
			fromSet.add(toNode);//toSet里的元素加入formSet里
			setMap.put(toNode, fromSet);//同时录入setMap
		}
	}
}
//--------------------kruskalMST--------------------------------//
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) {//M条边
		priorityQueue.add(edge);//O(logM)
	}
	Set<Edge> result = new HashSet<>();
	while (!priorityQueue.isEmpty()) {//M条边
		Edge edge = priorityQueue.poll();//O(logM)
		if (!unionFind.isSameSet(edge.from, edge.to)) {//O(1)
			result.add(edge);
			unionFind.union(edge.from, edge.to);
		}
	}
	return result;
}



prim算法

图解:选个起始点不断把其他的拉进来
HashSet就行,一个一个加进来, 不用一下合并

public static Set<Edge> primMST(Graph graph) {
	//解锁的边进入小根堆
	PriorityQueue<Edge> priorityQueue = new PriorityQueue<>(new EdgeComparator());
	HashSet<Node> set = new HashSet<>();//考察过的点就在这,一个set就替代集合结构
	Set<Edge> result = new HashSet<>();//依次挑选的边在result里
	for (Node node : graph.nodes.values()) {//森林;处理多个图各自不连通,让其各自成为最小生成树(1个图可以不要for,随便拿一个点,)
	
		if (!set.contains(node)) {
			//node是起始点
			set.add(node);
			for (Edge edge : node.edges) {//由一个点,解锁其相连的所有的边,全放入小根堆
				priorityQueue.add(edge);
			}
			while (!priorityQueue.isEmpty()) {
				Edge edge = priorityQueue.poll();//弹出解锁的边中的最小边
				Node toNode = edge.to;//看下最小边的to节点,是否是新节点
				if (!set.contains(toNode)) {//不含,就是新的点
					set.add(toNode);
					result.add(edge);
					for (Edge nextEdge : toNode.edges) {//把to节点的所有边,放入优先级队列
						priorityQueue.add(nextEdge);//边会重复,不影响; 因为重复的边的to节点已经访问过了不是新的节点,最终重复的边会弹出,不会影响结果					}
				}
			}
		}
	}
	return result;
}

Dijkstra算法

图解:
适用范围:环的权值累加和不能为负数,越转越小(floyd),不存在负权边
找最小边的通路
改进: 用自定义改进堆实现, 如果进入堆之后,不许要改值,只让他弹顶-用系统堆。
否则,用自己写的堆
图解

//从head出发到所有节点的最小距离
public static HashMap<Node, Integer> dijkstra1(Node head) {
	//key: 从head出发到达key
	//value: 从head出发到达key的最小距离
	//如果表中无T的记录, 即head和T的距离为 ∞
	HashMap<Node, Integer> distanceMap = new HashMap<>();
	distanceMap.put(head, 0);//自己个儿
	HashSet<Node> selectedNodes = new HashSet<>();//已经求过距离的节点, 存入selectedNodes, 再也不碰
	Node minNode = getMinDistanceAndUnselectedNode(distanceMap, selectedNodes);//distanceMap找最小且未选择过的节点,找不到return null
	while (minNode != null) {
		int distance = distanceMap.get(minNode);
		for (Edge edge : minNode.edges) {
			Node toNode = edge.to;
			if (!distanceMap.containsKey(toNode)) {//Map没有记录的边放进去
				distanceMap.put(toNode, distance + edge.weight);
			}
			distanceMap.put(edge.to, 
			Math.min(distanceMap.get(toNode), distance + edge.weight));//然后,更新路径最小值(之前的路径,和经过当前节点得到的路径,Ec(先)和Ed(后))
		}
		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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值