探秘基带算法:从原理到5G时代的通信变革【二】Viterbi解码

news2025/4/22 3:37:50

文章目录

    • 二、关键算法原理剖析
      • 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调制/解调。其他博客链接如下:

  1. 探秘基带算法:从原理到5G时代的通信变革【一】引言
  2. 探秘基带算法:从原理到5G时代的通信变革【二】Viterbi解码
  3. 探秘基带算法:从原理到5G时代的通信变革【三】Turbo 编解码
  4. 探秘基带算法:从原理到5G时代的通信变革【四】Polar 编解码(一)
  5. 探秘基带算法:从原理到5G时代的通信变革【四】Polar 编解码(二)
  6. 探秘基带算法:从原理到5G时代的通信变革【五】CORDIC算法
  7. 探秘基带算法:从原理到5G时代的通信变革【六】CRC 校验
  8. 探秘基带算法:从原理到5G时代的通信变革【七】FFT/DFT
  9. 探秘基带算法:从原理到5G时代的通信变革【八】QAM 调制 / 解调
  10. 探秘基带算法:从原理到5G时代的通信变革【九】QPSK调制/解调
  11. 探秘基带算法:从原理到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都会被舍弃掉。

img

图:卷积编码过程示意

卷积编码是有限状态机器件。只有有限个状态机制,状态提供了有关过去序列过程以及一组将来可能输出序列的限制,即下一状态总是受到前一状态的限制。

img

图:有限状态机示意

网格图

为了更直观地展示卷积码码字之间的相关性,引入了网格图的概念。网格图是一种按时间顺序排列的状态结点矩阵,每一列代表当前时刻的所有可能状态,最左侧第一列代表初始状态(t = 0),随着时间的推移,从左至右依次表示后续时刻的状态。在网格图中,从一个状态到另一个状态的转移用线段表示,这些线段被称为分支,每个分支都对应着一个输入比特和一个输出码字。

给定输入数据序列,根据网格图,可以方便的得到编码输出序列。虚线代表输入比特为1,实线代表输入比特为0。例如,当输入数据序列为1 1 0 1 1时,可根据网格图得到其对应的编码输出序列为11 01 01 00 01,如下图所示:

img

图:(2, 1, 3) 卷积码的网格图

一个(n,k,K)卷积编码器由Kk级移位寄存器和n个模2加法器(输出发生器)组成。编码输出的n比特不仅取决于正在移入的k比特,还与这之前输入的K-1个k位有关。所以卷积编码器是有“记忆”的。

img

