0.目录

1.前置知识

本文内容基于《Accelerating exact k-means algorithms with geometric reasoning
KDTree
k-means

2.思路介绍

k-means算法在初始化中心点后C通过以下迭代步骤得到局部最优解:

  a.将数据集D中的点x赋给距离最近的中心点

  b.在每个聚类中,重新计算中心点

传统算法中,a步需要计算n*k个距离(n为D的大小,k为聚类个数),b步需要相加n个数据点

而在KDTree中,每个非叶子节点,都存储了其包含的数据的数据范围信息h。

二维空间中的h可以使用矩形来表示
图中*为点,红色矩形为数据范围h 

a.
如果通过范围信息,能判断节点中数据都属于中心点c,则能省去节点中数据到中心点距离的计算

    
如果能判断h中数据都不属于某中心点c,则能省去节点中数据到中心点c距离的计算

  b.
当知道节点中数据全部属于c,能将h中事先加好的统计量直接加到c的统计量中

3.详述

3.1
确定h的中心点(h中所有数据都离这个中心点近而离其他中心点远)

KDTree的节点中存储的Max(各维度上的最大值)和Min(各维度上的最小值)确定了节点中数据的范围

中心点有(c1,c2,...,ck)

a.
判断是否可能存在

  计算各中心点到h的最小距离(参考KDTree最近邻查找,第5步) d(ci,h)

  如果存在一个最小距离,则这个ci可能是h的中心点(还需要进一步判断)

  若存在不止一个最小距离,则h的中心点不存在,需要将h分割为更小(在h的左右树上)后查找

正方形表示的点都在h的内部
所以他们到h的最小距离相同,都为0
此h不存在中心点     

b.
进一步判断,ci是否为中心点

L12为c1和c2连线的中位线,h全部落在c1一边,

所以h中的全部点离c1比离c2近,称c1优于c2


而对于c1和c3来说,h有一部分落在c1,有一部分落在c3

c1不优于c3
判断c1是否优于c3:

取向量v=(c3-c1),找到点p属于h,使<v,p>内积最大

v各维度正负情况(+,-),则p在x轴上尽可能大,y轴上尽可能小,取到p13

p13离c3近,所以c1不优于c3

如果ci在优于其他点,则可以判定ci即为h的中心点;否则ci不是h的中心点;
  虽然ci不是h的中心点,但是得到的信息,如ci优于c2,能将c2从h的子树的中心点候选列表中排除

3.2
算法步骤

KDTree中每个非叶子节点特殊属性:
sumOfPoints:m维向量(m是数据的维度),其i维度的值为节点中数据第i维的和
n:节点中数据的个数
输入:KDTree,C 包括中心点(c1,c2,...,ck)
输出:CNEW 新的k个中心点
node=KDTree.root
centers=k*m的数组//每行存储属于这个中心点的数据的和
datacount=k*1的数组//存储属于这个中心点的数据个数
UPDATE(node,C):
IF node为叶子节点
  遍历计算得到离node最近的节点ct
  centers[t]+=node.value;
  datacount[t]+=1;
  RETURN;

FOR(ci in C)  计算d(ci,node.h)
IF 有多个最小的d(ci,node.h)
  UPDATE(node.left,C);
  UPDATE(node.right,C);
  RETURN;
//假设d(ci,node.h)最小的是ct
CTOVER=[]//存储劣于ct的
FOR(ci in C(除了ct))  IF(ct 优于 ci) CTOVER.ADD(ci)
IF(LEN(CTOVER)=LEN(C)-1)//ct优于其他的中心点
  centers[t]+=node.sumOfPoints;
  datacount[t]+=node.n;
  RETURN;
CT=(ci in C 且 ci not in CTOVER)//排除比ct差的中心点
UPDATE(node.left,CT);
UPDATE(node.right,CT);
RETURN;

4.java实现

