提示:昼短苦夜长,何不秉烛游!

文章目录

  • 一、哈夫曼解析
    • 1、手写哈夫曼树
    • 2、为什么这样构成哈夫曼树?
      • 路径
      • 路径长度:
      • 带权路径长度
      • 树的最带权路径长度
      • 哈夫曼树
  • 二、构建哈夫曼树
  • 三、哈夫曼树编码
  • 四、哈夫曼解码
  • 五、求树的权值
  • 六、整体代码
  • 总结

再此声明因为构造过程中会有重复值的出现 导致构造的树 并不一定一样 也就导致解码 编码的不同

一、哈夫曼解析

1、手写哈夫曼树

在学这个之前 首先来借用使用一个图来帮助我们来理解 先来教大家怎么构建这样一个树,首先给定一系列数字, 从中选取两个最小的值来构成左右子树, 假如现在给定的就是 1 2 3 4 5 四个节点 从中选择 最小的两个值 但是两个值中小的一个作为 左子树,大的值一个作为右子树 (这个大的作为右子树 也是默认的,其实没有规定 若是你想 大的作为左子树也是可以的 但是要统一)此时你应该也可以看出 其中最小的两个值就是1和2,,选择1 2 来构成一棵树,那么根结点应该是谁呢 这里是使用两者值作为根 也就构成如图所示

然后将合并后的值3作为新结点放入序列中,删除序列中已经使用过的值 此时也就是 3 3 4 5 此时再选择两个小的
但是此时最小的两个的值是一样的 都是三 此时谁应该放在左边呢? 这里都可以 计算出来树的带权路径长度是一样的, 所以 通过这个可以使用序列尽管相同 但是构成的树 却不一定是 一样的 我认为 若是序列在初始 或者处理的过程中 若是有重复的值 也就会造成 树的不同 但是构成的树的最短带权路径长度依然是一样的
也就构成下图

重复上以上步骤继续选择两个构成 添加新合并的值 作为新结点放入序列中,然后删两个 较小的值 此时是6 4 5
再从中选取两个最小值

再填加一个值,再删两个值 6 9 此时序列中也就两个值 树成!!!

2、为什么这样构成哈夫曼树?

在解决这个问题之前,我们需要知道什么是几个概念,路径长度 带权路径长度

路径

从一个结点往下可以到达的结点之间的通路 称为路径

路径长度:

某一个路径所经过的边的数量 称为该路径的路径长度 等于经过的结点数减一

带权路径长度

将树中结点赋值给一个带有某种含义的数值,则该数值称为该结点的权 从根节点到该结点之间的路径长度与该结点的权的乘积 称为该结点的带权路径长度

树的最带权路径长度

树的带权路径长度规定为所有叶子结点的带权路径长度之和,记为WPL。

哈夫曼树

给定n个权值作为n个叶子结点,构造一棵二叉树,若该树的带权路径长度达到最小,则称该二叉树为哈夫曼树,也被称为最优二叉树 但是如何才能使得带权路径最小 根据计算方式 我们想到我们应该尽可能地让权值大的叶子结点靠近根结点,让权值小的叶子结点远离根结点,这样便能使得这棵二叉树的带权路径长度达到最小

二、构建哈夫曼树

使用之前的三叉动态链表的方式也是可以实现的 ,但是这里具体较之前的优点在哪 暂时寡人也没有看出来

//哈夫曼树结点结构
typedef struct HTNode
{char data; //数据,非叶节点为NULLdouble weight;//权重int parent;//双亲,-1表示没有双亲,即根节点int lchild;//左孩子,数组下标,-1表示无左孩子,即叶节点int rchild;//右孩子
}HTnode;

简单来说 其实也就是一个填表的过程 ,将下表填充完整

数组上方五个存放的是叶子结点下面四个存放的是非叶子节点, 其中lchild 和rchild 存储的都是数组的下标 ,也就是从0以开始的,根结点是没有父亲结点 我们使用一个-1标志标识他没有父节点 同理若是为叶子节点其中孩子结点为-1
难度还是有的 ,但是事实却是一遍也可以写出来 大家自己也尝试一下

