SPFA算法:只要最短路径存在,SPFA算法必定能求出最小值,SPFA对Bellman-Ford算法优化的关键之处在于意识到:只有那些在前一遍松弛中改变了距离估计值的点,才可能引起他们的邻接点的距离估计值的改变。为什么队列为空就不改变了呢?就是因为要到下一点必须经过它的前一个邻接点。。SPFA可以处理负权边。很多时候,给定的图存在负权边,这时类似Dijkstra等算法便没有了用武之地,而Bellman-Ford算法的复杂度又过高,SPFA算法便派上用场了。简洁起见,我们约定有向加权图G不存在负权回路,即最短路径一定存在。当然,我们可以在执行该算法前做一次拓扑排序,以判断是否存在负权回路。
初始化: dis数组全部赋值为Inf(无穷大,不能是map[s][i]),path数组全部赋值为s(即源点),或者赋值为-1,表示还没有知道前驱,然后dis[s]=0; 表示源点不用求最短路径,或者说最短路就是0。将源点入队;另外记住在整个算法中有顶点入队了要记得标记vis数组,有顶点出队了记得消除那个标记(可能多次入队)。
核心:读取队头顶点u,并将队头顶点u出队(记得消除标记);将与点u相连的所有点v进行松弛操作,如果能更新估计值(即令d[v]变小),那么就更新,另外,如果点v没有在队列中,那么要将点v入队(记得标记),如果已经在队列中了,那么就不用入队以此循环,直到队空为止就完成了单源最短路的求解。
判断有无负环:如果某个点进入队列的次数超过N次则存在负环(SPFA无法处理带负环的图),假设这个节点的入度是k(无向权则就是这个节点的连接的边)如果进入这个队列超过k,说明必然有某个边重复了,即成环;换一种思路:用DFS,假设存在负环a1->a2->…->an->a1。那么当从a1深搜下去时又遇到了a1,那么直接可以判断负环了所有用。当某个节点n次进入队列,则存在负环,此时时间复杂度为O(n*m),n为节点,m为边。
SPFA算法有两个优化算法 SLF 和 LLL: SLF:Small Label First 策略,设要加入的节点是j,队首元素为i,若dist(j)<dist(i),则将j插入队首,否则插入队尾。 LLL:Large Label Last 策略,设队首元素为i,队列中所有dist值的平均值为x,若dist(i)>x则将i插入到队尾,查找下一元素,直到找到某一i使得dist(i)<=x,则将i出对进行松弛操作。 SLF 可使速度提高 15 ~ 20%;SLF + LLL 可提高约 50%。 在实际的应用中SPFA的算法时间效率不是很稳定,为了避免最坏情况的出现,通常使用效率更加稳定的Dijkstra算法。个人觉得LLL优化每次要求平均值,不太好,为了简单,我们可以之间用c++STL里面的优先队列来进行SLF优化。
- function [x_hat, success, k] = decode(y,f0,f1,H)
- % Example:
- % We assume G is systematic G=[A|I] and, obviously, mod(G*H',2)=0
- % sigma = 1; % AWGN noise deviation
- % x = (sign(randn(1,size(G,1)))+1)/2; % random bits
- % y = mod(x*G,2); % coding
- % z = 2*y-1; % BPSK modulation
- % z=z + sigma*randn(1,size(G,2)); % AWGN transmission
- %
- % f1=1./(1+exp(-2*z/sigma^2)); % likelihoods
- % f0=1-f1;
- % [z_hat, success, k] = ldpc_decode(z,f0,f1,H);
- % x_hat = z_hat(size(G,2)+1-size(G,1):size(G,2));
- % x_hat = x_hat';
- [m,n] = size(H);
- if m>n
- H=H';
- [m,n] = size(H);
- end
- if ~issparse(H) % make H sparse if it is not sparse yet如果不是稀疏矩阵,将H变为稀疏矩阵
- [ii,jj,sH] = find(H);
- H = sparse(ii,jj,sH,m,n);
- end
- %initialization初始化
- [ii,jj] = find(H); % subscript index to nonzero elements of H 将非零元素标上
- indx = sub2ind(size(H),ii,jj); % linear index to nonzero elements of H 找出H中非零元素的位置索引
- q0 = H * spdiags(f0(:),0,n,n);
- sq0 = full(q0(indx));
- sff0 = sq0;
- q1 = H * spdiags(f1(:),0,n,n);
- sq1 = full(q1(indx));
- sff1 = sq1;
- %iterations
- k=0;
- success = 0;
- max_iter =20;
- while ((success == 0) & (k < max_iter))
- k = k+1;
- %for iter=1:max_iter
- %horizontal step第一步
- sdq = sq0 - sq1;
- sdq(find(sdq==0)) = 1e-20; % if f0 = f1 = .5若先验概率一样的话,令sdq为一个很小很小的数
- dq = sparse(ii,jj,sdq,m,n);%以ii,jj为横纵坐标,用概率差sdq构成m*n的稀疏矩阵
- Pdq_v = full(real(exp(sum(spfun('log',dq),2)))); % this is ugly but works :)
- %将dq在对数域求和,即将dq相乘,构成数组 Pdq_v
- Pdq = spdiags(Pdq_v(:),0,m,m) * H;%更新了rij,以坐标形式6*12的矩阵
- sPdq = full(Pdq(indx));%变成了一列
- sr0 = (1+sPdq./sdq)./2; sr0(find(abs(sr0) < 1e-20)) = 1e-20;%将每个元素的rij都归一化,更新rij。概率译码的第一步完成
- sr1 = (1-sPdq./sdq)./2; sr1(find(abs(sr1) < 1e-20)) = 1e-20;%若比值非常小,则设为1e-20
- r0 = sparse(ii,jj,sr0,m,n);%将更新完的rij存入对应的位置,方便后面要用
- r1 = sparse(ii,jj,sr1,m,n);
- %vertical step第二步目的更新qij
- Pr0_v = full(real(exp(sum(spfun('log',r0),1))));
- Pr0 = H * spdiags(Pr0_v(:),0,n,n);
- sPr0 = full(Pr0(indx));
- Q0 = full(sum(sparse(ii,jj,sPr0.*sff0,m,n),1))';
- sq0 = sPr0.*sff0./sr0;
- Pr1_v = full(real(exp(sum(spfun('log',r1),1))));
- Pr1 = H * spdiags(Pr1_v(:),0,n,n);
- sPr1 = full(Pr1(indx));
- Q1 = full(sum(sparse(ii,jj,sPr1.*sff1,m,n),1))';
- sq1 = sPr1.*sff1./sr1;
- sqq = sq0+sq1;
- sq0 = sq0./sqq;
- sq1 = sq1./sqq;
- %tentative decoding伪后验概率(判决信息)
- QQ = Q0+Q1;
- Q0 = Q0./QQ;
- Q1 = Q1./QQ;
- x_hat = (sign(Q1-Q0)+1)/2;%判决输出
- if rem(H*x_hat,2) == 0, success = 1;
- break;
- end
- end