Huffman编解码算法实现与压缩效率分析

一.背景知识及相关公式

1.信源熵

信源熵是信息的度量单位,一般用H表示,单位是比特,对于任意一个随机变量,它的熵定义为,变量的不确定性越大,熵也就越大。

2.Huffman编码

(1)Huffman编码是一种无失真编码的编码方式,可变长编码的一种;
(2)Huffman编码基于信源的概率统计模型,它的基本思路是,出现概率小的信源符号编长码,出现概率大的信源符号编短码,从而使平均码长最小。
(3)在程序实现中常使用一种叫做树的数据结构实现Huffman编码,由它编出的码是即时码。

3.Huffman编码算法

(1)将文件以ASCII字符流的形式读入,统计每个符号的发生频率;
(2)将所有文件中出现过的字符按照频率从小到大的顺序排列 ;
(3)每一次选出最小的两个值,作为二叉树的两个叶子节点,将和作为它们根节点,这两个叶子节点不再参与比较,新的根节点参与比较;
(4)重复 3,直到最后得和为1的根节点;
(5)将形成的二叉树左节点标0,右节点标1,把从最上面的根节点到最下面叶子点途中遇到的0、1序列串起来,得到了各个字符的编码表示。

二.实验过程

1.数据结构

(1)Huffman节点
typedef struct huffman_node_tag
{unsigned char isLeaf;  /*是否为叶节点,1表示是叶节点,0表示不是叶节点*/unsigned long count;   /*文件中符号出现的频数*/struct huffman_node_tag *parent;  /*父节点的指针*/union/*如果是树叶,则此项为该节点的左右孩子的指针,否则为某个信源符号*/{struct{struct huffman_node_tag *zero, *one;/*该节点左右孩子的指针*/};unsigned char symbol;/*信源符号,一个字节的二进制数值*/};
} huffman_node;

(2)Huffman码字节点
typedef struct huffman_code_tag
{unsigned long numbits;/*码字长度 *//*码字的第1位存于bits[0]的第1位,码字的第2位存于bits[0]的第2位,码字的第8位存于bits[0]的第8位,码字的第9位存于bits[1]的第1位,*/unsigned char *bits;
} huffman_code;

2.Huffman编码

(1)编码流程
 