a.用下列matlab方法生成测试数据

  1. #centers为中心点个数,dimention为数据维度,persize为每个中心点包含的数据量
    function cdata(centers,dimention,persize)
  2.  
  3. d=zeros(centers*persize,dimention);
  4. sigma=eye(dimention);
  5. for i=1:centers
  6. mu=randi(20,1,dimention);
  7. d(((i-1)*persize+1):i*persize,:)=mvnrnd(mu,sigma,persize);
  8. end
  9. dlmwrite('d.txt',d,'delimiter','\t','precision','%10.4f')
  10. end

b.kdtree

  1. package cc;
  2. import java.util.ArrayList;
  3. import java.util.HashMap;
  4.  
  5. public class MRKDTree {
  6.  
  7. private Node mrkdtree;
  8.  
  9. private class Node{
  10. //分割的维度
  11. int partitionDimention;
  12. //分割的值
  13. double partitionValue;
  14. //如果为非叶子节点,该属性为空
  15. //否则为数据
  16. double[] value;
  17. //是否为叶子
  18. boolean isLeaf=false;
  19. //左树
  20. Node left;
  21. //右树
  22. Node right;
  23. //每个维度的最小值
  24. double[] min;
  25. //每个维度的最大值
  26. double[] max;
  27.  
  28. double[] sumOfPoints;
  29. int n;
  30. }
  31.  
  32. private static class UtilZ{
  33. /**
  34. * 计算给定维度的方差
  35. * @param data 数据
  36. * @param dimention 维度
  37. * @return 方差
  38. */
  39. static double variance(ArrayList<double[]> data,int dimention){
  40. double vsum = 0;
  41. double sum = 0;
  42. for(double[] d:data){
  43. sum+=d[dimention];
  44. vsum+=d[dimention]*d[dimention];
  45. }
  46. int n = data.size();
  47. return vsum/n-Math.pow(sum/n, 2);
  48. }
  49. /**
  50. * 取排序后的中间位置数值
  51. * @param data 数据
  52. * @param dimention 维度
  53. * @return
  54. */
  55. static double median(ArrayList<double[]> data,int dimention){
  56. double[] d =new double[data.size()];
  57. int i=0;
  58. for(double[] k:data){
  59. d[i++]=k[dimention];
  60. }
  61. return median(d);
  62. }
  63.  
  64. private static double median(double[] a){
  65. int n=a.length;
  66. int L = 0;
  67. int R = n - 1;
  68. int k = n / 2;
  69. int i;
  70. int j;
  71. while (L < R) {
  72. double x = a[k];
  73. i = L;
  74. j = R;
  75. do {
  76. while (a[i] < x)
  77. i++;
  78. while (x < a[j])
  79. j--;
  80. if (i <= j) {
  81. double t = a[i];
  82. a[i] = a[j];
  83. a[j] = t;
  84. i++;
  85. j--;
  86. }
  87. } while (i <= j);
  88. if (j < k)
  89. L = i;
  90. if (k < i)
  91. R = j;
  92. }
  93. return a[k];
  94. }
  95.  
  96. static double[][] maxmin(ArrayList<double[]> data,int dimentions){
  97. double[][] mm = new double[2][dimentions];
  98. //初始化 第一行为min,第二行为max
  99. for(int i=0;i<dimentions;i++){
  100. mm[0][i]=mm[1][i]=data.get(0)[i];
  101. for(int j=1;j<data.size();j++){
  102. double[] d = data.get(j);
  103. if(d[i]<mm[0][i]){
  104. mm[0][i]=d[i];
  105. }else if(d[i]>mm[1][i]){
  106. mm[1][i]=d[i];
  107. }
  108. }
  109. }
  110. return mm;
  111. }
  112.  
  113. static double distance(double[] a,double[] b){
  114. double sum = 0;
  115. for(int i=0;i<a.length;i++){
  116. sum+=Math.pow(a[i]-b[i], 2);
  117. }
  118. return sum;
  119. }
  120.  
  121. /**
  122. * 在max和min表示的超矩形中的点和点a的最小距离
  123. * @param a 点a
  124. * @param max 超矩形各个维度的最大值
  125. * @param min 超矩形各个维度的最小值
  126. * @return 超矩形中的点和点a的最小距离
  127. */
  128. static double mindistance(double[] a,double[] max,double[] min){
  129. double sum = 0;
  130. for(int i=0;i<a.length;i++){
  131. if(a[i]>max[i])
  132. sum += Math.pow(a[i]-max[i], 2);
  133. else if (a[i]<min[i]) {
  134. sum += Math.pow(min[i]-a[i], 2);
  135. }
  136. }
  137.  
  138. return sum;
  139. }
  140.  
  141. public static double[] sumOfPoints(ArrayList<double[]> data,
  142. int dimentions) {
  143. double[] res = new double[dimentions];
  144. for(double[] d:data){
  145. for(int i=0;i<dimentions;i++){
  146. res[i]+=d[i];
  147. }
  148. }
  149. return res;
  150. }
  151. /**
  152. * 判断centerd是否在h上优于c
  153. * @param centerd
  154. * @param c
  155. * @param max
  156. * @param min
  157. * @return
  158. */
  159. public static boolean isOver(double[] center, double[] c,
  160. double[] max, double[] min) {
  161. double discenter = 0;
  162. double disc = 0;
  163. for(int i=0;i<c.length;i++){
  164. if(c[i]-center[i]>0){
  165. disc+=Math.pow(max[i]-c[i],2);
  166. discenter+=Math.pow(max[i]-center[i],2);
  167. }else if(c[i]-center[i]<0) {
  168. disc+=Math.pow(min[i]-c[i],2);
  169. discenter+=Math.pow(min[i]-center[i],2);
  170. }
  171.  
  172. }
  173. return discenter<disc;
  174. }
  175. }
  176.  
  177. private MRKDTree() {}
  178. /**
  179. * 构建树
  180. * @param input 输入
  181. * @return KDTree树
  182. */
  183. public static MRKDTree build(double[][] input){
  184. int n = input.length;
  185. int m = input[0].length;
  186.  
  187. ArrayList<double[]> data =new ArrayList<double[]>(n);
  188. for(int i=0;i<n;i++){
  189. double[] d = new double[m];
  190. for(int j=0;j<m;j++)
  191. d[j]=input[i][j];
  192. data.add(d);
  193. }
  194.  
  195. MRKDTree tree = new MRKDTree();
  196. tree.mrkdtree = tree.new Node();
  197. tree.buildDetail(tree.mrkdtree, data, m,0);
  198.  
  199. return tree;
  200. }
  201. /**
  202. * 循环构建树
  203. * @param node 节点
  204. * @param data 数据
  205. * @param dimentions 数据的维度
  206. */
  207. private void buildDetail(Node node,ArrayList<double[]> data,int dimentions,int lv){
  208. if(data.size()==1){
  209. node.isLeaf=true;
  210. node.value=data.get(0);
  211. return;
  212. }
  213.  
  214. //选择方差最大的维度
  215. /*
  216. node.partitionDimention=-1;
  217. double var = -1;
  218. double tmpvar;
  219. for(int i=0;i<dimentions;i++){
  220. tmpvar=UtilZ.variance(data, i);
  221. if (tmpvar>var){
  222. var = tmpvar;
  223. node.partitionDimention = i;
  224. }
  225. }
  226. //如果方差=0,表示所有数据都相同,判定为叶子节点
  227. if(var<1e-10){
  228. node.isLeaf=true;
  229. node.value=data.get(0);
  230. return;
  231. }
  232. */
  233. double[][] maxmin=UtilZ.maxmin(data, dimentions);
  234.  
  235. node.min = maxmin[0];
  236. node.max = maxmin[1];
  237.  
  238. //选取方差大的维度,会需要很长时间
  239. //改成使用选取数据范围最大的维度
  240. //这样构建kdtree的速度会变快,但是在kmean更新中心点会变慢
  241. boolean isleaf = true;
  242. for(int i=0;i<node.min.length;i++)
  243. if(node.min[i]!=node.max[i]){
  244. isleaf=false;
  245. break;
  246. }
  247.  
  248. if(isleaf){
  249. node.isLeaf=true;
  250. node.value=data.get(0);
  251. return;
  252. }
  253.  
  254. node.partitionDimention=-1;
  255. double diff = -1;
  256. double tmpdiff;
  257. for(int i=0;i<dimentions;i++){
  258. tmpdiff=node.max[i]-node.min[i];
  259. if (tmpdiff>diff){
  260. diff = tmpdiff;
  261. node.partitionDimention = i;
  262. }
  263. }
  264.  
  265. node.sumOfPoints = UtilZ.sumOfPoints(data,dimentions);
  266. node.n = data.size();
  267.  
  268. //选择分割的值
  269. node.partitionValue=UtilZ.median(data, node.partitionDimention);
  270. if(node.partitionValue==node.min[node.partitionDimention]){
  271. node.partitionValue+=1e-5;
  272. }
  273.  
  274. int size = (int)(data.size()*0.55);
  275. ArrayList<double[]> left = new ArrayList<double[]>(size);
  276. ArrayList<double[]> right = new ArrayList<double[]>(size);
  277.  
  278. for(double[] d:data){
  279. if (d[node.partitionDimention]<node.partitionValue) {
  280. left.add(d);
  281. }else {
  282. right.add(d);
  283. }
  284. }
  285.  
  286. Node leftnode = new Node();
  287. Node rightnode = new Node();
  288. node.left=leftnode;
  289. node.right=rightnode;
  290. buildDetail(leftnode, left, dimentions,lv+1);
  291. buildDetail(rightnode, right, dimentions,lv+1);
  292. }
  293.  
  294. public double[][] updateCentroids(double[][] cs){
  295. int k = cs.length;
  296. int m = cs[0].length;
  297. double[][] entroids = new double[k][m];
  298. int[] datacount = new int[k];
  299. HashMap<Integer, double[]> cscopy = new HashMap<Integer, double[]>();
  300. for(int i=0;i<k;i++)
  301. cscopy.put(i, cs[i]);
  302.  
  303. updateCentroidsDetail(mrkdtree,cscopy,entroids,datacount,k,m);
  304. double[][] csnew = new double[k][m];
  305. for(int i=0;i<k;i++){
  306. for(int j=0;j<m;j++){
  307. csnew[i][j]=entroids[i][j]/datacount[i];
  308. }
  309. }
  310.  
  311. return csnew;
  312. }
  313.  
  314. private void updateCentroidsDetail(Node node,
  315. HashMap<Integer, double[]> cs, double[][] entroids,
  316. int[] datacount,int k,int m) {
  317. //如果是叶子节点
  318. if(node.isLeaf){
  319. double[] v=node.value;
  320. double dis=Double.MAX_VALUE;
  321. double tdis;
  322. int index = -1;
  323. //找到所属的中心点
  324. for(Integer i: cs.keySet()){
  325. double[] c = cs.get(i);
  326. tdis = UtilZ.distance(c, v);
  327. if(tdis<dis){
  328. dis=tdis;
  329. index=i;
  330. }
  331. }
  332.  
  333. //更新统计信息
  334. datacount[index]++;
  335. for(int i=0;i<m;i++){
  336. entroids[index][i]+=v[i];
  337. }
  338. return;
  339. }
  340.  
  341. double[] stack = new double[k];
  342. int stackpoint = 0;
  343. int center=0;
  344. double tdis;
  345. for(Integer i: cs.keySet()){
  346. double[] c = cs.get(i);
  347. tdis = UtilZ.mindistance(c, node.max, node.min);
  348. if(stackpoint==0){
  349. stack[stackpoint++]=tdis;
  350. center=i;
  351. }else if (tdis<stack[stackpoint-1]) {
  352. stackpoint=1;
  353. stack[0]=tdis;
  354. center=i;
  355. }else if (tdis==stack[stackpoint-1]) {
  356. stack[stackpoint++]=tdis;
  357. }
  358.  
  359. }
  360. //stackpoint>1,说明有多个最小值,不存在中心点
  361. if(stackpoint!=1){
  362. updateCentroidsDetail(node.left, cs, entroids, datacount, k, m);
  363. updateCentroidsDetail(node.right, cs, entroids, datacount, k, m);
  364. return;
  365. }
  366.  
  367. HashMap<Integer, Boolean> ctover = new HashMap<Integer, Boolean>();
  368. double[] centerd = cs.get(center);
  369. for(Integer i: cs.keySet()){
  370. if(i==center) continue;
  371. double[] c = cs.get(i);
  372. if(UtilZ.isOver(centerd,c,node.max,node.min)){
  373. ctover.put(i, true);
  374. }
  375. }
  376.  
  377. if(ctover.size()==cs.size()-1){
  378. //此时中心点即为center,更新信息
  379. datacount[center]+=node.n;
  380. for(int i=0;i<m;i++){
  381. entroids[center][i]+=node.sumOfPoints[i];
  382. }
  383. return;
  384. }
  385.  
  386. //将其比center差的中心点排除
  387. HashMap<Integer, double[]> csnew = new HashMap<Integer, double[]>();
  388. for(Integer i:cs.keySet()){
  389. if(!ctover.containsKey(i))
  390. csnew.put(i, cs.get(i));
  391. }
  392.  
  393. updateCentroidsDetail(node.left, csnew, entroids, datacount, k, m);
  394. updateCentroidsDetail(node.right, csnew, entroids, datacount, k, m);
  395. }
  396. }

