Obsidian Dataview数据索引与查询引擎:构建智能知识库的完整技术方案
2026/4/18 18:42:21
哈夫曼编码是一种基于字符出现频率进行数据压缩的最优前缀编码方法,其核心思想是:
为高频字符分配较短的编码,为低频字符分配较长的编码,并保证任意一个字符的编码都不是其他编码的前缀(即前缀码),从而确保译码的唯一性。
0,右分支标记为1。给定字符集{a, b, c, d, e},对应权值{0.30, 0.25, 0.15, 0.22, 0.08}:
构造哈夫曼树后得到编码:
000111100101编码满足前缀性质,例如:
00不是任何其他编码的前缀;00101→a,e。voidHuffmanCoding(HuffmanTree HT,HuffmanCode HC,intn){char*cd;inti,start,c,f;if(n<=1)return;cd=(char*)malloc(n*sizeof(char));// 临时存储编码cd[n-1]='\0';// 字符串结尾for(i=1;i<=n;++i){start=n-1;// 从数组末尾向前写入编码c=i;f=HT[i].parent;// 自底向上回溯至根节点while(f!=0){if(HT[f].lchild==c)cd[--start]='0';elsecd[--start]='1';c=f;f=HT[f].parent;}// 分配空间并复制编码HC[i]=(char*)malloc((n-start)*sizeof(char));strcpy(HC[i],&cd[start]);}free(cd);// 释放临时数组}cd临时保存从叶子到根的逆向编码序列。'0'或'1'。HC[i]中(通过指针偏移实现)。typedefstruct{doubleweight;intlchild,rchild,parent;}HTNode,*HuffmanTree;typedefchar**HuffmanCode;// 指向字符串数组的指针HuffmanTree是一个结构体数组,共2n-1个结点(n 个叶子 + n-1 个内部结点)。HuffmanCode HC实际上是一个char**,HC[i]指向第 i 个字符的编码字符串。哈夫曼编码广泛应用于无损数据压缩领域,如:
它是贪心算法的经典实例:每一步选择最小权重的两棵树合并,最终获得全局最优解。
构造哈夫曼树的核心思想是贪心算法:每次选择两个权值最小的结点合并,直到所有结点合并成一棵树。这样可以保证带权路径长度(WPL)最小。
w[1..n],共n个叶子结点。2n - 1个结点。1. 初始化: - 创建 2n - 1 个结点的数组 HT。 - 前 n 个结点为叶子结点,分别赋予权值 w[i],lchild、rchild 和 parent 初始为 0。 2. 循环执行以下操作 (n - 1) 次,以构建 n - 1 个内部结点: a. 在当前所有“未被选作子树”的根结点中(即 parent == 0 的结点), 找出两个权值最小的结点,记为 m1 和 m2(m1 ≤ m2)。 b. 创建一个新的内部结点,下标为 i = n + 当前第几次合并。 c. 设置该结点的: weight = m1.weight + m2.weight lchild = m1 下标 rchild = m2 下标 parent = 0 d. 更新 m1 和 m2 的 parent 为新结点的下标。 3. 最终得到一棵哈夫曼树,根结点为 HT[2n-1]。设权值{0.30, 0.25, 0.15, 0.22, 0.08}
最终形成哈夫曼树,根为最后一个生成的结点。
O(n)个结点。n - 1次合并,每次扫描O(n)。O(n)O(log n)O(log n)n - 1次操作 →O(n log n)实际应用中通常采用堆优化版本提升效率。
voidCreateHuffmanTree(HuffmanTree&HT,intn,doublew[]){if(n<=1)return;intm=2*n-1;HT=(HuffmanTree)malloc(m*sizeof(HTNode));// 初始化前n个叶子结点for(inti=1;i<=n;++i){HT[i].weight=w[i];HT[i].lchild=HT[i].rchild=HT[i].parent=0;}// 初始化内部结点(暂无连接)for(inti=n+1;i<=m;++i){HT[i].weight=HT[i].lchild=HT[i].rchild=HT[i].parent=0;}// 构造内部结点for(inti=n+1;i<=m;++i){intm1=0,m2=0;// 存储最小和次小的下标doublemin1=DBL_MAX,min2=DBL_MAX;// 遍历前i-1个结点,找parent==0且权值最小的两个for(intj=1;j<i;++j){if(HT[j].parent==0&&HT[j].weight<min1){min2=min1;m2=m1;min1=HT[j].weight;m1=j;}elseif(HT[j].parent==0&&HT[j].weight<min2){min2=HT[j].weight;m2=j;}}// 设置新结点HT[m1].parent=HT[m2].parent=i;HT[i].lchild=m1;HT[i].rchild=m2;HT[i].weight=min1+min2;}}