├── .gitignore ├── .idea ├── DataMining.iml ├── dictionaries │ ├── K9A2S.xml │ └── stormlin.xml ├── inspectionProfiles │ └── Project_Default.xml ├── libraries │ └── commons_codec_1_10.xml ├── misc.xml ├── modules.xml ├── uiDesigner.xml └── vcs.xml ├── README.md ├── lib ├── commons-codec-1.10.jar ├── commons-collections4-4.1.jar ├── commons-logging-1.2.jar ├── curvesapi-1.04.jar ├── junit-4.12.jar ├── log4j-1.2.17.jar ├── poi-3.16.jar ├── poi-examples-3.16.jar ├── poi-excelant-3.16.jar ├── poi-ooxml-3.16.jar ├── poi-ooxml-schemas-3.16.jar ├── poi-scratchpad-3.16.jar └── xmlbeans-2.6.0.jar ├── src ├── Main.java └── com │ └── stormlin │ ├── fpgrowth │ ├── FPGrowth.java │ ├── ProcessingUtils.java │ └── TNode.java │ └── kmeans │ ├── KMeans.java │ └── Point.java └── test ├── dictionary.csv ├── output.txt └── test.csv /.gitignore: -------------------------------------------------------------------------------- 1 | out/ 2 | .idea/workspace.xml 3 | -------------------------------------------------------------------------------- /.idea/DataMining.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /.idea/dictionaries/K9A2S.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | thisnode 5 | 6 | 7 | -------------------------------------------------------------------------------- /.idea/dictionaries/stormlin.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | fpgrowth 5 | kmeans 6 | xlsx 7 | 8 | 9 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/Project_Default.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 36 | -------------------------------------------------------------------------------- /.idea/libraries/commons_codec_1_10.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/uiDesigner.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

FP-Growth 和 K-Means 学习报告

2 |

stormlin 2017-06-07

