精华内容
下载资源
问答
  • set采用了什么算法
    千次阅读
    2021-02-22 15:50:50

    背景

    CRDT (Conflict-free Replicated Data Types) 直译的话即 冲突避免可复制数据类型

    在研究分布式系统时,尤其是要实现最终一致性分布式系统的过程中,一个最基本的问题就是,应该采用什么样的数据结构保证最终一致性,目前关于这个问题有一个讨论较为详尽的论文

    CRDT 简介

    在分布式系统中,CRDT 是指一种能够无需合作就可以在网络中多个主机中并行地复制的一种数据结构,并且总能够解决可能的不一致性。

    CRDT 的类型

    有两种 CRDT 都可以实现数据的最终一致性

    • 基于操作的 CRDT
    • 基于状态的 CRDT

    这两种 CRDT 在数学上等价的,可以相互转换。基于操作的 CRDT 需要消息中间件的提供额外的支持,对操作命名并保证通信过程中不会丢失或者交递信息时保证消息唯一。基于状态的消息中间件则需要消息通信时保证全部的状态都完好地交递。

    基于操作的 CRDT

    基于操作的 CRDT 又被称作 commutative replicated data types 或者 CmRDTs.CmRDT 在传播时只包含数据更新操作信息。举个栗子,一个整数在执行了(+10), (-20) 操作后,CmRDT 广播时则只包含关于这个整数进行了(+10), (-20) 操作。其他的 DC 收到这个 CmRDT 后会在本地对相应的数据执行 CmRDT 中包含的操作。CmRDT 所能携带的操作必须是可交换的,通信模块必须保证所有 CmRDT 包都能被正确交递,但是顺序无需保证。

    基于状态的 CRDTs

    基于状态的 CRDTs 全称为 convergent replicated data types 或者 CvRDTs.CvRDTs 会将本地全部的数据状态传播给其他 DC, 这些状态在接受到后会被一个函数做 merge 处理,所以这些状态必须是可交换的,关联的,幂等的。merge 函数会为在 CvRDT 之间提供一个 join 操作,将接收到 CvRDT 与本地数据合并。

    CRDT 的几种实现

    目前 CRDT 有以下几种常见的实现

    • G-Counter(Grow-Only Set)
    • PN-Counter(Positive-Negative Counter)
    • G-Set(Grow-only Set)
    • 2P-Set(Two-Phase Set)
    • LWW-Element-Set(Last Write Wins element Set)
    • OR-Set(Observed-Removed Set)
    • Sequence CRDTs

    G-Counter(Grow-only Counter)

    这是一个只增不减的计数器,对于 N 个节点,每个节点上维护一个长度为 N 的向量 V=P0,P1,P2,…,Pn−1,PmV=P0,P1,P2,…,Pn−1,Pm该向量表示节点 m 上的计数,当需要增加这个计数器时,只需要任意选择一个节点操作,操作会将对应节点的计数器 Pm:=Pm+1Pm:=Pm+1. 当要统计整个集群的计数器总和时,只需要对向量 V 中的所有元素求和即可。

    计算伪代码如下

    payload integer[n] p
        initial [0, 0, ..., 0]
    update increment()
        let g = myId()
        P[g] := P[g] + 1
    query value(): integer v
        let v  = sum(p)
    compare (X, Y): boolean b
        let b = i if Y.p[i] >= X.p[i]
    merge (X, Y): payload Z
        for i = 0, 1, 2, ..., n - 1:
            let Z.p[i] = max(X.p[i], Y.p[i])
    
    

    PN-Counter(Positive-Negative Counter)

    G-Counter 有一个限制,即计数器只能增加,不能减少。不过可以使用两个计数器来实现一个既能增加也能减少计数器(PN-Counter). 简单来说,就是用一个 G-Counter 来记录所有累加结果,另一个 G-Counter 来记录累减结果,查询当前结果时,只需要计算两个 G-Counter 的差即可

    payload integer[n] P, integer[n] N
        initial [0, 0, 0, ..., 0], [0, 0, 0, ..., 0]
    update increment()
        let g = myID()
        P(g) := P(g) + 1
    update decrement()
        let g = myID()
        N(g) := N(g) + 1
    query value(): integer v
        let v = sum(P) - sum(N)
    merge (X, Y): payload Z
        for i = 0, 1, ..., n - 1:
            Z.p[i] = max(X.p[i], Y.p[i])
            Z.n[i] = max(X.n[i], Y.n[i])
    
    

    LWW-Element-Set(last-Write-Wins-Element-Set)

    LWW-Element-Set 包含一个 add setremove set, 在每个元素上都有一个时间戳.每个元素都在添加到 LWW-Element-Set 时都会添加到 add-set 中,并且附带一个时间戳。将元素从 LWW-Element-Set 中移除也会添加到 remove set 中一行,并且附带一个时间戳。

    当一个元素在 add-set 中时,他就会认为是 LWW-Element-Set 中的成员,或者在 remove-set 中时,就不是 LWW-Element-Set 的成员。但是如果两者都在,且 remove-set 中的时间戳小于 add-set 中的时间戳,也认为在数据库中,相反则不在。

    合并两个 LWW-Element-Set 需要求两个数据库的 add-set 和 remove-set 中的并集,并且只保留时间戳最新的即可,如果时间戳相等,就需要引入 LWW-Element-Set 中的 bias.

    整理中.....

    更多相关内容
  • 采用了levelset技术实现的图像分割技术,其中包括了多种算法
  • PID算法详解及实例分析

    千次阅读 多人点赞 2022-05-13 10:42:11
    1.PID算法入门 PID算法算是控制领域最经典,最重要,也是最实用的算法了。所谓的PID,指的是proportion,integration,differentiation,比例,积分,微分。 因此,PID是结合了比例积分微分三个模块于一身的控制算法...

    1.PID算法入门

    PID算法算是控制领域最经典,最重要,也是最实用的算法了。所谓的PID,指的是proportion,integration,differentiation,比例,积分,微分。
    因此,PID是结合了比例积分微分三个模块于一身的控制算法。

    先看公式:
    u ( t ) = K p ( e ( t ) + 1 T i ∫ 0 t e ( t ) d t + T d d e ( t ) d t ) u(t) = K_p\left(e(t) + \frac{1}{T_i} \int_{0}^{t}e(t)dt + T_d \frac{de(t)}{dt}\right) u(t)=Kp(e(t)+Ti10te(t)dt+Tddtde(t))
    如果公式看不懂,没关系,我们先往后面走,回头再分析公式。

    2.通过实例对PID进行理解

    为了更好了解PID算法,我们选取一个例子进行分析,这个例子在很多地方被使用,我们也选过来作为例子进行分析。

    小明现在有一个任务:有个水桶,水桶的水位高度需要时刻保持1m,目前水桶的水是0.2m,小命采用比例的方式加水(即P),即每次测量与1m 的误差,并加入与误差成比例的水量。

    K p = 0.5 K_p = 0.5 Kp=0.5
    第一次的误差:1-0.2=0.8,于是加入的水为0.50.8=0.4,此时桶内水0.6。
    第二次的误差:1-0.6=0.4,于是加入的水为0.5
    0.6=0.3,此时桶内水0.9。
    第三次的误差:1-0.9=0.1,于是加入的水为0.5*0.1=0.05,此时桶内水0.95。
    以此类推,不断加下去,通过P控制就可以将水加满到1,完美!

    3.积分环节

    上面的比例环节,貌似就可以解决问题。但是实际中没有这么理想的情况,比如水桶有个洞,每次加水都会流出0.1m。这个时候就比较接近真实情况了,比如系统的各种摩擦力,阻力什么的。

    如果我们还是用上面的比例控制(P环节)
    第一次的误差:1-0.2=0.8,于是加入的水为0.50.8=0.4,此时桶内水0.6-0.1=0.5。
    第二次的误差:1-0.5=0.5,于是加入的水为0.5
    0.5=0.25,此时桶内水0。5+0.25-0.1=0.65。
    第三次的误差:1-0.65=0.35,于是加入的水为0.5*0.35=0.175,此时桶内水0.65+0.175-0.1=0.725。

    我们多算几次以后发现,水位最终会在0.8m处稳定。如果反推一下,我们也很好理解:当误差为0.2m时,每次加水量为0.1,漏掉的也是0.1,正好加的等于漏掉的。

    这就是系统稳定误差的概念。

    关于系统稳态误差,引用胡寿松老爷子自动控制原理一书中的描述:

    控制系统的稳态误差,是系统控制准确度的一种度量,通常称为稳态性能。在控制系统设计中,稳态误差是一项重要的技术指标,对于一个实际的控制系统,由于系统结构,输入作用的类型(控制量或扰动量) ,输入函数的形式(阶跃,斜坡或加速度)不同,控制系统的稳态输出不可能在任何情况下与输入量一致,也不可能在任何形式的扰动作用下都能准确地恢复到平衡位置。此外,控制系统中不可避免的存在摩擦,间隙,不灵敏区,零位输出等非线性因素,都会造成附加稳态误差。因此,控制系统的稳态误差是不可避免的,控制系统设计的任务之一,就是尽量减小系统的稳态误差,或者使稳态误差小于某一容许值。显然,只有当系统稳定时,研究稳态误差才有意义。对于不稳定系统而言,根本不存在研究稳态误差的可能性。

    为了消除稳态误差,我们的做法就是引入积分项,就是PID中的I,积分控制就是将历史误差累加起来再乘以积分常数,即
    1 T i ∫ 0 t e ( t ) d t \frac{1}{T_i} \int_{0}^{t}e(t)dt Ti10te(t)dt

    前面的例子,我们还是设置为Kp=0.5,Ki= 0.3。
    第一次,误差为0.8, 比例部分 Kp0.8=0.4, 积分部分 Ki(e(1))= 0.24,加入水量u为0.4+0.24=0.64. 最终水位0.2+0.64-0.1= 0.74m
    第二次,误差为0.26,比例部分Kp0.26=0.13,积分部分Kp*(e(1)+e(2))= 0.318,加入水量u为 0.13+0.318=0.448.最终水位 0.74+0.448-0.1=1.088m。

    如果这样一直下去,最终会到达一个稳定值。

    4.微分环节

    前面我们已经分析了,积分环节能消除稳态误差,但是积分环境又会带来另外一个问题:积分环节会带来超调量。而且随着Ki值的变大,超调量也会边大。
    所谓的超调量,是指峰值超过终止的比例。从直观上来说,超调量对应的对就是波峰位置。波峰越高,超调量越大。

    为了消减超调,我们引入微分运算,也就PID中的D。
    上面的例子,我们假设Kp=0.5,Ki= 0.5,Kd=0.3。

    第一次: 误差为0.8, 比例部分 Kp0.8=0.4, 积分部分 Ki(e(1))= 0.24,微分部分 =0 (因为没加水前水位差就是0.8) 加入水量u为0.4+0.4=0.8. 最终水位0.2+0.8-0.1= 0.9m

    第二次: 误差为0.1,比例部分Kp0.1=0.5,积分部分Kp(e(1)+e(2))= 0.45,微分部分为Kd*(e(2)-e(1))加入水量u为 0.5+0.45-0.21=0.29.最终水位 0.9+0.29-0.1=1.09m

    最后我们计算发现,引入微分运算以后,超调量比之前有减小。

    5.PID各模块小结

    比例部分P:
    比例环节的作用是对偏差瞬间作出反应。偏差一旦产生控制器立即产生控制作用, 使控制量向减少偏差的方向变化。 控制作用的强弱取决于比例系数Kp, 比例系数Kp越大,控制作用越强, 则过渡过程越快, 控制过程的静态偏差也就越小; 但是Kp越大,也越容易产生振荡, 破坏系统的稳定性。 故而, 比例系数Kp选择必须恰当, 才能过渡时间少, 静差小而又稳定的效果。

    积分部分I:
    从积分部分的数学表达式可以知道, 只要存在偏差, 则它的控制作用就不断的增加; 只有在偏差e(t)=0时, 它的积分才能是一个常数,控制作用才是一个不会增加的常数。 可见,积分部分可以消除系统的偏差。
    积分环节的调节作用虽然会消除静态误差,但也会降低系统的响应速度,增加系统的超调量。积分常数Ti越大,积分的积累作用越弱,这时系统在过渡时不会产生振荡; 但是增大积分常数Ti会减慢静态误差的消除过程,消除偏差所需的时间也较长, 但可以减少超调量,提高系统的稳定性。

    微分部分D:
    实际的控制系统除了希望消除静态误差外,还要求加快调节过程。在偏差出现的瞬间,或在偏差变化的瞬间, 不但要对偏差量做出立即响应(比例环节的作用), 而且要根据偏差的变化趋势预先给出适当的纠正。为了实现这一作用,可在 PI 控制器的基础上加入微分环节,形成 PID 控制器。
    微分环节的作用使阻止偏差的变化。它是根据偏差的变化趋势(变化速度)进行控制。偏差变化的越快,微分控制器的输出就越大,并能在偏差值变大之前进行修正。微分作用的引入, 将有助于减小超调量, 克服振荡, 使系统趋于稳定, 特别对髙阶系统非常有利, 它加快了系统的跟踪速度。但微分的作用对输入信号的噪声很敏感,对那些噪声较大的系统一般不用微分, 或在微分起作用之前先对输入信号进行滤波。

    6.系统稳定性判断

    前面提到了系统稳定性的问题,顺便我们复习一下系统稳定性判据。
    同样来自胡寿松老爷子自动控制原理一书

    线性系统稳定的充分必要条件是:闭环系统特征方程的所有跟具有负实部,或者说,闭环传递函数的极点均位于s左半平面。

    劳斯-赫尔维茨稳定性判据:
    根据稳定的充分必要条件判别线性系统的稳定性,假设线性系统的特征方程为
    D ( s ) = a 0 s 0 + a 1 s 1 + a 2 s 2 + ⋯ + a n − 1 s n − 1 = 0 , a 0 > 0 D(s) = a_0s^0 + a_1s^1 + a_2s^2 +\cdots + a_{n-1}s^{n-1} = 0, a_0 > 0 D(s)=a0s0+a1s1+a2s2++an1sn1=0,a0>0

    即线性系统稳定的必要条件是,在特征方程中,各项系数均为正数。

    7.PID的简单实例实现

    假设我们的采样间隔为T,那么在第K个T时刻:
    偏差err(K) = rin(K) - rout(K)
    积分环节用加和的方式表示, err(1) + err(2) + … + err(K)
    微分的环节则用斜率的形式表示, [err(K) - err(K-1)] / T

    #include <iostream>
    
    using namespace std;
    
    struct _pid{
        float SetSpeed; //定义设定值
        float ActualSpeed; //定义实际值
        float err; //定义偏差值
        float err_last; //定义上一个偏差值
        float Kp,Ki,Kd; //定义比例、积分、微分系数
        float voltage; //定义电压值(控制执行器的变量)
        float integral; //定义积分值
    }pid;
    
    void PID_init(){
        printf("PID_init begin \n");
        pid.SetSpeed=0.0;
        pid.ActualSpeed=0.0;
        pid.err=0.0;
        pid.err_last=0.0;
        pid.voltage=0.0;
        pid.integral=0.0;
        pid.Kp=0.2;
        pid.Ki=0.015;
        pid.Kd=0.2;
        printf("PID_init end \n");
    }
    
    float PID_realize(float speed){
        pid.SetSpeed=speed;
        pid.err=pid.SetSpeed-pid.ActualSpeed;
        pid.integral+=pid.err;
        pid.voltage=pid.Kp*pid.err+pid.Ki*pid.integral+pid.Kd*(pid.err-pid.err_last);
        pid.err_last=pid.err;
        pid.ActualSpeed=pid.voltage*1.0;
        return pid.ActualSpeed;
    }
    
    int run_pid(){
        printf("System begin \n");
        PID_init();
        int count=0;
        while(count<1000) {
            float actual_speed=PID_realize(200.0);
            printf("count is: %d, actual_speed is: %f\n", count, actual_speed);
            count++;
        }
        return 0;
    }
    
    int main(int argc, char const *argv[])
    {
        run_pid();
        return 0;
    }
    

    代码的最终输出为

    System begin 
    PID_init begin 
    PID_init end 
    count is: 0, actual_speed is: 83.000000
    count is: 1, actual_speed is: 11.554998
    count is: 2, actual_speed is: 59.559681
    count is: 3, actual_speed is: 28.175407
    count is: 4, actual_speed is: 52.907417
    count is: 5, actual_speed is: 38.944157
    ...
    count is: 996, actual_speed is: 199.999435
    count is: 997, actual_speed is: 199.999451
    count is: 998, actual_speed is: 199.999466
    count is: 999, actual_speed is: 199.999481
    

    上述代码,模拟的就是通过PID算法,将速度最终控制在200的场景。

    参考文献

    1.https://zhuanlan.zhihu.com/p/74131690
    2.https://www.cxyzjd.com/article/weibo1230123/80812211
    3.https://zhuanlan.zhihu.com/p/41425508

    展开全文
  • 分别采用PSO粒子群优化和GWO灰狼优化算法对神经网络权值进行优化的MATLAB仿真,matlab2021a测试。
  • 随着国内外对西夏研究的不断深入,收藏于...Level Set演化函数在空间方向上采用了四阶紧致差分逼近式离散,计算过程中加入了窄带算法及全局优化方法。实验表明,算法在不增加计算时间的基础上可以得到较精确的西夏字轮廓。
  • Disjoint Set高效地支持集合的合并(Union)和集合内元素的查找(Find)两种操作,所以Disjoint Set中文翻译为并查集。 就《算法导论》21章来讲,主要设计这几个知识点:  用并查集计算图的连通区域;  判断...

    WiKi

    Disjoint是“不相交”的意思。Disjoint Set高效地支持集合的合并(Union)和集合内元素的查找(Find)两种操作,所以Disjoint Set中文翻译为并查集。
    就《算法导论》21章来讲,主要设计这几个知识点:
     用并查集计算图的连通区域;
     判断两个顶点是否属于同一个连通区域;
     链表实现并查集;
     Rooted tree实现并查集;
     Rooted tree实现并查集时采用rank方法和路径压缩算法。
    《算法导论》21.4给出了一个结论:总计m个MAKE-SET、UNION、FIND-SET操作,其中MAKE-SET的个数为n,则采用rank和路径压缩算法实现的并查集最坏时间复杂度是O(m α(n) )。其中α是Ackerman函数的某个反函数,这个函数的值可以看成是不大于4。所以,并查集的三种典型操作的时间复杂度是线性的
    #相关资料
    并查集的维基百科
    #并查集的java实现
    这里根据《算法导论》的21.3节的伪代码,实现了一个泛型的并查集。输出时,打印节点及其集合的代表元素(即根元素,representative)。

    import java.util.ArrayList;
    import java.util.List;
    import java.util.TreeSet;
    
    /**
     * <p>并查集的实现<p/>
     * <p>参考:《算法导论》21.3节<p/>
     * <p>2016-08-31<p/>
     * 
     * */
    public class DisjointSet<T> {
    	private List<Node> forests;//所有节点
    	public DisjointSet(){
    		forests=new ArrayList<Node>();
    	}
    	/**
    	 * 内部类,并查集的rooted node
    	 * */
    
    	private class Node{
    		Node parent;
    		int rank;
    		T t;
    		private Node(T t){
    			parent=this;
    			rank=0;
    			this.t=t;
    		}
    	}
    	//向森林中添加节点
    	public void makeSet(T t){
    		Node node=new Node(t); 
    		forests.add(node);
    	}
    	//将包含x和包含y的两个集合进行合并
    	public void union(T x,T y){
    		Node xNode=isContain(x);
    		Node yNode=isContain(y);
    		if (xNode!=null&&yNode!=null) {
    			link(findSet(xNode), findSet(yNode));
    		}
    	}
    	//查找到节点node的根节点
    	public Node findSet(Node node){
    		if (node!=node.parent) {
    			//路径压缩,参考《算法导论》插图21.5
    			node.parent=findSet(node.parent);
    		}
    		return node.parent;
    	}
    	//查找到节点node的根节点
    		public Node findSet(T t){
    			Node node=isContain(t);
    			if (node==null) {
    				throw new IllegalArgumentException("不含该节点!");
    			}else {
    				return findSet(node);
    			}
    			
    		}
    	//将两个根节点代表的集合进行连接
    	private void link(Node xNode,Node yNode){
    		if (xNode.rank>yNode.rank) {
    			yNode.parent=xNode;
    		}else {
    			xNode.parent=yNode;
    			if (xNode.rank==yNode.rank) {
    				yNode.rank+=1;
    			}
    		}
    	}
    	//森林是否包含这个节点
    	private Node isContain(T t){
    		for (Node node : forests) {
    			if (node.t.equals(t)) {
    				return node;
    			}
    		}
    		return null;
    	}
    	@Override
    	public String toString() {
    		// TODO Auto-generated method stub
    		if (forests.size()==0) {
    			return "并查集为空!";
    		}
    		StringBuilder builder=new StringBuilder();
    		for (Node node : forests) {
    			Node root=findSet(node);
    			builder.append(node.t).append("→").append(root.t);
    			builder.append("\n");
    		}
    		
    		return builder.toString();
    	}
    }
    
    

    然后测试一下

    public class Main{
    
    	public static void main(String[] args) {
    		// TODO Auto-generated method stub
    
    		DisjointSet<String> disjointSet=new DisjointSet<String>();
    		disjointSet.makeSet("cao");
    		disjointSet.makeSet("yan");
    		disjointSet.makeSet("feng");
    		disjointSet.union("cao", "yan");
    		disjointSet.union("cao", "feng");
    		System.out.println(disjointSet.toString());
    	}
    }
    
    

    输出格式,元素→代表元素

    cao→yan
    yan→yan
    feng→yan
    
    

    表明3个节点的代表元素一致,即处于一个集合中。
    #图的连通区域计算`
    《算法导论》21.1节的伪代码,这里给出连通区域计算的例子。图的数据结构采用“【算法导论-35】图算法JGraphT开源库介绍 “中的无向图。

    private static void connectedComponents(){
    		UndirectedGraph<String, DefaultEdge> g =
                    new SimpleGraph<>(DefaultEdge.class);
    
            String v1 = "v1";
            String v2 = "v2";
            String v3 = "v3";
            String v4 = "v4";
    
            // add the vertices
            g.addVertex(v1);
            g.addVertex(v2);
            g.addVertex(v3);
            g.addVertex(v4);
    
            // add edges to create a circuit
            g.addEdge(v1, v2);
            g.addEdge(v2, v3);
    
            //连通区域计算
            //参考《算法导论》21.1节
            DisjointSet<String> disjointSet=new DisjointSet<String>();
            for ( String v : g.vertexSet()) {
    			disjointSet.makeSet(v);
    		}
            
    //        for ( DefaultEdge e : g.edgeSet()) {
    //        	String source=e.getSource();//protected访问类型
    //        	String target=e.getTarget();//protected访问类型
    //        	if (disjointSet.findSet(source)!=disjointSet.findSet(target)) {
    //    			disjointSet.union(source, target);
    //    		}
    //		}
            
            if (disjointSet.findSet(v1)!=disjointSet.findSet(v2)) {
    			disjointSet.union(v1, v2);
    		}
            if (disjointSet.findSet(v2)!=disjointSet.findSet(v3)) {
    			disjointSet.union(v2, v3);
    		}
            System.out.println(disjointSet.getSetCounter());
    		 
    	}
    

    输出

    v1→v2
    v2→v2
    v3→v2
    v4→v4
    
    

    v1、v2、v3的代表元素一致,表明三者在一个集合中,即三者连通。v4是另外一个集合。
    #实例应用
    举个例子,某人结婚时宴请宾客,A来宾认识B来宾,B来宾认识C来宾,则A、B、C安排在一桌。A来宾认识B来宾,且A、B的熟人及其熟人的熟人(熟人链)不包括C,则C与A、B不在一桌。问,需要多少桌子才能满足要求呢?
    这个例子其实就是连通区域的具体到社交关系的1度、2度……n度关系。
    稍微修改并查集的实例,添加集合的计数setCounter,每次makeset时递增,union时递减,这样就得到最后的集合个数。

    import java.util.ArrayList;
    import java.util.List;
    import java.util.TreeSet;
    
    /**
     * <p>并查集的实现<p/>
     * <p>参考:《算法导论》21.3节<p/>
     * <p>2016-08-31<p/>
     * 
     * */
    public class DisjointSet<T> {
    	private List<Node> forests;//所有节点
    	private int setCounter;//集合计数
    	public DisjointSet(){
    		forests=new ArrayList<Node>();
    		setCounter=0;
    	}
    	
    	public int getSetCounter() {
    		return setCounter;
    	}
    
    	/**
    	 * 内部类,并查集的rooted node
    	 * */
    
    	private class Node{
    		Node parent;
    		int rank;
    		T t;
    		private Node(T t){
    			parent=this;
    			rank=0;
    			this.t=t;
    		}
    	}
    	//向森林中添加节点
    	public void makeSet(T t){
    		Node node=new Node(t); 
    		forests.add(node);
    		setCounter++;
    	}
    	//将包含x和包含y的两个集合进行合并
    	public void union(T x,T y){
    		if (x.equals(y)) {
    			throw new IllegalArgumentException("Union的两个元素不能相等!");
    		}
    		Node xNode=isContain(x);
    		Node yNode=isContain(y);
    		if (xNode!=null&&yNode!=null) {
    			link(findSet(xNode), findSet(yNode));
    			setCounter--;
    		}
    	}
    	//查找到节点node的根节点
    	public Node findSet(Node node){
    		if (node!=node.parent) {
    			//路径压缩,参考《算法导论》插图21.5
    			node.parent=findSet(node.parent);
    		}
    		return node.parent;
    	}
    	//查找到节点node的根节点
    		public Node findSet(T t){
    			Node node=isContain(t);
    			if (node==null) {
    				throw new IllegalArgumentException("不含该节点!");
    			}else {
    				return findSet(node);
    			}
    			
    		}
    	//将两个根节点代表的集合进行连接
    	private void link(Node xNode,Node yNode){
    		if (xNode.rank>yNode.rank) {
    			yNode.parent=xNode;
    		}else {
    			xNode.parent=yNode;
    			if (xNode.rank==yNode.rank) {
    				yNode.rank+=1;
    			}
    		}
    	}
    	//森林是否包含这个节点
    	private Node isContain(T t){
    		for (Node node : forests) {
    			if (node.t.equals(t)) {
    				return node;
    			}
    		}
    		return null;
    	}
    	@Override
    	public String toString() {
    		// TODO Auto-generated method stub
    		if (forests.size()==0) {
    			return "并查集为空!";
    		}
    		StringBuilder builder=new StringBuilder();
    		for (Node node : forests) {
    			Node root=findSet(node);
    			builder.append(node.t).append("→").append(root.t);
    			builder.append("\n");
    		}
    		
    		return builder.toString();
    	}
    }
    
    

    连通区域的计算,不过这里输出的是集合个数。

    private static void connectedComponents(){
    		UndirectedGraph<String, DefaultEdge> g =
                    new SimpleGraph<>(DefaultEdge.class);
    
            String v1 = "v1";
            String v2 = "v2";
            String v3 = "v3";
            String v4 = "v4";
    
            // add the vertices
            g.addVertex(v1);
            g.addVertex(v2);
            g.addVertex(v3);
            g.addVertex(v4);
    
            // add edges to create a circuit
            g.addEdge(v1, v2);
            g.addEdge(v2, v3);
    
            //连通区域计算
            //参考《算法导论》21.1节
            DisjointSet<String> disjointSet=new DisjointSet<String>();
            for ( String v : g.vertexSet()) {
    			disjointSet.makeSet(v);
    		}
            
    //        for ( DefaultEdge e : g.edgeSet()) {
    //        	String source=e.getSource();//protected访问类型
    //        	String target=e.getTarget();//protected访问类型
    //        	if (disjointSet.findSet(source)!=disjointSet.findSet(target)) {
    //    			disjointSet.union(source, target);
    //    		}
    //		}
            
            if (disjointSet.findSet(v1)!=disjointSet.findSet(v2)) {
    			disjointSet.union(v1, v2);
    		}
            if (disjointSet.findSet(v2)!=disjointSet.findSet(v3)) {
    			disjointSet.union(v2, v3);
    		}
            System.out.println(disjointSet.getSetCounter());
    		 
    	}
    
    

    输出是2。

    请我喝咖啡

    如果觉得写得不错,可以扫描我的微信二维码请我喝咖啡哦~

    在这里插入图片描述
    或者点击 打赏地址 请我喝杯茶~

    展开全文
  • 数据结构与算法必知基础知识

    千次阅读 多人点赞 2021-01-06 22:58:12
    原创公众号:bigsai 文章已收录在 全网都在关注的数据结构与算法学习仓库 欢迎star 前言 数据结构与算法是程序员内功体现的重要标准...为什么学习数据结构与算法?如果你还是学生,那么这门课程是必修的,考研基本也.

    原创公众号:bigsai
    文章已收录在 全网都在关注的数据结构与算法学习仓库 欢迎star

    前言

    数据结构与算法是程序员内功体现的重要标准之一,且数据结构也应用在各个方面,业界更有程序=数据结构+算法这个等式存在。各个中间件开发者,架构师他们都在努力的优化中间件、项目结构以及算法提高运行效率和降低内存占用,在这里数据结构起到相当重要的作用。此外数据结构也蕴含一些面向对象的思想,故学好掌握数据结构对逻辑思维处理抽象能力有很大提升。

    为什么学习数据结构与算法?如果你还是学生,那么这门课程是必修的,考研基本也是必考科目。工作在内卷严重的大厂中找工作数据结构与算法也是面试、笔试必备的非常重要的考察点。如果工作了数据结构和算法也是内功提升一个非常重要的体现,对于程序员来说,想要得到满意的结果,数据结构与算法是必备功力!

    数据结构

    image-20201108002732048

    概念

    数据结构是计算机存储、组织数据的方式。数据结构是指相互之间存在一种或多种特定关系的数据元素的集合。通常情况下,精心选择的数据结构可以带来更高的运行或者存储效率。

    简言之,数据结构是一系列的存储结构按照一定

    展开全文
  • 程序员为什么要学习数据结构与算法?

    千次阅读 多人点赞 2022-06-30 13:55:04
    数据结构Q与算法是程序员内功体现的重要标准之一,且数据结构也应用在各个方面, 业界更有程序-数据结构+算法这个等式存在。 各个中间件开发者,架构师Q他们都在努力的优化中间件、项目结构以及算法提高运行效率和...
  • G1垃圾回收算法概述

    千次阅读 2020-05-17 07:00:00
    FGC采用的标记清除算法,在JDK10之前,G1的FGC采用的串行实现,从JDK10开始,FGC被优化成并行执行。串行和并行的实现基本类似,唯一的不同是,在串行执行时标记清除是针对整个内存,而在并行执行时,多个并行执行的...
  • 优化算法综述

    千次阅读 2021-06-30 21:21:03
    依据 分类 具体算法 分类名 全局优化 遗传算法(GA)、帝国竞争算法(ICA)、 粒子群优化(PSO) 局部优化 模拟退火(SA)、贪婪算法(Greedy)、 邻域搜索(NS) 是否精确算法 精确算法 ...
  • 社区发现算法——Louvain 算法

    千次阅读 热门讨论 2022-02-08 16:07:40
    Louvain 算法 原始论文为:《Fast unfolding of communities in large networks》。 所以又被称为Fast unfolding算法。 Louvain算法是一种基于模块度的社区发现算法。其基本思想是网络中节点尝试遍历所有邻居的社区...
  • 文章目录帕累托多目标遗传算法代码实现排序函数查找所需索引函数Pareto 法得到帕累托前沿函数拥挤度计算从父代和子代中选择下一个父代 帕累托多目标遗传算法 在优化领域,遗传算法绝对占得上一席之地,但由于在...
  • 本系列主要是基于Spark的推荐算法实战系列,本文为首篇,欢迎关注! 1.Swing算法介绍 Swing算法原理比较简单,是阿里早期使用到的一种召回算法,在阿里多个业务被验证过非常有效的一种召回方式,它认为 user-item-...
  • 通俗易懂的AI算法原理

    万次阅读 2019-06-27 08:24:55
    我想尽量用直白的语言、较少的数学知识给各位产品经理讲清楚各个算法的原理是什么。 机器学习的过程 机器学习的过程从本质上来说就是通过一堆的训练数据找到一个与理想函数(f)相接近的函数。在理想情况下,对于...
  • 非对称加密,与对称加密的算法是有所不同的,非对称加密算法需要两个密钥,即公开密钥和私有密钥,非对称加密算法在加密和解密过程使用了不同的密钥,非对称密钥也称为公钥加密,在密钥对中,其中一个密钥是对外公开...
  • jvm的三种算法以及10种垃圾收集器

    千次阅读 2020-05-10 18:13:17
    jvm的三种算法以及10种垃圾收集器 jvm怎么知道堆里面的对象是无用数据,有两种方式: 1.引用计数法: 每个对象有一个引用计数属性,新增一个引用时计数加1,引用释放时计数减1,计数为0时可以回收。此方法简单,无法...
  • 水平集算法简介(Level Set) 一、水平集的定义  与实数c对应的可微函数f:R^n—>R的水平集是实点集{(x1, x2, ...,xn) | f(x1, x2,...,xn) = c} ,称可微函数f为水平集函数。  [举例]  函数f(x,y,z)=x^2+y^2...
  • 遗传算法里多个个体组成一个种群、蚁群算法的蚂蚁、粒子群算法的粒子都是群优化算法的体现。 本文分别利用以上三种算法来解决31个城市的TSP问题。 二、遗传算法解决TSP问题 遗传算法简介: 遗传算法是一种...
  • 人工智能之搜索算法

    千次阅读 多人点赞 2020-03-21 21:41:32
    通过搜索来解决问题 文章目录通过搜索来解决问题1. 什么算法?2. 什么是搜索?3. 搜索算法3.1 如何做路径规划?3.2 搜索过...
  • 简称RISC)的Christoph Koutschan博士在自己的页面上发布了一篇文章,提到他做了一个调查,参与者大多数是计算机科学家,他请这些科学家投票选出最重要的算法,以下是这次调查的结果,按照英文名称字母顺序排序。...
  • kmeans算法

    千次阅读 2021-11-02 20:49:37
    文章目录1、概述1.1 无监督学习与聚类算法1.2 sklearn中的聚类算法2、KMeans2.1 KMeans是如何工作的?2.2 簇内误差平方和的定义和解惑3、sklearn.cluster.KMeans3.1 重要参数n_clusters3.2 聚类算法的模型评估指标...
  • Apriori算法详解

    千次阅读 2021-06-05 20:11:14
    Apriori算法采用了向下闭包性,极大的提高了挖掘效率。 2.Apriori 简述: 挖掘频繁项集在逻辑上就是搜索一颗枚举树并计算结点支持率的过程。在不知道Apriori算法的情况下,我们应该自然而然地想到暴力算法:穷举所有...
  • 二维码识别算法简介

    千次阅读 2022-02-08 14:13:45
    halcon的二维码识别还是可以兼容一大部分,对于图像对比度比较好的识别还是比较容易。 要想识别二维码,首先就要...下面给出一个例子可以识别文件下所有图像格式支持的图片测试,读取二维码,采用循环遍历的方法识别
  • 算法工程师-笔(面)试题汇总

    千次阅读 2021-02-04 16:26:59
    算法常用概念 训练集(train set)、验证集(validation set)、测试集(test set) 一般需要将样本分成独立的三部分训练集(train set),验证集(validation set)和测试集(test set)。其中训练集用来估计模型,验证集用来...
  • Paxos算法和Raft算法

    千次阅读 2020-09-30 15:31:10
    Paxos算法是莱斯利·兰伯特(英语:Leslie Lamport,LaTeX中的“La”)于1990年提出的一种基于消息传递且具有高度容错特性的一致性算法。用于解决在多个节点间确定一个值。 例如这样的场景: 节点A、B、C是如何...
  • CMS算法介绍

    千次阅读 2018-04-27 13:21:41
    原文:http://anduo.me/2017/03/04/gc_cms/#31_Initial_Mark1. 垃圾回收基本操作...如下图所示,GC算法会从GC Root开始,标记所有目前所有可达的对象。1.1.1 GCRootsGCRoots大致有如下几种: 当前执行函数的局部变...
  • 主宰操作系统的经典算法

    万次阅读 多人点赞 2020-07-24 15:22:50
    此篇文章带你梳理一下操作系统中都出现过哪些算法 进程和线程管理中的算法 进程和线程在调度时候出现过很多算法,这些算法的设计背景是当一个计算机是多道程序设计系统时,会频繁的有很多进程或者线程来同时竞争 ...
  • Matlab仿真实现条带SAR频率变标成像算法,信号采用dechirp接收。下载之后运行FSA.m文件
  • 这个技巧是很多高效算法的基础,如排序算法(快速排序,归并排序),傅立叶变换(快速傅立叶变换)等。 能用分治法的基本特征: 问题缩小到一定规模容易解决; 分解成的子问题是相同种类的子问题,即该问题...
  • 分水岭算法及案例

    万次阅读 多人点赞 2017-11-29 13:04:32
    分水岭算法Watershed Algorithm(分水岭算法),顾名思义,就是根据分水岭的构成来考虑图像的分割。现实中我们可以或者说可以想象有山有湖的景象,那么那一定是水绕 山,山围水的情形。当然在需要的时候,要人工构筑...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 301,152
精华内容 120,460
关键字:

set采用了什么算法