我做了一個小例子,將k-means算法用在我最近做的一個系統中。以下介紹k-means算法。
(1)k-means算法的簡介
本系統使用k-means算法來計算一維數據的聚集程度,實現圈子的劃分,這裏的一維數據是所有的點,用A、B、C、D來表示每一個點,任意兩個點之間的最短距離的計算方法已經封裝成爲接口,直接調用即可。K-Means算法的基本思想是初始隨機給定K個簇中心,按照最鄰近原則把待分類樣本點分到各個簇。然後重新計算各個簇的質心,從而確定新的簇心。一直迭代,直到簇心不再發生改變爲止,算法結束。
(2)k-means算法的設計思想
如圖所示,圖中共有5個點,分別是A、B、C、D、E,灰色的點是種子點,也就是用來找點羣的點,因爲有兩個種子點,所以k=2.
(3)k-means算法的具體步驟
如上圖所示,隨機在圖中取K(這裏K=2)個種子點。
①然後對圖中的所有點求到這K個種子點的距離,假如點Pi離種子點Si最近,那麼Pi屬於Si點羣。(上圖中,可以看到A,B屬於上面的種子點,C,D,E屬於下面中部的種子點)。
②接下來,需要移動種子點到屬於他的“點羣”的中心。(見圖上的第三步)
③然後重複第2)和第3)步,直到,種子點沒有移動(可以看到上圖中的第四步上面的種子點聚合了A,B,C,下面的種子點聚合了D,E)。
我做的例子中對於k-means的應用具體步驟:
①隨機在數據庫中取K個種子點,一般情況下,取數據庫所存儲數據的前n個數據作爲初始種子點。
②然後對數據庫中的所有點求到這K個種子點的距離,假如點Pi離種子點
Si最近,那麼Pi屬於Si點羣。這樣就將所有點劃分成了k個圈子。
③接下來,要移動種子點到屬於他的“點羣”的中心,移動思路是使得點羣中所有點到中心點的距離之和最短,我們將這個中心點稱點羣的中心。求最短距離之和的過程中,本系統使用了Dijkstra算法,將求某個點到其餘所有點的最短距離之和的過程寫成了接口,返回更新後的種子點和最短距離之和。
④然後重複第(2)和第(3)步,直到,種子點不再發生變化。則分圈結束。
(4)k-means算法的流程圖
(5)k-means算法的結果測試:
本系統設定將所有的點分成三個圈子,程序總共循環分圈了兩次,進行兩次分圈之後,中心點不再發生變化,於是程序停止運行。
上圖是第一次分圈的過程,將A、B、C 作爲初始種子點,計算其餘各點距離這三個種子點的距離,距離A、B、C哪個點最近,就將它歸爲這個點羣中。下圖是第一次分圈結束後的結果,最後的分圈結果包含三行,第一行是點羣A,其中D、E、F都屬於A點所在圈子,第二行是點羣B,其中B、H屬於B點所在圈子,第三行是點羣C,其中G點屬於C點所在的圈子。
經過一次分圈之後,本系統的中心種子點從A,B,C三點變成了A,H,C,如下圖,是系統將A、H、C 作爲更新後的種子點,計算其餘各點距離這三個種子點的距離,距離A、H、C哪個點最近,就將它歸爲這個點羣中。最後的分圈結果包含三行,第一行是點羣A,其中B、D、E、F都屬於A點所在圈子,第二行是點羣H,其中G屬於H點所在圈子,第三行是點羣C,只包含C點本身。
第二次分圈後,中心點不再改變,程序終止。
第二次分圈結束後結果:
這也是最終分圈的結果。
(5) k-means算法的java實現:
public class Basickmeanstest {
//給定需要分圈的初始節點
static String[] p = { "A", "B", "C", "D", "F","E","G", "H" };
public static void main(String[] args) throws Exception {
//k表示分圈的個數,這裏需要分爲3個圈,k根據數據量大小自定義。
int k = 3;
//二維數組g用來存儲分圈之後的結果
//二維數組,每一行代表一個圈子,單獨一行中的所有點表示這個圈子之內的元素。
String[][] g;
g = cluster(p, k);
//將分圈結果輸出
for (int i = 0; i < g.length; i++) {
for (int j = 0; j < g[i].length; j++) {
System.out.print(g[i][j]+'\t');
}
System.out.println();
}
}
public static String[][] cluster(String[] p, int k) throws Exception {
//c存放原始的中心點
String[] c = new String[k];
//nc存放更新之後的中心點
String[] nc = new String[k];
String[][] g;
for (int i = 0; i < k; i++) {
c[i] = p[i];
}
for(int i=0; i<c.length; i++ ){
}
while (true) {
g = group(p, c);
for(int i=0;i<g.length;i++){
for(int j=0;j<g[i].length;j++)
System.out.print(g[i][j]+' ');
System.out.println();
}
System.out.println("----------------------");
for (int i = 0; i < g.length; i++) {
nc[i] = center(g[i]);
}
//當更新後的中心點和初始中心點不同的時候,更新中心點。
if (!equal(nc, c)) {
c = nc;
nc = new String[k];
} else {
break;
}
}
return g;
}
public static Object getMinValue(HashMap<String, Integer> map) {
if (map == null)
return null;
Collection<Integer> c = map.values();
Object[] obj = c.toArray();
Arrays.sort(obj);
return obj[0];
}
//這裏是我更新中心點的依據,調用的是我寫好的接口,依據是在有向圖中,若某個點到其餘所有點的距離之和最短,則將這個點作爲新的中心點。
public static String center(String[] p) throws Exception {
return Main.getShort(p);
}
//這是分圈的核心方法
public static String[][] group(String[] p, String[] c) throws Exception {
int[] gi = new int[p.length];
for (int i = 0; i < p.length; i++) {
// 存放距離
int[] d = new int[c.length];
// 計算到每個聚類中心的距離
for (int j = 0; j < c.length; j++) {
d[j] = distance(p[i], c[j]);
}
// 找出最小距離
int ci = min(d);
System.out.println("較小的值爲"+d[ci]);
System.out.println("ci="+ci);
// 標記屬於哪一組
//ci表示的是屬於哪一個組,在本例中,k=3,分爲3個全,則ci的可能取值爲0,1,2.當ci=0的時候,表示屬於第一個圈子,即會被分到結果g的第一行,以此類推。
gi[i] = ci;
}
String[][] g = new String[c.length][];
// 遍歷每個聚類中心,分組
for (int i = 0; i < c.length; i++) {
// 中間變量,記錄聚類後每一組的大小
int s = 0;
// 計算每一組的長度
for (int j = 0; j < gi.length; j++)
if (gi[j] == i)
s++;
// 存儲每一組的成員
g[i] = new String[s];
s = 0;
// 根據分組標記將各元素歸位
for (int j = 0; j < gi.length; j++)
if (gi[j] == i) {
g[i][s] = p[j];
s++;
}
}
return g;
}
//這是求兩個節點之間的距離的方法
//此方法是我已經寫好的接口,距離直接從數據庫中讀出來即可。
public static int distance(String x, String y) throws Exception {
DijstTestDao dijstra = DijstraFactory.getDijistra();
int len;
if(x.equals(y)) {
len= 0;
} else {
Main.getShort(p);
len = Main.findShortInstance(x, y);
}
System.out.println("第一個點"+x+"和第二個點"+y+"之間距離爲:"+len);
return len;
}
public static int min(int[] p) {
int i = 0;
int m = p[0];
for (int j = 1; j < p.length; j++) {
if (p[j] < m) {
i = j;
m = p[j];
}
}
return i;
}
public static boolean equal(String[] a, String[] b) {
if (a.length != b.length)
return false;
else {
for (int i = 0; i < a.length; i++) {
if (a[i] != b[i])
return false;
}
}
return true;
}
}