四种经典图算法(未debug版)

前置知识

为啥要自建类

有些平台不给用集合,就很牛

path定义

1 长度为3
2 分别为权重,初始点,连接点(对于有向图而言两者是有区别的)

并查集

并查集思想

并查集是一种集合划分方法,而且还可以找到上一个点的路径
如果两个元素根节点不同,说明不在同一集合,可以创建路径,否则不能
对于像kruskal这种直接选取边的算法来说,每条边选取时都需要判断环,而成环原因就是一个集合中有点再次相连
又比如prim,每次取边时,还要判断这条边是否有点存在于生成树中(因为prim每次取和生成数相连最近的节点),我们使用集合表示生成树就可以轻易解出,由于

并查集优化

1 每次查找这个点是否属于某个集合时,顺便将这个点父节点直接改为根(但是压缩后会导致无法打印路径)
2 两个集合合并后,一个根节点会指向另一个,这样有一个集合相当于其中每个元素都增加遍历一次,所以使用根节点记录节点数,这样每次可以拿大的合并小的,减少合并后遍历平均消耗

单源最短路径

dijkstra

核心思想

维护一个最小数组,每次获取数组最小点,然后拿最小点路径更新数组,直到整个数组都被访问过

实现

class HeadNode{
    Node head;
    Node end;
}
class Node{
    Node next;
    int dist;
    int spend;
    public Node(int dist,int spend){
        this.dist = dist;
        this.spend = spend;
    }
}
class Solution {
    public int[] dijkstra(int N,int[][] path){
        HeadNode[] nodes = new HeadNode[N];
        boolean[] visited = new boolean[N];
        int[] spendArr = new int[N];
        int len = path.length;
        Arrays.fill(spendArr, Integer.MAX_VALUE);
        spendArr[0] = 0;
        for(int i=0;i<N;i++){
            nodes[i] = new HeadNode();
        }
        //初始化图
        for(int i=0;i<len;i++){
            int val = path[i][0];
            int src = path[i][1];
            int dist = path[i][2];
            Node newNode = new Node(dist,val);
            HeadNode list = nodes[src];
            if(list.head==null){
                list.head = list.end = newNode;
            }else{
                list.end.next = newNode;
                list.end = list.end.next;
            }
        }
        DFS(0,visited,nodes,spendArr);
        return spendArr;
    }
    /**
    	每次在数组更新当前节点到其它节点距离,然后选取数组最大值,作为新节点,进入递归
    */
    public void DFS(int src,boolean[] visited,HeadNode[] nodes,int[] spendArr){
        visited[src] = true;
        Node head = nodes[src].head;
        while(head!=null){
            int dist = head.dist;
            int val = spendArr[src]+head.spend;
            if(val<spendArr[dist]){
                spendArr[dist] = val;
            }
            head = head.next;
        }
        int len = visited.length;
        int nextDistIndex = -1;
        int nextDistSpend = Integer.MAX_VALUE;
        for(int i=0;i<len;i++){
            if((spendArr[i]<nextDistSpend)&&!visited[i]){
                nextDistSpend = spendArr[i];
                nextDistIndex = i;
            }
        }
        if(nextDistIndex!=-1){
            DFS(nextDistIndex,visited,nodes,spendArr);
        }
    }
}

多源最短路径

Floyd

思想

首先记录所有连通边,然后拿各顶点作为中间点,让所有两点都尝试经过这个点,来更新两点间最短距离。
首先更新所有到零的边
可能有人会想,中间点按0-k增加是否会导致错漏,如果最短路径为1->2->0->3,看起来就不是顺序的,首先我们知道,每个节点两点间必然存在,也就是1->2,2->0,0->3,然后会获得2->0->3,1->2->0如果1->2->0->3是最短路径,那么2->0->3一定也是2->3的最短路径,不然为什么不是2->4->3呢
如果最短路径是1->2->4->3,这个就不用验证了,1->2->0->3就算存在,那么也会被1->2->4->3直接取代,因为这是顺序的
ee行吧我证不出来我就是在胡说八道

实现

public void Floyd(int N,int[][] path){
	int[][] graph = new int[N][N];
	int[][] pathGraph = new int[N][N];
	//初始化图,并连接边
	for(int i =0;i<N;i++){
		for(int j=0;j<N;j++){
			graph[i][j] = Integer.MAX_VALUE;
			pathGraph[i][j] = -1;
		}
	}
	for(int[] p:path){
		int val = p[0];
		int a = p[1];
		int b = p[2];
		graph[a][b] = graph[b][a] = val;
	}
	/**
		每次以不同节点作为中间点,更新图
	*/
	for(int k=0;k<N;k++){
		for(int i=0;i<N;i++){
			for(int j=0;j<N;j++){
				if(graph[i][j]>graph[i][k]+graph[k][j]){
					graph[i][j] = graph[i][k]+graph[k][j];
					pathGraph[i][j] = k;
				}
			}
		}
	}
	for(int i=0;i<N;i++){
		for(int j=0;j<N;j++){
			if(graph[i][j]!=Interger.MAX_VALUE){
				print(i,j,pathGraph);
			}
		}
	}
}
/**
	如果是-1,说明两点直接连接
	如果不是,那就是i,j之间中间点k了,那我们输出i,拿k,j再次代入
*/
public void print(int i,int j,int[][] pathGraph){
	System.out.println(i+"->");
	if(pathGraph[i][j]==-1){
		System.out.println(j);
		return;
	}else{
		print(pathGraph[i][j],j);
	}
}

