1. 程式人生 > >藍橋杯_演算法訓練_安慰奶牛(用Kruskal、Prim演算法分別實現)

藍橋杯_演算法訓練_安慰奶牛(用Kruskal、Prim演算法分別實現)

問題描述
Farmer John變得非常懶,他不想再繼續維護供奶牛之間供通行的道路。
道路被用來連線N個牧場,牧場被連續地編號為1到N。每一個牧場都是一個奶牛的家。
FJ計劃除去P條道路中儘可能多的道路,但是還要保持牧場之間 的連通性。
你首先要決定那些道路是需要保留的N-1條道路。
第j條雙向道路連線了牧場Sj和Ej(1 <= Sj <= N; 1 <= Ej <= N; Sj != Ej),而且走完它需要Lj的時間。
沒有兩個牧場是被一條以上的道路所連線。
奶牛們非常傷心,因為她們的交通系統被削減了。
你需要到每一個奶牛的住處去安慰她們。每次你到達第i個牧場的時候(即使你已經到過),你必須花去Ci的時間和奶牛交談。
你每個晚上都會在同一個牧場(這是供你選擇的)過夜,直到奶牛們都從悲傷中緩過神來。
在早上 起來和晚上回去睡覺的時候,你都需要和在你睡覺的牧場的奶牛交談一次。
這樣你才能完成你的 交談任務。假設Farmer John採納了你的建議,請計算出使所有奶牛都被安慰的最少時間。

輸入格式
第1行包含兩個整數N和P。
接下來N行,每行包含一個整數Ci。
接下來P行,每行包含三個整數Sj, Ej和Lj。

輸出格式
輸出一個整數, 所需要的總時間(包含和在你所在的牧場的奶牛的兩次談話時間)。
樣例輸入
5 6
10
10
20
6
30
1 2 5
2 3 5
2 4 12
3 4 17
2 5 15
3 5 6
樣例輸出
176
資料規模與約定
5 <= N <= 10000,N-1 <= P <= 100000,0 <= Lj <= 1000,1 <= Ci <= 1,000。

Kruskal演算法簡述:
假設 WN=(V,{E}) 是一個含有 n 個頂點的連通網,則按照克魯斯卡爾演算法構造最小生成樹的過程為:
先構造一個只含 n 個頂點,而邊集為空的子圖,
若將該子圖中各個頂點看成是各棵樹上的根結點,則它是一個含有 n 棵樹的一個森林。
之後,從網的邊集 E 中選取一條權值最小的邊,若該條邊的兩個頂點分屬不同的樹,則將其加入子圖,
也就是說,將這兩個頂點分別所在的兩棵樹合成一棵樹;反之,若該條邊的兩個頂點已落在同一棵樹上,則不可取,
而應該取下一條權值最小的邊再試之。依次類推,直至森林中只有一棵樹,也即子圖中含有 n-1條邊為止。

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Scanner;
import java.util.function.Function;
import java.util.function.ToDoubleFunction;
import java.util.function.ToIntFunction;
import java.util.function.ToLongFunction;

/**
 * @author 翔
 *
 */