#include<bits/stdc++.h>
#define ElemType int
#define Max 100;
using namespace std;
typedef struct HTNode{char data;double weight;int parent=-1;int lchild=-1;int rchild=-1;
}HTNode;
int n;
HTNode HTree[100];
void Creat(){//创建一个哈夫曼树cout<<"请输入你要填入字符的个数"<<endl;vector<int> Vec; cin>>n;cout<<"请输入字符以及对应的权值"<<endl;for(int i=0;i<n;i++){cin>>HTree[i].data>>HTree[i].weight; Vec.push_back(HTree[i].weight);}//上面几句完成了初始化  接下来进行造树(填表)for(int i=n;i<2*n-1;i++){ //将HTree数组中的剩下的填充完即可结束 if(Vec.size()==1) break;sort(Vec.begin(),Vec.end());//遍历来寻找最小值以及次小值在HTree中的坐标  但是要注意此时是给这两个结点找父亲 所以他们不能已经有了父亲 for(int j=0;j<i;j++){if(Vec[0]==HTree[j].weight&&HTree[j].parent==-1){//找最小值的父亲 HTree[j].parent=i;HTree[i].lchild=j;   break;//是为了避免重复值的问题 }} for(int j=0;j<i;j++){if(Vec[1]==HTree[j].weight&&HTree[j].parent==-1){HTree[j].parent=i;HTree[i].rchild=j;break;}} HTree[i].weight=Vec[0]+Vec[1];//成功一次之后 需要删除两个 添加一个Vec.erase(Vec.begin(),Vec.begin()+2);Vec.push_back(HTree[i].weight); }
}
void PrintHT()
{cout << "下标\t" << "数据\t" << "权重\t" << "双亲\t" << "左孩子\t" << "右孩子" << endl;for (int i = 0; i<n*2-1; i++){cout << i << "\t" << HTree[i].data << "\t" << HTree[i].weight << "\t" << HTree[i].parent << "\t" << HTree[i].lchild << "\t" << HTree[i].rchild << endl;}
}
int main(){Creat();PrintHT();return 0;
}

三、哈夫曼树编码

从根结点到叶子结点 往左是零 往右是一
这里不知道大家想到之前写过的一个题目 二叉树的所有路径 其中就是寻找根结点到所有叶子结点的路径,虽然这道题我们不使用所有路径的方法哈哈哈 因为每一个结点都有父亲并且 并且叶子结点就在数组的前n个 找到之后反转一下即可 并且我们需要绑定每一个字符对应的哈夫曼编码 所以也就是需要定义一个结构体

typedef struct HFNode{char data;string str;
}HFNode;

整体代码如下