最小生成树

prim

核心思想

每次取最近点加入
简单的并查集思想,纳入生成树时边值设为0,这个点其余连接生成树的边不可能比这个更小了,所以相当于加入集合

实现

class HeadNode{
    Node head;
    Node end;
}
class Node{
    Node next;
    int dist;
    int spend;
    public Node(int dist,int spend){
        this.dist = dist;
        this.spend = spend;
    }
}
class Solution {
    public int[] prim(int N,int[][] path){
        HeadNode[] nodes = new HeadNode[N];
        int[] spendArr = new int[N];
        int len = path.length;
        Arrays.fill(spendArr, Integer.MAX_VALUE);
        //默认由0出发,所以到0点消耗为0
        spendArr[0] = 0;
        for(int i=0;i<N;i++){
            nodes[i] = new HeadNode();
            result[i] = new HeadNode();
        }
        for(int i=0;i<N;i++){
            for(int j=0;j<N;j++){
            	result[i][j] = Integer.MAX_VALUE;
        	}
        }
        //初始化图
        for(int i=0;i<len;i++){
            int val = path[i][0];
            int src = path[i][1];
            int dist = path[i][2];
            Node newNode = new Node(dist,val);
            HeadNode list = nodes[src];
            if(list.head==null){
                list.head = list.end = newNode;
            }else{
                list.end.next = newNode;
                list.end = list.end.next;
            }
        }
        DFS(0,nodes,spendArr,result);
        //判断最小生成树是否成立
        for(int i=0;i<N;i++){
        	if(spendArr[i]!=0) return null;
        }
        return result;
    }
    public void DFS(int src,HeadNode[] nodes,int[] spendArr){
        Node head = nodes[src].head;
        //和dijkstra一样,更新数组
        while(head!=null){
            int dist = head.dist;
            int val = spendArr[src]+head.spend;
            if(val<spendArr[dist]){
                spendArr[dist] = val;
            }
            head = head.next;
        }
        
        int len = nodes.length;
        int nextDistIndex = -1;
        int nextDistSpend = Integer.MAX_VALUE;
        //选择最接近生成树的点
        for(int i=0;i<len;i++){
        	//如果spendArr等于零,代表加入生成树了,直接跳过不管
            if((spendArr[i]<nextDistSpend)&&spendArr[i]!=0){
                nextDistSpend = spendArr[i];
                nextDistIndex = i;
            }
        }
        //将其加入生成树,并写入结果图
        if(nextDistIndex!=-1){
        	spendArr[nextDistIndex] = 0;
        	result[src][nextDistIndex] = val;
            DFS(nextDistIndex,nodes,spendArr);
        }
    }
}

kruskal

核心思想

每次取最小边,融入边集

实现

初始化一个visited数组
给所有边排序,每次从小到大取
使用负数代表根节点,数值代表这个根下有几个点

细心的小伙伴可能会发现两个优化点
1 传入的path可以用链表承装,这样每次就可以删除用过的路径了
2 如果将set也返回,就可以利用set快速得到result中的路径,而不用慢慢遍历result
这两点就交给你们辣!!

public int[][] prim(int N,int[][] path){
    boolean[] visited = new boolean[N];
    int[] set = new int[N];
    int[][] result = new int[N][N];
    int len = path.length;
    Arrays.fill(set,-1);
    Arrays.sort(path,new Comparator<int[]>() {
        @Override
        public int compare(int[] o1, int[] o2){
            return o1[0]-o2[0];
        }
    });
    int[][] graph = new int[N][N];
    for(int i=0;i<N;i++){
        for(int j=0;j<N;j++){
            graph[i][j]=Integer.MAX_VALUE;
        }
    }
    for(int i=0;i<N;i++){
    	boolean success = false;
    	for(int j=0;j<len;j++){
    		//说明这条边用过了
    		if(path[j][0]==-1) continue;
	        int a= path[j][1];
	        int b = path[j][2];
	        int aVal = set[a];
	        int bVal = set[b];
	        //寻找根节点
	        while(aVal>0){
	        	a = set[a];
	        	aval = set[a];
	        }
	        while(bVal>0){
	        	b = set[b];
	        	bval = set[b];
	        }
	        //说明是两个集合
	        if(a!=b){
	        	result[a][b] = val;
	        	//由于负数表示,越小数越多,拿数多的合并少的
	        	if(aVal<=bVal){
	        		set[a] = aVal + bVal;
	        		set[b] = a;
	        	}else{
	        		set[b] = aVal + bVal;
	        		set[a] = b;
	        	}
	        	path[i][0] = -1;
	        	success = true;
	        	break;
	       	}
   		}
   		if(!success){
   			//每趟纳入一个节点,如果某一趟没有纳入,说明没有生成树
   			return null;
   		 }
    }
    return result;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值