스패닝 트리 최소 [오늘 데이터 구조를 연습하기 시작도 - 가장 명확하게 이해할 수 프림 알고리즘과 알고리즘을 설명하고 크루스 칼을 구현하는

연결 총리는, 몇 가지 조사 알고리즘 후, 데이터 구조, 그래서 대조 총알이 당신 자신의 말로 스패닝 트리를 최소에 누워 물린, 희망을 우리가 할 수있는 것을 코딩 스타일이 이전에 관련된 인접 행렬의 정의와 함께 사용하기에 더 적합 거짓말 발견 이해합니다.

  첫째, 최소 스패닝 트리

    1 문제

      해결해야 할 최소 스패닝 트리가 N-1 접속 그래프 접속면 및 최소 가중치와 같은으로도 웹 구조는 n 개의 정점 가중되어 문제가된다. 그것은 널리 도로 교량, 파이프 라인 운송, 택배 네트워크의 다른 측면에서 사용할 수 있습니다. 우리는 연결 그래프는 최소 스패닝 트리가된다 트리 구조에 걸쳐 최소 비용을했습니다.

      두 가지 최소 스패닝 트리 알고리즘을하고 Fapulimu 크루스 칼 알고리즘을 계산

    2, 프림의 알고리즘

      프림의 알고리즘을 생각 (1) 입구 노드에서 다음 가까운 찾기 위해 항목에서 입력, 이제 두 개의 노드를 가지고, 포인트는 두 통신 노드 가중치로 발견 최소한, 지금은 세 개의 노드가 세 개의 노드 유니콤 포인트, 최소 무게와 함께 발견되었다가, ...

      으로는 인 아이디어에서 볼 수있는 분할 문제 . 가중 된 그래프의 점은 최소 가중치 샛길 최소 스패닝 트리 서브 그래프 에지에 속해야 구별. (가로 측 정점의 양측 부를 연결 수단)가 발견 된 지점이고, 상기 포인트는 발견되지 구별하기.

      아이디어는 최소 스패닝 트리 가장자리를 찾기 위해 분할 정리를 사용하여 욕심 알고리즘을 해결하기 위해, 당신은 최소 스패닝 트리를 찾을 때까지 모든 가장자리를 반복했다.

      (2) 아이디어의 실현

       여기에서 우리는 프림의 알고리즘을 달성하기 위해 단계별로 단계를 취할. 논의의 편의를 위해, 우리는 먼저 뭔가를 처방. A, 단지 서브 그래프 분리 상황을 고려하지도 통신을 고려한다. 스패닝 트리의 원인이됩니다 같은 값에 대한 권리가 고유하지 않은 경우 나, 각 에지의 무게를 고려하고는, 다른 상황입니다

        C []에 저장된 그 정점 첨자 vi에서 첨자에 대응한다. D, 우리가 V0에서 입구를 입력한다. 즉, 여기에서는 달성하기에 저장된 문서의 구성을 보여주는 인접 행렬도를 사용한다.

      첫째, 우리는 그물을 얻을.

      

 

     우리는 V0에서 입력 V0는 가장자리와 연결, 이웃과 가중치를 찾을 수 있습니다. 우리는, 그래서 여기에 인접성 매트릭스가 저장되기 때문에, 컨테이너를 저장해야 우리가 이러한 요소를 저장하는 1 차원 배열을 사용하여, 우리는 코드 책 스타일을 모델로 adjvex [numVertex]과 lowcost [numVertex]을 정의합니다. 의미와 우리 뒤에이 두 가지 특정 배열의 사용은 이제 단지 그들이 측면에 인접한 점과 가중치를 통과하는 데 사용되는 것을 알 필요가 말한다. 우리가 입력 V0에서 시작했기 때문에 값 adjvex는 [], 0으로 초기화됩니다. lowcost [] 값은 우리가이 배열을 통해 최소의 무게를 찾으려 때문에 이후의 비교를 용이하게하기 위해, 불가능에 대한 큰 값을 초기화 할 수 있도록, INFINITY로 초기화되지만, 가장자리가 되었기 때문에 INFINITE 여기에 수동으로 설정할 필요가 없습니다 난 그냥 내 동료가 복사 무게에 복사 할 필요가 있도록 위치, I의 측면에 설정되어 있지 않습니다.

    이제 우리는도 10 및도 11은 가중치를 각각 인접한 지점 V1, V5, 우리 대응 가중치 V1, V5는 정점 []의 각도에 따라 저장된 가로 가장자리 분할 점 V0과 다른 구별 lowcost 증착하는데 1,5- 첨자, 및 V1, V5 인접 점 V0는 (사실, 첨자에 대응하는 모든 정점에 인접한 지점을 0으로 초기화한다)의 대응 첨자 0 adjvex []에 저장된다. 그래서 지금 우리가 얻을

    adjvex [] = {0, 0, 0, 0, 0, 0, 0, 0, 0 }; 
    lowcost [] = {0, 10, I, I, I 11, I, I, I};

  여기에 우리가이 두 배열의 의미를 이해해야한다, 나는 조심스럽게 설명한다. lowcost 비 I 요소의 위치가 첨자 가로 가장자리에도 스패닝 트리에 저장되지 않는 정점의 일단에 접속하는 것을 나타내는 정점 [] 바이 첨자는,도 동등 는 "노드 인덱스가 방금 발견되었습니다."로 첨자도 이해 될 수있다

  이어서 adjvex []의 영이 아닌 요소에 저장된 인덱스 lowcost 의미의 인덱스와 동일하지만, 그 배열의 0이 아닌 요소가없는 0이므로 여기 점은 분할된다.

  lowcost 횡 에지 가중치 최소 중량 위치가 기존의 최소 스패닝 트리의 정점의 일측에 대응 나타내는 비 I 소자있다.

  adjvex 비제 정점 요소가 정점 []의 첨자 첨자 가리 해당 인덱스 정점 [인덱스] 그리고 두 꼭지점 사이의 최소 adjvex [인덱스] 체중 가중치는 lowcost [인덱스]이다.

  그래서 우리는이 부분에 누워있는 데이터의 구조의 완전한 이해를 가질 수 있어야합니다.   

  우리는 지금 최소 무게 Lowcost []의 값을 다음 최소 스패닝 트리에 해당하는 중량 값이 추가 정점을 찾을 수 있습니다. 추가 가중치 방식은 0으로 설정하고, 인접하는 포인트는 새로운 노드의 adjvex 첨자 인덱스 집합에 새로운 노드에 대응.

  다음과 같이 현재의 경우에 해당하는, 스패닝 트리, V1으로 조정이 개 배열을 추가 :

    adjvex [] = {0, 0, 1, 0, 0, 0, 1, 0, 1 }; 
    [lowcost = {I, 0, 18, I, I, 11, 16, I, 12};

 

 

우리는 네 개의 후보 측이 다음 Lowcost 발견 작은 비 - 제로 가중치가 11이고, 해당 인덱스가 5, 정점 [5] V5 찾았 V5는 Lowcost가 [5]가 0으로 설정 스패닝되도록 이번에 추가 다음 인접 점과 V5 Lowcost가 추가 adjvex

    adjvex [] = {0, 0, 1, 0, 5, 0, 1, 0, 1 }; 
    [lowcost = {I, 0, 18, I 26, 0, 16, I, 12};

 

 위 동작을 반복 한 후, V-1 번 반복 결합 된 가장자리는 V-1, 최소 스패닝 트리를 제공 할 수 있습니다.

다음과 같이 코드가 실행된다 :

    public void MiniSpanTree_Prim(){

        int[] adjvex = new int[numVertex];
        int[] lowcost = new int[numVertex];

        adjvex[0] = 0;
        lowcost[0] = 0;
        /*
        for (int i = 1; i < numVertex; i++){
            lowcost[i] = INFINITY;
        }

         */

        for (int i = 1; i < numVertex; i++)
        {
            lowcost[i] = edges[0][i];
            adjvex[0] = 0;
        }

        for (int i = 1; i < numVertex; i++){
            int min = INFINITY;
            int k = 0;
            for (int j = 1; j < numVertex; j++){
                if (lowcost[j] != 0 && lowcost[j] < min){
                  min = lowcost[j];
                  k = j;
                }
            }
            System.out.printf("(%d,%d,w = %d)加入生成树",adjvex[k],k,min);
            System.out.println();

            lowcost[k] = 0;
            for (int j = 1; j < numVertex; j++){
                if (lowcost[j] != 0 && lowcost[j] > edges[k][j]){
                    lowcost[j] = edges[k][j];
                    adjvex[j] = k;
                }
            }
        }
    }

  2,克鲁斯卡尔(Kruskal)算法

  如果说普里姆算法是面向顶点进行运算,那么克鲁斯卡尔算法就是面向边来进行运算的。

  (1)思路:克鲁斯卡尔算法的思路是,在离散的顶点集中,不断寻找权值最小的边,如果加入该边不会在点集中生成环,则加入这条边,直到获得最小生成树。

      所以我们的问题就是两个,第一个:将边按照权值排序  第二个:判断加入一条边之后会不会生成环

    将边按照权值排序很容易,这里我们用边集数组

    那么如何判断环呢? 克鲁斯卡尔判断环的依据是,在一棵树结构中,如果对其中两个结点添加一条原本不存在的边,那么一定会产生一个环。而一棵树中,根节点是唯一确定的。我们将添加边抽象为建立一棵树,并用数组parent[numVertex]存储这棵树的结构,下标index与结点在vertex[]中的位置vertex[index]相同,parent[index]是这个结点在这棵树中的父亲结点。每次添加一条边,就是在扩大森林中某一棵树,当森林全部连成一棵树,则得到了最小生成树。

  (2)具体步骤:我们这里使用与普里姆相同的例子

 

其边集数组排序后为

 

 我们遍历边集数组,首先拿到edges[0],分别判断4和7是否拥有相同的最大顶点,方法是进入parent[]中查询它们所在的树的根结点是否相同。因为是第一次查询,所以结果都是0,即它们不是同一棵树,可以连线。连线时,将parent[4]置7或者将parent[7]置4都可以,这里我们选择前者。

 

 

 下面拿到edges[1],查询parent[2],parent[8]得均为0,则不是同一棵树,可以连线。我们将parent[2]置8

 

 下面是edges[2],查询0,1,可以连线。

 

 接下来edges[3],查询0,5,此时V0的父是V1,V1对应的parent[1]中存储的0表示V1是这棵树的父,parent[5]=0,即V0和V5不是同一棵树,可以连线。将parent[1]置为5

 

 

接下来edges[4], 查询1,8,不在同一棵树,此时1所在树的根是5,将1和8连线,此时树合并应该将根节点5的parent[5]置为8.现在上图的两个棵树合并了

 

 接下来是edges[5],查询3,7,不在同一子树,连线。

 

 

 接下来是edges[6],查询1,6,不在同一子树,连线。

 

 

 接下来是edges[7]查询5,6,发现它们的根节点都是8,在同一棵子树,所以不连线。

下面我就不再重复了,总之这样循环检测,可以得到最终的最小生成子树。(注!最小生成子树和我们上面用来判断是否连通的树是不同的!parent数组也并不是唯一的!因为在构造判断树的时候,不管把谁做父,谁做子,都可以构建树,并不影响判断环的结果)

 

 

   (3)代码实现

   

    /*
        定义边结构的内部类。
     */
    private class Edge implements Comparable<Edge> {
        private int begin;
        private int end;
        private int weight;

        private Edge(int begin, int end, int weight){
            this.begin = begin;
            this.end = end;
            this.weight = weight;
        }

        @Override
        public int compareTo(Edge e) {
            return this.weight - e.weight;
        }

        public int getBegin() {
            return begin;
        }

        public void setBegin(int begin) {
            this.begin = begin;
        }

        public int getEnd() {
            return end;
        }

        public void setEnd(int end) {
            this.end = end;
        }

        public int getWeight() {
            return weight;
        }

        public void setWeight(int weight) {
            this.weight = weight;
        }
    }
    /**
     * 得到排序好的边集数组,用ArrayList存储
     * @return
     */
    public ArrayList<Edge> getOrderedEdges() {
        ArrayList<Edge> edgeList = new ArrayList<>();
        for (int row = 0; row < numVertex; row++){
            for (int col = row; col < numVertex; col++){
                if(edges[row][col] != 0 && edges[row][col] != INFINITY){
                    edgeList.add(new Edge(row, col, edges[row][col]));
                }
            }
        }
        Collections.sort(edgeList);
        return edgeList;
    }

    /**
     * 克鲁斯卡尔算法
     */
    public void MiniSpanTree_Kruskal(){
        ArrayList<Edge> edgeList = getOrderedEdges();
        int[] parent = new int[numVertex];
        for (int i = 0; i < numVertex; i++){
            parent[i] = 0;
        }

        for (int i = 0; i < edgeList.size(); i++){
            int m = findRoot(edgeList.get(i).getBegin(), parent);
            int n = findRoot(edgeList.get(i).getEnd(), parent);
            if (m != n){
                link(edgeList.get(i), parent, m, n);
            }
        }


    }

    /*
        连接两点,并且设置parent数组
     */
    private void link(Edge edge, int[] parent, int m, int n) {
        System.out.printf("(%d,%d),weight = %d 加入最小生成树", edge.getBegin(), edge.getEnd(), edge.getWeight());
        System.out.println();

        parent[m] = n;
    }

    /*
    找到本子树的根节点
     */
    private int findRoot(int root, int[] parent) {
        while (parent[root] > 0){
            root = parent[root];
        }
        return root;
    }

总结:克鲁斯卡尔的FindRoot函数的时间复杂度由边数e决定,时间复杂度为O(loge),而外面有一个for循环e次,所以克鲁斯卡尔的时间复杂度是O(eloge)

  对立两个算法,克鲁斯卡尔主要针对边展开,边少时时间效率很高,对于稀疏图有很大优势,;而普里姆算法对于稠密图会更好一些。

추천

출처www.cnblogs.com/Joey777210/p/12054499.html