(2)读入源文件
int main(int argc, char** argv)
{char memory = 0;char compress = 1;int opt;const char *file_in = NULL, *file_out = NULL;const char *file_out_table = NULL;FILE *in = stdin;FILE *out = stdout;FILE * outTable = NULL;/*获取命令行参数*/while((opt = getopt(argc, argv, "i:o:cdhvmt:")) != -1) {switch(opt){case 'i'://输入文件file_in = optarg;break;case 'o'://输出文件file_out = optarg;break;case 'c'://编码compress = 1;break;case 'd'://解码compress = 0;break;case 'h'://参数用法输出到屏幕usage(stdout);return 0;case 'v'://版本信息输出到屏幕version(stdout);return 0;case 'm':memory = 1;//对内存数据进行编码break;case 't'://编码结果输出file_out_table = optarg;           break;default:usage(stderr);return 1;}}/* If an input file is given then open it. */if(file_in){in = fopen(file_in, "rb");if(!in){fprintf(stderr,"Can't open input file '%s': %s\n",file_in, strerror(errno));return 1;}}/* If an output file is given then create it. */if(file_out){out = fopen(file_out, "wb");if(!out){fprintf(stderr,"Can't open output file '%s': %s\n",file_out, strerror(errno));//strerror函数返回指向错误原因字符串的指针,errno是错误代码,errno.h中包含很多错误定义return 1;}}if(file_out_table){outTable = fopen(file_out_table, "w");if(!outTable){fprintf(stderr,"Can't open output file '%s': %s\n",file_out_table, strerror(errno));return 1;}}if(memory)//memeory=1对内存数据进行编码,反之解码{return compress ?memory_encode_file(in, out) : memory_decode_file(in, out);}if(compress)//compress=1对文件数据进行编码,反之解码huffman_encode_file(in, out,outTable);elsehuffman_decode_file(in, out);if(in)fclose(in);if(out)fclose(out);if(outTable)fclose(outTable);return 0;
}

在解析命令行参数时用到getopt()函数。函数原型:int getopt(int nargc, char * const *nargv, const char* ostr)。参数nargc和nargv是由main()传递的参数个数和内容。第三个参数代表欲处理的选项字符串,i、o、t后加一个冒号是带值的参数,c、d、h、v、m后不带冒号是不带值的参数,变量optarg 即会指向这些额外参数,如果参数本身后面加两个冒号,则为可选值的参数。

strerror()函数, 头文件:#include <string.h>,原型:char * strerror(int errnum),strerror()用来依参数errnum 的错误代码来查询其错误原因的描述字符串, 然后将该字符串指针返回。在程序示例中,如果输入文件不存在,则输出:No such file or directory.

(3)文件编码函数,包含编码流程的具体操作
int
huffman_encode_file(FILE *in, FILE *out, FILE *out_Table)
{SymbolFrequencies sf;SymbolEncoder *se;huffman_node *root = NULL;int rc;unsigned int symbol_count;huffman_stat hs;//第一次扫描文件,获取每个信源符号在文件中的出现次数symbol_count = get_symbol_frequencies(&sf, in); huffST_getSymFrequencies(&sf,&hs,symbol_count);//计算每个信源符号出现的概率se = calculate_huffman_codes(&sf);//建立huffman树,编码表root = sf[0];//huffman树的根节点为sf[0];huffST_getcodeword(se, &hs);//获取码字output_huffman_statistics(&hs,out_Table);//以表格形式输出编码后各个信源符号的频率,码长及码字rewind(in);//回到文件开头,准备第二次扫描rc = write_code_table(out, se, symbol_count);//在输出文件中写入码表if(rc == 0)//写码表成功rc = do_file_encode(in, out, se);//对文件查表进行huffman编码,并写入输出文件/* Free the Huffman tree. */free_huffman_tree(root);free_encoder(se);return rc;
}

(4)编码过程所用到的函数

a.统计信源符号频数
#define MAX_SYMBOLS 256  //按字节编码,最多出现256个信源符号
typedef huffman_node* SymbolFrequencies[MAX_SYMBOLS];//数组中的每个元素都是指向一个Huffman节点的指针
typedef huffman_code* SymbolEncoder[MAX_SYMBOLS];//码字数组
static unsigned int
get_symbol_frequencies(SymbolFrequencies *pSF, FILE *in)
{int c;unsigned int total_count = 0;//扫描过的信源符号总数初始化为0;/* Set all frequencies to 0. */init_frequencies(pSF);//将所有信源符号的频数初始化为0;/* 统计输入文件中每个信源符号的频率 */while((c = fgetc(in)) != EOF)//第一次扫描文件{unsigned char uc = c;if(!(*pSF)[uc])(*pSF)[uc] = new_leaf_node(uc);//如果是新的信源符号,建立新的叶节点++(*pSF)[uc]->count;//如果是已经建立过叶节点的信源符号,当前符号出现的频数加1++total_count;//扫描过的信源符号总数加1}return total_count;//返回文件总的信源符号数
}
static huffman_node*
new_leaf_node(unsigned char symbol)//建立新的叶节点
{huffman_node *p = (huffman_node*)malloc(sizeof(huffman_node)); //开辟一个叶节点的空间p->isLeaf = 1;//表示当前节点为叶节点p->symbol = symbol;//表示该节点所存储的信源符号p->count = 0;//对应信源符号的出现频数初始化为0p->parent = 0;//该叶节点的父节点设为空return p;//返回指向该叶节点的指针
}
static void
init_frequencies(SymbolFrequencies *pSF)
{memset(*pSF, 0, sizeof(SymbolFrequencies));//把psF数组的每个元素都初始化为0
#if 0unsigned int i;for(i = 0; i < MAX_SYMBOLS; ++i){unsigned char uc = (unsigned char)i;(*pSF)[i] = new_leaf_node(uc);}
#endif
}

b.统计各个信源符号出现概率

int huffST_getSymFrequencies(SymbolFrequencies *SF, huffman_stat *st,int total_count)
{int i,count =0;for(i = 0; i < MAX_SYMBOLS; ++i){    if((*SF)[i]){st->freq[i]=(float)(*SF)[i]->count/total_count;//计算每个信源符号的出现概率count+=(*SF)[i]->count;//已经统计过的字节总数}else {st->freq[i]= 0;//该信源符号没有在文件中出现的过,频率为0}}if(count==total_count)return 1;//文件所有的信源符号都统计完毕elsereturn 0;
}

c.建立huffman树,编码表

static SymbolEncoder*
calculate_huffman_codes(SymbolFrequencies * pSF)
{unsigned int i = 0;unsigned int n = 0;huffman_node *m1 = NULL, *m2 = NULL;SymbolEncoder *pSE = NULL;#if 1printf("BEFORE SORT\n");print_freqs(pSF);   //演示堆栈的使用
#endif//按照信源符号出现的概率大小排序,小概率符号在前,排序的依据是SFCompqsort((*pSF), MAX_SYMBOLS, sizeof((*pSF)[0]), SFComp);#if 1   printf("AFTER SORT\n");print_freqs(pSF);
#endiffor(n = 0; n < MAX_SYMBOLS && (*pSF)[n]; ++n);  //获取当前文件中实际出现信源符号的种类总数,有可能没有到256种
//需要合并n-1次for(i = 0; i < n - 1; ++i){m1 = (*pSF)[0];m2 = (*pSF)[1];//m1,m2置为当前频率最小的两个信源符号(*pSF)[0] = m1->parent = m2->parent =new_nonleaf_node(m1->count + m2->count, m1, m2);//将m1,m2合并成新的节点加入到数组中,频数为m1,m2的频数之和(*pSF)[1] = NULL;//第二个节点设为空qsort((*pSF), n, sizeof((*pSF)[0]), SFComp);//建立新节点之后重新排序}//Huffman树建立完成/* Build the SymbolEncoder array from the tree. */pSE = (SymbolEncoder*)malloc(sizeof(SymbolEncoder));//为码字数组分配空间memset(pSE, 0, sizeof(SymbolEncoder));//初始化build_symbol_encoder((*pSF)[0], pSE);//计算每个信源符号的码字return pSE;
}

函数原型:void qsort( void *base, size_t num, size_t width, int(__cdecl*compare)(const void*,const void*)),参数依次为,数组的起始地址,数组的元素数,每个元素的大小,比较函数的指针。

static huffman_node*
new_nonleaf_node(unsigned long count, huffman_node *zero, huffman_node *one)//建立内部节点
{huffman_node *p = (huffman_node*)malloc(sizeof(huffman_node));//分配内部节点空间p->isLeaf = 0;//表示该节点不是叶节点,为内部节点p->count = count;//节点对应的频数p->zero = zero;//左子节点p->one = one;//右子节点p->parent = 0;//父节点为空return p;
}
static int
SFComp(const void *p1, const void *p2)//比较函数
{const huffman_node *hn1 = *(const huffman_node**)p1;const huffman_node *hn2 = *(const huffman_node**)p2;//两个比较的元素为节点if(hn1 == NULL && hn2 == NULL)return 0;//两个节点为空,返回相等if(hn1 == NULL)return 1;if(hn2 == NULL)return -1;//两者均不为空的情况下,比较两节点的计数countif(hn1->count > hn2->count)return 1;//节点1大,排在后面else if(hn1->count < hn2->count)return -1;//节点2大,排在后面return 0;
}
//递归遍历Huffman树
static void
build_symbol_encoder(huffman_node *subtree, SymbolEncoder *pSF)
{if(subtree == NULL)return;//如果已经到达root,则编码结束if(subtree->isLeaf)(*pSF)[subtree->symbol] = new_code(subtree);//如果是叶节点则进行编码,生成码字else{build_symbol_encoder(subtree->zero, pSF);build_symbol_encoder(subtree->one, pSF);//如果不是叶节点,则开始左访问子节点和右子节点}
}
//走到叶节点后,开始从叶节点爬到根节点生成码字的函数
static huffman_code*
new_code(const huffman_node* leaf)
{unsigned long numbits = 0;//码长unsigned char* bits = NULL;//指向码字首地址的指针huffman_code *p;while(leaf && leaf->parent)//leaf!=0表示当前字符存在,应该编码,leaf->parent!=0表示未到达根节点,编码未完成{huffman_node *parent = leaf->parent;unsigned char cur_bit = (unsigned char)(numbits % 8);//所编位在当前字节中的位置unsigned long cur_byte = numbits / 8;//到当前位为止占用的字节数if(cur_bit == 0)//如果所编位在当前字节中的位置为0,则需要增加一个字节来保存余下的码字{size_t newSize = cur_byte + 1;//新的字节数等于当前字节数加1bits = (char*)realloc(bits, newSize);//保持原有数据不变重新分配空间bits[newSize - 1] = 0; //新分配的一个字节初始化为0}if(leaf == parent->one)//如果叶节点为右子节点,把当前位置1bits[cur_byte] |= 1 << cur_bit;++numbits;//码长加1leaf = parent;//转移到父节点这一层,向上爬一级}
//回到根节点,编码完成if(bits)reverse_bits(bits, numbits);//码字逆序p = (huffman_code*)malloc(sizeof(huffman_code));//分配节点空间p->numbits = numbits;//码长p->bits = bits;//码字数组,需要与码长numbits配合才可得到真正的码字。return p;
}
static void
reverse_bits(unsigned char* bits, unsigned long numbits)
{unsigned long numbytes = numbytes_from_numbits(numbits);//获取码字所需的字节数unsigned char *tmp =(unsigned char*)alloca(numbytes);//分配空间,alloca是在栈(stack)上申请空间,用完马上就释放.unsigned long curbit;//当前位码字在字节中的位置,即该码字存在字节的哪一位long curbyte = 0;//当前位码字在bit数组中字节位置,即该码字存在哪个字节memset(tmp, 0, numbytes);for(curbit = 0; curbit < numbits; ++curbit){unsigned int bitpos = curbit % 8;if(curbit > 0 && curbit % 8 == 0)++curbyte;//判断当前位到字节的哪一位,满8位后要增加一个字节继续存储码字//从高位到低位获取码字的每一位,并移动到正确的位置,暂存在tmp数组中 tmp[curbyte] |= (get_bit(bits, numbits - curbit - 1) << bitpos);}memcpy(bits, tmp, numbytes);
}
//获取码字的第i位的函数,把i/8 字节的第 i%8 位移到字节的最低位,再和1相与
static unsigned char
get_bit(unsigned char* bits, unsigned long i)
{return (bits[i / 8] >> i % 8) & 1;
}
在这里增加reverse_bits()函数的原因?
举例说明从根节点到叶节点的序列为1100 0111 1,生成码字时从叶爬到根,从低位向高位逐位存储存在bit数组中,bit[0]=1000  1111 ,bit[1]=0000 0001;
正确的码字顺序为1111 0001 1,从低位到高位存在bit数组中,bit[0]= 1110 0011,bit[1]=0000 0001,reverse_bits()函数的作用就是逆转码字的存储顺序,方便读出正确的码字。

d.以表格形式输出编码的结果,包括信源符号、概率、码长、码字

void output_huffman_statistics(huffman_stat *st,FILE *out_Table)
{int i,j;unsigned char c;fprintf(out_Table,"symbol\t   freq\t   codelength\t   code\n");for(i = 0; i < MAX_SYMBOLS; ++i){   fprintf(out_Table,"%d\t   ",i);fprintf(out_Table,"%f\t   ",st->freq[i]);fprintf(out_Table,"%d\t    ",st->numbits[i]);if(st->numbits[i]){for(j = 0; j < st->numbits[i]; ++j){c =get_bit(st->bits[i], j);fprintf(out_Table,"%d",c);}}fprintf(out_Table,"\n");}
}

e.写码表

static int
write_code_table(FILE* out, SymbolEncoder *se, unsigned int symbol_count)
{unsigned long i, count = 0;//获取se中存储的实际码字种类for(i = 0; i < MAX_SYMBOLS; ++i){if((*se)[i])++count;}i = htonl(count);   //htonl就是把本机字节顺序转化为网络字节顺序,即大尾序,这里作一个转换,方便文件可以从左到右读数据//在网络传输中,采用big-endian序,对于0x0A0B0C0D ,传输顺序就是0A 0B 0C 0D ,//因此big-endian作为network byte order,little-endian作为host byte order。//little-endian的优势在于unsigned char/short/int/long类型转换时,存储位置无需改变if(fwrite(&i, sizeof(i), 1, out) != 1)return 1;/* Write the number of bytes that will be encoded. */symbol_count = htonl(symbol_count);if(fwrite(&symbol_count, sizeof(symbol_count), 1, out) != 1)return 1;//写码表,码表的内容包括信源符号,码长和码字for(i = 0; i < MAX_SYMBOLS; ++i){huffman_code *p = (*se)[i];if(p){unsigned int numbytes;fputc((unsigned char)i, out);//写符号fputc(p->numbits, out);//写码长numbytes = numbytes_from_numbits(p->numbits);//获取该符号的码字所需要的字节数if(fwrite(p->bits, 1, numbytes, out) != numbytes)//写码字return 1;}}return 0;
}

(5)第二次扫描,对源文件进行编码输出

static int
do_file_encode(FILE* in, FILE* out, SymbolEncoder *se)
{unsigned char curbyte = 0;unsigned char curbit = 0;int c;while((c = fgetc(in)) != EOF)//第二次扫描文件,遍历文件的每一个字符{unsigned char uc = (unsigned char)c;huffman_code *code = (*se)[uc];//查表unsigned long i;//将码字写入文件for(i = 0; i < code->numbits; ++i){curbyte |= get_bit(code->bits, i) << curbit;//获取码字的每一位,并放到编码字节的位置//可能存在某些信源符号的码字的长度不满一个字节,这个时候继续编码,直到满一个字节为止再写入文件if(++curbit == 8){fputc(curbyte, out);curbyte = 0;curbit = 0;}}}//如果最后一个信源符号编码完成后不满足一个字节,则直接写入文件if(curbit > 0)fputc(curbyte, out);return 0;
}

3.Huffman解码

(1)解码流程
(2)读码表
static huffman_node*
read_code_table(FILE* in, unsigned int *pDataBytes)
{huffman_node *root = new_nonleaf_node(0, NULL, NULL);unsigned int count;//读取countif(fread(&count, sizeof(count), 1, in) != 1){free_huffman_tree(root);return NULL;}
//因为count是以大端模式存储,所以这里用ntohl函数把它转换为小端模式count = ntohl(count);//读取文件的总字节数if(fread(pDataBytes, sizeof(*pDataBytes), 1, in) != 1){free_huffman_tree(root);return NULL;}
//改变存储模式为小端*pDataBytes = ntohl(*pDataBytes);//读取码表,码表的每一项包括信源符号,码长,码字while(count-- > 0){int c;unsigned int curbit;unsigned char symbol;unsigned char numbits;unsigned char numbytes;unsigned char *bytes;huffman_node *p = root;if((c = fgetc(in)) == EOF)//读取信源符号{free_huffman_tree(root);return NULL;}symbol = (unsigned char)c;if((c = fgetc(in)) == EOF)//读取码长{free_huffman_tree(root);return NULL;}numbits = (unsigned char)c;//获取该码字所需要的字节数并分配空间numbytes = (unsigned char)numbytes_from_numbits(numbits);bytes = (unsigned char*)malloc(numbytes);if(fread(bytes, 1, numbytes, in) != numbytes)//读码字{free(bytes);free_huffman_tree(root);return NULL;}//读取当前码字的每一位,建立由根节点到叶节点的huffman树for(curbit = 0; curbit < numbits; ++curbit){if(get_bit(bytes, curbit))//判断当前码字的读取位是否为1{//读取位为1if(p->one == NULL)//如果没有右子节点,则新建一个右子节点{p->one = curbit == (unsigned char)(numbits - 1)? new_leaf_node(symbol): new_nonleaf_node(0, NULL, NULL);//如果当前位是码字的最后一位则建立叶节点,否则建立内部节点p->one->parent = p;//1的一支的父节点指向当前节点}p = p->one;//把右子节点当做新的父节点,沿着1的方向向下移一级}else{//读取位为0if(p->zero == NULL)//如果没有左子节点,则新建一个左子节点{p->zero = curbit == (unsigned char)(numbits - 1)? new_leaf_node(symbol): new_nonleaf_node(0, NULL, NULL);//同右子节点的建立p->zero->parent = p;}p = p->zero;//把左子节点当做新的父节点,沿着0的方向向下移一级}}free(bytes);}return root;//返回根节点
}

(3)解码

int
huffman_decode_file(FILE *in, FILE *out)
{huffman_node *root, *p;int c;unsigned int data_count;root = read_code_table(in, &data_count);//读码表,建huffman树if(!root)return 1;//树建立失败p = root;//开始解码,每次读取一个字节,data_count > 0表示逻辑上仍有数据,(c = fgetc(in)) != EOF表示文件中还有数据while(data_count > 0 && (c = fgetc(in)) != EOF){unsigned char byte = (unsigned char)c;//一个字节的码字unsigned char mask = 1;//用于逐位读取码字while(data_count > 0 && mask){p = byte & mask ? p->one : p->zero;//沿着树前进mask <<= 1;//准备读取下一位if(p->isLeaf)//如果走到叶节点{fputc(p->symbol, out);//输出对应的信源符号p = root;//返回根节点,准备读取下一个码字--data_count;//还未解码的符号数减1}}}free_huffman_tree(root);return 0;
}

三.实验结果分析

以doc类型文件为例,依次输出符号,频率,码长和码字。概率较大的符号明显码字长度要比概率小的符号短很多。
以下有10种类型文件
结果分析
各种类型文件的符号概率分布图
由图表可见,Huffman编码对于符号概率分布越均匀的文件,压缩效率越低。比如rm,AVI文件概率分布较均匀,压缩比接近1。对于符号概率分布不均匀的文件,压缩效果越好。比如bmp文件,压缩比达到1.4.。表格中信源熵总是小于等于平均码长,实际上,信源熵是平均码长的下限。

Huffman编解码相关推荐

  1. 多媒体技术与应用之图像Huffman编解码

    多媒体技术与应用之图像Huffman编解码 一.实验内容 1.了解BMP图像的格式,实现BMP图片格式的数据域及文件头的分离 2.熟悉Huffman编码原理 3.使用Huffman编码算法对给定图像文 ...

  2. 实验三 Huffman编解码算法实现与压缩效率分析

    一.Huffman编解码原理 1. Huffman编码 对原始文件进行Huffman编码,首先需要解决以下几点问题: 文件符号的概率分布情况是怎样的? Huffman树是如何建立的? 建立起Huffm ...

  3. Huffman编解码完全注释

    Huffman编解码完全注释 /** huffman - Encode/Decode files using Huffman encoding.* Copyright (C) 2003 Douglas ...

  4. 数据压缩 实验三 Huffman编解码算法实现与压缩效率分析

    实验目的 掌握Huffman编解码实现的数据结构和实现框架, 进一步熟练使用C编程语言, 并完成压缩效率的分析. 实验原理 1.本实验中Huffman编码算法 (1)将文件以ASCII字符流的形式读入 ...

  5. huffman编解码算法实验与压缩效率分析

    一.基本原理 1.huffman编码原理 huffman编码是一种无失真编码方式,是可变长(VLC)编码的一种. huffman编码基于信源的概率统计模型,基本思路是出现概率大的信源符号编长码,出现概 ...

  6. 实验三—Huffman编解码

    一.实验原理 1.Huffman编码的步骤: (1)首先将所有字符发生的概率从小到大进行排序: (2)将最小的两个概率进行两两一合并,之后继续找最小的两个概率进行合并包括前面已经合并的和数: (3)一 ...

  7. Huffman编解码实现文本压缩

    编码方案设计: 实现最基本的哈夫曼编码,对文件进行两次扫描,第一次统计概率,第二次进行编码.压缩和解压缩时分别重新建立树,重新编码,以减小压缩后文件大小. 系统实现方案: typedef struct ...

  8. 数据压缩实验三--Huffman编解码及压缩率的比较

    一,Huffman码 1 Huffman 编码 Huffman Coding (霍夫曼编码)是一种无失真编码的编码方式,Huffman编码是可变字长编码(VLC)的一种. Huffman 编码基于信源 ...

  9. 数据压缩原理 实验三 Huffman编解码算法实现与压缩效率分析

    实验原理 Huffman编码是一种无失真编码方式,是一种可变长编码,它将出现概率大的信源符号短编码,出现概率小的信源符号长编码. 编码步骤: ①将文件以ASCII字符流的形式读入,统计每个符号的发生概 ...

最新文章

  1. 你所不知道的Python奇技淫巧
  2. mysql主从字符集不一致_MySQL多字节字符集造成主从数据不一致问题
  3. 初识tmux---编译安装tmux
  4. 动态调用WebService方法
  5. The Genymotion virtual device could not obtain an IP address
  6. 动态规划_数字三角形
  7. CCF201912-2 回收站选址(100分)【序列处理】
  8. 腾讯产品经理能力模型
  9. schedule-调度器
  10. 用python输入三角形边长_python实现输入三角形边长自动作图求面积案例
  11. 多目标粒子群 无功优化matlab。采用天牛须改进多目标粒子群算法求解含有sst的无功优化程序,程序采用交替迭代法,以网损和电压偏差为目标
  12. 校园二手交易平台-程序流程图
  13. 数据通信是计算机网络最基本功能,计算机网络最基本的功能是数据通信和()。A.打印文件B.文件调用C.降低成本D.资源共享...
  14. OUC_SE_Group04_Blog2
  15. MySQL查询和删除重复记录
  16. LYNC2010两台虚机部署笔记2
  17. RDKit中的分子3D构象生成
  18. 初学安卓之二维码的简单实现,android开发网上购物app
  19. 【tio-websocket】3、tio-websocket-server实现任何地方都能对用户发送消息
  20. 用java获取一维数组的平均值_java中一维数组常见运算

热门文章

  1. 阿里云ACP认证之云服务器ECS知识整理(考题占比 31%)
  2. ftp服务器连接时间太长(耗时20s或40s)问题解决(超详细图文教程)
  3. 最好用的jpg转pdf软件
  4. 计算机安全意识小故事,求一篇关于消防救火的事迹,消防安全小故事
  5. 2018年38种自由职业大盘点
  6. Java线程等待唤醒机制(加深理解)
  7. Python selenium —— 一定要会用selenium的等待,三种等待方式解读
  8. 详解typora配置华为云图床
  9. 编写一个程序,输入一个句子,然后统计出这个句子当中不同的单词个数。
  10. 大数据24小时:九章云极宣布获近亿元B轮融资,我国成功研发智能辅助驾驶系统