c.kmeans

  1. import cc.MRKDTree;
  2.  
  3. public class KMeans {
  4. private double[][] centroids;
  5.  
  6. private KMeans(){}
  7.  
  8. public static class UtilZ{
  9. static double[][] randomCentroids(double[][] data,int k){
  10. double[][] res = new double[k][];
  11. for(int i=0;i<k;i++){
  12. res[i] = data[(int)(Math.random()*data.length)];
  13. }
  14. return res;
  15. }
  16.  
  17. static boolean converged(double[][] c1,double[][] c2,double c){
  18. for(int i=0;i<c1.length;i++){
  19. if(changed(c1[i],c2[i])>c){
  20. return false;
  21. }
  22. }
  23. return true;
  24. }
  25. private static double changed(double[] c1,double[] c2){
  26. double change=0;
  27. double total=0;
  28. for(int i=0;i<c1.length;i++){
  29. total+=Math.pow(c1[i], 2);
  30. change+=Math.pow(c1[i]-c2[i], 2);
  31. }
  32. return Math.sqrt(change/total);
  33. }
  34.  
  35. static double distance(double[] c1,double[] c2){
  36. double sum = 0;
  37. for(int i=0;i<c1.length;i++){
  38. sum+=Math.pow(c1[i]-c2[i], 2);
  39. }
  40. return sum;
  41. }
  42. }
  43. public static KMeans build(double[][] input,int k,double c,double[][] cs){
  44. long start = System.currentTimeMillis();
  45. MRKDTree tree = MRKDTree.build(input);
  46. System.out.println("treeConstruct:"+(System.currentTimeMillis()-start));
  47.  
  48. double[][] csnew = tree.updateCentroids(cs);
  49. while(!UtilZ.converged(cs, csnew, c)){
  50. cs=csnew;
  51. csnew=tree.updateCentroids(cs);
  52. }
  53. KMeans km = new KMeans();
  54. km.centroids=csnew;
  55. return km;
  56. }
  57.  
  58. public static KMeans buildOri(double[][] input,int k,double c,double[][] cs){
  59.  
  60. double[][] csnew = updateOri(input,cs);
  61. while(!UtilZ.converged(cs, csnew, c)){
  62. cs=csnew;
  63. csnew=updateOri(input,cs);
  64. }
  65. KMeans km = new KMeans();
  66. km.centroids=csnew;
  67. return km;
  68. }
  69.  
  70. private static double[][] updateOri(double[][] input,double[][] cs){
  71. int[] center = new int[input.length];
  72. for(int i=0;i<input.length;i++){
  73. double dismin = Double.MAX_VALUE;
  74. for(int j=0;j<cs.length;j++){
  75. double dis = UtilZ.distance(input[i], cs[j]);
  76. if(dis<dismin){
  77. dismin=dis;
  78. center[i]=j;
  79. }
  80. }
  81. }
  82.  
  83. double[][] nct =new double[cs.length][cs[0].length];
  84. int[] datacount = new int[cs.length];
  85. for(int i=0;i<input.length;i++){
  86. double[] n = input[i];
  87. int belong = center[i];
  88. for(int j=0;j<cs[0].length;j++){
  89. nct[belong][j]+=n[j];
  90. }
  91. datacount[belong]++;
  92. }
  93.  
  94. for(int i=0;i<nct.length;i++){
  95. for(int j=0;j<nct[0].length;j++){
  96. nct[i][j]/=datacount[i];
  97. }
  98. }
  99. return nct;
  100. }
  101.  
  102. public void printCentroids(){
  103. java.text.DecimalFormat df=new java.text.DecimalFormat("0.00");
  104. for(int i=0;i<centroids.length;i++){
  105. for(int j=0;j<centroids[i].length;j++)
  106. System.out.print(df.format(centroids[i][j])+",");
  107. System.out.println();
  108. }
  109. }
  110. }

