数据结构大作业-DBLP科学文献管理系统(三)聚团分析(并查集,最大团问题)

         并查集这部分个人用了C#实现。一开始把题目理解错了,以为完全子图就是一个简单的弱联通图,直到用并查集秒完,等到DLL快交上去才发现原来指的是强连通子图。这里讲一下这里的设计思路。

并查集

        并查集是一种树型的数据结构,用于处理一些不相交集合(disjoint sets)的合并及查询问题。在探索各个作者间的合作关系中用到了并查集思想。并查集需要实现三个功能:

初始化:把每个点所在集合初始化为其自身。在每次使用该数据结构时只需要执行一次,无论何种实现方式,时间复杂度均为O(1)

查找:查找元素所在的集合,即根节点。

合并:将两个元素所在的集合合并为一个集合。

通常来说,合并之前,应先判断两个元素是否属于同一集合,这可用上面的“查找”操作实现。

        因为并查集时基于线性数组实现的,而在DBLP中的节点是一个个string的作者名称。因此需要将每个作者用一个int的离散化编号来表示。

        这里使用了C#中Dictionary<K,T>的数据结构,可以实现键值对的映射,该结构是使用哈希表和采用链接(Chainning)技术解决冲突的字典,即对于每个冲突的哈希值元素,都会被添加到桶(bucket)的链表中,在查询和添加操作时时间复杂度为O(1),有较好的效率,采用了这个实现了数据离散化和String字符串的作者名到一个整数类型的映射。

        最终,并查集算法设计思路为遍历每篇文章,检查该文章下的每一个作者,当读入一个新的作者,若检测到字典中没有该作者对应的编号,则分配一个数据编号给该作者,并与在同一片文章中的其他作者节点合并。

Dictionary<string, int> Discretization = new Dictionary<string, int>(); //作者名字数据离散化
int[] Parallel = new int[3500000]; //300w并查集     
for (int i = 0; i < 3500000; i++) //并查集初始化
{
     Parallel[i] = i;
}
int titleAua = 0;
for (遍历所有文章) 
{
    if (如果该行数据是标题)     
    {
        titleAua = 0; //重置序列号
    }
    else
    {
        titleAua++;  //记录一次序列号
        if (!Discretization.ContainsKey(infile[auI]))//如果该作者没有编号
        {
            Discretization.Add(infile[auI], authorIndex); //分配一个离散化编号
            authorIndex++;
        }
        if (titleAua > 2) //如果该作者不是该文章中的第一个作者
            ParallelNodeMerge(ref Parallel, Discretization[infile[auI]],             
        Discretization[infile[auI - 1]]); //与前一个作者节点合并
    }
}

        合并并查集时,采用了递归调用并赋值的思想。在查询到最新的节点,也会将结果回溯到前面的节点及时更新数据,这样保证了再次查找时的效率和总体O(1)的复杂度。(这点对优化效率是非常重要的)最后通过遍历并查集,重载了C#的比较函数,将较大的并查集结果写入文件进行保存。测试时280w数据的包括IO+并查集分析+排序时间,最终耗时没有超过1min。

扫描二维码关注公众号,回复: 14682915 查看本文章
private static int Findx(ref int[] Parallel, int x) //查找并查集父节点
    {
        if (Parallel[x] != x)
            return Parallel[x] = Findx(ref Parallel, Parallel[x]);
        return x;
}

重载比较函数对并查集内一个集合的数目进行排序,并写入文件

{  
    List<int> aua = new List<int>(); //大于100需要统计的聚团编号
    Discret.Clear();
    List<string> content = new List<string>();
    int tot = 0;
    foreach (var pr in Discretization)
    {
        tot++;
        content.Add(pr.Key + " " + Findx(ref Parallel, Parallel[pr.Value]));
        if (!Discret.ContainsKey(Parallel[pr.Value]))
            Discret.Add(Parallel[pr.Value], new List<string>());
        Discret[Parallel[pr.Value]].Add(pr.Key);

        if (Discret[Parallel[pr.Value]].Count == 100)
            aua.Add(Parallel[pr.Value]);
    }
    File.AppendAllLines(Program.filePath + "database\\aua_result.dat", content);

    Console.WriteLine("作者数大于100的集合数目:" + aua.Count);
    Console.WriteLine("将排行榜数据写入文件中..." + aua.Count);

    aua.Sort(new cmp());

    File.WriteAllText(Program.filePath + "database\\aua_rank_result.dat", "作者数>100的聚团数目 " + aua.Count + "\n");
        if (!Directory.Exists(Program.filePath + "database\\aua_result"))
            Directory.CreateDirectory(Program.filePath + "database\\aua_result");

        foreach (var i in aua)
        {
            File.AppendAllText(Program.filePath + "database\\aua_rank_result.dat", i + " " + Discret[i].Count + "\n");
            File.WriteAllLines(Program.filePath + "database\\aua_result\\" + i + ".ini", Discret[i]);
        }

}
    class cmp : IComparer<int>
    {
        public int Compare(int x, int y)
        {
            return Discret[y].Count - Discret[x].Count;
        }
    }

    private static int GetParallelCount(ref int[] Parallel, int n) //查询并查集中拥有的总集合数
    {
        int ans = 0;
        for (int i = 0; i < n; i++)
        {
            if (i == Findx(ref Parallel, i))
                ans++;
        }
        return ans;
    }

完全子图问题

        完全子图问题实际上就是最大团问题,是一个图论中经典NP问题。这里受限于当时项目时间限制没有写完,这里抛一些当时的想法供思考。

        当时我们隔壁组用的是fp-tree(然后内存炸了)在这里讨论用的是类似DP的思想。即当N-1阶完全子图已被确定时,是否讲当前节点 x 放入N阶子图,只需要判断x是否于N-1阶中的子图中全部相连即可。参考和优化可以借鉴其他网上的思路:

猜你喜欢

转载自blog.csdn.net/m0_51776409/article/details/124768630