K-means算法原理以及java實現

我做了一個小例子,將k-means算法用在我最近做的一個系統中。以下介紹k-means算法。

(1)k-means算法的簡介

本系統使用k-means算法來計算一維數據的聚集程度,實現圈子的劃分,這裏的一維數據是所有的點,用A、B、CD來表示每一個點,任意兩個點之間的最短距離的計算方法已經封裝成爲接口,直接調用即可。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屬於上面的種子點,CDE屬於下面中部的種子點)。
②接下來,需要移動種子點到屬於他的“點羣”的中心。(見圖上的第三步)
③然後重複第2)和第3)步,直到,種子點沒有移動(可以看到上圖中的第四步上面的種子點聚合了A,B,C,下面的種子點聚合了DE)。

我做的例子中對於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;
    }
}
發佈了61 篇原創文章 · 獲贊 37 · 訪問量 12萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章