Algo_Tarjan,Scc强联通分量

catalog

Scc

Scc: Strong connected component 强联通分量: 针对有向图, 将这(有向图), 按照 点集 进行划分, 可以得到若干个 scc

在这里插入图片描述

该有向图有4个scc

scc的定义为: 一个scc里的 任意两个点ab, 均可达 (a可以达到b, b也可以到达a)


若将 原有向图, 按照 scc, 进行 缩点, 则原图会变成一个 DAG( directed acyclic graph)

在这里插入图片描述

因为, 不同的scc之间, 要么是不可达的, 要么是 单向可达; 这正好与scc的定义相悖; 所以, 缩点后是一个dag

我们的目的是: 设置一个scc_id[ ], 对于同一个scc里的点, 他的scc_id都是一样的; 表示, 该点 所在的scc 的编号


tarjan算法, 是O(n)的时间

int Scc_count = 0; ' 总共的scc个数; 且每个scc的编号是[0, Scc_count) '
int Scc_id[ PointCount]; ' 每个点所在的scc的 编号 '
int Scc_size[ PointCount]; ' 每个scc里面, 有多少个点 ' 

int Vis_ind = 0;
int Vis_id[ PointCount]; '  tarjan是dfs, 每个点只会进行1次dfs; '
' 每访问一个点, 他的Vis_id就设置为 Vis_ind; 然后Vis_ind ++; 最终, 所有点的Vis_id 均为 [0, n)之间 '

int Stk[ PointCount]; ' 因为是dfs, dfs本身就符合 栈stack结构 '  
int Stk_size = 0;
bool Is_inStk[ PointCount];

void Tarjan_scc( int _node){
	Vis_id[ _node] = Vis_ind;
	Vis_ind ++;
	
	Stk[ Stk_size] = _node;
	Stk_size ++;
	Is_inStk[ _node] = true;
	
	Scc_id[ _node] = Vis_id[ _node];
	
	for( int i = Head[ _node]; i != -1; i = Nex[ i]){
		int j = Ver[ i];
		if( -1 == Vis_id[ j]){
			Tarjan_scc( j);
			if( Is_inStk[ j]){
				Scc_id[ _node] = min( Scc_id[ _node], Scc_id[ j]);
			}
		}
		else{
			if( Is_inStk[ j]){
				Scc_id[ _node] = min( Scc_id[ _node], Scc_id[ j]);
			}
		}
	}
	
	if( Scc_id[ _node] == Vis_id[ _node]){
		while( Stk_size > 0){
			int i = Stk[ Stk_size - 1];
			-- Stk_size;
			Is_inStk[ i] = false;
			
			Scc_id[ i] = Scc_count;
			Scc_size[ Scc_count] ++;
			
			if( i == _node){
				break;
			}
		}
		Scc_count ++;
	}
}

int main(){

	memset( Is_inStk, false, sizeof( Is_inStk));
	memset( Vis_id, -1, sizeof( Vis_id));
	memset( Scc_size, 0, sizeof( Scc_size));
	for( int i = 1; i <= n; ++i){
		if( Vis_id[ i] == -1){
			Tarjan_scc( i);
		}
	}
}