public class Main { private static int nodeNum;//節點個數 private static int edgeNum;//邊數 private static int[] C;//節點權值 private static int Cmin;//最小節點權值 private static ArrayList<Edge> edges=new ArrayList<Edge>();//邊集合 private static ArrayList<Edge> Enew=new ArrayList<Edge>();//Kruskal演算法中的Enew private static int[] root;//root[i]:代表i節點所在樹對應的根節點 /** * @param args */ public static void main(String[] args) { // TODO Auto-generated method stub init(); kruskal(); System.out.println(caculate()); } //計算所需要的總時間 private static int caculate(){ int sum=0; for(int i=0;i<Enew.size();i++){ sum+=Enew.get(i).weight; } return sum+Cmin; } //Kruskal演算法的主要步驟 private static void kruskal(){ for(int i=0;i<edges.size()&&Enew.size()<nodeNum-1;i++){ Edge edge=edges.get(i); if(!inSameTree(edge.from,edge.to)){ Enew.add(edge); root[edge.to]=root[edge.from]; } } } //判斷兩個頂點是否在同一棵樹裡 private static boolean inSameTree(int from,int to){ if(findRoot(from)==findRoot(to)){ return true; }else{ return false; } } //尋找某節點的根節點 private static int findRoot(int i){ if(root[i]==i){ return i; }else{ return root[i]=findRoot(root[i]); } } //初始化各引數的值 private static void init(){ Scanner sc=new Scanner(System.in); nodeNum=sc.nextInt(); edgeNum=sc.nextInt(); C=new int[nodeNum+1]; root=new int[nodeNum+1]; for(int i=1;i<=nodeNum;i++){ C[i]=sc.nextInt(); root[i]=i; } findCmin();//尋找出最小節點權值 for(int i=0;i<edgeNum;i++){ Edge edge=new Edge(sc.nextInt(),sc.nextInt(),sc.nextInt()); edges.add(edge); } //重新計算邊權!!!!重點!!!!!!!!!!!! for(int i=0;i<edgeNum;i++){ edges.get(i).weight=edges.get(i).weight*2+C[edges.get(i).from]+C[edges.get(i).to]; } Collections.sort(edges,new EdgeComparator());//按照權值對邊集合排序 sc.close(); } //尋找出最小節點權值 private static void findCmin(){ int min=C[1]; for(int i=2;i<=nodeNum;i++){ if(C[i]<min){ min=C[i]; } } Cmin=min; } } class Edge{ int from; int to; int weight; public Edge(int from,int to,int weight){ this.from=from; this.to=to; this.weight=weight; } } class EdgeComparator implements Comparator<Edge>{ public int compare(Edge arg0, Edge arg1) { // TODO Auto-generated method stub return arg0.weight-arg1.weight; } public static <T, U extends Comparable<? super U>> Comparator<T> comparing( Function<? super T, ? extends U> arg0) { // TODO Auto-generated method stub return null; } public static <T, U> Comparator<T> comparing( Function<? super T, ? extends U> arg0, Comparator<? super U> arg1) { // TODO Auto-generated method stub return null; } public static <T> Comparator<T> comparingDouble( ToDoubleFunction<? super T> arg0) { // TODO Auto-generated method stub return null; } public static <T> Comparator<T> comparingInt(ToIntFunction<? super T> arg0) { // TODO Auto-generated method stub return null; } public static <T> Comparator<T> comparingLong(ToLongFunction<? super T> arg0) { // TODO Auto-generated method stub return null; } public static <T extends Comparable<? super T>> Comparator<T> naturalOrder() { // TODO Auto-generated method stub return null; } public static <T> Comparator<T> nullsFirst(Comparator<? super T> arg0) { // TODO Auto-generated method stub return null; } public static <T> Comparator<T> nullsLast(Comparator<? super T> arg0) { // TODO Auto-generated method stub return null; } public static <T extends Comparable<? super T>> Comparator<T> reverseOrder() { // TODO Auto-generated method stub return null; } public Comparator<Edge> reversed() { // TODO Auto-generated method stub return null; } public Comparator<Edge> thenComparing(Comparator<? super Edge> arg0) { // TODO Auto-generated method stub return null; } public <U extends Comparable<? super U>> Comparator<Edge> thenComparing( Function<? super Edge, ? extends U> arg0) { // TODO Auto-generated method stub return null; } public <U> Comparator<Edge> thenComparing( Function<? super Edge, ? extends U> arg0, Comparator<? super U> arg1) { // TODO Auto-generated method stub return null; } public Comparator<Edge> thenComparingDouble( ToDoubleFunction<? super Edge> arg0) { // TODO Auto-generated method stub return null; } public Comparator<Edge> thenComparingInt(ToIntFunction<? super Edge> arg0) { // TODO Auto-generated method stub return null; } public Comparator<Edge> thenComparingLong(ToLongFunction<? super Edge> arg0) { // TODO Auto-generated method stub return null; } }

Prim演算法簡述
1).輸入:一個加權連通圖,其中頂點集合為V,邊集合為E;
2).初始化:Vnew= {x},其中x為集合V中的任一節點(起始點),Enew= {},為空;
3).重複下列操作,直到Vnew= V:
a.在集合E中選取權值最小的邊

