接続温首相は、いくつかの研究アルゴリズムの後に、データ構造は、それが照合弾丸が自分の言葉でスパニングツリーを最小限に横たわっかむ、と希望我々ができることをコーディングスタイルは以前に関連付けられた隣接行列で定義されているとの使用に適している倒れているのを発見しました理解しています。
まず、最小スパニングツリー
1、問題
解決すべき最小スパニングツリーは、n-1に接続されたグラフに接続された辺と最小重み値、並びにと、図ウェブ構造であるn個の頂点重み付けされる問題です。これは、広く道路橋、パイプライン輸送、宅配便、ネットワークの他の態様で使用することができます。私たちは、接続されているグラフは、最小スパニングツリーになっツリー構造にまたがる最小コストを置きます。
2つがあり、最小スパニングツリーアルゴリズムをしているとFapulimuクラスカルのアルゴリズムを数えます
2、プリム法
今、あなたは、2つのノードを持って、ポイントは2つの通信ノードで発見され、重み;(1)プリム法は、入り口ノードから次の最寄りのを見つけるために、エントリーから入力された思考最小;今、あなたは3つのノードは、3つのノードユニコムポイント、最小の重みで発見された持っている。......
アイデアから分かるセグメンテーション問題。重み付きグラフの点は、その最小重量切り返し最小スパニングツリーは、サブグラフエッジに属している必要があり区別する。(横側、頂部の両側部を接続手段)が発見された点であり、点が見つかりません区別します。
アイデアは、最小スパニングツリーエッジを見つけるために、セグメンテーション定理を使用して、貪欲なアルゴリズムを解決することであり、そしてあなたが最小全域木を見つけるまで、すべてのエッジを繰り返しました。
(2)アイデアの実現
ここでは、プリム法を達成するために一歩一歩を取ります。議論を簡単にするために、我々は最初に何かを規定します。、唯一の部分グラフ切断の状況を考慮していない、図のコミュニケーションを考えます。スパニングツリーの原因になります同じ値に権利が一意でない場合、B、各エッジの重量のみを考慮するとは、異なる状況です
C、[]内の格納された頂点の添字にViの添字に対応します。dが、ここではV0から入口を入力してください。E、ここでは達成するために、物品に保存されている構成を示す隣接行列図を使用しています。
まず、我々はネットを取得します。
我々は、V0から入力V0は、エッジと接続し、隣人や重みを見つけます。隣接行列が格納されているので、我々はので、ここで私たちは、これらの要素を格納するための1次元配列を使用して、我々はadjvex [numVertex]と低コスト[numVertex]を定義し、コードブックのスタイルをモデル化し、コンテナを保管する必要があります。意味と私たちの背後にあるこれら2つの特定の配列の使用は、今、あなたはちょうど彼らが側に隣接する点と重みを横断するために使用されていることを知っておく必要があり、言います。私たちが入ることV0から始めたので、バリューadjvex []は、0に初期化されます。低コスト我々はこの配列によって最小重量を見つけたいので、あなたが、その後の比較を容易にするため、不可能のために大きな値を初期化したいので、[]値は、INFINITYに初期化されますが、エッジがされているので、ここではINFINITEが、手動で設定する必要はありません。私はちょうど私の同僚は、コピー重量にコピーされる必要があるので場所は、私の側に設定されていません。
今、我々は10と11は、重み、それぞれ隣接点V1、V5、我々対応する重みV1、V5頂点[]角度の応じて格納されている横エッジ分割点V0と他の区別します(実際には、添字に対応するすべての頂点に隣接するポイントが0に初期化される)の預金低コストに添字1,5、及びV1、V5隣接点V0は、[対応する添え字0 adjvexに格納されています]。だから今、私たちが得ます
adjvex [] = {0、0、0、0、0、0、0、0、0 }。
低コスト[] = {0、10、I、I、I、11、I、I、I}。
ここでは、私は慎重に説明し、これら2つの配列の意味を理解する必要があります。低コストの非I要素の位置にあり、添え字の横方向縁部があっても、スパニングツリーに格納されていない頂点の一端に接続されていることを示す頂点[] Viがある添字にも等しいです「ノードインデックスがちょうど発見された。」と添字も理解することができます
次いでadjvex []の非ゼロ要素に格納されたインデックス低コスト意味のインデックスと同じであるが、それは、配列中に非ゼロ要素が存在しない0であるため、ここでのポイントは、分割されています。
低コスト横エッジの重みの最小重み位置が既存の最小スパニングツリーの頂点の一方の側に対応することを示す非Iの要素があります。
adjvex非ゼロ要素が頂点頂点[]の添え字があり、下付き文字は、2つの頂点間の対応するインデックス頂点[インデックス]と最小adjvex [インデックス]の重量を示し重みは、低コスト[インデックス]です。
だから我々はこの部分に横たわっているデータの構造を完全に理解していることができるはずです。
現在、最小の重量低コスト[]の値、および、最小スパニングツリーに対応する重み値が追加された頂点を見つけます。追加ウェイトアプローチは0に設定され、隣接する点は、新しいノードのadjvex添字インデックスセット内の新しいノードに対応します。
本発明の場合に対応し、スパニングツリーはV1、次のように調整二つの配列に追加されます。
adjvex [] = {0、0、1、0、0、0、1、0、1 }。
低コスト[] = {I、0、18、I、I、11、16、I、12}。
次に、我々は4つの候補の辺を有し、低コストは、頂点[5]に、対応するインデックスが5で、最小の非ゼロ重みが11である見出さV5は、V5は、低コスト[5]にまたがるように、この時点で添加さが0に設定されているが見つかりました次のように隣接する点V5及び低コストは、追加adjvex
adjvex [] = {0、0、1、0、5、0、1、0、1 }。
低コスト[] = {I、0、18、I 26、0、16、I、12}。
繰り返し以上の動作は、次に、V-1回繰り返される接合縁はV-1、最小スパニングツリーを与えます。
コードは次のように実装されています。
公共 のボイドMiniSpanTree_Prim(){ INT [] adjvex = 新しい int型[numVertex]。 INT []低コスト= 新しい int型[numVertex]。 adjvex [ 0] = 0 ; 低コスト[ 0] = 0 ; / * 以下のために(; I <numVertex、整数iが1 = I ++){ [I] = INFINITY低コスト。 } * / 以下のために(int型 ; I <numVertex、私は1を= I ++ ) { 低コスト[I] =縁[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)
对立两个算法,克鲁斯卡尔主要针对边展开,边少时时间效率很高,对于稀疏图有很大优势,;而普里姆算法对于稠密图会更好一些。