【图】Dijkstra(迪杰特斯拉)算法、左神Java版

什么是Dijkstra

给定一个图,从某点出发到达某点给出最短的路径
在这里插入图片描述
比如上述图,从A出发,到其余点的最短路径,返回这样的表
在这里插入图片描述

思路

我们先用一个表格记录A到其余点的距离,初始值是A到A的距离为0,与其余点距离正无穷,然后从A出发看可以直接到达的点距离为多少,接着获得距离更新表,有B,C,D,如果比原来的值小,就更新。锁定A点,然后用B,C,D其中一个点为起点再出发找最近的点,如果有更短的路径就更新,比如B到达C是2,而原本A到B为1,现在从起始点到C更短的距离是1+2=3<7所以更新C下的距离。当用了哪一个点,就锁定,以后再也不用它了。剩余的点与上同理。当所有的点都锁死时候,就返回了。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

代码

简易版

我们使用图的一般表示法来表示图
图的一般表示法

public static HashMap<Node, Integer> dijkstra1(Node from) {
	HashMap<Node, Integer> distanceMap = new HashMap<>();
	//key:从起始点出发到达key
	//value:从起始点出发到达key的最小距离
	//如果在表中,没有T的记录,含义是从from出发到T点的距离为正无穷
	distanceMap.put(from, 0);
	// 打过对号的点
	HashSet<Node> selectedNodes = new HashSet<>();
	
	//从未选择过的点(selectedNodes之外的)选择距离最小的点,selectedNodes是黑名单,不要从里面选
	Node minNode = getMinDistanceAndUnselectedNode(distanceMap, selectedNodes);
	while (minNode != null) {
		//  原始点  ->  minNode(跳转点)   最小距离distance
		int distance = distanceMap.get(minNode);
		for (Edge edge : minNode.edges) {
			Node toNode = edge.to;
			if (!distanceMap.containsKey(toNode)) {
				distanceMap.put(toNode, distance + edge.weight);
			} else { // toNode 
				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;
}

进阶版

我们找距离最短的点是遍历集合获得的,效率不够高,如果使用小根堆的话,可以不用遍历,直接拿最上面的。但是小根堆也有缺点,那就是不容易修改,所以我们可以自己写一个小根堆。
接下来是分析
小根堆需要的方法:add,update,ignore方法。增加,更新,忽略

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; // 实际的堆结构
	// key 某一个node, value 上面堆中的位置
	//如果进来之后弹出了,我们记录下标(value)为-1
	private HashMap<Node, Integer> heapIndexMap;
	// key 某一个节点, value 从源节点出发到该节点的目前最小距离
	private HashMap<Node, Integer> distanceMap;
	private int size; // 堆上有多少个点

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

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

	// 有一个点叫node,现在发现了一个从源节点出发到达node的距离为distance
	// 判断要不要更新,如果需要的话,就更新
	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]);
		// free C++同学还要把原本堆顶节点析构,对java同学不必
		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;
	}
}

// 改进后的dijkstra算法
// 从head出发,所有head能到达的节点,生成到达每个节点的最小路径记录并返回
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;
}

这个改进之后的算法,我目前还没理解明白,不是太懂,等学完堆和堆排序再回来看一遍把

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值