import java.util.ArrayList;
import java.util.Scanner;

/**
 * @author 翔
 *
 */
public class Main {

    private static int nodeNum;//節點個數
    private static int edgeNum;//邊數
    private static int[] C;//節點權值
    private static int Cmin;//最小節點權值
    private static ArrayList<Edge> edges=new ArrayList<Edge>();//邊集合
    private static ArrayList<Edge> Enew=new ArrayList<Edge>();//Prim演算法中的Enew
    private static ArrayList<Integer> V_Vnew=new ArrayList<Integer>();//Prim演算法中的V-Vnew
    private static ArrayList<Integer> Vnew=new ArrayList<Integer>();//Prim演算法中的Vnew

    /**
     * @param args
     */
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        init();
        prim();
        System.out.println(caculate());
    }

    //計算所需要的總時間
    private static int caculate(){
        int sum=0;
        for(int i=0;i<Enew.size();i++){
            sum+=Enew.get(i).weight;
        }
        return sum+Cmin;

    }

    //Prim演算法的主要步驟
    private static void prim(){
        for(int i=2;i<=nodeNum;i++){
            int min=Integer.MAX_VALUE;
            Edge temp=null;
            for (int j = 0; j < edges.size(); j++) {
                if (((V_Vnew.contains(edges.get(j).from)
                        && Vnew.contains(edges.get(j).to))
                        || (Vnew.contains(edges.get(j).from)
                        && V_Vnew.contains(edges.get(j).to)))
                        && edges.get(j).weight < min) {
                    temp = edges.get(j);
                    min = temp.weight;
                }
            }
            if(V_Vnew.contains(temp.to)){
                V_Vnew.remove(new Integer(temp.to));
                Vnew.add(temp.to);
            }else{
                V_Vnew.remove(new Integer(temp.from));
                Vnew.add(temp.from);
            }
            edges.remove(temp);
            Enew.add(temp);         
        }   
    }

    //初始化各引數的值
    private static void init(){
        Scanner sc=new Scanner(System.in);
        nodeNum=sc.nextInt();
        edgeNum=sc.nextInt();
        C=new int[nodeNum+1];
        for(int i=1;i<=nodeNum;i++){
            C[i]=sc.nextInt();
            V_Vnew.add(i);
        }
        int CminFlag=findCmin();//尋找出C[]陣列中值最小的數對應的下標
        V_Vnew.remove(new Integer(CminFlag));
        Vnew.add(CminFlag);
        for(int i=0;i<edgeNum;i++){
            Edge edge=new Edge(sc.nextInt(),sc.nextInt(),sc.nextInt());
            edges.add(edge);
        }

        //重新計算邊權!!!!重點!!!!!!!!!!!!
        for(int i=0;i<edgeNum;i++){
            edges.get(i).weight=edges.get(i).weight*2+C[edges.get(i).from]+C[edges.get(i).to];
        }
        sc.close();
    }

    //尋找出C[]陣列中值最小的數對應的下標
    private static int findCmin(){
        int min=C[1];
        int flag=1;
        for(int i=2;i<=nodeNum;i++){
            if(C[i]<min){
                min=C[i];
                flag=i;
            }
        }
        Cmin=min;
        return flag;
    }
}
class Edge{
    int from;
    int to;
    int weight;
    public Edge(int from,int to,int weight){
        this.from=from;
        this.to=to;
        this.weight=weight;
    }
}