注意点:

  • 外界每次调用的: Tarjan_scc( )之后, 整个Stk栈会是空的!!!
    即, 每次Tarjan_scc()完后, 所访问到点, 他们的scc就确立下来了;
    而且, 比如, 之前的scc 已经有: 0, 1, 2, 3, 本次Tarjan_scc() 新产生的scc有: 4, 5, 6;
    那么, 本次Tarjan_scc()所 遍历到的点, 他们所在的scc 一定都是4, 5, 6; 不会是其他的scc
    而且, 本次Tarjan_scc()完后, 对于没有图中 还没有访问到的点, 他们所的scc, 一定是: > 6的;

  • dfs顺序是未知的
    在这里插入图片描述
    虽然A所在的scc, 是DAG的起点, 但, 他并不是 第一个的tarjan; 第一次的tarjan, 是(D, E, F)

  • scc的编号
    在这里插入图片描述
    共有3个scc, 我们可以将这3个scc的编号, 设置为: (3) -> (0) -> (1)
    即, 每个scc的编号, 为 该scc里所有点的 最小的vis_id;
    这是很容易做到的, 因为根据tarjan算法, 一个scc的确立 是取决于 (该scc里 最小的vis_id这个点)
    但是, 我们通常不会这样做!!! 因为不方便, 虽然知道有3个scc, 但是, 他们的编号, 我们很不方便得到
    我们会把每个scc的编号, 设置为: [0, 1, 2]; 即有n个scc, 则每个scc编号为: [0, n)

  • Is_inStk, 这个标识, 不仅表示: 元素是否在stk里
    还表示一个重要含义: 若Is_inStk[ x] == true, 则表示: x已经dfs访问过了, 但还没有求出scc
    但是, 如果Is_inStk == false, (可能: x已经求出scc了) (也可能: x还没有dfs访问过)

  • dfs回溯更新

    if( -1 == Vis_id[ j]){
    		Tarjan_scc( j);
    		if( Is_inStk[ j]){
    			Scc_id[ _node] = min( Scc_id[ _node], Scc_id[ j]);
    		}
    	}
    
  • 正在进行tarjan算法时, scc_id表示什么呢? 所谓正在进行tarjan, 即if( Scc_id[ _node] == Vis_id[ _node]) 还没有执行
    我们知道, 最终的scc_id, 代表 该scc的编号; (最先求出来的scc, 编号是0; 依次从0开始递增)
    但是, 这是在 (已经求出scc, scc已经确立了)的前提下; 那么, 在还没求出scc, scc_id表示什么呢?
    这真的是, tarjan算法的核心;

    1. 当访问一个点时, 会有: Scc_id[ _node] = Vis_id[ _node];, 即该点的 scc_id 和 其vis_id 是相同的;
    2. 然后, 执行dfs; 这个过程, scc_id 可能会 变小, 因为只有min操作
    3. 当一个scc确立时, 即在执行: if( Scc_id[ _node] == Vis_id[ _node])时, 此时会有:
      比如一个scc, 他所有的点的 编号, 是: 3, 5, 6, 7, 假设, vis_id[ 5]最小; 即5相对3/6/7, 是最先dfs访问到的
      根据tarjan, 我们知道, 一个scc 由(vis_id最小的点) 确立, 即当dfs回溯到5时, 会确立这个 scc(3, 5, 6, 7)
      此时当回溯到5, 要确立scc时,会有: scc_id[ 5] = vis_id[ 5]; scc_id[ other] = {>= vis_id[5]}
      从上面的1, 我们知道, scc_id 最初= vis_id; 因为vis_id[ 5]最小, 即vis_id[ other] > vis_id[ 5]; 再根据1, 有: scc_id[ other] > vis_id[ 5]
      但是, 由于2, scc_id 可能会变小; 会变小多少呢? 最小为vis_id[ 5]
      在这里插入图片描述
      当回溯到5时, 此时这个scc就可以确立下来的; 该scc里点的 scc_id, 有0 也是{>0}的; 是挺混乱的
      所以, 确立一个scc, 不是根据一个点的scc_id来确立的!!!; 而是根据stk栈里元素, 具体讲: 当回溯到5时 (知道, 5是scc里最小的vis_id), 则此时 stk栈里, 所有在5前面的 元素, 都是该scc里的元素 (这是判定scc的依据)