图:(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=mjmj1mj2 c 2 = m j ⊕ m j − 2 c₂= mⱼ⊕mⱼ₋₂ c2=mjmj2。这种线性组合赋予卷积码连续的关联特性,使其纠错能力不仅依赖当前输入,还与前面K-1个历史比特相关。约束长度K作为关键参数,决定了编码器的记忆深度和状态复杂度,通常K值增大会提升纠错性能,但同时也增加解码复杂度。

理想情况下解码过程

卷积码的编码过程与网格图中的一条路径对应,即接收端接收到的序列的编码过程与网格图中的路径一一对应。当序列长度为K时,网格中有2K条不同的路径和编码器的2K种输入序列对应。在网格图中,每个状态转移的过程中都会输出编码码字。译码器建立和编码器相同的网格图, 据此查询可得到解码码流。
理想状况下,假设在传输过程中没有错误发生,在接收端,根据已经存在的网格图很容易解码得到原始比特流。

以tail-bits卷积码为例,假如接收端收到的比特流为’1101010001’, 先按2 bit宽度分组:11 01 01 00 01,解码过程如下:

  1. 从t=0的初始状态开始,tail-bits编码器的初始状态为’00’

  2. 查询输出为11时的转移路径,如下图粗线标志,此时编码器输入bit为1才能走该转移路径,因此可得到解码的第一bit:1

    img

  3. t=1时,状态为‘10’,根据接收到的第二组‘01’,查询从该状态出发的两条转移路径

  4. 只有输入为1时,才能得到‘01’的输出,因此可得到此刻的转移路径,把其他无关路径删除。因此解码得到第二比特为1.

    img

  5. 以此类推,可在网格图上得到完整的状态转移路径及所有解码数据:11011.

img

现实中不会存在这种理想状态,信号经过复杂信道后均会引入不同程度错误,因此在接收端不会真正使用这种解码算法。

参考链接:维特比译码器(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分支量度对应于存在位错误的情况。

image-20250303160938382

图:分支度量计算

参考链接:
[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 算法在网格图中搜索最优路径的过程,从图中可以看到算法如何在每个时刻选择最优路径,最终找到从初始状态到最终状态的最优路径。

img

图:Viterbi 算法在网格图中搜索最优路径

2.1.3 路径度量与状态转移机制

路径度量是 Viterbi 算法中衡量路径优劣的关键指标,它定义为路径上所有分支的度量值之和。分支度量值则根据接收符号与预期符号之间的差异来计算,在硬判决情况下,常使用汉明距离作为分支度量值,即两个等长比特序列中对应比特不同的个数。例如,接收序列为 1011,假设路径的输出序列为 1101,那么它们之间的汉明距离为 3,因为有 3 个比特位不同。在软判决中,由于考虑了信号的幅度等更多信息,常采用欧几里得距离作为分支度量值。欧几里得距离是指在多维空间中,两个点之间的直线距离,在通信中,可将接收信号和假设路径输出信号看作多维空间中的点,通过计算它们之间的欧几里得距离来衡量差异。

计算路径度量

  1. 时刻i的路径度量:假设接收机在时刻i已计算好每个状态s的路径量度PM[s,i] ,卷积码编码约束度为K时,状态数为2K-1。在硬判决译码中,PM[s,i]是接收监督比特与最可能发送消息比较得到的差错比特总数,常将状态“00”作为起始状态。
  2. 最可能状态的判断:在时刻i的所有可能状态里,具有最小路径度量的状态就是最可能的状态。若存在多个具备最小路径度量的状态,那么它们成为最可能状态的可能性相等。
  3. 时刻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 译码算法在一定程度上能够纠正部分错误,提高通信的可靠性。

1/11 (0)
0/00 (2)
0/01 (0)
1/11 (2)
0/10 (0)
1/00 (2)
0/01 (1)
1/11 (2)
t0: 00 (0)
t1: 10 (0)
t1: 00 (2)
t2: 01 (0)
t2: 11 (2)
t3: 10 (0)
t3: 11 (2)
t4: 01 (1)
t4: 11 (2)

然而,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} 2L1个状态,所以每个时刻的计算量为 2 k × 2 L − 1 = 2 k + L − 1 2^k\times2^{L - 1}=2^{k + L - 1} 2k×2L1=2k+L1。假设译码长度为 N N N,则总的计算复杂度为 O ( N × 2 k + L − 1 ) O(N\times2^{k + L - 1}) O(N×2k+L1),随着约束长度 L L L增加,计算量呈指数增长。
  • 存储复杂度:需存储每个状态的幸存路径和路径度量值。每个状态有 2 L − 1 2^{L - 1} 2L1个,每个幸存路径长度最大为 N N N,所以存储路径的空间复杂度为 O ( N × 2 L − 1 ) O(N\times2^{L - 1}) O(N×2L1) ;存储路径度量值的空间复杂度为 O ( 2 L − 1 ) O(2^{L-1}) O(2L1)。总的存储复杂度也是随着约束长度 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;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2309537.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

金融项目实战

测试流程 测试流程 功能测试流程 功能测试流程 需求评审制定测试计划编写测试用例和评审用例执行缺陷管理测试报告 接口测试流程 接口测试流程 需求评审制定测试计划分析api文档编写测试用例搭建测试环境编写脚本执行脚本缺陷管理测试报告 测试步骤 测试步骤 需求评审 需求评…

命令行参数和环境变量 ─── linux第13课

目录 命令行参数 命令行参数列表: 如何实现命令行参数传递到此进程 环境变量 基本概念 常见环境变量 查看环境变量方法 ​编辑 环境变量如何写入 总结: 测试PATH 命令行参数 同一个程序,可以根据命令行参数的不同,表现不同功能 比如:指令中的选项的实现. ls -al…

基于 openEuler 22.09 的 OpenStack Yoga 部署

openEuler 虚拟化环境部署 使用 VMWare Workstation 创建三台 2 CPU、8G内存、100 GB硬盘 的虚拟机 主机 IP 作用 Controller 192.168.184.110 控制节点 Compute 192.168.184.111 计算节点 Storage 192.168.184.112 存储节 一 基础配置 1.1 配置 yum 源 由于 op…

【Linux实践系列】:用c语言实现一个shell外壳程序

&#x1f525;本文专栏&#xff1a;Linux Linux实践项目 &#x1f338;博主主页&#xff1a;努力努力再努力wz 那么今天我们就要进入Linux的实践环节&#xff0c;那么我们之前学习了进程控制相关的几个知识点&#xff0c;比如进程的终止以及进程的等待和进程的替换&#xff0c;…

conda安装及超详细避坑实战

1. Anaconda介绍。 Anaconda是一站式数据科学与机器学习平台&#xff0c;专为开发者、数据分析师设计,并带有python中超过180个科学包及其依赖项。通过 Anaconda&#xff0c;您可以轻松管理数据环境、安装依赖包&#xff0c;快速启动数据分析、机器学习项目。 Anaconda集成了…

LM studio 加载ollama的模型

1.LM 下载&#xff1a; https://lmstudio.ai/ 2.ollama下载&#xff1a; https://ollama.com/download 3.打开ollama&#xff0c;下载deepseek-r1。 本机设备资源有限&#xff0c;选择7B的&#xff0c;执行ollama run deepseek-r1:7b 4.windows chocolatey下载&#xff1a; P…

深入探索像ChatGPT这样的大语言模型-02-POST training supervised finetuning

参考 【必看珍藏】2月6日&#xff0c;安德烈卡帕西最新AI普及课&#xff1a;深入探索像ChatGPT这样的大语言模型&#xff5c;Andrej Karpathy fineweb知乎翻译介绍 fineweb-v1原始连接 fineweb中文翻译版本 Chinese Fineweb Edu数据集 查看网络的内部结果&#xff0c;可以参…

Kaldi环境配置与Aishell训练

一、项目来源 代码来源&#xff1a;kaldi-asr/kaldi: kaldi-asr/kaldi is the official location of the Kaldi project. (github.com) 官网文档&#xff1a;Kaldi: The build process (how Kaldi is compiled) (kaldi-asr.org) 踩着我的同门李思成-CSDN博客填上的坑kaldi环境…

数据集/API 笔记:新加坡PSI(空气污染指数)API

data.gov.sg 数据范围&#xff1a;2016年2月 - 2025年3月 1 获取API方式 curl --request GET \--url https://api-open.data.gov.sg/v2/real-time/api/psi 2 返回数据 API 的数据结构可以分为 3 大部分&#xff1a; 区域元数据&#xff08;regionMetadata&#xff09; →…

【GPU使用】如何在物理机和Docker中指定GPU进行推理和训练

我的机器上有4张H100卡&#xff0c;我现在只想用某一张卡跑程序&#xff0c;该如何设置。 代码里面设置 import os # 记住要写在impot torch前 os.environ[CUDA_VISIBLE_DEVICES] "0, 1"命令行设置 export CUDA_VISIBLE_DEVICES0,2 # Linux 环境 python test.py …

【Java项目】基于SpringBoot的CSGO赛事管理系统

【Java项目】基于SpringBoot的CSGO赛事管理系统 技术简介&#xff1a;采用SpringBoot框架、Java语言、MySQL数据库等技术实现。 系统简介&#xff1a;CSGO赛事管理系统是一个基于B/S架构的管理系统&#xff0c;主要功能包括前台和后台管理模块。前台系统功能模块分为&#xf…

MIPI接口:(4)MIPI CSI-2协议详解(上)

1. 什么是CSI&#xff1f; CSI&#xff08;Camera Serial Interface&#xff09;是MIPI联盟早期制定的摄像头接口标准&#xff0c;主要用于连接摄像头和处理器。 CSI-2是CSI的第二代版本&#xff0c;在原有基础上进行了全面优化&#xff1a; &#xff08;1&#xff09;分层架…

防火墙旁挂组网双机热备负载均衡

一&#xff0c;二层交换网络&#xff1a; 使用MSTPVRRP组网形式 VLAN 2--->SW3为主,SW4 作为备份 VLAN 3--->SW4为主,SW3 作为备份 MSTP 设计 --->SW3 、 4 、 5 运行 实例 1 &#xff1a; VLAN 2 实例 2 &#xff1a; VLAN 3 SW3 是实例 1 的主根&#xff0c;实…

视频教育网站开源系统的部署安装 (roncoo-education)服务器为ubuntu22.04.05

一、说明 前端技术体系&#xff1a;Vue3 Nuxt3 Vite5 Vue-Router Element-Plus Pinia Axios 后端技术体系&#xff1a;Spring Cloud Alibaba2021 MySQL8 Nacos Seata Mybatis Druid redis 后端系统&#xff1a;roncoo-education&#xff08;核心框架&#xff1a;S…

CF 886A.ACM ICPC(Java实现)

题目分析 输入6个值&#xff0c;判断某三个值的和能够等于另外三个值的和 思路分析 首先判断总和是不是一个偶数&#xff0c;如果不是就“NO”。由于小何同学算法不好&#xff0c;只能使用三层for循环强行判断某三个值是否能等于总和的一半&#xff0c;可以就“YES”。 代码 …

Spring Boot 自动装配深度解析与实践指南

目录 引言&#xff1a;自动装配如何重塑Java应用开发&#xff1f; 一、自动装配核心机制 1.1 自动装配三大要素 1.2 自动装配流程 二、自定义自动配置实现 2.1 创建自动配置类 2.2 配置属性绑定 2.3 注册自动配置 三、条件注解深度应用 3.1 常用条件注解对比 3.2 自定…

【windows driver】 开发环境简明安装教程

一、下载路径 https://learn.microsoft.com/en-us/windows-hardware/drivers/other-wdk-downloads 二、安装步骤&#xff1a; 1、安装Visual Studio IDE 笔者建议安装最新版本&#xff0c;可以向下兼容。发文截止到目前&#xff0c;VS2022是首选&#xff0c;当前笔者由于项…

探秘基带算法:从原理到5G时代的通信变革【八】QAM 调制 / 解调

文章目录 2.7 QAM 调制 / 解调2.7.1 概述2.7.2 星座图星座图的结构与性能发射端的信息编码与接收端的解码差分编码的分类与实现差分编码的模4格雷加法器公式16QAM星座图与映射关系 2.7.3 信号表达式正交振幅调制的基本原理与系统分析相位误差对QAM性能的影响多电平正交振幅调制…

Flink性能指标详解MetricsAnalysis

文章目录 Flink 组成1.JobManager2.TaskManager3.ResourceManager4.Dispatcher5.Client6. Env JobManager MetricsTaskManager Metrics Flink 组成 1.JobManager 管理任务 作业调度&#xff1a;负责接收和调度作业&#xff0c;分配任务到 TaskManager。资源管理&#xff1a;…

Halcon 车牌识别-超精细教程

车牌示例 流程: 读取图片转灰度图阈值分割,找车牌内容将车牌位置设置变换区域形状找到中心点和弧度利用仿射变换,斜切车牌旋转转正,把车牌抠出来利用形态学操作拼接车牌号数字训练ocr开始识别中文车牌 本文章用到的算子(解析) Halcon 算子-承接车牌识别-CSDN博客 rgb1_to_gray…