# 二维码门禁梯控门禁公司还在靠卖硬件赚钱?这3个转型方向让利润翻3倍“去年我们公司卖了8000套二维码门禁设备,净利润却不到80万,平均一套设备赚不到100块钱。”这是深圳一家门禁系统集成商老板
2026/6/15 7:23:54
资料:https://pan.quark.cn/s/43d906ddfa1b、https://pan.quark.cn/s/90ad8fba8347、https://pan.quark.cn/s/d9d72152d3cf
有向无环图(Directed Acyclic Graph,简称DAG)是一类特殊的有向图,其核心特征是不存在任何有向环,即从任意顶点出发,沿着有向边遍历,无法回到该顶点。
有向无环图可形式化表示为G=(V,E),其中V为顶点集合,E为有向边集合,且图中不存在满足v₀=vₖ且包含至少一条边的有向路径v₀→v₁→…→vₖ。
对DAG顶点的一种线性排序,满足:若图中存在有向边<u,v>,则在序列中u一定位于v之前。
在带权DAG中,从源点到汇点的最长路径称为关键路径,路径上的顶点和边为关键任务,决定了整个工程的最短完成时间。
对于DAG中的两个顶点u和v,若存在从u到v的有向路径,则称v可由u到达,DAG的可达性可通过拓扑排序或DFS高效判断。
DAG的存储方式与普通有向图一致,常用以下两种:
用n×n二维数组存储,adj[i][j]表示是否存在从顶点i到j的有向边,无环特性不影响存储结构,仅需保证矩阵对应的图无环。
O(1);O(n²),适合顶点数较少的DAG。为每个顶点维护一个邻接顶点列表(带权DAG则存储(邻接顶点,权重)二元组),同时可维护入度数组,用于拓扑排序。
O(|V|+|E|),适合稀疏DAG;O(outdeg(u))。拓扑排序是DAG的标志性算法,有两种经典实现方式:
O(|V|+|E|),可同时检测图是否为DAG。O(|V|+|E|),适合需要同时处理连通分量的场景。ve):ve[v] = max(ve[u] + weight(u,v)),其中u是v的前驱顶点;vl):vl[u] = min(vl[v] - weight(u,v)),其中v是u的后继顶点;ve等于vl,则该顶点在关键路径上,对应的边为关键边。在DAG中可通过拓扑排序高效求解单源最长路径:
O(|V|+|E|)。fromcollectionsimportdequeclassDAG:def__init__(self,num_vertices):self.num_vertices=num_vertices self.adj_list=[[]for_inrange(num_vertices)]# (v, weight)self.indegree=[0]*num_vertices# 入度数组defadd_edge(self,u,v,weight=1):"""添加有向边<u,v>,默认权重为1"""self.adj_list[u].append((v,weight))self.indegree[v]+=1deftopological_sort_kahn(self):"""Kahn算法实现拓扑排序,返回拓扑序列"""queue=deque()# 初始化入度为0的顶点foriinrange(self.num_vertices):ifself.indegree[i]==0:queue.append(i)topo_order=[]whilequeue:u=queue.popleft()topo_order.append(u)# 遍历u的出边,更新邻接顶点入度forv,_inself.adj_list[u]:self.indegree[v]-=1ifself.indegree[v]==0:queue.append(v)iflen(topo_order)!=self.num_vertices:raiseValueError("图中存在环,不是有向无环图")returntopo_orderdefcritical_path(self):"""求解关键路径,返回关键顶点列表"""try:topo_order=self.topological_sort_kahn()exceptValueErrorase:returnstr(e)# 1. 计算最早开始时间veve=[0]*self.num_verticesforuintopo_order:forv,winself.adj_list[u]:ifve[v]<ve[u]+w:ve[v]=ve[u]+w# 2. 计算最晚开始时间vlvl=[ve[-1]]*self.num_vertices# 汇点的vl等于其veforuinreversed(topo_order):forv,winself.adj_list[u]:ifvl[u]>vl[v]-w:vl[u]=vl[v]-w# 3. 筛选关键顶点(ve[u] == vl[u])critical_vertices=[uforuinrange(self.num_vertices)ifve[u]==vl[u]]returncritical_vertices,ve[-1]# 返回关键顶点和工程最短完成时间# 初始化6个顶点的DAG(模拟项目任务依赖)dag=DAG(6)# 添加带权边:u->v,权重为任务耗时dag.add_edge(0,1,3)dag.add_edge(0,2,2)dag.add_edge(1,3,2)dag.add_edge(2,3,4)dag.add_edge(3,4,3)dag.add_edge(3,5,2)dag.add_edge(4,5,1)# 拓扑排序topo=dag.topological_sort_kahn()print("拓扑序列:",topo)# 输出如[0,2,1,3,4,5]# 关键路径critical_vertices,min_time=dag.critical_path()print("关键顶点:",critical_vertices)# 输出关键顶点列表print("工程最短完成时间:",min_time)# 输出总耗时