<think>嗯,用户给了一段C++代码,说在doc.HasMember("limitRule")这里崩溃了。首先,我需要理解这段代码的结构和可能的问题。代码里使用了rapidjson库来解析JSON字符串algo_params,然后检查是否有"limitRule"这个成员。崩溃可能出现在解析或访问成员的过程中。 首先,用户提到即使algo_params非空,也调用了Parse方法。根据rapidjson的文档,Parse的返回值需要检查是否解析成功。用户代码里使用了!doc.Parse(...).HasParseError(),这看起来是正确的,但可能还有其他问题。比如,即使Parse成功,doc可能仍然是空的,或者不是对象类型。 接下来,doc.HasMember("limitRule")崩溃的可能原因有几个。最常见的是doc不是一个对象,而是一个数组或其他类型。这时候调用HasMember会导致未定义行为,比如断言失败或崩溃。另外,检查字符串是否正确,比如是否使用了正确的引号或转义字符,但用户代码里用了"limitRule",看起来没问题。 另外,还需要检查algo_params.data()是否正确。如果algo_params是一个std::string,那么data()返回的C风格字符串应该是正确的,但要注意是否以空字符结尾。rapidjson的Parse需要以空字符结尾的字符串,否则可能解析错误。不过,如果algo_params是正常的std::string,data()应该没问题,因为C++11之后data()返回的是以空字符结尾的。 另一个可能性是多线程问题,但用户没有提到,可能不太相关。或者内存损坏,但需要更多信息。或者rapidjson版本的问题,某些旧版本可能有bug。 解决方法的话,首先应该确保在调用HasMember之前,doc是一个对象。可以检查doc.IsObject()。此外,检查Parse后的结果是否正确,即使HasParseError为false,可能因为字符串是空对象或其他结构。比如,如果algo_params是"null",那么解析后的doc是Null类型,不是对象,这时候调用HasMember就会崩溃。 用户注释掉的部分里有一个判断algo_params是否为empty、"null"等,但后来被注释掉了。可能需要恢复这部分逻辑,或者在解析后检查IsObject。此外,在解析之后,应该先检查doc.IsObject(),再调用HasMember。 所以,可能的修复步骤是:在调用HasMember之前,先判断doc是否为对象。如果不是,跳过检查。或者处理解析后的文档类型是否正确。 另外,用户代码里在解析后直接进入if块,但可能此时doc的类型不是对象,比如是数组、null或其他类型。因此,添加doc.IsObject()的判断可以避免崩溃。 总结下来,崩溃的原因可能是doc不是对象类型,导致HasMember调用失败。解决方法是在调用HasMember前检查doc.IsObject(),并且确保解析后的文档确实是对象。</think>在C++代码中出现`doc.HasMember("limitRule")`崩溃的问题,可能是由以下原因导致。让我们逐步分析并给出解决方案: --- ### **第一步:检查JSON解析有效性** ```cpp if (!algo_params.empty() && !doc.Parse(algo_params.data()).HasParseError()) ``` 虽然此处检查了JSON解析错误,但还需确认: 1. **`doc`是否为对象类型** 若JSON字符串是空对象`{}`或非对象类型(如数组),`HasMember()`会引发崩溃。需增加类型检查: ```cpp if (doc.IsObject() && doc.HasMember("limitRule")) ``` 2. **空字符串的特殊处理** 即使`algo_params`非空,也可能包含无效内容(如`"null"`),此时`doc`的类型是`Null`而非`Object`。 --- ### **第二步:验证输入字符串的合法性** 确保传入的`algo_params`是**合法JSON对象**: - 示例合法输入:`{"limitRule": 1}` - 非法输入示例:`""`, `"null"`, `"invalid json"` --- ### **第三步:代码修复方案** 修改后的代码逻辑应包含以下保护: ```cpp if (!algo_params.empty() && !doc.Parse(algo_params.data()).HasParseError()) { LOGI << "Parsed JSON: " << algo_params; // 增加类型检查和空对象验证 if (doc.IsObject() && !doc.ObjectEmpty()) { if (doc.HasMember("limitRule")) { // 安全操作 } } else { LOGW << "Invalid JSON structure (not an object or empty)"; } } ``` --- ### **第四步:关键错误点分析** | 问题原因 | 现象 | 解决方案 | |-------------------------|--------------------------------|------------------------------| | 解析结果非对象类型 | 调用`HasMember()`时崩溃 | 增加`doc.IsObject()`检查 | | JSON字符串内容非法 | 解析失败或生成非对象类型 | 严格校验输入格式 | | RapidJSON内存管理问题 | 未定义行为(如悬空指针) | 确保字符串生命周期 | --- ### **第五步:附加调试建议** 1. **打印原始JSON内容** 在解析前输出原始字符串,确认其符合预期: ```cpp LOGI << "Raw algo_params: " << algo_params; ``` 2. **使用RapidJSON的断言检查** 在Debug模式下,RapidJSON会检查`HasMember()`的对象类型有效性。若触发断言,说明`doc`不是对象。 --- ### **最终修复代码** ```cpp TradeRule getTradeRule(string algo_params) { TradeRule trade_rule; LOGI << "Parsing algo_params: " << algo_params; rapidjson::Document doc; if (!algo_params.empty()) { if (!doc.Parse(algo_params.data()).HasParseError()) { // 关键保护:类型检查 + 非空对象 if (doc.IsObject() && !doc.ObjectEmpty()) { if (doc.HasMember("limitRule")) { // 安全操作 } } else { LOGW << "Invalid JSON object"; } } else { LOGW << "JSON parse error"; } } return trade_rule; } ``` --- 通过以上步骤,可有效避免因JSON结构错误导致的崩溃问题。核心思路是:**严格校验输入合法性** + **保护性类型检查**。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值