d.调用

  1. import java.io.BufferedReader;
  2. import java.io.FileReader;
  3.  
  4. public class Test {
  5. static void compare(double[][] input){
  6. double[][] cs = KMeans.UtilZ.randomCentroids(input, 20);
  7. int t=1;
  8. long start = System.currentTimeMillis();
  9. while(t-->0)
  10. KMeans.build(input, 20, 0.001,cs);
  11. long kdtree = System.currentTimeMillis()-start;
  12. t=1;
  13. start = System.currentTimeMillis();
  14. while(t-->0)
  15. KMeans.buildOri(input, 20, 0.001,cs);
  16. long ori = System.currentTimeMillis()-start;
  17.  
  18. System.out.println("kdtree:"+kdtree);
  19. System.out.println("linear:"+ori);
  20. System.out.println(ori*1.0/kdtree);
  21. }
  22.  
  23. public static void main(String[] args) throws Exception{
  24. BufferedReader reader = new BufferedReader(new FileReader("d.txt"));
  25. String line=null;
  26. double[][] input = new double[600000][10];
  27. int i=0;
  28. while((line=reader.readLine())!=null){
  29. String[] numstrs=line.split("\t");
  30. for(int j=0;j<10;j++)
  31. input[i][j] = Double.parseDouble(numstrs[j]);
  32. i++;
  33. }
  34.  
  35. compare(input);
  36. }
  37. }

