百度百科:聚类是一个将数据集中在某些方面相似的数据成员进行分类组织的过程.
Kmeans则是聚类算法中的一种
执行效果图:
KMeansE.class
/// <summary>
/// K-means 聚类算法
/// </summary>
public class KMeansE<T>
{
/// <summary>
/// 针对一维 double 数组。指定聚类数目 k。 内部已对p去重!!!!
/// </summary>
/// <param name="p">聚类函数主体。 内部已去重!</param>
/// <param name="k">将数据聚成 k 类。</param>
/// <returns>聚类结果</returns>
public static KMeansItem<T>[][] Cluster(KMeansItem<T>[] p, int k)
{
p = DelArraySame(p);
// 存放聚类旧的聚类中心
KMeansItem<T>[] c = new KMeansItem<T>[k];
// 存放新计算的聚类中心
KMeansItem<T>[] nc = new KMeansItem<T>[k];
// 存放放回结果
KMeansItem<T>[][] g;
// 初始化聚类中心
// 经典方法是随机选取 k 个
// 本例中采用前 k 个作为聚类中心
// 聚类中心的选取不影响最终结果
for (int i = 0; i < k; i++)
c[i] = p[i];
// 循环聚类,更新聚类中心
// 到聚类中心不变为止
while (true)
{
// 根据聚类中心将元素分类
g = Group(p, c);
// 计算分类后的聚类中心
for (int i = 0; i < g.Length; i++)
{
nc[i] = Center(g[i]);
}
// 如果聚类中心不同
if (!Equal(nc, c))
{
// 为下一次聚类准备
c = nc;
nc = new KMeansItem<T>[k];
}
else // 聚类结束
break;
}
// 返回聚类结果
return g;
}
/// <summary>
/// 聚类中心函数 可扩展
/// </summary>
/// <param name="p"></param>
/// <returns></returns>
private static KMeansItem<T> Center(KMeansItem<T>[] p)
{
return p.OrderBy(x => x.value).Skip(p.Length / 2).First();
}
/// <summary>
/// 给定 double 型数组 p 和聚类中心 c。
/// 根据 c 将 p 中元素聚类。返回二维数组。
/// </summary>
/// <param name="p"></param>
/// <param name="c"></param>
/// <returns>存放各组元素。</returns>
private static KMeansItem<T>[][] Group(KMeansItem<T>[] p, KMeansItem<T>[] c)
{
// 中间变量,用来分组标记
int[] gi = new int[p.Length];
// 考察每一个元素 pi 同聚类中心 cj 的距离
// pi 与 cj 的距离最小则归为 j 类
for (int i = 0; i < p.Length; i++)
{
// 存放距离
//KMeansItem<T>[] d = new KMeansItem<T>[c.Length];
double[] dd = new double[c.Length];
//计算到每个聚类中心的距离
for (int j = 0; j < c.Length; j++)
{
try
{
dd[j] = Distance(p[i], c[j]);
}
catch (Exception)
{
throw;
}
}
// 找出最小距离
int ci = Min(dd);
// 标记属于哪一组
gi[i] = ci;
}
// 存放分组结果
KMeansItem<T>[][] g = new KMeansItem<T>[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 KMeansItem<T>[s];
s = 0;
// 根据分组标记将各元素归位
for (int j = 0; j < gi.Length; j++)
if (gi[j] == i)
{
if (p[j] != null)
{
g[i][s] = p[j];
}
s++;
}
}
// 返回分组结果
return g;
}
/// <summary>
/// 计算两个点之间的距离, 这里采用最简单得一维欧氏距离, 可扩展。
/// </summary>
/// <param name="a"></param>
/// <param name="b"></param>
/// <returns></returns>
private static double Distance(KMeansItem<T> a, KMeansItem<T> b)
{
return Math.Abs(a.value - b.value);
}
/// <summary>
/// 返回给定 double 数组各元素之和。
/// </summary>
/// <param name="p"></param>
/// <returns></returns>
private static double Sum(double[] p)
{
//double sum = 0.0;
//for (int i = 0; i < p.Length; i++)
// sum += p[i];
return p.Sum();
}
/// <summary>
/// 给定 double 类型数组,返回最小值得下标。
/// </summary>
/// <param name="p"></param>
/// <returns></returns>
private static int Min(double[] p)
{
int i = 0;
double m = p[0];
for (int j = 1; j < p.Length; j++)
{
if (p[j] < m)
{
i = j;
m = p[j];
}
}
return i;
}
/// <summary>
/// 判断两个 double 数组是否相等。 长度一样且对应位置值相同返回真。
/// </summary>
/// <param name="a"></param>
/// <param name="b"></param>
/// <returns></returns>
private static bool Equal(KMeansItem<T>[] a, KMeansItem<T>[] b)
{
if (a.Length != b.Length)
return false;
else
{
for (int i = 0; i < a.Length; i++)
{
if (Math.Abs(a[i].value - b[i].value) > 0.01)
return false;
}
}
return true;
}
public static I[] DelArraySame<I>(I[] TempArray)
{
List<I> ls = new List<I>();
for (int i = 0; i < TempArray.Length; i++)
{
if (!ls.Contains(TempArray[i]))
{
ls.Add(TempArray[i]);
}
}
return ls.ToArray();
}
}
KMeansItem.cs
public class KMeansItem<T>
{
public T item;
public double value;
public KMeansItem(T item,double value){
this.item = item;
this.value = value;
}
public override bool Equals(object obj)
{
if (obj == null)
{
return false;
}
return value == ((KMeansItem<T>)obj).value;
}
public override int GetHashCode()
{
return (int)((value * 1000000) % 224236);
}
}
调用示例:
class Program
{
class KT
{
public string name;
public KT(string name) { this.name = name; }
public override string ToString()
{
return name;
}
}
static void Main(string[] args)
{
int testCount = 30;
KMeansItem<KT>[] kMeansItems = new KMeansItem<KT>[testCount];
string dataStr = "";
Random random = new Random(new Guid().GetHashCode());
for (int i = 0; i < testCount; i++)
{
int d = random.Next(1, 50);
KMeansItem<KT> kMeansItem = new KMeansItem<KT>(new KT("V-" + d), d);
dataStr += d + "\t";
kMeansItems[i] = kMeansItem;
}
Console.WriteLine("Data:");
Console.WriteLine(dataStr);
Console.WriteLine();
int k = kMeansItems.Length / 10;
KMeansItem<KT>[][] result = KMeansE<KT>.Cluster(kMeansItems, k);
int kc = 1;
foreach (KMeansItem<KT>[] item in result)
{
Console.WriteLine("------------K:{0}-------------------",kc++);
foreach (KMeansItem<KT> kMeansItem in item)
{
Console.WriteLine(kMeansItem.item);
}
}
Console.WriteLine("kMeansItemsL:{0},k:{1}", kMeansItems.Length, k);
Console.ReadLine();
}
}
输出结果:
Data:
36 41 38 28 11 28 45 22 48 14 15 23 32 24 49
2 43 49 34 16 41 42 49 2 35 26 46 34 27 4
------------K:1-------------------
V-28
V-11
V-22
V-14
V-15
V-23
V-24
V-2
V-16
V-26
V-27
V-4
------------K:2-------------------
V-41
V-45
V-48
V-49
V-43
V-42
V-46
------------K:3-------------------
V-36
V-38
V-32
V-34
V-35
kMeansItemsL:30,k:3