3 | 4 | **最近学习了数据挖掘常用的两种算法:FP-Growth 和 K-Means。现在把我的学习结果分享给大家。** 5 | 6 | 以下是本文的目录,大家可以根据需要跳过一些章节: 7 | 8 | 9 | - [1. FP-Growth](#1-fp-growth) 10 | - [1.1 支持度计数筛选](#11-支持度计数筛选) 11 | - [1.2 步骤简介](#12-步骤简介) 12 | - [1.3 实例分析](#13-实例分析) 13 | - [1.3.1 Overview](#131-overview) 14 | - [1.3.1.1 预处理](#1311-预处理) 15 | - [1.3.1.2 重整输出](#1312-重整输出) 16 | - [1.3.2 如何把 FP-Growth 算法的输出还原成可阅读的频繁项集?](#132-如何把-fp-growth-算法的输出还原成可阅读的频繁项集) 17 | - [1.3.3 如何判断两个频繁项之间有交集?](#133-如何判断两个频繁项之间有交集) 18 | - [1.3.4 处理结果与应用场合](#134-处理结果与应用场合) 19 | - [2. K-Means](#2-k-means) 20 | - [2.1 分类原理](#21-分类原理) 21 | - [2.1.1 欧几里得距离的高效计算](#211-欧几里得距离的高效计算) 22 | - [2.1.2 计算质心的方法:](#212-计算质心的方法) 23 | - [2.2 步骤简介](#22-步骤简介) 24 | - [2.3 计算实例](#23-计算实例) 25 | - [2.4 实现要点](#24-实现要点) 26 | - [2.4.1 随机初始质心的获取](#241-随机初始质心的获取) 27 | - [2.4.2 Point[] 数组的复制与判同](#242-point-数组的复制与判同) 28 | - [3.Reference](#3reference) 29 | 30 | 31 | 32 | 文中的引用以上标表示。所有源代码都可以在我的 GitHub:[https://github.com/K9A2/DataMining](https://github.com/K9A2/DataMining) 上找到。大家也可以到我的网站查看更多新鲜内容:[http://www.stormlin.com](http://www.stormlin.com)。 33 | 34 | ## 1. FP-Growth 35 | 36 | 在生活中,我们常常会遇到一些需要分析事物之间的关联性的场合。例如,在分析超市的销售数据时,我们可能会想知道,顾客在买牛奶的时候,还会买什么别的东西。还有数据挖掘领域里面著名的啤酒与尿布的故事R1。 37 | 38 | 要解决这些问题,我们就需要一种算法来帮我们寻找这些事务项之间的关联性。常用的**关联分析(Association Analysis)** 39 | 算法有 Apriori 算法和 FP-Growth 算法。Apriori 算法的时空复杂度都比较高,现在已经不常用了,故本文略去对 Apriori 算法的介绍,专注于对 FP-Growth 的介绍与分析。 40 | 41 | ### 1.1 支持度计数筛选 42 | 43 | 在 FP-Growth 算法里面,需要对每一个事项计算各自的**支持度计数**(即此事务在全集中出现的次数)。如果支持度不满足设定的最小值,那么这项记录将不能被算法所收录。 44 | 45 | ### 1.2 步骤简介 46 | 47 | FP-Growth 的步骤相对于 Apriori 会简单一点,但绝对值也不低。其伪代码步骤简介如下R5: 48 | ```java 49 | 输入:事务集合 List> transactions 50 | 输出:频繁模式集合 List> fpOutput 51 | 52 | public void getFPOutput(List> transactions, List postPattern, List> fpOutput) { 53 | 构建头表项 HeaderTable:buildHeaderTable(transactions); 54 | 构建 FP 树:buildFPTree(headerTable, transactions); 55 | if (树空) return; 56 | 输出频繁项集; 57 | 遍历每一个头项表节点并递归; 58 | } 59 | ``` 60 | 具体操作步骤请参考源代码。 61 | 62 | ### 1.3 实例分析 63 | 64 | #### 1.3.1 Overview 65 | 66 | 针对 FP-Growth 的实例分析,我们采用了一个具有 27 万测试数据的数据集(示例见 Fig.1,可以通过[度盘链接](http://pan.baidu.com/s/1eRZ0vke)下载)。在经过预处理阶段之后(即源代码中的 `preProcess` 方法),数据量下降为 6 万多,全过程处理时间大约为 10 秒。不同机器可能需要不同的处理时间,具体请参照在程序起止是输出的时间戳。 67 | 68 |
69 |

Fig.1 Data Sample

70 | 71 | 测试程序主要采用了三级处理的方式,预处理、FP-Growth 计算频繁项集和重整输出三个阶段: 72 | 1. 预处理:
73 | `fpInput = preProcess(csvFilePath, inputSeparator, requiredClass);` 74 | 2. FP-Growth 算法生成频繁项:
75 | `FPGrowth fpGrowth = new FPGrowth(2);`
76 | `fpGrowth.getFPOutput(fpInput, null, fpOutput);` 77 | 3. 重整输出:
78 | `result = reProcess(dictionaryFilePath, fpSeparator, fpOutput)` 79 | 80 | 由于频繁项的计算已在 1.2 节中介绍,故以下只介绍预处理和重整输出两个阶段。 81 | 82 | ##### 1.3.1.1 预处理 83 | 84 | 预处理阶段的主要任务是为 FP-Growth 准备需要的输入数据。 85 | 86 | 如前文所述,原始数据为日志文件,其数据项按行排列,并非 FP-Growth 所要求的多个事物项处在同一行。故预处理阶段的第一个任务就是把这些“属于”同一行的数据全部合并到同一行中: 87 | ```java 88 | 算法输入: 89 | 1. String csvFilePath:以 CSV 文件格式存储的原始数据 90 | 2. String inputSeparator:CSV 文件中用来分隔同一行中的不同数据项的分隔符 91 | 3. String targetClass:目的图书分类 92 | 93 | 算法输出: 94 | List> FPInput:FP-Growth 算法输入数据 95 | 96 | List> preProcess(String csvFilePath, String inputSeparator, char targetClass) { 97 | //1.读取 CSV 文件,构建原始输入 98 | ... 99 | //2.筛选出符合类别要求的记录项 100 | ... 101 | //3.读取数据并合并同一次结束记录的连续几行记录 102 | ... 103 | return FPInput; 104 | } 105 | ``` 106 | 107 | ##### 1.3.1.2 重整输出 108 | 109 | 重整输出阶段的任务就是把 FP-Growth 算法输出的杂乱无章的结果重整为在条目之间具有唯一性的输出结果。 110 | ```java 111 | 算法输入: 112 | 1. String dictionaryFilePath:CSV 文件格式的书名字典文件的位置 113 | 2. String dictionarySeparator:字典文件中每一行的各项之间的分隔符 114 | 3. List> fpOutput:欲处理的 FP-Growth 输出 115 | 116 | 算法输出: 117 | List> result:可以直接输出的结果 118 | 119 | List> reProcess(String dictionaryFilePath, String dictionarySeparator, List> fpOutput) { 120 | //1.制作字典 121 | Dictionary dictionary = new Hashtable<>(); 122 | ... 123 | //2.合并与去重 124 | getResultFiltered(result, fpOutput); 125 | //3.替换其中的书号以得到最终结果 126 | ... 127 | return result; 128 | } 129 | ``` 130 | 131 | 其中,`getResultFiltered(List> result, List> removedPrefix)` 会在下一节详细介绍。 132 | 133 | #### 1.3.2 如何把 FP-Growth 算法的输出还原成可阅读的频繁项集? 134 | 135 | FP-Growth 算法输出是杂乱无章的,所以我们就需要对它的输出进行重整。而为了尽可能扩大相关联事务的范围,我们采用了合并所有有交集的行的方法: 136 | ```java 137 | 算法输入: 138 | 1. List> result:处理结果 139 | 2. List> fpOutput:FP-Growth 处理结果与应用场合 140 | 141 | 算法输出: 142 | 在参数 result 中 143 | 144 | private static void getResultFiltered(List> result, List> fpOutput) { 145 | //合并与去重 146 | HashSet temp = new HashSet<>(); 147 | int elementLeft = fpOutput.size(); 148 | //任意两行之间如果有交集,就合并他们 149 | while (fpOutput.size() != 0) { 150 | temp.addAll(fpOutput.get(0)); 151 | for (int j = 1; j < elementLeft - 1; j++) { 152 | //若两项之间有交集,则 isIntersected 返回 true 153 | if (isIntersected(temp, fpOutput.get(j))) { 154 | temp.addAll(fpOutput.get(j)); 155 | fpOutput.remove(j); 156 | elementLeft--; 157 | j--; 158 | } 159 | } 160 | result.add(new ArrayList<>(temp)); 161 | temp.clear(); 162 | elementLeft--; 163 | fpOutput.remove(0); 164 | } 165 | } 166 | ``` 167 | 以上算法中,HashSet 的使用有效加快了匹配的速度。同时,由于算法会删去已添加的行,重整算法的时间复杂度近似为 O(nlogn),空间复杂度不会超过输入数组的大小,即 O(n)。 168 | 169 | #### 1.3.3 如何判断两个频繁项之间有交集? 170 | 171 | 为了能尽可能地扩大结果中一条频繁项集的数据,为推荐算法提供更多样化的结果,在合并频繁项集的时候,需要判断两个频繁项之间是否有交集。如果两个频繁项之间有交集,则合并两者。 172 | ```java 173 | 算法输入: 174 | 1. HashSet a:已有结果的条件模式基 175 | 2. List b:需要检测的频繁项集 176 | 177 | 算法输出: 178 | true:两者之间有交集 179 | false:两者之间无交集 180 | 181 | private static boolean isIntersected(HashSet a, List b) { 182 | for (String anA : a) { 183 | for (String aB : b) { 184 | if (anA.equals(aB)) { 185 | return true; 186 | } 187 | } 188 | } 189 | return false; 190 | } 191 | ``` 192 | 193 | 据其他资料分析,使用 List 的 retainAll() 方法也能检测两者之间是否有交集,大家可以去试试这种方法。 194 | 195 | #### 1.3.4 处理结果与应用场合 196 | 197 | 本算法能在给定的事务集中高效计算频繁项集。那么,我们就能把这个算法移植到服务器端,并在小数据量的情况下实现根据用户的指定的类别,实时计算频繁项集,并在结果页面推荐给用户。 198 | 199 | ## 2. K-Means 200 | 201 | 在对数据进行了关联分析之后,有时候还需要对数据进行**聚簇分析(Clustering Analysis)**。聚类分析的算法较多,这里只介绍 K-Means 算法。这个算法的输入有数据集和分类数目 K;输出是分在 K 个簇中的数据项。 202 | 203 | ### 2.1 分类原理 204 | 205 | 分类主要涉及计算欧几里得距离和计算一群质点的质心两个算法,下面分别介绍: 206 | 207 | #### 2.1.1 欧几里得距离的高效计算 208 | 209 | 分类的方法主要是计算某个点与所有 K 个质心之间的欧几里得距离。计算两个 n 维点之间的欧几里得距离R4: 210 | 211 |
212 |

Fig.2 EuclidDistance

213 | 214 | 实用的计算方法: 215 | ```java 216 | private static double getEuclidDistance(Point a, Point b) { 217 | double result = 0; 218 | result += Math.pow(a.getX() - b.getX(), 2); 219 | result += Math.pow(a.getY() - b.getY(), 2); 220 | result += Math.pow(a.getZ() - b.getZ(), 2); 221 | return Math.sqrt(result); 222 | } 223 | ``` 224 | 225 | #### 2.1.2 计算质心的方法: 226 | 227 | 在 K-Means 算法中,分类需要按照欧几里得距离最小的原则。但在实用的算法中,通常采用重心来代替质心: 228 | ```java 229 | private static Point getClusterCenter(List points) { 230 | if (points.size() == 0) { 231 | return null; 232 | } 233 | if (points.size() == 1) { 234 | return new Point(points.get(0).getX(), points.get(0).getY(), points.get(0).getZ()); 235 | } 236 | double x = 0; 237 | double y = 0; 238 | double z = 0; 239 | for (Point point : points) { 240 | x += point.getX(); 241 | y += point.getY(); 242 | z += point.getZ(); 243 | } 244 | x = x / points.size(); 245 | y = y / points.size(); 246 | z = z / points.size(); 247 | return new Point(x, y, z); 248 | } 249 | ``` 250 | 251 | ### 2.2 步骤简介 252 | 253 | K-Means 算法是一种很好理解的算法,其步骤异常简单。 254 | 255 | 1. 用户提供输入数据集。数据集中的每一项都需要包含若干属性。如输入一个二维点集,那其中的一项就需要至少包含 X 和 Y 两个坐标; 256 | 2. 由用户指定初始质心或者由算法在输入的数据集中随机选取 K 个点作为初始质心; 257 | 3. 计算每一项到每一个质心之间的欧几里得距离; 258 | 4. 按照欧几里得距离最小的原则,把这些点分到 K 个簇中的某一个; 259 | 5. 重新计算 K 个簇中的质心(通常用计算重心代替); 260 | 6. 如果质心与分类时使用的质心相同,则算法结束;否则就需要重复 2-6 步。 261 | 262 | ### 2.3 计算实例 263 | 264 | 由于每一个点不仅仅需要保存自身的三轴坐标,同时还要保存自身的类别以及名字,故新建了用于表示点的 `Point` 类,并以 `Point[]` 来表示点集。 265 | 266 | 计算实例采用了 *CPDA 数据分析天地*提供的足球数据R3。由于设计的时候采用了三维点集,所以无法采用通常的二维分类着色图R2来表示,故直接输出三种分类。其实验结果如下: 267 | ```txt 268 | 日本,韩国,澳大利亚, 269 | 印尼,泰国, 270 | 中国,朝鲜,伊拉克,伊朗,沙特,阿联酋,卡塔尔,乌兹别克斯坦,巴林,阿曼,约旦, 271 | ``` 272 | 其结果符合球队实际排位。 273 | 274 | 另外,由于本次实验中尚未添加对分类的排序功能,即在输出的时候并非按照质心的“权重”来进行排序,故输出的结果是不能直接提取到别的程序中的。 275 | 276 | ### 2.4 实现要点 277 | 278 | #### 2.4.1 随机初始质心的获取 279 | 280 | 获取随机初始质心有两种方法: 281 | 1. 第一种是采用 `Collection.shuffle()` 来直接打乱排序,然后直接取前 K 位作为随机初始质心; 282 | 2. 第二种就是使用随机数。先计算出 K 个不重复的随机数,然后按照获得的随机数到 `Point[]` 数组中获取随机初始质心,其计算过程如下: 283 | ```java 284 | private static int[] getUnrepeatedRandomNumbers(int min, int max, int count) { 285 | int[] result = new int[count]; 286 | int i = 0; 287 | HashSet temp = new HashSet<>(); 288 | while (true) { 289 | if (temp.size() == count) { 290 | break; 291 | } 292 | temp.add((int) (Math.random() * (max - min)) + min); 293 | } 294 | for (Integer item : temp) { 295 | result[i] = item; 296 | i++; 297 | } 298 | return result; 299 | } 300 | ``` 301 | 302 | #### 2.4.2 Point[] 数组的复制与判同 303 | 304 | 1. 如果直接调用系统提供的 `System.arraycopy(b, 0, a, 0, a.length)`,那在复制的时候就是浅复制:即两个数组都是“引用了”同一个来源。在其中一个数组被改变的时候,另外一个数组由于引用了同一块内存区域,其值也会被改变。故要实现数组的“深拷贝”,则需要自行编写复制方法: 305 | ```java 306 | private static Point[] getArrayCopy(Point[] b) { 307 | Point[] a = new Point[b.length]; 308 | if (a.length == 0 || b.length == 0) { 309 | return null; 310 | } 311 | System.arraycopy(b, 0, a, 0, a.length); 312 | return a; 313 | } 314 | ``` 315 | 2. 那么如何判断两个质心是否移动呢?我们可以直接采用逐行判断的方式: 316 | ```java 317 | private static boolean isClusterCenterChanged(Point[] a, Point[] b) { 318 | for (int i = 0; i < a.length; i++) { 319 | if (a[i].getX() != b[i].getX()) { 320 | return true; 321 | } else if (a[i].getY() != b[i].getY()) { 322 | return true; 323 | } else if (a[i].getZ() != b[i].getZ()) { 324 | return true; 325 | } 326 | } 327 | return false; 328 | } 329 | ``` 330 | 331 | 在任意一次比较中,如果两者的 X、Y 和 Z 三个值中的任意一个不相等,方法就会返回 `true`,即质心已移动;否则返回 `false`,表示质心未移动。 332 | 333 | ## 3.Reference 334 | 335 | 1. [Grant Stanley - Diapers, Beer, and Data Science in Retail](http://canworksmart.com/diapers-beer-retail-predictive-analytics/) 336 | 2. [听云博客 - JAVA实现K-means聚类](http://www.tuicool.com/articles/VBBnie) 337 | 3. [CPDA 数据分析天地 - 用K-means看透中国男足!](http://www.sohu.com/a/135368994_354986) 338 | 4. [tianlan_new_start - 欧几里得距离、曼哈顿距离和切比雪夫距离](http://blog.csdn.net/tianlan_sharon/article/details/50904641) 339 | 5. [人非木石_xst - 单机和集群环境下的FP-Growth算法java实现(关联规则挖掘)](http://blog.csdn.net/shimin520shimin/article/details/49281381) -------------------------------------------------------------------------------- /lib/commons-codec-1.10.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/K9A2/DataMining/0a45a00fbfe3228594abd65d9e89993e0adbd681/lib/commons-codec-1.10.jar -------------------------------------------------------------------------------- /lib/commons-collections4-4.1.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/K9A2/DataMining/0a45a00fbfe3228594abd65d9e89993e0adbd681/lib/commons-collections4-4.1.jar -------------------------------------------------------------------------------- /lib/commons-logging-1.2.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/K9A2/DataMining/0a45a00fbfe3228594abd65d9e89993e0adbd681/lib/commons-logging-1.2.jar -------------------------------------------------------------------------------- /lib/curvesapi-1.04.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/K9A2/DataMining/0a45a00fbfe3228594abd65d9e89993e0adbd681/lib/curvesapi-1.04.jar -------------------------------------------------------------------------------- /lib/junit-4.12.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/K9A2/DataMining/0a45a00fbfe3228594abd65d9e89993e0adbd681/lib/junit-4.12.jar -------------------------------------------------------------------------------- /lib/log4j-1.2.17.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/K9A2/DataMining/0a45a00fbfe3228594abd65d9e89993e0adbd681/lib/log4j-1.2.17.jar -------------------------------------------------------------------------------- /lib/poi-3.16.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/K9A2/DataMining/0a45a00fbfe3228594abd65d9e89993e0adbd681/lib/poi-3.16.jar -------------------------------------------------------------------------------- /lib/poi-examples-3.16.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/K9A2/DataMining/0a45a00fbfe3228594abd65d9e89993e0adbd681/lib/poi-examples-3.16.jar -------------------------------------------------------------------------------- /lib/poi-excelant-3.16.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/K9A2/DataMining/0a45a00fbfe3228594abd65d9e89993e0adbd681/lib/poi-excelant-3.16.jar -------------------------------------------------------------------------------- /lib/poi-ooxml-3.16.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/K9A2/DataMining/0a45a00fbfe3228594abd65d9e89993e0adbd681/lib/poi-ooxml-3.16.jar -------------------------------------------------------------------------------- /lib/poi-ooxml-schemas-3.16.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/K9A2/DataMining/0a45a00fbfe3228594abd65d9e89993e0adbd681/lib/poi-ooxml-schemas-3.16.jar -------------------------------------------------------------------------------- /lib/poi-scratchpad-3.16.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/K9A2/DataMining/0a45a00fbfe3228594abd65d9e89993e0adbd681/lib/poi-scratchpad-3.16.jar -------------------------------------------------------------------------------- /lib/xmlbeans-2.6.0.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/K9A2/DataMining/0a45a00fbfe3228594abd65d9e89993e0adbd681/lib/xmlbeans-2.6.0.jar -------------------------------------------------------------------------------- /src/Main.java: -------------------------------------------------------------------------------- 1 | import com.stormlin.fpgrowth.FPGrowth; 2 | import com.stormlin.kmeans.Point; 3 | 4 | import java.util.ArrayList; 5 | import java.util.List; 6 | 7 | import static com.stormlin.fpgrowth.ProcessingUtils.*; 8 | import static com.stormlin.kmeans.KMeans.Analyze; 9 | 10 | /** 11 | * @Author stormlin 12 | * @DATE 2017/5/11 13 | * @TIME 0:12 14 | * @PROJECT DataMining 15 | * @PACKAGE PACKAGE_NAME 16 | */ 17 | 18 | public class Main { 19 | 20 | public static void main(String[] args) { 21 | 22 | //kmeans(); 23 | fpgorwth(); 24 | 25 | } 26 | 27 | /** 28 | * 数据挖掘算法 K-Means 部分 29 | */ 30 | private static void kmeans() { 31 | //输入数据 32 | double[][] input = { 33 | {0.29, 1, 1}, 34 | {0.29, 0.18, 0.58}, 35 | {0.12, 0.3, 0.54}, 36 | {1, 1, 1}, 37 | {0.06, 0.42, 0.6}, 38 | {0.53, 0.64, 0.8}, 39 | {0.24, 1, 0.8}, 40 | {1, 1, 1}, 41 | {0.29, 0.8, 0.56}, 42 | {0.53, 0.8, 1}, 43 | {0.18, 0.8, 1}, 44 | {0.53, 0.8, 0.8}, 45 | {0.29, 0.8, 0.8}, 46 | {0.53, 0.8, 1}, 47 | {0.53, 1, 0.8}, 48 | {0.53, 1, 0.8} 49 | }; 50 | 51 | String[] names = {"中国", "日本", "韩国", "印尼", "澳大利亚", "朝鲜", "伊拉克", "泰国", "伊朗", "沙特", "阿联酋" 52 | , "卡塔尔", "乌兹别克斯坦", "巴林", "阿曼", "约旦"}; 53 | 54 | List points = new ArrayList<>(input.length); 55 | 56 | for (int i = 0; i < input.length; i++) { 57 | Point newPoint = new Point(input[i][0], input[i][1], input[i][2]); 58 | newPoint.setName(names[i]); 59 | newPoint.setClassID(0); 60 | points.add(newPoint); 61 | } 62 | 63 | List> result = Analyze(points, 3); 64 | for (List aResult : result) { 65 | for (Point anAResult : aResult) { 66 | System.out.print(anAResult.getName() + " "); 67 | } 68 | System.out.println(); 69 | } 70 | } 71 | 72 | /** 73 | * 数据挖掘算法 FP-Growth 部分 74 | */ 75 | private static void fpgorwth() { 76 | //当前路径 77 | String workingDictionaryPath = System.getProperty("user.dir"); 78 | 79 | //默认 CSV 文件输入位置 80 | String csvFilePath = workingDictionaryPath + "\\test\\test.csv"; 81 | 82 | //默认书名字典位置 83 | String dictionaryFilePath = workingDictionaryPath + "\\test\\dictionary.csv"; 84 | 85 | //输出文件路径 86 | String outputFilePath = workingDictionaryPath + "\\test\\output.txt"; 87 | 88 | //分隔符 89 | String inputSeparator = ","; 90 | 91 | //FP-Growth 输出分隔符 92 | String fpSeparator = ","; 93 | 94 | //频繁项中间结果 95 | List> fpOutput = new ArrayList<>(); 96 | 97 | //CSV 输入结果 98 | List> fpInput; 99 | 100 | //最终结果 101 | List> result; 102 | 103 | //需要计算的分类 104 | char requiredClass = 'B'; 105 | 106 | printCurrentTime(); 107 | 108 | System.out.println(System.getProperty("user.dir")); 109 | 110 | /* 111 | 预处理过程 112 | */ 113 | fpInput = preProcess(csvFilePath, inputSeparator, requiredClass); 114 | 115 | /* 116 | FP-Growth 算法处理 117 | */ 118 | FPGrowth fpGrowth = new FPGrowth(2); 119 | fpGrowth.getFPOutput(fpInput, null, fpOutput); 120 | 121 | System.out.println("开始再处理"); 122 | 123 | /* 124 | 再处理过程 125 | */ 126 | if ((result = reProcess(dictionaryFilePath, fpSeparator, fpOutput)) == null) { 127 | System.out.println("无法进行再处理过程,程序退出。"); 128 | System.exit(-1); 129 | } 130 | 131 | System.out.println("处理完成,开始输出计算结果"); 132 | /* 133 | 结果输出 134 | */ 135 | getOutput(outputFilePath, result); 136 | 137 | System.out.println("输出完成,程序结束"); 138 | 139 | printCurrentTime(); 140 | } 141 | 142 | } 143 | -------------------------------------------------------------------------------- /src/com/stormlin/fpgrowth/FPGrowth.java: -------------------------------------------------------------------------------- 1 | package com.stormlin.fpgrowth; 2 | 3 | import java.util.*; 4 | 5 | public class FPGrowth { 6 | 7 | //最小支持度 8 | private int minSupport; 9 | 10 | /** 11 | * 自定义构造函数,在此处设定 FP-Growth 的最小支持度 12 | * 13 | * @param minSupport 最小支持度 14 | */ 15 | public FPGrowth(int minSupport) { 16 | this.minSupport = minSupport; 17 | } 18 | 19 | /** 20 | * 计算频繁项集 21 | * 22 | * @param transactions 待处理的事物集 23 | * @param postPattern 累积的前缀模式基 24 | * @param fpOutput 计算结果集 25 | */ 26 | public void getFPOutput(List> transactions, List postPattern, List> fpOutput) { 27 | 28 | //构建头项表 29 | List headerTable = buildHeaderTable(transactions); 30 | //构建FP树 31 | TNode tree = buildFPTree(headerTable, transactions); 32 | 33 | //当树为空时退出 34 | if (tree.getChildren() == null || tree.getChildren().size() == 0) { 35 | return; 36 | } 37 | 38 | //输出频繁项集 39 | if (postPattern != null) { 40 | for (TNode head : headerTable) { 41 | StringBuilder line = new StringBuilder(); 42 | line.append(head.getItemName()); 43 | for (String item : postPattern) { 44 | line.append(" ").append(item); 45 | } 46 | fpOutput.add(Arrays.asList(line.toString().split(" "))); 47 | } 48 | } 49 | 50 | //遍历每一个头项表节点 51 | for (TNode head : headerTable) { 52 | List newPostPattern = new LinkedList<>(); 53 | //添加本次模式基 54 | newPostPattern.add(head.getItemName()); 55 | //加上将前面累积的前缀模式基 56 | if (postPattern != null) { 57 | newPostPattern.addAll(postPattern); 58 | } 59 | //定义新的事务数据库 60 | List> newTransaction = new LinkedList<>(); 61 | TNode nextNode = head.getNext(); 62 | //去除名称为 head.getItemName() 的模式基,构造新的事务数据库 63 | while (nextNode != null) { 64 | int count = nextNode.getCount(); 65 | //nextNode 节点的所有祖先节点 66 | List parentNodes = new ArrayList<>(); 67 | TNode parent = nextNode.getParent(); 68 | while (parent.getItemName() != null) { 69 | parentNodes.add(parent.getItemName()); 70 | parent = parent.getParent(); 71 | } 72 | //向事务数据库中重复添加 count 次 parentNodes 73 | while ((count--) > 0) { 74 | //添加模式基的前缀 ,因此最终的频繁项为: parentNodes -> newPostPattern 75 | newTransaction.add(parentNodes); 76 | } 77 | //下一个同名节点 78 | nextNode = nextNode.getNext(); 79 | } 80 | //每个头项表节点重复上述所有操作,递归 81 | getFPOutput(newTransaction, newPostPattern, fpOutput); 82 | } 83 | 84 | } 85 | 86 | /** 87 | * 构建头项表,按降序排列 88 | * 89 | * @return 构建好的头表项 90 | */ 91 | private List buildHeaderTable(List> transactions) { 92 | 93 | List list = new ArrayList<>(); 94 | Map nodeMap = new HashMap<>(); 95 | 96 | //为每一个 item 构建一个节点 97 | for (List lines : transactions) { 98 | for (String itemName : lines) { 99 | //为 item 构建节点 100 | if (!nodeMap.keySet().contains(itemName)) { 101 | nodeMap.put(itemName, new TNode(itemName)); 102 | } else { 103 | //若已经构建过该节点,出现次数加 1 104 | nodeMap.get(itemName).increaseCount(); 105 | } 106 | } 107 | } 108 | 109 | //筛选满足最小支持度的item节点 110 | for (TNode item : nodeMap.values()) { 111 | if (item.getCount() >= minSupport) { 112 | list.add(item); 113 | } 114 | } 115 | 116 | //按 count 值从高到低排序 117 | Collections.sort(list); 118 | 119 | return list; 120 | } 121 | 122 | /** 123 | * 构建 FP-Tree 124 | * 125 | * @param headerTable 头项表 126 | * @return 按照头表项构建好的 FP-Tree 127 | */ 128 | private TNode buildFPTree(List headerTable, List> transactions) { 129 | 130 | TNode rootNode = new TNode(); 131 | 132 | for (List items : transactions) { 133 | LinkedList itemsDesc = sortItemsByDesc(items, headerTable); 134 | //寻找添加 itemsDesc 为子树的父节点 135 | TNode subtreeRoot = rootNode; 136 | if (subtreeRoot.getChildren().size() != 0) { 137 | TNode tempNode = subtreeRoot.findChildren(itemsDesc.peek()); 138 | while (!itemsDesc.isEmpty() && tempNode != null) { 139 | tempNode.increaseCount(); 140 | subtreeRoot = tempNode; 141 | itemsDesc.poll(); 142 | tempNode = subtreeRoot.findChildren(itemsDesc.peek()); 143 | } 144 | } 145 | //将 itemsDesc 中剩余的节点加入作为 subtreeRoot 的子树 146 | addSubTree(headerTable, subtreeRoot, itemsDesc); 147 | } 148 | 149 | return rootNode; 150 | 151 | } 152 | 153 | /** 154 | * 把子树添加到头表项中 155 | * 156 | * @param headerTable 头项表 157 | * @param subtreeRoot 子树父节点 158 | * @param itemsDesc 被添加的子树 159 | */ 160 | private void addSubTree(List headerTable, TNode subtreeRoot, LinkedList itemsDesc) { 161 | 162 | if (itemsDesc.size() > 0) { 163 | //构建新节点 164 | TNode thisNode = new TNode(itemsDesc.pop()); 165 | subtreeRoot.getChildren().add(thisNode); 166 | thisNode.setParent(subtreeRoot); 167 | //将 thisNode 加入头项表对应节点链表的末尾 168 | for (TNode node : headerTable) { 169 | if (node.getItemName().equals(thisNode.getItemName())) { 170 | TNode lastNode = node; 171 | while (lastNode.getNext() != null) { 172 | lastNode = lastNode.getNext(); 173 | } 174 | lastNode.setNext(thisNode); 175 | } 176 | } 177 | //更新父节点为当前节点 178 | subtreeRoot = thisNode; 179 | //递归添加剩余的 items 180 | addSubTree(headerTable, subtreeRoot, itemsDesc); 181 | } 182 | } 183 | 184 | /** 185 | * 把 Item 以降序排序 186 | * 187 | * @param items 待排序的 items 188 | * @param headerTable 有序的 headTable 189 | * @return 已排序的 items 190 | */ 191 | private LinkedList sortItemsByDesc(List items, List headerTable) { 192 | 193 | LinkedList itemsDesc = new LinkedList<>(); 194 | 195 | for (TNode node : headerTable) { 196 | if (items.contains(node.getItemName())) { 197 | itemsDesc.add(node.getItemName()); 198 | } 199 | } 200 | 201 | return itemsDesc; 202 | 203 | } 204 | 205 | } -------------------------------------------------------------------------------- /src/com/stormlin/fpgrowth/ProcessingUtils.java: -------------------------------------------------------------------------------- 1 | /* 2 | Created by stormlin on 2017/6/5. All rights reserved. 3 | */ 4 | 5 | package com.stormlin.fpgrowth; 6 | 7 | import org.jetbrains.annotations.Nullable; 8 | 9 | import java.io.*; 10 | import java.util.*; 11 | 12 | /** 13 | * 实用工具类,包含了所需的工具方法 14 | */ 15 | public class ProcessingUtils { 16 | 17 | /** 18 | * 打印当前系统时间 19 | */ 20 | public static void printCurrentTime() { 21 | System.out.println(new Date()); 22 | } 23 | 24 | /** 25 | * 预处理方法。负责把提供原数据的 csv 文件处理成这个 FP-Growth 算法可以处理的格式。 26 | * 27 | * @param csvFilePath 数据源 csv 文件路径 28 | * @param inputSeparator csv 文件中的分隔符 29 | * @return 可用于 FP-Growth 算法的输入结果 30 | */ 31 | @Nullable 32 | public static List> preProcess(String csvFilePath, String inputSeparator, char targetClass) { 33 | 34 | List> csvInput = new ArrayList<>(); 35 | 36 | //读取 CSV 文件,构建原始输入 37 | File csvFile = new File(csvFilePath); 38 | 39 | if (!csvFile.exists() || !csvFile.isFile()) { 40 | System.out.println("无法读取指定文件,程序退出"); 41 | return null; 42 | } 43 | 44 | //按文本文件的方式读取 CSV 文件 45 | System.out.println("正在读取 CSV 文件"); 46 | try { 47 | String line; 48 | BufferedReader reader = new BufferedReader(new FileReader(csvFile)); 49 | while ((line = reader.readLine()) != null) { 50 | csvInput.add(Arrays.asList(line.split(inputSeparator))); 51 | } 52 | } catch (Exception e) { 53 | e.printStackTrace(); 54 | return null; 55 | } 56 | //去除 CSV 文件中的标题行 57 | csvInput.remove(0); 58 | 59 | //筛选出符合类别要求的记录项 60 | List> result = new ArrayList<>(); 61 | for (List aCsvInput : csvInput) { 62 | if ((aCsvInput.size() == 3) && aCsvInput.get(2).charAt(0) == targetClass) { 63 | result.add(aCsvInput); 64 | } 65 | } 66 | 67 | List merged = new ArrayList<>(); 68 | List> FPInput = new ArrayList<>(); 69 | int i; 70 | int j; 71 | int inputItemCount = result.size(); 72 | 73 | System.out.println("CSV 文件读取完毕,开始预处理过程"); 74 | 75 | //读取数据并合并同一次结束记录的连续几行记录 76 | for (i = 0; i < inputItemCount; i++) { 77 | merged.add(result.get(i).get(1)); 78 | for (j = i; j < inputItemCount - 1; j++) { 79 | if (Objects.equals(result.get(j).get(0), result.get(j + 1).get(0))) { 80 | merged.add(result.get(j + 1).get(1)); 81 | } else { 82 | break; 83 | } 84 | } 85 | i = j; 86 | if (merged.size() == 1) { 87 | merged.clear(); 88 | continue; 89 | } 90 | Collections.sort(merged); 91 | //note: add() 是否是引用式的添加,还是复制式的添加? 92 | FPInput.add(new ArrayList<>(merged)); 93 | merged.clear(); 94 | } 95 | 96 | return FPInput; 97 | 98 | } 99 | 100 | /** 101 | * 再处理方法。负责把 FP-Growth 算法输出的数据重整为人类可以阅读的频繁项集结果序列。 102 | * 103 | * @param dictionaryFilePath 字典文件路径 104 | * @param dictionarySeparator FP-Growth 输出中的分隔符 105 | * @param fpOutput FP-Growth 的输出结果 106 | * @return 再处理结果,可以直接输出 107 | */ 108 | @Nullable 109 | public static List> reProcess(String dictionaryFilePath, String dictionarySeparator, List> fpOutput) { 110 | 111 | List> result = new ArrayList<>(); 112 | 113 | //制作字典 114 | Dictionary dictionary = new Hashtable<>(); 115 | try { 116 | String line; 117 | BufferedReader reader = new BufferedReader(new FileReader(dictionaryFilePath)); 118 | while ((line = reader.readLine()) != null) { 119 | //fixme: dictionary 中还没能添加 b3589980 的键值对 120 | List dictionaryLine = Arrays.asList(line.split(dictionarySeparator)); 121 | if (dictionaryLine.size() == 2) { 122 | dictionary.put(dictionaryLine.get(0), dictionaryLine.get(1)); 123 | } 124 | } 125 | } catch (Exception e) { 126 | e.printStackTrace(); 127 | return null; 128 | } 129 | //以文件流的形式读取 CSV 文件时,会把标题行读出来,所以需要去除 130 | dictionary.remove("图书记录号(种)"); 131 | 132 | //合并与去重 133 | getResultFiltered(result, fpOutput); 134 | 135 | //替换 136 | for (int i = 0; i < result.size(); i++) { 137 | List line = result.get(i); 138 | for (int j = 0; j < line.size(); j++) { 139 | line.set(j, dictionary.get(line.get(j))); 140 | } 141 | result.set(i, line); 142 | } 143 | 144 | writeResultToFile(new File("E:\\replace.txt"), result); 145 | 146 | return result; 147 | } 148 | 149 | /** 150 | * 对 FP-Growth 输出结果进行合并与去重 151 | * 152 | * @param result 空输出结果集 153 | * @param fpOutput 逆转后的 154 | */ 155 | private static void getResultFiltered(List> result, List> fpOutput) { 156 | 157 | //合并与去重 158 | HashSet temp = new HashSet<>(); 159 | 160 | int elementLeft = fpOutput.size(); 161 | 162 | //任意两行之间如果有交集,就合并他们 163 | while (fpOutput.size() != 0) { 164 | temp.addAll(fpOutput.get(0)); 165 | for (int j = 1; j < elementLeft - 1; j++) { 166 | if (isIntersected(temp, fpOutput.get(j))) { 167 | temp.addAll(fpOutput.get(j)); 168 | fpOutput.remove(j); 169 | elementLeft--; 170 | j--; 171 | } 172 | } 173 | result.add(new ArrayList<>(temp)); 174 | temp.clear(); 175 | elementLeft--; 176 | fpOutput.remove(0); 177 | } 178 | 179 | } 180 | 181 | /** 182 | * 判定两个集合是否有交集。有交集返回 true;没有交集返回 false。 183 | * 184 | * @param a 集合 A 185 | * @param b 集合 B 186 | * @return 结果 187 | */ 188 | private static boolean isIntersected(HashSet a, List b) { 189 | for (String anA : a) { 190 | for (String aB : b) { 191 | if (anA.equals(aB)) { 192 | return true; 193 | } 194 | } 195 | } 196 | return false; 197 | } 198 | 199 | /** 200 | * 把计算结果输出到指定的文件中 201 | * 202 | * @param outputFile 指定的输出文件 203 | * @param result 计算结果 204 | */ 205 | private static void writeResultToFile(File outputFile, List> result) { 206 | try { 207 | BufferedWriter writer = new BufferedWriter(new FileWriter(outputFile)); 208 | for (List line : result) { 209 | for (String item : line) { 210 | writer.write(item + ","); 211 | } 212 | writer.newLine(); 213 | } 214 | writer.flush(); 215 | writer.close(); 216 | } catch (IOException e) { 217 | e.printStackTrace(); 218 | } 219 | } 220 | 221 | /** 222 | * 把最终的计算结果输出到文件中 223 | * 224 | * @param outputFilePath 输出文件路径 225 | * @param result 计算结果 226 | */ 227 | public static void getOutput(String outputFilePath, List> result) { 228 | File outputFile = new File(outputFilePath); 229 | if (!outputFile.exists()) { 230 | try { 231 | if (!outputFile.createNewFile()) { 232 | System.out.println("无法输出计算结果,程序退出"); 233 | System.exit(-1); 234 | } 235 | System.out.println("创建输出文件:" + outputFile.getPath()); 236 | writeResultToFile(outputFile, result); 237 | } catch (Exception e) { 238 | e.printStackTrace(); 239 | } 240 | } else { 241 | try { 242 | if (!outputFile.delete()) { 243 | System.out.println("无法输出计算结果,程序退出"); 244 | System.exit(-1); 245 | } 246 | System.out.println("目标文件已存在,将删除源文件,并创建新的同名文件:" + outputFile.getPath()); 247 | if (!outputFile.createNewFile()) { 248 | System.out.println("无法输出计算结果,程序退出"); 249 | System.exit(-1); 250 | } 251 | writeResultToFile(outputFile, result); 252 | } catch (IOException e) { 253 | e.printStackTrace(); 254 | } 255 | } 256 | } 257 | 258 | } 259 | -------------------------------------------------------------------------------- /src/com/stormlin/fpgrowth/TNode.java: -------------------------------------------------------------------------------- 1 | package com.stormlin.fpgrowth; 2 | 3 | import org.jetbrains.annotations.NotNull; 4 | 5 | import java.util.ArrayList; 6 | import java.util.List; 7 | 8 | /** 9 | * @Author stormlin 10 | * @DATE 2017/6/2 11 | * @TIME 9:13 12 | * @PROJECT DataMining 13 | * @PACKAGE PACKAGE_NAME 14 | */ 15 | public class TNode implements Comparable { 16 | private String itemName; //项目名 17 | private int count; //事务数据库中出现次数 18 | private TNode parent; //父节点 19 | private List children; //子节点 20 | private TNode next;//下一个同名节点 21 | 22 | TNode() { 23 | this.children = new ArrayList<>(); 24 | } 25 | 26 | TNode(String name) { 27 | this.itemName = name; 28 | this.count = 1; 29 | this.children = new ArrayList<>(); 30 | } 31 | 32 | TNode findChildren(String childName) { 33 | for (TNode node : this.getChildren()) { 34 | if (node.getItemName().equals(childName)) { 35 | return node; 36 | } 37 | } 38 | return null; 39 | } 40 | 41 | TNode getNext() { 42 | return next; 43 | } 44 | 45 | void setNext(TNode next) { 46 | this.next = next; 47 | } 48 | 49 | TNode getParent() { 50 | return parent; 51 | } 52 | 53 | void setParent(TNode parent) { 54 | this.parent = parent; 55 | } 56 | 57 | void increaseCount() { 58 | count += 1; 59 | } 60 | 61 | int getCount() { 62 | return count; 63 | } 64 | 65 | String getItemName() { 66 | return itemName; 67 | } 68 | 69 | List getChildren() { 70 | return children; 71 | } 72 | 73 | @Override 74 | public int compareTo(@NotNull TNode o) { 75 | return o.getCount() - this.getCount(); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/com/stormlin/kmeans/KMeans.java: -------------------------------------------------------------------------------- 1 | package com.stormlin.kmeans; 2 | 3 | import org.jetbrains.annotations.Nullable; 4 | 5 | import java.io.BufferedWriter; 6 | import java.io.FileWriter; 7 | import java.io.IOException; 8 | import java.util.ArrayList; 9 | import java.util.HashSet; 10 | import java.util.List; 11 | 12 | /** 13 | * 用于聚类分析的 K-Means 算法。 14 | * 15 | * @author: stormlin 16 | * @project: DataMining 17 | * @package: com.stormlin.kmeans 18 | * @DataTime: 2017/6/5 22:15 19 | */ 20 | public class KMeans { 21 | 22 | /** 23 | * 在输入的三维点集 points 中按照 K 类进行聚类分析 24 | * 25 | * @param points 输入点集 26 | * @param K 分类数目 27 | */ 28 | public static List> Analyze(List points, int K) { 29 | 30 | List> result = new ArrayList<>(K); 31 | for (int k = 0; k < K; k++) { 32 | result.add(new ArrayList<>()); 33 | } 34 | 35 | //随机选择基准样本点 36 | int[] rands = getUnrepeatedRandomNumbers(0, points.size(), K); 37 | Point[] randomClusterCenter = new Point[K]; 38 | for (int i = 0; i < rands.length; i++) { 39 | randomClusterCenter[i] = points.get(rands[i]); 40 | } 41 | 42 | //新旧两个质心数组 43 | Point[] oldClusterCenter; 44 | Point[] newClusterCenter; 45 | 46 | oldClusterCenter = getArrayCopy(randomClusterCenter); 47 | newClusterCenter = getArrayCopy(randomClusterCenter); 48 | 49 | //两点之间的欧几里得距离 50 | double[][] distance = new double[points.size()][K]; 51 | //计算每一行与 K 个不同质心之间的欧几里得距离,重复计算直至收敛 52 | while (true) { 53 | //计算所有点对 K 个不同质心之间的欧几里得距离,并分到欧几里得距离最小的一类中 54 | for (int i = 0; i < points.size(); i++) { 55 | //计算 56 | for (int k = 0; k < K; k++) { 57 | distance[i][k] = getEuclidDistance(points.get(i), oldClusterCenter[k]); 58 | } 59 | double min = distance[i][0]; 60 | //分类 61 | for (int k = 0; k < K; k++) { 62 | if (distance[i][k] < min) { 63 | min = distance[i][k]; 64 | points.get(i).setClassID(k); 65 | } 66 | } 67 | //把每一类的点加入到各自的分类中 68 | result.get(points.get(i).getClassID()).add(points.get(i)); 69 | } 70 | dumpToFile(result); 71 | //重新计算每一类的质心 72 | for (int k = 0; k < K; k++) { 73 | newClusterCenter[k] = getClusterCenter(result.get(k)); 74 | } 75 | //质心不再移动则退出 76 | if (!isClusterCenterChanged(oldClusterCenter, newClusterCenter)) { 77 | break; 78 | } 79 | for (int k = 0; k < K; k++) { 80 | result.set(k, new ArrayList<>()); 81 | } 82 | oldClusterCenter = getArrayCopy(newClusterCenter); 83 | } 84 | 85 | return result; 86 | } 87 | 88 | /** 89 | * 判断质心是否移动 90 | * 91 | * @param a 质心数组 a 92 | * @param b 质心数组 b 93 | * @return 如果移动,则返回 true;否则返回 false 94 | */ 95 | private static boolean isClusterCenterChanged(Point[] a, Point[] b) { 96 | 97 | for (int i = 0; i < a.length; i++) { 98 | if (a[i].getX() != b[i].getX()) { 99 | return true; 100 | } else if (a[i].getY() != b[i].getY()) { 101 | return true; 102 | } else if (a[i].getZ() != b[i].getZ()) { 103 | return true; 104 | } 105 | } 106 | 107 | return false; 108 | 109 | } 110 | 111 | /** 112 | * 计算三维点集 points 中的质心 113 | * 114 | * @param points 需要计算质心的三维点集 115 | * @return 质心 116 | */ 117 | private static Point getClusterCenter(List points) { 118 | if (points.size() == 0) { 119 | return null; 120 | } 121 | if (points.size() == 1) { 122 | return new Point(points.get(0).getX(), points.get(0).getY(), points.get(0).getZ()); 123 | } 124 | 125 | double x = 0; 126 | double y = 0; 127 | double z = 0; 128 | 129 | for (Point point : points) { 130 | x += point.getX(); 131 | y += point.getY(); 132 | z += point.getZ(); 133 | } 134 | x = x / points.size(); 135 | y = y / points.size(); 136 | z = z / points.size(); 137 | 138 | return new Point(x, y, z); 139 | } 140 | 141 | /** 142 | * 获得数组 b 的拷贝 143 | * 144 | * @param b 数组 b 145 | * @return 数组 b 的拷贝 146 | */ 147 | @Nullable 148 | private static Point[] getArrayCopy(Point[] b) { 149 | 150 | Point[] a = new Point[b.length]; 151 | 152 | if (a.length == 0 || b.length == 0) { 153 | return null; 154 | } 155 | 156 | System.arraycopy(b, 0, a, 0, a.length); 157 | 158 | return a; 159 | } 160 | 161 | /** 162 | * 计算 [min, max) 范围内不重复的 n 个随机数 163 | * 164 | * @param min 范围最小值 165 | * @param max 范围最大值 166 | * @param count 所需随机数个数 167 | * @return 不重复的随机数数组 168 | */ 169 | private static int[] getUnrepeatedRandomNumbers(int min, int max, int count) { 170 | 171 | int[] result = new int[count]; 172 | int i = 0; 173 | HashSet temp = new HashSet<>(); 174 | 175 | while (true) { 176 | if (temp.size() == count) { 177 | break; 178 | } 179 | temp.add((int) (Math.random() * (max - min)) + min); 180 | } 181 | 182 | for (Integer item : temp) { 183 | result[i] = item; 184 | i++; 185 | } 186 | 187 | return result; 188 | 189 | } 190 | 191 | /** 192 | * 计算 a,b 两点之间的欧几里得距离 193 | * 194 | * @param a 第一个数 195 | * @param b 第二个数 196 | * @return 两数之间的欧几里得距离 197 | */ 198 | private static double getEuclidDistance(Point a, Point b) { 199 | 200 | double result = 0; 201 | 202 | result += Math.pow(a.getX() - b.getX(), 2); 203 | result += Math.pow(a.getY() - b.getY(), 2); 204 | result += Math.pow(a.getZ() - b.getZ(), 2); 205 | 206 | return Math.sqrt(result); 207 | } 208 | 209 | /** 210 | * 按列规格化输入数组 211 | * 212 | * @param input 未规格化的数组 213 | * @return 规格化结果 214 | */ 215 | private static void getInputNormalized(double[][] input) { 216 | double[] max = new double[3]; 217 | double[] min = new double[3]; 218 | double tempMax = 0; 219 | double tempMin = 0; 220 | 221 | //按列计算最大值和最小值 222 | for (int i = 0; i < input.length; i++) { 223 | for (int j = 0; j < input.length; j++) { 224 | if (input[i][j] >= tempMax) { 225 | tempMax = input[i][j]; 226 | } 227 | if (input[i][j] <= tempMin) { 228 | tempMin = input[i][j]; 229 | } 230 | } 231 | max[i] = tempMax; 232 | min[i] = tempMin; 233 | } 234 | 235 | for (double[] line : input) { 236 | for (int i = 0; i < line.length; i++) { 237 | line[i] = (line[i] - min[i]) / (max[i] - min[i]); 238 | } 239 | } 240 | 241 | } 242 | 243 | /** 244 | * 把计算结果输出到文件中 245 | * 246 | * @param result 计算结果 247 | */ 248 | private static void dumpToFile(List> result) { 249 | 250 | String outputFilePath = System.getProperty("user.dir") + "\\test\\output.txt"; 251 | 252 | try { 253 | BufferedWriter writer = new BufferedWriter(new FileWriter(outputFilePath, true)); 254 | for (List line : result) { 255 | for (Point item : line) { 256 | writer.write(item.getName() + ","); 257 | } 258 | writer.newLine(); 259 | } 260 | writer.flush(); 261 | writer.close(); 262 | } catch (IOException e) { 263 | e.printStackTrace(); 264 | } 265 | 266 | } 267 | 268 | } 269 | -------------------------------------------------------------------------------- /src/com/stormlin/kmeans/Point.java: -------------------------------------------------------------------------------- 1 | package com.stormlin.kmeans; 2 | 3 | /** 4 | * 代表一个点 5 | * 6 | * @Author stormlin 7 | * @DATE 2017/6/6 8 | * @TIME 14:38 9 | * @PROJECT DataMining 10 | * @PACKAGE com.stormlin.kmeans 11 | */ 12 | public class Point { 13 | 14 | private double x = 0; 15 | private double y = 0; 16 | private double z = 0; 17 | 18 | private int classID = 0; 19 | private String name = ""; 20 | 21 | public Point(double x, double y, double z) { 22 | this.x = x; 23 | this.y = y; 24 | this.z = z; 25 | } 26 | 27 | public double getX() { 28 | return x; 29 | } 30 | 31 | public void setX(double x) { 32 | this.x = x; 33 | } 34 | 35 | public double getY() { 36 | return y; 37 | } 38 | 39 | public void setY(double y) { 40 | this.y = y; 41 | } 42 | 43 | public double getZ() { 44 | return z; 45 | } 46 | 47 | public void setZ(double z) { 48 | this.z = z; 49 | } 50 | 51 | public int getClassID() { 52 | return classID; 53 | } 54 | 55 | public void setClassID(int classID) { 56 | this.classID = classID; 57 | } 58 | 59 | public String getName() { 60 | return name; 61 | } 62 | 63 | public void setName(String name) { 64 | this.name = name; 65 | } 66 | 67 | } -------------------------------------------------------------------------------- /test/output.txt: -------------------------------------------------------------------------------- 1 | 西方哲学史,西方哲学史,西方哲学史,精神分析引论,梦的解析, 2 | 整理的艺术,整理的艺术,整理的艺术, 3 | 规训与惩罚,疯癫与文明, 4 | 世界上最伟大的50种思维方法,重口味心理学实验,大忙人情绪管理枕边书,聪明人如何应对工作压力 ,微表情,哈佛最神奇的8堂情商课, 5 | 向着光亮那方,你只是还未全力以赴, 6 | 像哲学家一样思考,中国哲学简史,西方哲学史, 7 | 重口味心理学,诡异心理学,重口味心理学, 8 | 走出思维的误区,理性动物,逻辑思维简易入门,思考的艺术, 9 | 性学三论,日常生活的心理分析, 10 | 当你的才华还撑不起你的梦想时,谁说咸鱼没有梦想,你所谓的稳定,不过是在浪费生命, 11 | 神学美学,基督教与文学,巴尔塔萨神学美学思想研究,圣经文学二十讲,圣经文学通论, 12 | 每天懂一点性格心理学,直面内心的恐惧, 13 | 批判性思维教程,批判性思维训练手册, 14 | 超级催眠术,图解博弈心理学,FBI识人术与测谎术, 15 | 我怎么没想到?,决策与判断, 16 | 象征交换与死亡,后身体,身体、空间与后现代性, 17 | 意义的本体论,胡塞尔的意义理论,时间性,时间性: 自身与他者,意义诠释与未来时间维度,意识与意义,本源与意义, 18 | 人生永远没有太晚的开始,人生只有一次,去做自己喜欢的事, 19 | 哲学的慰藉,理想国, 20 | 大学生心理咨询与治疗案例解析,人性的弱点, 21 | Gifts to a magus :,The spirit of Zoroastrianism /, 22 | 动机心理学,行为背后的动机, 23 | 爱在136.1,好好爱自己, 24 | 人类的未来,PHI,黑格尔导论, 25 | 遇见心想事成的自己,重遇未知的自己, 26 | 弗洛伊德心理分析书,弗洛伊德性学经典, 27 | 纯粹理性批判,查拉图斯特拉如是说,权力意志, 28 | 当时忍住就好了,自控力心理学, 29 | 道教与唐代社会,宗教文化与唐五代笔记小说,道教史,道教与唐代文学,道教医学,逻辑新引,中国道教史,中国的道教,汉唐道教修炼方式与道教女性观之变化研究, 30 | 庄子發微,南華雪心編, 31 | 思维导图,思维导图宝典, 32 | 周易程氏傳,大易识阶,十八名家解周易, 33 | 正见,金刚经修心课, 34 | 人生要经得起诱惑,耐得住寂寞,真希望我20几岁就知道的事,卡耐基成功学白金70年, 35 | 自控力,精力管理,掌控, 36 | 荣格文集,荣格文集, 37 | 卢梭全集,现代社会中的人性及教育,卢梭全集, 38 | 易学思维研究,思维的版图,像中国人一样思考,中国传统思维方法研究,灵感,中国系统思维,钱学森思维科学思想,智慧的钥匙,思维科学概论, 39 | 辜鸿铭讲论语,荣格心理术,沉思录, 40 | 法兰克福学派,法兰克福学派史, 41 | 实现罗尔斯,罗尔斯论文全集, 42 | 奥古斯丁图传,忏悔录,恩典与自由, 43 | 哈佛情商课 哈佛财商课大全,荣格文集, 44 | 共情时代,心理学常识速查速用大全集, 45 | 情绪心理学,情绪心理学,情绪调节手册,穿越抑郁的正念之道,情绪表达、文化与心理健康,重建情绪与人格心理学,情绪,情绪管理压力应对,情绪心理学,情绪管理原理与方法,情绪的解析, 46 | 家训之祖,做个人见人爱的性格美女, 47 | 希腊罗马神话的文化鉴赏,希腊罗马神话, 48 | 存在与虚无,萨特与存在主义和人道主义, 49 | 诸般不美好皆可温柔相待,null, 50 | 哲学纲要,批判哲学的批判,中国古代思想史论, 51 | 逻辑学,逻辑学, 52 | 单向度的人,爱欲与文明, 53 | 阴阳家简史,紫微斗数讲义, 54 | 日常生活批判,现代性的平庸与神奇, 55 | 神话与传说,雅克·拉康,空间的诗学,怀疑主义与动物信仰, 56 | 青春就是不妥协,展现魅力 给青春添彩, 57 | 柏拉图对话六种,智者,巴曼尼得斯篇,蒂迈欧篇, 58 | 马礼逊回忆录,卫三畏生平及书信, 59 | 哈贝马斯,审美乌托邦的想象,公共领域与生活世界,哈贝马斯公共领域思想研究, 60 | 中国佛教与传统文化,中国佛教与民间社会,中古的佛教与社会, 61 | 王夫之评传,船山思问录, 62 | 苏格拉底之死,理想国, 63 | 性商与爱商,自尊有毒, 64 | null,心灵之光,游过生活六道弯,成为你自己, 65 | 新教伦理与资本主义精神,新教伦理与资本主义精神,韦伯《新教伦理与资夲主义精神》导读, 66 | 读懂厚黑学的第一本书,厚黑学大全集, 67 | 媒介时代的审美问题研究,消费时代审美问题研究, 68 | 图解道教,道士,道教风俗谈, 69 | 柏拉图《理想国》剑桥指南,认识你自己, 70 | 每天懂一点潜伏心理学,每天懂一点行为心理学, 71 | 生活美学,形象设计,世界又平又美,生活中的美学, 72 | 语言 身体 他者,身体的神秘, 73 | 孟子譯注,老子译注, 74 | 我的人生哲学,幸福的方法, 75 | 王弼集校释,王弼集校释, 76 | 不能不去爱的两件事,崭新的理所当然, 77 | 高效能人士的七个习惯,全球高效能人士给靑年人的50个忠告, 78 | 不抱怨的世界,不抱怨的世界, 79 | 谶纬叙事研究,谶纬与汉代文学,敦煌占卜文书与唐五代占卜研究, 80 | 最重要的事只有一件,释放更快乐的自己, 81 | 老子,老子·庄子, 82 | 德国哲学概观,康德哲学原著选读, 83 | “全球化”的宗教与当代中国,中国宗教与文化战略,宗教理解, 84 | 论天人之际,论语新解,人类思想史,浙学传统与浙江精神论集,古代思想文化的世界, 85 | 实在、心灵与信念,普特南文选, 86 | 世界哲学简史,现代哲学简史, 87 | 从结构到解构,从结构到解构, 88 | 荣格与分析心理学,荣格文集,百分百荣格,分析心理学的理论与实践,荣格, 89 | 怪诞心理学,怪诞心理学, 90 | 逻辑学基础教程,现代心理学,MBA、MPA、MPAc、GCT逻辑推理, 91 | 正能量,遇见最好的自己,只想静下来, 92 | 梦之旅,人生无常 当下最真, 93 | 当代回族伊斯兰法文化,伊斯兰威胁,古兰经, 94 | 六祖慧能研究,明镜与风幡,中国禅宗通史,佛寺探秘,中国禅寺, 95 | 古希腊罗马犬儒现象研究,颓废与沉默, 96 | 自我的超越性,对笛卡尔<<沉思>>的诘难, 97 | 指引生命的神话,追随直觉之路, 98 | 二重洗脑,宗教与资本主义的兴起, 99 | 如何安心如何空,般若波罗密多心经·讲录, 100 | 向君子借智慧,小心!逻辑思维陷阱, 101 | 皈信·同化·叠合身份认同,清教与美国, 102 | 实验心理学,实验心理学,实验心理学, 103 | 李泽厚对话集,李泽厚对话集, 104 | 哲学的故事,文明之门, 105 | 美学意识形态,黄与蓝的交响, 106 | 符号政治经济学批判,符号与象征, 107 | 一个悲观主义者的积极思考,曙光, 108 | 庄子,梦的释义, 109 | 善的研究,善の研究,続西田哲学, 110 | 风水的常识与应用,中国风水文化博览, 111 | 丑的象征,心智探奇,中国人审美心理研究, 112 | 心理学定律与经济学定律大全集,女性的负面, 113 | 心的面貌,与人联结, 114 | 犬儒主义与后现代性,意识形态的崇高客体, 115 | 解读《存在与时间》,存在与时间, 116 | 波德里亚,波德里亚, 117 | 在北大听到的80堂哲学课,用正确的方法解决问题, 118 | 墨子全译,墨子讲读, 119 | 大哲学家,纯粹理性批判,海德格尔与雅斯贝尔斯往复书简, 120 | 现代性、后现代性和全球化,梁启超与中国思想的过渡, 121 | 福柯,福柯思想肖像, 122 | 新书,白虎通疏证,白虎通疏证, 123 | 钱宾四先生全集,钱宾四先生全集, 124 | 如果你知道去哪,全世界都会为你让路,本性孤独, 125 | 马克思主义哲学基础教程,辩证唯物主义和历史唯物主义纲要,马克思主义哲学论稿, 126 | 中国思想史,中国思想史, 127 | 佛洛依德之日常心理分析,佛洛依德之精神分析論, 128 | Psychology /,心理学, 129 | 宗教人类学导论,宗教生活的基本形式, 130 | 游叙弗伦·苏格拉底的申辨·克力同,理想国, 131 | 从古典重新开始,中国的宗教, 132 | 日本的众神,中国创世神话, 133 | 人生观通论,中国思想论集, 134 | 日本陰陽道史総説,古代東アジアの「祈り」, 135 | 李叔同《晚晴集》人生解读,肉身供养, 136 | 心理学与微表情微反应,心理学与心理调节术, 137 | 宗教学通论新编,宗教学通论新编, 138 | 西方哲学简史,《理想国篇》译注与诠释, 139 | 美国 镜像,沉重的肉身,生命美学与生态美学的对话, 140 | 批判思维与论辩,批判与创意思考, 141 | 《五行大义》白话全解,阴阳家语录, 142 | 周易译注,先秦诸子百家争鸣, 143 | 女汉子的幸福宝典,你的努力,终将成就无可替代的自己, 144 | 先秦诸子思想精华与文学价值研究,鬼谷子全鉴, 145 | 改变心理学的40项研究,心理与行为科学统计, 146 | 庄子的思想世界,庄子,庄子的智慧,庄子智慧心解,庄子为什么这样跩,我读庄子, 147 | 基督教哲学在中国,基督教与明末儒学, 148 | 会简化工作的人升职更快,聪明人用方格笔记本, 149 | 性格决定领导力,性格决定领导力, 150 | 論佛洛伊德的「一個正挨打的小孩」,幻想與無意識, 151 | 哈贝马斯的现代性社会理论,哈贝马斯的批判理论, 152 | 幻想彼岸的救赎,弗洛姆新人道主义伦理思想研究, 153 | 计算机伦理学,信息技术与道德哲学, 154 | 听老子讲道,还原《道德经》本意, 155 | 众神喧哗中的十字架,妈祖信仰史研究, 156 | 当代西方哲学思潮,诠释学导论, 157 | 金刚经鉴赏辞典,非常金刚经, 158 | 西方哲学史,当代法国伦理思想, 159 | 论语,国学精粹, 160 | 超越自卑,为何越爱越孤独, 161 | 九型人格与领导力,九型人格读心术, 162 | 海德格尔选集,海德格尔选集, 163 | 学术与社会,清季民国时期的“思想界”, 164 | 诙谐及其与潜意识的关系,精神分析导论, 165 | 尼采,康德, 166 | 中国禅宗史,禅宗思想的形成与发展, 167 | 活学活用心理学,心理学考研重难点手册基础备考, 168 | 基督教与近代中国的不平等条约,基督教与近代中国社会, 169 | 民主与教育,创新的扩散, 170 | 个人实相的本质,解梦九讲, 171 | 王船山学术论丛,王船山学术讨论集, 172 | 福柯文选,福柯文选, 173 | 劳心者定律全集,《塔木德》智慧大全集, 174 | 成就的秘訣,觉悟的生活, 175 | 詩性智慧與非理性哲學,维柯的《新科学》及其对中西美学的影响, 176 | 唐代宗教信仰与社会,佛教与中国古代科技, 177 | 苏格拉底这样思考,男腔女调, 178 | --------------------------------------------------------------------------------