并查集与类型推导

某次参加笔试的最后一题大意如下:给定一组用户[0..n],以及他们之间的好友关系,问这些好友构成了多少个朋友圈?
例如有用户[1..5],好友关系有(1,2),(3,4),(4,5),则共有两个好友圈:[1,2]和[3,4,5]

并查集

题目的意思可以理解为给定一个无向图,求其中连通子图的个数。算法来自于《算法导论(第二版)》的第21章
对于一个并查集来说,我们通常希望其支持三种操作:
MakeSet(x):当我们新加入一个元素,且与当前任何节点有连接,因此它(目前为止)单独成为一个集合。
Union(x,y):添加两个元素之间的联系,因此两个元素属于的集合需要合并成为一个集合。
FindSet(x):查询一个元素到底属于哪个集合。
显然当有n个元素时,Union操作至多有n-1次,MakeSet操作至多为n次。

代码实现

可以用一个数组p来储存所有元素对应的集合。例如p[x]=1表示元素x属于编号为1的集合。然而整个编号为1的集合可能又属于另一个更大的集合,因此需要查询p[1]来找到其父集合……以此类推,直到有p[a]=a为止。由此可见,这其实是一种基于树的实现。

图片源自《算法导论》

  • 两种策略
  • 按秩合并(union by rank)
    rank可以看作是每个根节点的高度(并不严格是,其实是其高度的一个上界),合并两棵树树时,将高度较小的树的根指向高度较大的树可以保证合并后的树的高度上界不再增加。若高度相等,则合并后的树高度加一。
  • 路径压缩(path compression)
    当从某一结点以此查找到根节点时,可以将经过路径的所有节点的父节点都修改为根节点。但我们的方法中并不修改根节点的秩,因为修改一颗树的高度需要遍历整棵树的所有节点才能确定,这显然得不偿失,这也就解释了为什么秩并不是每棵树的高度,而是它高度的一个上界。
图片源自《算法导论》
int MakeSet(int x)
{
    p[x]=x;
    rank[x]=0;
}

void Union(int a,int b)
{
    int x=FindSet(a);
    int y=FindSet(b);
    if(rank[x]>rank[y])
    {
        p[y]=x;
    }
    else
    {
        p[x]=y;
    }
    if(rank[x]==rank[y])
    {
        rank[y]=rank[y]+1;
    }
}

int FindSet(int x)
{
    if(x!=p[x])
    {
        p[x]=FindSet(p[x]);
    }
    return p[x];
}

MakeSetUnionFindSet操作共m个,其中有n个MakeSet操作,单独使用按秩合并的策略运行时间为


单独使用路径压缩,若有n个MakeSet操作和f个FindSet操作,运行时间为![](http://latex.codecogs.com/svg.latex?\Theta\left(n+f\cdot\left(1+\textup{log}_{2+f/n}n\right)\right)

类型推导

在一个函数体中,各个参数的相互作用可以看作是一个无向有环图(DAG)。依据函数内的各个节点,可以生成一系列的类型约束等式。

图片来自Essentials of Porgramming languages

图中的代码proc(f)proc(x)-((f 3),(f x))等价于如下scheme代码:

(lambda (f) (lambda (x) (- (f 3) (f x))))

或者如下C++代码

template<typename T3,typename Tf,typename Tx>
T3 func(Tf f, Tx x)
{
  return (f 3)-(f x);
}

约束生成(constraints generation)的规则如下:


内容来自Programming Languages and Lambda Calculi

得到类型等式后,接下来是对等式进行合一(unify)和替换(substitute)操作。
合并规则如下:


内容来自Programming Languages and Lambda Calculi

关于多态的推导:

内容来自Programming Languages and Lambda Calculi

下面是Essentials of Programming Languages中的步骤演示


可以看到,每一个等式,都需要进行unify操作,因为类型相等即代表它们属于同一集合。但需要注意的是,这里的unify操作和之前提到的union并不完全相同,因为我们还需要处理函数类型。若有类似t1->t2=t3->t4的等式,则需要同时unify(t1,t3)unify(t2,t4)。另一方面,在合并类型时可能是有方向的,需要将泛化(generic)类型向具化(normalized)类型合并。例如有t1=t2,t2=int,则得到t1=int,t2=int,这也意味这上文提到的按秩合并的策略在此并不可用,但路径压缩依然可行。

更多阅读

  • 《算法导论》中关于并查集算法时间复杂度的证明
  • Hindley-Milner类型系统
  • 逻辑式编程与合一算法
  • Recursive Type(本文中的类型推导仅对STLC(simply typed lambda calculus)成立,根据STLC的strong normalization特性,所有STLC能表达的程序中不会有递归且一定终止,如果突破这一限制呢?)
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,080评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,422评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,630评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,554评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,662评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,856评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,014评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,752评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,212评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,541评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,687评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,347评论 4 331
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,973评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,777评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,006评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,406评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,576评论 2 349

推荐阅读更多精彩内容