5.总结

对于数据量较小、中心点较少、维度不多的情景中,使用kd-tree并不能加速,反而比原始的算法更慢,因为kd-tree的构建花费了很长时间;

此时在选择分割维度的时候不用方差,而用数据范围,能加快kd-tree 的构建,但会下降一定的kd-tree查询性能;

当数据量大,中心点多,维度大的情况下或者在x-mean算法中,应该使用方差作为选择分割维度,此时查询性能的提升能弥补kd-tee构建的耗时

使用kd-tree加速k-means的更多相关文章

  1. BZOJ 4520: [Cqoi2016]K远点对(k-d tree)

    Time Limit: 30 Sec  Memory Limit: 512 MBSubmit: 1162  Solved: 618[Submit][Status][Discuss] Descripti ...

  2. BZOJ4520:[CQOI2016]K远点对(K-D Tree)

    Description 已知平面内 N 个点的坐标,求欧氏距离下的第 K 远点对. Input 输入文件第一行为用空格隔开的两个整数 N, K.接下来 N 行,每行两个整数 X,Y,表示一个点 的坐标 ...

  3. BZOJ 3053: The Closest M Points(K-D Tree)

    Time Limit: 10 Sec  Memory Limit: 128 MBSubmit: 1235  Solved: 418[Submit][Status][Discuss] Descripti ...

  4. AOJ DSL_2_C Range Search (kD Tree)

    Range Search (kD Tree) The range search problem consists of a set of attributed records S to determi ...

  5. k-d tree 学习笔记

    以下是一些奇怪的链接有兴趣的可以看看: https://blog.sengxian.com/algorithms/k-dimensional-tree http://zgjkt.blog.uoj.ac ...

  6. K-D Tree

    这篇随笔是对Wikipedia上k-d tree词条的摘录, 我认为解释得相当生动详细, 是一篇不可多得的好文. Overview A \(k\)-d tree (short for \(k\)-di ...

  7. K-D Tree题目泛做(CXJ第二轮)

    题目1: BZOJ 2716 题目大意:给出N个二维平面上的点,M个操作,分为插入一个新点和询问到一个点最近点的Manhatan距离是多少. 算法讨论: K-D Tree 裸题,有插入操作. #inc ...

  8. k-d Tree in TripAdvisor

    Today, TripAdvisor held a tech talk in Columbia University. The topic is about k-d Tree implemented ...

  9. k-d tree算法

    k-d树(k-dimensional树的简称),是一种分割k维数据空间的数据结构.主要应用于多维空间关键数据的搜索(如:范围搜索和最近邻搜索). 应用背景 SIFT算法中做特征点匹配的时候就会利用到k ...

  10. k-d tree模板练习

    1. [BZOJ]1941: [Sdoi2010]Hide and Seek 题目大意:给出n个二维平面上的点,一个点的权值是它到其他点的最长距离减最短距离,距离为曼哈顿距离,求最小权值.(n< ...

随机推荐

  1. 创建一个Hello World模块

    这篇文章描述了怎样为Orchard建立一个非常小的模块,它只显示一个"Hello World"页面. 另一个模块[Module]的简单例子,你可以从这找到:Quick Start ...

  2. Seafile 推出 “分布式文件同步技术” 打造的私有云服务

    近两年来 Dropbox 等云储存服务迅速窜红,各大巨头纷纷推出自家的云储存服务(苹果的 iCloud, 微软的 SkyDrive, Google 即将推出的 GDrive),国内也有类似的服务(金山 ...

  3. cf459C Pashmak and Buses

    C. Pashmak and Buses time limit per test 1 second memory limit per test 256 megabytes input standard ...

  4. OpenFileDialog 害人的RestoreDirectory

    莫名其妙出现找不到文件的错误.经查,发现: OpenFileDialog,SaveFileDialog在选择文件后,会切换当前程序目录的路径(System.Environment.CurrentDir ...

  5. 关于mwArray和一般数组的区别

    可以用下面的代码详细理解mwArray和一般数组之间的区别 mwArray a(, , mxDOUBLE_CLASS); double *aData; aData = ]; int iii; ; ii ...

  6. hdu2574 Hdu Girls' Day (分解质因数)

    Hdu Girls' Day Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)To ...

  7. 【具体数学--读书笔记】1.1 The Power of Hanoi

    这一节借助汉诺塔问题引入了"Reccurent Problems". (Reccurence, 在这里解释为“the solution to each problem depend ...

  8. CentOS7 yum lamp 虚拟主机配置 lamp各组件简单影响性能的参数调整--for 一定的环境需求

    LAMP Server on CentOS 7 Updated Tuesday, January 13, 2015 by Joel Kruger This guide provides step-by ...

  9. 3.数据库操作相关术语,Oracle认证,insert into,批量插入,update tablename set,delete和truncate的差别,sql文件导入

     1相关术语 语句 含义 操作 DML语句 (Data Manipulation Language) 数据库操作语言 insert update delete select DDL语言 (Date ...

  10. CMS(Concurrent Mark-Sweep)

    CMS(Concurrent Mark-Sweep)是以牺牲吞吐量为代价来获得最短回收停顿时间的垃圾回收器.对于要求服务器响应速度的应用上,这种垃圾回收器非常适合.在启动JVM参数加上-XX:+Use ...