#include<bits/stdc++.h>
#define ElemType int
#define Max 100;
using namespace std;
typedef struct HTNode{char data;double weight;int parent=-1;int lchild=-1;int rchild=-1;
}HTNode;
typedef struct HFNode{char data;string str;
}HFNode;
int n;
HTNode HTree[100];
HFNode HFCode[100];
void Creat(){//创建一个哈夫曼树cout<<"请输入你要填入字符的个数"<<endl;vector<int> Vec; cin>>n;cout<<"请输入字符以及对应的权值"<<endl;for(int i=0;i<n;i++){cin>>HTree[i].data>>HTree[i].weight; HFCode[i].data=HTree[i].data;Vec.push_back(HTree[i].weight);}//上面几句完成了初始化  接下来进行造树(填表)for(int i=n;i<2*n-1;i++){ //将HTree数组中的剩下的填充完即可结束 if(Vec.size()==1) break;sort(Vec.begin(),Vec.end());//遍历来寻找最小值以及次小值在HTree中的坐标  但是要注意此时是给这两个结点找父亲 所以他们不能已经有了父亲 for(int j=0;j<i;j++){if(Vec[0]==HTree[j].weight&&HTree[j].parent==-1){//找最小值的父亲 HTree[j].parent=i;HTree[i].lchild=j; break;//是为了避免重复值的问题 }} for(int j=0;j<i;j++){if(Vec[1]==HTree[j].weight&&HTree[j].parent==-1){HTree[j].parent=i;HTree[i].rchild=j;break;}} HTree[i].weight=Vec[0]+Vec[1];//成功一次之后 需要删除两个 添加一个Vec.erase(Vec.begin(),Vec.begin()+2);Vec.push_back(HTree[i].weight); }
}
void DealHFCodeString(){for(int i=0;i<n;i++){//对n个结点进行string的赋值 int Cur=i;//指向此时的第一个叶子节点 string Str;while(HTree[Cur].parent!=-1){if(HTree[HTree[Cur].parent].lchild==Cur){Str+=to_string(0);}else{Str+=to_string(1);}Cur=HTree[Cur].parent;} reverse(Str.begin(),Str.end());HFCode[i].str=Str;}
}
//打印哈夫曼树
void PrintHT()
{cout << "下标\t" << "数据\t" << "权重\t" << "双亲\t" << "左孩子\t" << "右孩子" << endl;for (int i = 0; i<n*2-1; i++){cout << i << "\t" << HTree[i].data << "\t" << HTree[i].weight << "\t" << HTree[i].parent << "\t" << HTree[i].lchild << "\t" << HTree[i].rchild << endl;}
}
void PrintHFC(){cout<<"数据\t"<<"哈夫曼编码\t"<<endl;for(int i=0;i<n;i++){cout<<HTree[i].data<<"\t"<<HFCode[i].str<<"\t"<<endl;}
}
int main(){Creat();PrintHT();DealHFCodeString();PrintHFC();return 0;
}

四、哈夫曼解码

简单的来说 就是给定一个01构成的字符串根据这个字符串相对应的字符,不过这里要考虑不止一个字符的情况,所以这里使用一个字符串来放解码的结果,若是能到叶子结点则将此时到达的叶子结点放入结果集中,然后从头继续匹配,还有就是是否会输入不正确的时候 什么时候会输入不正确呢? 输入字符串的最后一段在从根结点匹配的过程中到达不了叶子结点 即为输入不正确 若是Cur停留的是在根结点 就说明输入没有问题
下面写出代码

//哈夫曼解码
string HFDecode(){string Str;string result;cout<<"请输入一段01 构成的字符串"<<endl;cin>>Str; int Cur=n*2-2;//我们知道树根是放在数组的最后一个的位置上while(!Str.empty()){//字符串中为空的时候 也是要退出的 if(Str.front()=='1'){//向右深入cout<<"向右深入"<<endl; Cur=HTree[Cur].rchild;Str.erase(Str.begin(),Str.begin()+1);}else{//向左深入 cout<<"向左深入"<<endl; Cur=HTree[Cur].lchild;Str.erase(Str.begin(),Str.begin()+1);} if(HTree[Cur].lchild==-1){//说明此时是叶子结点 将此时对应的结果放在result中 cout<<"将"<<HTree[Cur].data<<"放入结果集合中"<<endl; result+=HTree[Cur].data;Cur=n*2-2;//到达叶子节点重新指向根结点 }} if(HTree[Cur].parent!=-1){//遍历完成的时候若是Cur指向的不是叶子结点 输入有问题cout<<"此时你输入的有问题"<<endl;result.erase(result.begin(),result.end());return result;}return result;
}

解码需要根据编码来看,若是多加一个数子 则输入错误

可运行如下

#include<bits/stdc++.h>
#define ElemType int
#define Max 100;
using namespace std;
typedef struct HTNode{char data;double weight;int parent=-1;int lchild=-1;int rchild=-1;
}HTNode;
typedef struct HFNode{char data;string str;
}HFNode;
int n;
HTNode HTree[100];
HFNode HFCode[100];
void Creat(){//创建一个哈夫曼树cout<<"请输入你要填入字符的个数"<<endl;vector<int> Vec; cin>>n;cout<<"请输入字符以及对应的权值"<<endl;for(int i=0;i<n;i++){cin>>HTree[i].data>>HTree[i].weight; HFCode[i].data=HTree[i].data;Vec.push_back(HTree[i].weight);}//上面几句完成了初始化  接下来进行造树(填表)for(int i=n;i<2*n-1;i++){ //将HTree数组中的剩下的填充完即可结束 if(Vec.size()==1) break;sort(Vec.begin(),Vec.end());//遍历来寻找最小值以及次小值在HTree中的坐标  但是要注意此时是给这两个结点找父亲 所以他们不能已经有了父亲 for(int j=0;j<i;j++){if(Vec[0]==HTree[j].weight&&HTree[j].parent==-1){//找最小值的父亲 HTree[j].parent=i;HTree[i].lchild=j; break;//是为了避免重复值的问题 }} for(int j=0;j<i;j++){if(Vec[1]==HTree[j].weight&&HTree[j].parent==-1){HTree[j].parent=i;HTree[i].rchild=j;break;}} HTree[i].weight=Vec[0]+Vec[1];//成功一次之后 需要删除两个 添加一个Vec.erase(Vec.begin(),Vec.begin()+2);Vec.push_back(HTree[i].weight); }
}
//哈夫曼编码
void DealHFCodeString(){for(int i=0;i<n;i++){//对n个结点进行string的赋值 int Cur=i;//指向此时的第一个叶子节点 string Str;while(HTree[Cur].parent!=-1){if(HTree[HTree[Cur].parent].lchild==Cur){Str+=to_string(0);}else{Str+=to_string(1);}Cur=HTree[Cur].parent;} reverse(Str.begin(),Str.end());HFCode[i].str=Str;}
}
//哈夫曼解码
string HFDecode(){string Str;string result;cout<<"请输入一段01 构成的字符串"<<endl;cin>>Str; int Cur=n*2-2;//我们知道树根是放在数组的最后一个的位置上while(!Str.empty()){//字符串中为空的时候 也是要退出的 if(Str.front()=='1'){//向右深入cout<<"向右深入"<<endl; Cur=HTree[Cur].rchild;Str.erase(Str.begin(),Str.begin()+1);}else{//向左深入 cout<<"向左深入"<<endl; Cur=HTree[Cur].lchild;Str.erase(Str.begin(),Str.begin()+1);} if(HTree[Cur].lchild==-1){//说明此时是叶子结点 将此时对应的结果放在result中 cout<<"将"<<HTree[Cur].data<<"放入结果集合中"<<endl; result+=HTree[Cur].data;Cur=n*2-2;//到达叶子节点重新指向根结点 }} if(HTree[Cur].parent!=-1){//遍历完成的时候若是Cur指向的不是叶子结点 输入有问题cout<<"此时你输入的有问题"<<endl;result.erase(result.begin(),result.end());return result;}return result;
}
//打印哈夫曼树
void PrintHT()
{cout << "下标\t" << "数据\t" << "权重\t" << "双亲\t" << "左孩子\t" << "右孩子" << endl;for (int i = 0; i<n*2-1; i++){cout << i << "\t" << HTree[i].data << "\t" << HTree[i].weight << "\t" << HTree[i].parent << "\t" << HTree[i].lchild << "\t" << HTree[i].rchild << endl;}
}
void PrintHFC(){cout<<"数据\t"<<"哈夫曼编码\t"<<endl;for(int i=0;i<n;i++){cout<<HTree[i].data<<"\t"<<HFCode[i].str<<"\t"<<endl;}
}
int main(){Creat();PrintHT();DealHFCodeString();PrintHFC();cout<<"此时的解码之后的是"<<HFDecode()<<endl;; while(1){cout<<"是否继续解码 是的话 请输入1  否则请输入0"<<endl;int flag; cin>>flag;if(1==flag){cout<<"此时的解码之后的是"<<HFDecode()<<endl;}else{break;}}return 0;
}

五、求树的权值

其实思想倒是简单 也就是所以叶子结点路径长度乘上权重 叶子结点好找 也就是数组前n个就是叶子结点 路径长度叶子结点往上找到根结点就可以了,下面写出代码

//计算树的权重
int HFWeight(){int sum=0;for(int i=0;i<n;i++){//遍历前n个叶子结点求所有叶子结点带权路径长度的和int Cur=i;int Deep=0;while(HTree[Cur].parent!=-1){ Deep++;Cur=HTree[Cur].parent;} sum+=HTree[i].weight*Deep;} return sum;
}

六、整体代码

#include<bits/stdc++.h>
#define ElemType int
#define Max 100;
using namespace std;
typedef struct HTNode{char data;double weight;int parent=-1;int lchild=-1;int rchild=-1;
}HTNode;
typedef struct HFNode{char data;string str;
}HFNode;
int n;
HTNode HTree[100];
HFNode HFCode[100];
void Creat(){//创建一个哈夫曼树cout<<"请输入你要填入字符的个数"<<endl;vector<int> Vec; cin>>n;cout<<"请输入字符以及对应的权值"<<endl;for(int i=0;i<n;i++){cin>>HTree[i].data>>HTree[i].weight; HFCode[i].data=HTree[i].data;Vec.push_back(HTree[i].weight);}//上面几句完成了初始化  接下来进行造树(填表)for(int i=n;i<2*n-1;i++){ //将HTree数组中的剩下的填充完即可结束 if(Vec.size()==1) break;sort(Vec.begin(),Vec.end());//遍历来寻找最小值以及次小值在HTree中的坐标  但是要注意此时是给这两个结点找父亲 所以他们不能已经有了父亲 for(int j=0;j<i;j++){if(Vec[0]==HTree[j].weight&&HTree[j].parent==-1){//找最小值的父亲 HTree[j].parent=i;HTree[i].lchild=j; break;//是为了避免重复值的问题 }} for(int j=0;j<i;j++){if(Vec[1]==HTree[j].weight&&HTree[j].parent==-1){HTree[j].parent=i;HTree[i].rchild=j;break;}} HTree[i].weight=Vec[0]+Vec[1];//成功一次之后 需要删除两个 添加一个Vec.erase(Vec.begin(),Vec.begin()+2);Vec.push_back(HTree[i].weight); }
}
//哈夫曼编码
void DealHFCodeString(){for(int i=0;i<n;i++){//对n个结点进行string的赋值 int Cur=i;//指向此时的第一个叶子节点 string Str;while(HTree[Cur].parent!=-1){if(HTree[HTree[Cur].parent].lchild==Cur){Str+=to_string(0);}else{Str+=to_string(1);}Cur=HTree[Cur].parent;} reverse(Str.begin(),Str.end());HFCode[i].str=Str;}
}
//哈夫曼解码
string HFDecode(){string Str;string result;cout<<"请输入一段01 构成的字符串"<<endl;cin>>Str; int Cur=n*2-2;//我们知道树根是放在数组的最后一个的位置上while(!Str.empty()){//字符串中为空的时候 也是要退出的 if(Str.front()=='1'){//向右深入cout<<"向右深入"<<endl; Cur=HTree[Cur].rchild;Str.erase(Str.begin(),Str.begin()+1);}else{//向左深入 cout<<"向左深入"<<endl; Cur=HTree[Cur].lchild;Str.erase(Str.begin(),Str.begin()+1);} if(HTree[Cur].lchild==-1){//说明此时是叶子结点 将此时对应的结果放在result中 cout<<"将"<<HTree[Cur].data<<"放入结果集合中"<<endl; result+=HTree[Cur].data;Cur=n*2-2;//到达叶子节点重新指向根结点 }} if(HTree[Cur].parent!=-1){//遍历完成的时候若是Cur指向的不是叶子结点 输入有问题cout<<"此时你输入的有问题"<<endl;result.erase(result.begin(),result.end());return result;}return result;
}
//打印哈夫曼树
void PrintHT()
{cout << "下标\t" << "数据\t" << "权重\t" << "双亲\t" << "左孩子\t" << "右孩子" << endl;for (int i = 0; i<n*2-1; i++){cout << i << "\t" << HTree[i].data << "\t" << HTree[i].weight << "\t" << HTree[i].parent << "\t" << HTree[i].lchild << "\t" << HTree[i].rchild << endl;}
}
//打印字母对应的哈夫曼编码
void PrintHFC(){cout<<"数据\t"<<"哈夫曼编码\t"<<endl;for(int i=0;i<n;i++){cout<<HTree[i].data<<"\t"<<HFCode[i].str<<"\t"<<endl;}
}
//计算树的权重
int HFWeight(){int sum=0;for(int i=0;i<n;i++){//遍历前n个叶子结点求所有叶子结点带权路径长度的和int Cur=i;int Deep=0;while(HTree[Cur].parent!=-1){ Deep++;Cur=HTree[Cur].parent;} sum+=HTree[i].weight*Deep;} return sum;
}
int main(){Creat();PrintHT();DealHFCodeString();PrintHFC();cout<<"此时树的权重是"<<HFWeight()<<endl; cout<<"此时的解码之后的是"<<HFDecode()<<endl;while(1){cout<<"是否继续解码 是的话 请输入1  否则请输入0"<<endl;int flag; cin>>flag;if(1==flag){cout<<"此时的解码之后的是"<<HFDecode()<<endl;}else{break;}}return 0;
}

总结

有些时候是需要自己来写一下的 建议读者尝试一下 理解了其实也不难 大家加油!感觉不错点个赞呗

哈夫曼树建树编码解码相关推荐

  1. 20172305 2018-2019-1 蓝墨云班课实验--哈夫曼树的编码

    20172305 2018-2019-1 蓝墨云班课实验--哈夫曼树的编码 实验要求 设有字符集:S={a,b,c,d,e,f,g,h,i,j,k,l,m,n.o.p.q,r,s,t,u,v,w,x, ...

  2. 让人头疼的哈夫曼树与编码

    哈夫曼树(Huffman Tree): 给定n个权值作为n个叶子结点,构造一棵二叉树,若该树的带权路径长度达到最小,称这样的二叉树为最优二叉树,也称为哈夫曼树.哈夫曼树是带权路径长度最短的树,权值较大 ...

  3. 关于哈夫曼树与编码,带权路径长度

    哈夫曼树 哈夫曼树与哈夫曼编码的定义 建立哈夫曼树 哈夫曼树编码 通过译码求输入的序列 具体题目与完整代码: 建哈夫曼树,求哈夫曼编码,求解译码 带权路径长度 哈夫曼树与哈夫曼编码的定义 typede ...

  4. 优先级队列实现哈夫曼树的编码和译码

    //优先级队列实现的哈夫曼树的编码和译码 #include<iostream> #include<queue> #include<string> using nam ...

  5. java哈夫曼树编码_哈夫曼树的编码实验

    Java哈夫曼编码实验--哈夫曼树的建立,编码与解码 建树,造树,编码,解码 一.哈夫曼树编码介绍 1.哈夫曼树: (1)定义:假设有n个权值{w1, w2, ..., wn},试构造一棵含有n个叶子 ...

  6. 数据结构(六)霍夫曼树与编码

    1.算法流程 (1)构建霍夫曼树:自底向上 根据统计频率构建霍夫曼树: A.把所有的节点扔进排序队列queue中: B.从queue选择选择前面两个最小的元素a.b,把最小的树a作为左节点,把最小的b ...

  7. 20172328--蓝墨云班课实验--哈夫曼树的编码

    哈夫曼编码测试 任务详情 设有字符集:S={a,b,c,d,e,f,g,h,i,j,k,l,m,n.o.p.q,r,s,t,u,v,w,x,y,z}. 给定一个包含26个英文字母的文件,统计每个字符出 ...

  8. 哈夫曼树及编码讲解及例题

    哈弗曼树及编码 哈弗曼树算法 第一步: 初始化n个单节点的树,并为它们表上字母中的字符.把每个字符的概率记在树的根中,用来指出树的权重(更一般地说,树的权重等于树中所有叶子的概率之和). 第二部: 重 ...

  9. c语言赫夫曼树的编码与译码,哈夫曼树与编码译码实现

    一.哈弗曼树的基本概念. 哈夫曼树,又称最优树,是一类带权路径长度最短的树.下面有几个概念: (1)路径. 树中一个结点到另一个结点之间的分支构成这两个结点之间的路径. (2)路径长度. 路径上的分枝 ...

最新文章

  1. Leetcode 234. 回文链表 解题思路及C++实现
  2. 重拾IP路由选择:CCNA学习指南中的IP路由选择
  3. friend之友元函数和友元类
  4. MapReduce将小文件合并成大文件,并设置每个切片的大小的案例
  5. redis实现session共享,哨兵
  6. application/x-www-form-urlencoded 与multipart/form-data
  7. [NOIP2016 提高组] 愤怒的小鸟
  8. 阿里云能耗宝发布,助力中小企业绿色升级,参与碳中和万亿市场
  9. Mysql事务,并发问题,锁机制
  10. 函数式反应型编程(FRP)
  11. Informatic学习总结_day03_update组件学习
  12. java代码实现的帧动画
  13. Android keeps stopping
  14. 同步消息和异步消息传递的区别?
  15. 深入理解金融交易报文Iso8583协议
  16. 用wireshark捕捉查看登录时账号密码的传输方式
  17. 搭载“鸿蒙”的华为Watch 3,是智能手表的标准答案吗?
  18. 用 Python 去除 PDF 水印,你学会吗?
  19. 【JDK7】新特性(1) 概述
  20. 互联网暗潮汹涌,开放平台机遇空前

热门文章

  1. 中心睿典计算机考试题,中星睿典全国专业技术人员计算机应用能力考试模拟试题库答案_WindowsXP的窗口...
  2. 免费音频转文字的软件有哪些?这几个软件真的很不错
  3. 【3d建模】用Maya和ZBrush制作《黎明杀机》中的鬼武士模型,全流程解读
  4. Android 手机配office365邮箱
  5. Pandas 11-综合练习
  6. Web安全测试工具WVS介绍及安装教程
  7. Web Design-网页设计 LOGO 标准尺寸
  8. Google黑客常用搜索语句(新增)
  9. Clickhouse的数据存储原理、二进制文件内容分析与索引详解
  10. Unity Shader Graph 制作Hologram全息效果