「左程云-算法与数据结构笔记」| P9 详解前缀树和贪心算法(下)

230 阅读3分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第八天,点击查看活动详情。

最近在看左神的数据结构与算法,考虑到视频讲的内容和给出的资料的PDF有些出入,不方便去复习,打算出一个左神的数据结构与算法的笔记系列供大家复习,同时也可以加深自己对于这些知识的掌握,该系列以视频的集数为分隔,今天是第六篇:P9|详解前缀树和贪心算法


六、Prim算法


1、实现思路

  • 首先随机选定一个结点作为最小生成树的起始结点
  • 然后获取该结点连接的边,找到权重最小的边,加入到最小生成树中,同时也要判断是否成环,如果成环就要跳过这个边

2、图解举例

image.png

3、实现代码

	public static int prim(int[][] graph) {  
	   int size = graph.length;  
	   int[] distances = new int[size];  
	   boolean[] visit = new boolean[size];  
	   visit[0] = true;  
	   for (int i = 0; i < size; i++) {  
	      distances[i] = graph[0][i];  
	   }  
	   int sum = 0;  
	   for (int i = 1; i < size; i++) {  
	      int minPath = Integer.MAX_VALUE;  
	      int minIndex = -1;  
	      for (int j = 0; j < size; j++) {  
	         if (!visit[j] && distances[j] < minPath) {  
	            minPath = distances[j];  
	            minIndex = j;  
	         }  
	      }  
	      if (minIndex == -1) {  
	         return sum;  
	      }  
	      visit[minIndex] = true;  
	      sum += minPath;  
	      for (int j = 0; j < size; j++) {  
	         if (!visit[j] && distances[j] > graph[minIndex][j]) {  
	            distances[j] = graph[minIndex][j];  
	         }  
	      }  
	   }  
	   return sum;  
	}
  1. 上述 graph[i][j] 表示 i与j结点的距离
  2. 首先选取起始结点0,然后使用distance[]记录0结点到各个结点的距离
  3. 同时使用visit标记结点0已经被访问过了
  4. 选取distance中最小的值,同时要保证该值没有被访问过(不成环)
  5. 选中之后,让选中的结点的visit标记它被访问了
  6. 然后要重新选定distance的值,但是值得注意的是,此时只有小于原本distance的值,并且该结点值没有被访问,同时满足这两个条件的值才会放进distance

3、Prim & Kruskal

  1. Prim算法需要进行遍历依次寻找邻边的最小值,进行多次比较;而Kruskal算法是需要对权重进行排序,即只需要对权重进行一次排序。
  2. 不难发现,Kruskal 在算法效率上比 Prim 快
  3. Kruskal 算法虽然是一个一个来,但是是按照边的权重依次来的,有可能产生两个独立的集合
  4. 而 Prim 算法按照点一次来,就不会产生两个独立的集合

七、Dijkastra算法


1、实现思路

  • Dijkstra算法算是贪心思想实现的
  • 首先把起点到所有点的距离存下来找个最短的,然后松弛一次再找出最短的
  • 所谓的松弛操作就是,遍历一遍看通过刚刚找到的距离最短的点作为中转站会不会更近,如果更近了就更新距离
  • 这样把所有的点找遍之后就存下了起点到其他所有点的最短距离

2、图解分析

  1. 对于该图,我们得到的理想数据是:
    1. 到结点②的最短路径为5
    2. 到结点③的最短路径为4
    3. 到结点④的最短路径为11
    4. 到结点⑤的最短路径为11

image.png 2. 起始时,使用一个HashMap记录结点到起始点的距离。即 Key 表示 该结点,value 表示 距离。对于没有记录的就是无穷大,即无法到达。 image.png 3. 选定最小的结点,即结点1,计算相邻的边,如果计算出来后,该结点不存在于HashMap中就加入,此处就加入了结点②、③、④。同时结点作为已选择的结点存入HashSet。 image.png 4. 在选定的结点以外的结点中,选择最小的路径的结点,结点③,此时结点③就确定下来,放进HashSet中,同时计算通过结点③到其他结点的距离,如果有更小的距离就做更新: 1. 到结点②的距离为:4+2=6>5 不做更新 2. 到结点④的距离为:4+7=11<12 更新 3. 到结点⑤的距离为:4+11=15 原本不存在该值,存入HashMap中 image.png 5. 在结点①、③之外的结点中选定一个值最小的结点,结点②,此时结点②也确定下来,放进HashSet中,同时计算通过结点②到其他结点的距离,如果有更小的就做更新: 1. 到结点⑤的距离为:5+6=11<15 更新 image.png 6. 最后两个结点同理,得到最后的结果: image.png

3、代码实现

	public static HashMap<Node, Integer> Dijkstra(Node head) {  
	   HashMap<Node, Integer> distanceMap = new HashMap<>();  
	   distanceMap.put(head, 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;  
	}