探秘基带算法:从原理到5G时代的通信变革【二】Viterbi解码
文章目录
- 二、关键算法原理剖析
- 2.1 Viterbi 解码
- 2.1.1 卷积码与网格图基础
- **卷积码**
- **网格图**
- **生成多项式**
- **理想情况下解码过程**
- 2.1.2 Viterbi 算法核心思想
- 2.1.3 路径度量与状态转移机制
- 2.1.4 算法流程与关键步骤详解
- 2.1.5 译码算法举例与复杂度分析
- 2.1.6 算法代码示例
本博客为系列博客,主要讲解各基带算法的原理与应用,包括:viterbi解码、Turbo编解码、Polar编解码、CORDIC算法、CRC校验、FFT/DFT、QAMtiaozhi/解调、QPSK调制/解调。其他博客链接如下:
- 探秘基带算法:从原理到5G时代的通信变革【一】引言
- 探秘基带算法:从原理到5G时代的通信变革【二】Viterbi解码
- 探秘基带算法:从原理到5G时代的通信变革【三】Turbo 编解码
- 探秘基带算法:从原理到5G时代的通信变革【四】Polar 编解码(一)
- 探秘基带算法:从原理到5G时代的通信变革【四】Polar 编解码(二)
- 探秘基带算法:从原理到5G时代的通信变革【五】CORDIC算法
- 探秘基带算法:从原理到5G时代的通信变革【六】CRC 校验
- 探秘基带算法:从原理到5G时代的通信变革【七】FFT/DFT
- 探秘基带算法:从原理到5G时代的通信变革【八】QAM 调制 / 解调
- 探秘基带算法:从原理到5G时代的通信变革【九】QPSK调制/解调
- 探秘基带算法:从原理到5G时代的通信变革【十】基带算法应用与对比
二、关键算法原理剖析
2.1 Viterbi 解码
2.1.1 卷积码与网格图基础
卷积码
在深入探讨 Viterbi 解码算法之前,有必要先了解其赖以依存的卷积码和网格图的基本概念。卷积码作为一种重要的纠错编码方式,与分组码有所不同,它并非将输入数据划分为独立的分组进行编码,而是通过将输入数据与编码器的状态进行卷积运算来生成输出码字。这一过程中,编码器的状态会随着输入数据的变化而不断更新,使得输出码字不仅与当前输入数据相关,还与之前的输入数据存在联系,从而能够利用历史信息来提高纠错能力。
以一个简单的 (2, 1, 3) 卷积码编码器为例,其中 “2” 表示输出码字的位数,“1” 表示输入数据的位数,“3” 表示约束长度。该编码器由两个移位寄存器和一些逻辑门组成,输入数据在时钟信号的驱动下依次进入移位寄存器,同时与移位寄存器中的状态进行逻辑运算,生成两个输出比特,形成输出码字。这种编码方式使得前后码字之间存在着紧密的相关性,为后续的解码提供了更多的信息。
在初始时刻,假设寄存器状态为(0,0,0)。 在t1时刻,输入1,则寄存器状态变为(1,0,0),相当于把初始寄存器状态(0,0,0)中从左往右第三个0“挤”掉了。 在t2时刻,输入0,则寄存器状态变为(0,1,0),相当于把t1时刻寄存器状态(1,0,0)中从左往右第三个0“挤”掉了。 在t3时刻,输入1,则寄存器状态变为(1,0,1),相当于把t2时刻寄存器状态(0,1,0)中从做往右第三个0“挤”掉了。
接着需要两个冲洗比特(连续输入两个0),用以清空寄存器。 在t4时刻,输入0,则寄存器状态从t3时刻的(1,0,1)变为(0,1,0),相当于把t3时刻寄存器状态(1,0,1)中从做往右第三个1“挤”掉了。 在t5时刻,输入0,则寄存器状态从t4时刻的(0,1,0)变为(0,0,1),相当于把t4时刻寄存器状态(0,1,0)中从做往右第三个0“挤”掉了。
**为什么用两个冲洗比特就可以达到清空寄存器的目的呢?为什么不是三个呢?毕竟再输入一个0,寄存器状态才恢复为初始状态(0,0,0)呀。**其实,输入两个就达到了清空寄存器的目的了,试想一下,如果在t6时刻输入一个1,这时候寄存器状态就从(0,0,1)变为(1,0,0),t5时刻的寄存器状态中的1被“挤”掉了,也就是这个1被舍弃掉了。换言之,这个1是没用的,所以两个冲洗比特足矣,只需要把寄存器状态变为(0,0,X)的形式即可,其中,X可为0或1。不管X为0还是1,都对下一个输入的寄存器状态没有影响。在下一个输入进来的时候,X都会被舍弃掉。
图:卷积编码过程示意
卷积编码是有限状态机器件。只有有限个状态机制,状态提供了有关过去序列过程以及一组将来可能输出序列的限制,即下一状态总是受到前一状态的限制。
图:有限状态机示意
网格图
为了更直观地展示卷积码码字之间的相关性,引入了网格图的概念。网格图是一种按时间顺序排列的状态结点矩阵,每一列代表当前时刻的所有可能状态,最左侧第一列代表初始状态(t = 0),随着时间的推移,从左至右依次表示后续时刻的状态。在网格图中,从一个状态到另一个状态的转移用线段表示,这些线段被称为分支,每个分支都对应着一个输入比特和一个输出码字。
给定输入数据序列,根据网格图,可以方便的得到编码输出序列。虚线代表输入比特为1,实线代表输入比特为0。例如,当输入数据序列为1 1 0 1 1时,可根据网格图得到其对应的编码输出序列为11 01 01 00 01,如下图所示:
图:(2, 1, 3) 卷积码的网格图
一个(n,k,K)卷积编码器由Kk级移位寄存器和n个模2加法器(输出发生器)组成。编码输出的n比特不仅取决于正在移入的k比特,还与这之前输入的K-1个k位有关。所以卷积编码器是有“记忆”的。
图:(n,k,K)卷积编码器
参考链接:卷积码(Convolutional Code) - 知乎
生成多项式
卷积码是一种重要的信道编码技术,其核心原理是通过线性移位寄存器对输入信息序列进行卷积运算,生成具有记忆特性的冗余码元。生成多项式是描述卷积码编码规则的核心数学工具,通常用一组二进制系数向量表示寄存器抽头连接关系。例如,约束长度K=3的(7,5)卷积码,其生成多项式可表示为八进制数(7,5),对应二进制g₁=111和g₂=101,分别代表两组模2加法器的连接方式。每个多项式中的"1"表示寄存器对应位置参与模2加法运算,"0"则表示断开连接。
在编码过程中,信息比特通过移位寄存器逐位输入,寄存器状态与生成多项式共同决定输出码字。对于码率R=1/2的典型结构,每个输入比特会生成两个输出比特: c 1 = m j ⊕ m j − 1 ⊕ m j − 2 c₁= mⱼ⊕mⱼ₋₁⊕mⱼ₋₂ c1=mj⊕mj−1⊕mj−2, c 2 = m j ⊕ m j − 2 c₂= mⱼ⊕mⱼ₋₂ c2=mj⊕mj−2。这种线性组合赋予卷积码连续的关联特性,使其纠错能力不仅依赖当前输入,还与前面K-1个历史比特相关。约束长度K作为关键参数,决定了编码器的记忆深度和状态复杂度,通常K值增大会提升纠错性能,但同时也增加解码复杂度。
理想情况下解码过程
卷积码的编码过程与网格图中的一条路径对应,即接收端接收到的序列的编码过程与网格图中的路径一一对应。当序列长度为K时,网格中有2K条不同的路径和编码器的2K种输入序列对应。在网格图中,每个状态转移的过程中都会输出编码码字。译码器建立和编码器相同的网格图, 据此查询可得到解码码流。
理想状况下,假设在传输过程中没有错误发生,在接收端,根据已经存在的网格图很容易解码得到原始比特流。
以tail-bits卷积码为例,假如接收端收到的比特流为’1101010001’, 先按2 bit宽度分组:11 01 01 00 01,解码过程如下:
-
从t=0的初始状态开始,tail-bits编码器的初始状态为’00’
-
查询输出为11时的转移路径,如下图粗线标志,此时编码器输入bit为1才能走该转移路径,因此可得到解码的第一bit:1
-
t=1时,状态为‘10’,根据接收到的第二组‘01’,查询从该状态出发的两条转移路径
-
只有输入为1时,才能得到‘01’的输出,因此可得到此刻的转移路径,把其他无关路径删除。因此解码得到第二比特为1.
-
以此类推,可在网格图上得到完整的状态转移路径及所有解码数据:11011.
现实中不会存在这种理想状态,信号经过复杂信道后均会引入不同程度错误,因此在接收端不会真正使用这种解码算法。
参考链接:维特比译码器(Viterbi Decoder)硬件架构(二)–卷积码解码算法_trellis diagram-CSDN博客
2.1.2 Viterbi 算法核心思想
Viterbi 算法是由 Andrew J. Viterbi 在 1967 年提出的,是基于卷积码网格图的最大似然译码算法。最大似然译码是把已接收序列与所有可能的发送序列进行比较,选出码距最小的序列作为发送序列。对于(n,k,K)卷积码,当发送 K 组信息比特时,网格图上可能有 2kL 个发送序列,译码器需存储并比较这些序列以找到码距最小的序列,但当传信率和信息组数 K 较大时,译码器难以实现。Viterbi 算法对最大似然解码进行简化,不是一次性比较所有可能的路径,而是接收一段、计算和比较一段,选择有最大似然可能的码段,使整个码序列具有最大似然值,其译码过程类似动态规划。
几个概念:
**分支度量(BM, Branch Metric)**是对于网格中每个路径而言,表示其对应的输出码字与接收到的码字之间的差距。分支度量计算的是发射和接收内容之间的“距离”,它是为网格中的每条分支路径定义的。在硬判决译码中,给出一组已经数字化的接收监督比特,分支度量就是监督比特预测值和接收监督比特之间的汉明距离。下图展示了一个示例,其中接收的位为00,对于每个状态转移,分支路径上的数字显示该转移的分支度量。其中有两条路径的分支量度为0,对应于汉明距离为0的唯一状态和转移关系,其他非0分支量度对应于存在位错误的情况。
图:分支度量计算
参考链接:
[Viterbi Decoding of Convolutional Codes](https://web.mit.edu/6.02/www/s2012/handouts/8.pdf#:~:text=This chapter describes an elegant and efficient method,and encoding we described in the previous chapter.)
**路径度量(PM,Path Metric)**是针对网格中每个状态节点,对应所有到达该节点所走过的最有可能路径的分支度量值的累计结果。对于硬判决解码,“最有可能”是指在计算从初始状态到当前状态之间的所有可能路径度量后,取汉明距离最小的那条。具有最小汉明距离的路径使误码最小化,并且当误码率较低时具有最大似然度。
Viterbi 算法作为一种基于动态规划的最优路径搜索算法,其核心思想是在卷积码对应的网格图中,按照时间顺序逐步搜索出一条最优路径,这条路径所对应的输入序列即为最有可能的原始信息序列。在搜索过程中,算法会根据当前接收到的符号以及状态转移概率,计算出到达每个状态的最优路径度量值,并保留这些最优路径。维特比算法的关键点在于,接收机可以使用分支度量和先前计算的状态路径度量递推地计算当前状态的路径度量。
以通信系统中的信号传输为例,假设发送端通过卷积码编码器将原始信息编码后发送出去,信号在传输过程中受到噪声干扰,接收端接收到的是带有噪声的信号序列。此时,Viterbi 算法在网格图中从初始状态开始,对于每个时刻的每个状态,计算从所有可能的前一状态转移到该状态的路径度量值。路径度量值通常反映了接收序列与假设路径的输出序列之间的差异程度,差异越小,路径度量值越小。
例如,在硬判决译码中,常采用汉明距离来计算路径度量值,即比较接收序列和假设路径输出序列对应比特位不同的个数;在软判决译码中,多采用欧几里得距离,它考虑了接收信号的幅度等信息,能更准确地反映信号之间的差异。在计算出所有可能路径的度量值后,算法选择其中最小的度量值对应的路径作为到达该状态的幸存路径,也就是当前最优路径,并记录下这条路径的相关信息,如路径的前一状态等。随着时间的推进,算法不断重复上述计算和选择过程,直到处理完接收序列的最后一个符号。此时,在所有状态中选择具有最小路径度量值的状态作为最终状态,然后从该最终状态开始,沿着之前记录的幸存路径回溯到初始状态,从而得到最有可能的原始信息序列。下图展示了 Viterbi 算法在网格图中搜索最优路径的过程,从图中可以看到算法如何在每个时刻选择最优路径,最终找到从初始状态到最终状态的最优路径。
图:Viterbi 算法在网格图中搜索最优路径
2.1.3 路径度量与状态转移机制
路径度量是 Viterbi 算法中衡量路径优劣的关键指标,它定义为路径上所有分支的度量值之和。分支度量值则根据接收符号与预期符号之间的差异来计算,在硬判决情况下,常使用汉明距离作为分支度量值,即两个等长比特序列中对应比特不同的个数。例如,接收序列为 1011,假设路径的输出序列为 1101,那么它们之间的汉明距离为 3,因为有 3 个比特位不同。在软判决中,由于考虑了信号的幅度等更多信息,常采用欧几里得距离作为分支度量值。欧几里得距离是指在多维空间中,两个点之间的直线距离,在通信中,可将接收信号和假设路径输出信号看作多维空间中的点,通过计算它们之间的欧几里得距离来衡量差异。
计算路径度量
- 时刻i的路径度量:假设接收机在时刻i已计算好每个状态s的路径量度PM[s,i] ,卷积码编码约束度为K时,状态数为2K-1。在硬判决译码中,PM[s,i]是接收监督比特与最可能发送消息比较得到的差错比特总数,常将状态“00”作为起始状态。
- 最可能状态的判断:在时刻i的所有可能状态里,具有最小路径度量的状态就是最可能的状态。若存在多个具备最小路径度量的状态,那么它们成为最可能状态的可能性相等。
- 时刻i+1的路径度量确定方法:时刻i + 1的状态s由时刻i的两种可能状态α和β转移而来,α和β仅取决于卷积码的编码约束度,与生成多项式无关。比如在下图的例子中,状态00对应的α = 00,β = 01;状态01对应的α = 10,β = 11 。确定时刻i+1下每个状态s的路径度量 P M [ s , i + 1 ] PM[s,i + 1] PM[s,i+1]时,要考虑从时刻i的α或β状态转移过来产生的误码情况。例如在下图,时刻i+1到达状态01,存在两种情况:
- 若发射机在时刻i位于状态10,且第i个信息比特为0,发射机输出监督位11,接收比特为00,产生2位误码,此时新的状态路径度量 P M [ 01 , i + 1 ] = P M [ 10 , i ] + 2 PM[01,i + 1]=PM[10,i]+2 PM[01,i+1]=PM[10,i]+2。
- 若发射机在时刻i位于状态11,且第i个信息比特为0,发射机输出监督位01,接收比特为00,产生1位误码,此时新的状态路径度量$PM[01,i + 1]=PM[11,i]+1 $。
- 总结可得计算路径度量的公式: $PM[s,i + 1] = \min(PM[\alpha,i]+BM[\alpha \to s],PM[\beta,i]+BM[\beta \to s]) $
- 在译码算法中,记录最小汉明距离对应的路径很重要,后续要通过跟踪该路径,从最终状态遍历到最初状态,再将估计比特倒序,从而得到最可能的消息。
图:分支度量计算
状态转移是指在网格图中从一个状态转移到另一个状态的过程,每个状态转移都对应着一个分支度量值。在卷积码的编码过程中,状态转移是由输入比特决定的。例如,在一个 (2, 1, 3) 卷积码中,当前状态为 00,若输入比特为 0,则状态转移到 00,输出码字为 00;若输入比特为 1,则状态转移到 10,输出码字为 11。在 Viterbi 解码过程中,当接收到一个符号时,需要根据当前状态和所有可能的输入比特,计算出从当前状态转移到下一个状态的分支度量值。假设当前状态为 10,接收到的符号为 11,当输入比特为 0 时,从状态 10 转移到状态 01,对应的输出码字为 01,此时计算接收符号 11 与输出码字 01 的分支度量值;当输入比特为 1 时,从状态 10 转移到状态 11,对应的输出码字为 11,再计算接收符号 11 与输出码字 11 的分支度量值。然后,将这些分支度量值与之前到达当前状态的路径度量值相加,得到到达下一个状态的路径度量值。通过比较不同输入比特对应的路径度量值,选择最小的路径度量值对应的路径作为幸存路径,更新路径度量和路径历史。这样,随着解码过程的推进,不断根据状态转移和路径度量的计算来确定最优路径。
2.1.4 算法流程与关键步骤详解
Viterbi 解码算法的流程主要包括初始化、递推、终止和回溯四个关键步骤。
初始化阶段,需要确定所有状态在时刻 t = 0 的路径度量值。通常将起始状态的路径度量设为 0,表示从起始状态出发没有任何差异;而对于其他所有状态,路径度量则设为无穷大,这意味着在初始阶段,这些状态是不可能直接到达的,只有通过后续的状态转移和路径度量计算,才有可能更新这些状态的路径度量值。同时,还需要初始化路径历史,记录每个状态的前一状态信息,以便在回溯阶段能够找到最优路径。
递推阶段是 Viterbi 算法的核心部分,在每个时刻 t 和每个状态 s,都要进行一系列复杂的计算。
- 首先是分支度量计算,对于每个状态和每个可能的输入比特,计算从当前状态转移到下一状态的分支度量。以硬判决为例,通过比较接收序列中当前时刻的符号与假设分支输出的符号,计算它们之间的汉明距离作为分支度量值。
- 接着进行**加 - 比较 - 选择(ACS)**操作,将所有进入该状态的路径的度量值(即前一状态的路径度量值加上当前分支度量值)相加,并与当前状态的幸存路径度量进行比较。选择具有最小度量值的路径作为新的幸存路径,更新路径度量和路径历史。例如,在某一时刻,有两条路径可以到达状态 s,路径 1 从状态 s1 转移过来,路径 1 的前一状态 s1 的路径度量值为 5,当前分支度量值为 3;路径 2 从状态 s2 转移过来,路径 2 的前一状态 s2 的路径度量值为 4,当前分支度量值为 2。通过计算,路径 1 到达状态 s 的路径度量值为 5 + 3 = 8,路径 2 到达状态 s 的路径度量值为 4 + 2 = 6,比较后发现路径 2 的度量值更小,所以选择路径 2 作为到达状态 s 的幸存路径,更新状态 s 的路径度量为 6,并记录路径 2 的前一状态为 s2。在这个过程中,需要不断地存储和更新每个状态和时刻的幸存路径历史信息,以便后续回溯使用。
终止阶段发生在达到接收序列的末尾时,此时需要选择具有最小路径度量的状态作为最终状态。这个最终状态代表了整个解码过程中最有可能的结束状态,从这个状态开始回溯,就能够得到最有可能的原始信息序列。
回溯阶段,从最终状态开始,沿着之前记录的幸存路径历史信息回溯到初始状态。在回溯过程中,根据每个状态记录的前一状态信息,逐步确定最优路径上每个状态的输入比特,从而得到最有可能的原始信息序列。例如,最终状态的前一状态是 s3,s3 的前一状态是 s5,以此类推,直到回溯到初始状态。在回溯过程中,根据状态转移的对应关系,确定每个状态转移时的输入比特,将这些输入比特依次排列,就得到了原始信息序列。下图展示了 Viterbi 解码算法的流程,从图中可以清晰看到各个步骤之间的关系和执行顺序。
图:Viterbi 解码算法流程
2.1.5 译码算法举例与复杂度分析
为了更直观地理解 Viterbi 译码算法的工作过程,以一个简单的 (2, 1, 3) 卷积码为例进行说明。假设发送端发送的信息序列为 1011,经过卷积码编码器编码后,通过噪声信道传输到接收端,接收端接收到的序列为 11010100(这里只是示例,简单理解原理即可,不用纠结细节)。
首先,构建 (2, 1, 3) 卷积码的网格图,确定初始状态的路径度量为 0,其他状态路径度量为无穷大。在 t = 1 时刻,从初始状态 00 出发,若输入比特为 0,转移到状态 00,输出码字为 00,与接收序列的第一个符号 11 的汉明距离为 2;若输入比特为 1,转移到状态 10,输出码字为 11,与接收序列的第一个符号 11 的汉明距离为 0。此时,选择汉明距离为 0 的路径(即输入比特为 1,转移到状态 10 的路径)作为幸存路径,更新状态 10 的路径度量为 0。
在 t = 2 时刻,从状态 10 出发,若输入比特为 0,转移到状态 01,输出码字为 01,与接收序列的第二个符号 01 的汉明距离为 0;若输入比特为 1,转移到状态 11,输出码字为 11,与接收序列的第二个符号 01 的汉明距离为 2。比较两条路径的度量值,选择汉明距离为 0 的路径(即输入比特为 0,转移到状态 01 的路径)作为幸存路径,更新状态 01 的路径度量为 0(0 + 0 = 0)。
在 t = 3 时刻,从状态 01 出发,若输入比特为 0,转移到状态 10,输出码字为 10,与接收序列的第三个符号 10 的汉明距离为 0;若输入比特为 1,转移到状态 11,输出码字为 00,与接收序列的第三个符号 10 的汉明距离为 2。比较两条路径的度量值,选择汉明距离为 0 的路径(即输入比特为 0,转移到状态 10 的路径)作为幸存路径,更新状态 10 的路径度量为 0(0 + 0 = 0)。
在 t = 4 时刻,从状态 10 出发,若输入比特为 0,转移到状态 01,输出码字为 01,与接收序列的第四个符号 00 的汉明距离为 1;若输入比特为 1,转移到状态 11,输出码字为 11,与接收序列的第四个符号 00 的汉明距离为 2。比较两条路径的度量值,选择汉明距离为 1 的路径(即输入比特为 0,转移到状态 01 的路径)作为幸存路径,更新状态 01 的路径度量为 1(0 + 1 = 1)。
经过这四个时刻的计算,完成了对接收序列的 Viterbi 译码。在整个过程中,通过不断比较不同路径的汉明距离,选择最优的幸存路径,从而确定最有可能的发送信息序列。
最终,根据各个时刻的幸存路径回溯得到译码后的信息序列。回溯过程如下:从最后时刻的状态 01 开始,因为在 t = 4 时刻,是从状态 10 输入 0 转移到状态 01 的,所以得到最后一个译码比特为 0;在 t = 3 时刻,是从状态 01 输入 0 转移到状态 10 的,得到倒数第二个译码比特为 0;在 t = 2 时刻,是从状态 10 输入 0 转移到状态 01 的,得到倒数第三个译码比特为 0;在 t = 1 时刻,是从状态 00 输入 1 转移到状态 10 的,得到第一个译码比特为 1。所以回溯得到的译码信息序列为 1000,与原始发送的信息序列 1011 存在一定差异,这是由于噪声信道对传输信号造成了干扰,使得接收序列发生了误码。但 Viterbi 译码算法在一定程度上能够纠正部分错误,提高通信的可靠性。
然而,Viterbi 译码算法的复杂度随约束长度的增加而指数增长。对于 (n, k, L) 卷积码,其中 L 为约束长度,在每个时刻,每个状态都有 2k 条可能的路径需要计算和比较。随着时间的推进,路径数量会迅速增加。例如,当约束长度 L = 3 时,在每个时刻,每个状态需要比较 21 * 23-1 = 8 条路径;当约束长度 L = 5 时,每个状态需要比较 21 * 25-1 = 32 条路径。这种指数增长的复杂度使得在实际应用中,当约束长度较大时,Viterbi 译码算法的计算量和存储需求会变得非常庞大,对硬件资源和计算能力提出了很高的要求。为了降低复杂度,在实际应用中常采用一些优化技术,如量化技术减少计算精度要求,剪枝技术去除一些不太可能的路径等,以提高算法的可行性和效率。
对于(n, k, L)卷积码的维特比译码算法,其复杂度主要体现在路径度量计算和幸存路径存储两方面:
- 计算复杂度:在每个时刻,每个状态有 2 k 2^k 2k条可能路径需要计算和比较。由于存在 2 L − 1 2^{L - 1} 2L−1个状态,所以每个时刻的计算量为 2 k × 2 L − 1 = 2 k + L − 1 2^k\times2^{L - 1}=2^{k + L - 1} 2k×2L−1=2k+L−1。假设译码长度为 N N N,则总的计算复杂度为 O ( N × 2 k + L − 1 ) O(N\times2^{k + L - 1}) O(N×2k+L−1),随着约束长度 L L L增加,计算量呈指数增长。
- 存储复杂度:需存储每个状态的幸存路径和路径度量值。每个状态有 2 L − 1 2^{L - 1} 2L−1个,每个幸存路径长度最大为 N N N,所以存储路径的空间复杂度为 O ( N × 2 L − 1 ) O(N\times2^{L - 1}) O(N×2L−1) ;存储路径度量值的空间复杂度为 O ( 2 L − 1 ) O(2^{L-1}) O(2L−1)。总的存储复杂度也是随着约束长度 L L L的增加呈指数增长。
维特比译码算法的复杂度较高,尤其在约束长度较大时,对计算资源和存储资源要求苛刻,这也是实际应用中需要优化技术来降低复杂度的原因。如果你还想了解这些优化技术的具体实现细节,欢迎随时提问。
2.1.6 算法代码示例
#include <iostream>
#include <vector>
#include <climits>
#include <algorithm>// 计算汉明距离
int hammingDistance(int a, int b) {int xorResult = a ^ b;int distance = 0;while (xorResult) {distance += xorResult & 1;xorResult >>= 1;}return distance;
}// (2, 1, 3) 卷积码编码器函数
std::vector<int> convolutionalEncoder(const std::vector<int>& input) {std::vector<int> output;int state = 0;for (int bit : input) {int bit1 = (bit + ((state >> 1) & 1) + (state & 1)) % 2;int bit2 = (bit + ((state >> 2) & 1) + (state & 1)) % 2;output.push_back(bit1);output.push_back(bit2);state = ((state << 1) | bit) & 7;}return output;
}// Viterbi 译码函数
std::vector<int> viterbiDecoder(const std::vector<int>& received) {int numStates = 8; // (2, 1, 3) 卷积码有 2^3 = 8 个状态int numSteps = received.size() / 2;std::vector<std::vector<int>> pathMetrics(numSteps + 1, std::vector<int>(numStates, INT_MAX));std::vector<std::vector<int>> survivorPaths(numSteps + 1, std::vector<int>(numStates, -1));// 初始化pathMetrics[0][0] = 0;for (int t = 1; t <= numSteps; ++t) {for (int state = 0; state < numStates; ++state) {int prevState1 = (state << 1) & 7;int prevState2 = ((state << 1) | 1) & 7;int inputBit1 = 0;int output1 = ((inputBit1 + ((prevState1 >> 1) & 1) + (prevState1 & 1)) % 2) * 2 +((inputBit1 + ((prevState1 >> 2) & 1) + (prevState1 & 1)) % 2);int receivedSymbol = received[2 * (t - 1)] * 2 + received[2 * (t - 1) + 1];int metric1 = pathMetrics[t - 1][prevState1] + hammingDistance(output1, receivedSymbol);int inputBit2 = 1;int output2 = ((inputBit2 + ((prevState2 >> 1) & 1) + (prevState2 & 1)) % 2) * 2 +((inputBit2 + ((prevState2 >> 2) & 1) + (prevState2 & 1)) % 2);int metric2 = pathMetrics[t - 1][prevState2] + hammingDistance(output2, receivedSymbol);if (metric1 < metric2) {pathMetrics[t][state] = metric1;survivorPaths[t][state] = prevState1;} else {pathMetrics[t][state] = metric2;survivorPaths[t][state] = prevState2;}}}// 回溯std::vector<int> decoded(numSteps);int finalState = std::min_element(pathMetrics[numSteps].begin(), pathMetrics[numSteps].end()) - pathMetrics[numSteps].begin();for (int t = numSteps; t > 0; --t) {int prevState = survivorPaths[t][finalState];decoded[t - 1] = (finalState ^ (prevState << 1)) & 1;finalState = prevState;}return decoded;
}int main() {std::vector<int> received = {1, 1, 0, 1, 0, 1, 0, 0};std::vector<int> decoded = viterbiDecoder(received);std::cout << "译码后的信息序列: ";for (int bit : decoded) {std::cout << bit;}std::cout << std::endl;return 0;
}
相关文章:
探秘基带算法:从原理到5G时代的通信变革【二】Viterbi解码
文章目录 二、关键算法原理剖析2.1 Viterbi 解码2.1.1 卷积码与网格图基础**卷积码****网格图****生成多项式****理想情况下解码过程** 2.1.2 Viterbi 算法核心思想2.1.3 路径度量与状态转移机制2.1.4 算法流程与关键步骤详解2.1.5 译码算法举例与复杂度分析2.1.6 算法代码示例…...
MAC 本地搭建部署 dify(含 github访问超时+Docker镜像源拉取超时解决方案)
目录 一、什么是 dify? 二、安装 docker 1. 什么是 docker? 2. docker下载地址 三、安装 dify 1. dify下载地址 2.可能遇到问题一: github访问超时 3.下载后完成解压 4.进入到 cmd 终端环境,执行下面三个命令 5.可能遇到…...
扫描纸质文件转pdf---少页数+手机+电脑协作
针对手机上扫描软件扫描文件转pdf要收费的问题,提供一种在页数较少时的免费替代方案 。 实现方法:手机软件的免费功能将文件扫描并保存为图片电脑端在word中将图片拼成文档word转pdf 1.借助于“扫描全能王”APP可以免费扫描文件为图片的功能࿰…...
[KEIL]单片机技巧 01
1、查看外设寄存器的值 配合对应的芯片开发手册以查看寄存器及其每一位的意义,可以解决90%以上的单纯的片内外设bug,学会如何通过寄存器的值来排外设上的蛊是嵌入式开发从小白到入门的重要一步,一定要善于使用这个工具,而不是外设…...
B站上优质的Java和SpringBoot相关视频教程
Java和SpringBoot相关视频教程 Java大师课 【Java】上部:Java Master Class大师课教程【Java】下部:Java Master Class大师课教程 Spring Boot & React 【Spring Boot & React】Spring Boot和React教程 Spring Boot 【Spring Boot】Spring…...
Spring学习笔记04:spring mvc和Spring Boot之间是什么关系?
Spring MVC 是什么? 想象你开了一家餐厅,顾客(用户)点菜、服务员传话、厨师做菜、最后服务员上菜。Spring MVC 就是规定这套流程的“餐厅管理规则”,专门用于处理网页请求(HTTP)和响应。 核心…...
【JavaEE】线程安全
【JavaEE】线程安全 一、引出线程安全二、引发线程安全的原因三、解决线程安全问题3.1 synchronized关键字(解决修改操作不是原子的)3.1.1 synchronized的特性3.1.1 synchronized的使用事例 3.2 volatile 关键字(解决内存可见性) …...
Centos7源码编译安装Sqlite最新版本
下载源码 https://www.sqlite.org/download.html 复制下载链接,然后用 wget 下载 wget https://www.sqlite.org/2025/sqlite-autoconf-3490100.tar.gz 解压缩编译安装 tar -zxf sqlite-autoconf-3490100.tar.gz cd sqlite-autoconf-3490100 ./configure --prefi…...
C++(蓝桥杯常考点)
前言:这个是针对于蓝桥杯竞赛常考的C内容,容器这些等下棋期再讲 C 在DEVC中注释和取消注释的方法:ctrl/ ASCII值(常用的): A-Z:65-90 a-z:97-122 0-9:48-57 换行/n:10科学计数法:eg:…...
java后端开发day24--阶段项目(一)
(以下内容全部来自上述课程) GUI:Graphical User Interface 图形用户接口,采取图形化的方式显示操作界面 分为两套体系:AWT包(有兼容问题)和Swing包(常用) 拼图小游戏…...
iterm2更新后主题报错
报错 .oh-my-zsh/themes/agnoster.zsh-theme:307: parse error near <<<。方法1:更新Oh My Zsh主题(以agnoster为例) 适用场景:使用Oh My Zsh自带主题(如agnoster)时出现语法错误。 备份当前主题…...
C++编程:常见内置算法
C中算法主要是由头文件<algorithm><functional><numeric>组成。 <algorithm>是所有STL头文件中最大的一个算法头文件,范围涉及到比较、 交换、查找、遍历操作、复制、修改等等。 <numeric>体积很小,只包括几个在序列上面…...
【开源-开源C++框架boost和poco的对比】
从各个维度对 Boost 和 Poco 进行对比分析 Boost 和 Poco 的对比 1. 核心定位 Boost: 定位: 高性能、通用性、标准化。特点: 提供底层、高度灵活的模块,许多库已被纳入 C 标准。适用场景: 需要高性能、精细控制的场景(如游戏开发、高频交易、科学计算&a…...
【Web安全方向编程语言学习顺序推荐】
Web安全方向编程语言学习顺序推荐 1. HTML/CSS/JavaScript(基础层)2. Python(工具与自动化层)3. SQL(数据库交互层)4. PHP(传统Web漏洞研究层)5. Ruby(红队工具链层&…...
h5 IOS端渐变的兼容问题 渐变实现弧形效果
IOS端使用渐变的时候有兼容问题 以下是问题效果,图中黑色部分期望的效果应该是白色的。但是ios端是下面的样子…… 安卓pc 支持: background-image: radial-gradient(circle 40rpx at 100% 0, #f3630c 40rpx, rgb(255, 255, 255) 50%);安卓pc ios支持…...
【朝夕教育】《鸿蒙原生应用开发从零基础到多实战》004-TypeScript 中的泛型
标题详情作者简介愚公搬代码头衔华为云特约编辑,华为云云享专家,华为开发者专家,华为产品云测专家,CSDN博客专家,CSDN商业化专家,阿里云专家博主,阿里云签约作者,腾讯云优秀博主&…...
【SQL】MySQL中的字符串处理函数:concat 函数拼接字符串,COALESCE函数处理NULL字符串
MySQL中的字符串处理函数:concat 函数 一、concat ()函数 1.1、基本语法1.2、示例1.3、特殊用途 二、COALESCE()函数 2.1、基本语法2.2、示例2.3、用途 三、进阶练习 3.1 条件和 SQL 语句3.2、解释 一、concat &…...
doris:Paimon Catalog
使用须知 数据放在 hdfs 时,需要将 core-site.xml,hdfs-site.xml 和 hive-site.xml 放到 FE 和 BE 的 conf 目录下。优先读取 conf 目录下的 hadoop 配置文件,再读取环境变量 HADOOP_CONF_DIR 的相关配置文件。当前适配的 Paimon 版本为 0…...
批量提取 Word 文档中的图片
在 Word 文档中,我们可以插入图片、文本、链接等各种各样的资源。在某些场景下我们需要提取这些信息,比如我们需要提取 Word 文档中的图片,将每一个 Word 文档中的图片都提取出来放到一个单独的文件夹中,那么我们应该怎么做呢&…...
Redis 的几个热点知识
前言 Redis 是一款内存级的数据库,凭借其卓越的性能,几乎成为每位开发者的标配工具。 虽然 Redis 包含大量需要掌握的知识,但其中的热点知识并不多。今天,『知行』就和大家分享一些 Redis 中的热点知识。 Redis 数据结构 Redis…...
QT5 GPU使用
一、问题1 1、现象 2、原因分析 出现上图错误,无法创建EGL表面,错误=0x300b。申请不上native window有可能是缺少libqeglfs-mali-integration.so 这个库 3、解决方法 需要将其adb push 到小机端的/usr/lib/qt5/plugins/egldeviceintegrati…...
【分享】网间数据摆渡系统,如何打破传输瓶颈,实现安全流转?
在数字化浪潮中,企业对数据安全愈发重视,网络隔离成为保护核心数据的重要手段。内外网隔离、办公网与研发网隔离等措施,虽为数据筑牢了防线,却也给数据传输带来了诸多难题。传统的数据传输方式在安全性、效率、管理等方面暴露出明…...
sql-labs less5-8
Less-5 双注入 基于单引号的字符型注入,涉及二次查询注入 Less-6 双注入 基于双引号的字符型注入,涉及二次查询注入 Less-7 字符型注入 基于单引号变形注入之导入文件 Less-8 布尔盲注 不返回任何错误信息,通过布尔逻辑判断 以下…...
iOS开发之最新Demo上传Github步骤(2025.02.28)
前几年的两篇文章: 将项目Demo上传到Github上的操作步骤 常用知识之将Demo上传到Github上的操作步骤(2021.09) 新的操作步骤,需要将两篇文章结合进行,从而达到最终的结果。 一、最新操作步骤: 1、先按…...
注意力机制详解笔记 Attention is all I donot understand!
注意力机制好奇了太久,QKV知道是什么但是一直没搞懂为什么,这段时间终于眼一闭心一横摁头看了一天视频,3B1B大佬太强了!基于GPT看了三个视频,基本讲的toy model,没有讲“硬核”的如何训练和码代码ÿ…...
分布式锁—2.Redisson的可重入锁二
大纲 1.Redisson可重入锁RedissonLock概述 2.可重入锁源码之创建RedissonClient实例 3.可重入锁源码之lua脚本加锁逻辑 4.可重入锁源码之WatchDog维持加锁逻辑 5.可重入锁源码之可重入加锁逻辑 6.可重入锁源码之锁的互斥阻塞逻辑 7.可重入锁源码之释放锁逻辑 8.可重入锁…...
Ribbon实现原理
文章目录 概要什么是Ribbon客户端负载均衡 RestTemplate核心方法GET 请求getForEntitygetForObject POST 请求postForEntitypostForObjectpostForLocation PUT请求DELETE请求 源码分析类图关系 与Eureka结合重试机制 概要 什么是Ribbon Spring Cloud Ribbon是一个基于HTTP和T…...
游戏引擎学习第133天
仓库:https://gitee.com/mrxiao_com/2d_game_3 回顾并设定今天的主题 今天的任务是进一步优化背景资源的流式加载,尤其是在内存管理方面。昨天,我们实现了资源流式加载,让游戏在加载时可以动态地加载背景,而不是一开始就把所有资…...
DeepSeek搭配Excel,制作自定义按钮,实现办公自动化!
今天跟大家分享下我们如何将DeepSeek生成的VBA代码,做成按钮,将其永久保存在我们的Excel表格中,下次遇到类似的问题,直接在Excel中点击按钮,就能10秒搞定,操作也非常的简单. 一、代码准备 代码可以直接询问…...
deepseek+mermaid【自动生成流程图】
成果: 第一步打开deepseek官网(或百度版(更快一点)): 百度AI搜索 - 办公学习一站解决 第二步,生成对应的Mermaid流程图: 丢给deepseek代码,或题目要求 生成mermaid代码 第三步将代码复制到me…...
递归遍历目录 和 普通文件的复制 [Java EE]
递归遍历目录 首先 先列出当前目录所包含的内容 File[] files currentDir.listFiles();if (files null || files.length 0) {// 若是空目录或非法目录, 则直接返回return;} 然后 遍历列出的文件, 分情况两种讨论 for (File f: files) {// 加个日志, 方便查看程序执行情…...
安防监控/视频集中存储EasyCVR视频汇聚平台如何配置AI智能分析平台的接入?
EasyCVR安防视频监控平台不仅支持AI边缘计算智能硬件设备的接入,还能快速集成AI智能分析平台,接收来自智能分析平台或设备的AI告警信息,如烟火检测、周界入侵检测、危险区域闯入检测、安全帽/反光衣佩戴检测等。 本文将详细介绍如何在EasyCVR…...
React 之 Redux 第二十八节 学习目标与规划大纲及概要讲述
接下来 开始Redux 全面详细的文档输出,主要基于一下几个方面,欢迎大家补充指正 一、Redux 基础概念 为什么需要 Redux? 前端状态管理的挑战(组件间通信、状态共享) Redux 解决的问题:集中式、可预测的状态…...
五分钟快速学习优秀网站的HTML骨架布局设计
一.编写多级过滤脚本,在控制台执行copy方法进行提取: 过滤脚本脚本 // 在浏览器F12的控制台里,直接执行以下脚本 copy(document.documentElement.outerHTML// 一级过滤:移除动态内容.replace(/<script\b[^>]*>[\s\S]*?…...
神经网络AI原理回顾
长期记忆存储在大模型的参数权重中,不经过推理和编码无法读取,且必须依赖输入的提示,因为大模型不会无缘无故的自言自语,毕竟输入层是它唯一 与外界交互的窗口。 目前个性化大模型的局限就是训练成本过高,除非使用RAG&…...
常见webshell工具的流量特征
1、蚁剑 1.1、蚁剑webshell静态特征 蚁剑中php使用assert、eval执行;asp只有eval执行;在jsp使用的是Java类加载(ClassLoader),同时会带有base64编码解码等字符特征。 1.2、蚁剑webshell动态特征 查看流量分析会发现…...
探秘基带算法:从原理到5G时代的通信变革【四】Polar 编解码(一)
文章目录 2.3 Polar 编解码2.3.1 Polar 码简介与发展背景2.3.2 信道极化理论基础对称容量与巴氏参数对称容量 I ( W ) I(W) I(W)巴氏参数 Z ( W ) Z(W) Z(W)常见信道信道联合信道分裂信道极化 本博客为系列博客,主要讲解各基带算法的原理与应用,包括&…...
【计算机网络】考研复试高频知识点总结
文章目录 一、基础概念1、计算机⽹络的定义2、计算机⽹络的目标3、计算机⽹络的组成4、计算机⽹络的分类5、计算机⽹络的拓扑结构6、计算机⽹络的协议7、计算机⽹络的分层结构8、OSI 参考模型9、TCP/IP 参考模型10、五层协议体系结构 二、物理层1、物理层的功能2、传输媒体3、 …...
nio多线程版本
多线程多路复用 多线程NIO,,就是多个线程,每个线程上都有一个Selector,,,比如说一个系统中一个线程用来接收请求,,剩余的线程用来读写数据,,每个线程独立干自…...
Lua | 每日一练 (5)
💢欢迎来到张胤尘的技术站 💥技术如江河,汇聚众志成。代码似星辰,照亮行征程。开源精神长,传承永不忘。携手共前行,未来更辉煌💥 文章目录 Lua | 每日一练 (5)题目参考答案浅拷贝深拷贝使用场景…...
IO的概念和标准IO函数
作业: 1.使用标准IO函数,实现文件的拷贝 #include <stdio.h>int main(int argc, char *argv[]) {// 检查是否提供了源文件和目标文件if (argc ! 3) {printf("Usage: %s <source_file> <destination_file>\n", argv[0]);re…...
影刀RPA开发拓展--SQL常用语句全攻略
前言 SQL(结构化查询语言)是数据库管理和操作的核心工具,无论是初学者还是经验丰富的数据库管理员,掌握常用的 SQL 语句对于高效管理和查询数据都至关重要。本文将系统性地介绍最常用的 SQL 语句,并为每个语句提供详细…...
零信任架构和传统网络安全模式的
零信任到底是一个什么类型的模型?什么类型的思想或思路,它是如何实现的,我们要做零信任,需要考虑哪些问题? 零信任最早是约翰金德瓦格提出的安全模型。早期这个模型也是因为在安全研究上考虑的一个新的信任式模型。他最…...
【前端】HTML 备忘清单(超级详细!)
文章目录 入门hello.html注释 Comment段落 ParagraphHTML 链接Image 标签文本格式标签标题Section Divisions内部框架HTML 中的 JavaScriptHTML 中的 CSS HTML5 标签页面标题导航HTML5 TagsHTML5 VideoHTML5 AudioHTML5 RubyHTML5 kdiHTML5 progressHTML5 mark HTML 表格Table …...
React 中 useState 的 基础使用
概念:useState 是一个React Hook(函数),它允许我们向组件添加状态变量,从而影响组件的渲染结果。 本质:和普通JS变量不同的是,状态变量一旦发生变化,组件的视图UI也会跟着变化&…...
蓝桥杯单片机组第十二届省赛第二批次
前言 第十二届省赛涉及知识点:NE555频率数据读取,NE555频率转换周期,PCF8591同时测量光敏电阻和电位器的电压、按键长短按判断。 本试题涉及模块较少,题目不难,基本上准备充分的都能完整的实现每一个功能,并…...
React Native 原理
React Native 是一个跨平台移动应用开发框架,它允许开发者使用 JavaScript 和 React 来开发 iOS 和 Android 原生应用。React Native 的核心原理是通过 桥接(Bridge) 技术,使用 JavaScript 来控制原生组件,并将应用逻辑…...
C++简易贪食蛇项目
一.案例介绍 二.制作思路 三.墙模块 #include "wall.h" //初始化墙 void initWall() { for (int i 0; i < HEIGHT; i) { for (int j 0; j < WIDTH;j) { if (i 0 || j 0 || i HEIGHT - 1 || j WIDTH - 1) …...
C++蓝桥杯基础篇(七)
片头 嗨~小伙伴们,大家好!今天我们来一起学习蓝桥杯基础篇(七),学习相关字符串的知识,准备好了吗?咱们开始咯! 一、字符与整数的联系——ASCII码 每个常用字符都对应一个-128~127的…...
Sass基础
目录 什么是sass? Sass的安装 Sass的编译 Sass的语法: Sass的基本使用: 一、Sass变量: 二、嵌套语法: 三、import的使用: 四、mixin混入和include: 五、extend: 六、注释 七、if和if: 八、for: 总结: 什么是sas…...