大家好,欢迎来到IT知识分享网。
一、LZW简介
二、LZW编解码算法及举例
1、LZW编码算法思想
举例:输入流 a b b a b a b a c
首先,以ASCII码为基础词典
已编码:a b
- 遇到a,用97表示,编码为97。
- 遇到b,用98表示,编码为98。
- 发现ab,加入词典,ab索引为256。
已编码:a b b a b
- 遇到b,编码为98,发现bb,加入词典,索引为257。
- 遇到a,发现ba,加入词典,索引为258。
- 遇到b,ab已存在词典内,编码为256。
已编码:a b b a b a b a c
- 遇到a,发现aba,加入词典,索引为259。
- 遇到b,ab已存在词典。
- 遇到a, aba已存在词典内,编码为259。(这里刚加入词典就用到了)
- 遇到c,编码为99,发现abac,加入词典,索引为260。
- 编码结束,编码码流和输入码流对比为:
a b b a b a b a c 97 98 98 256 259 99
2、LZW解码算法思想
97 98 98 256 259 99
- 遇到97,在基础词典,解码为a。
- 遇到98,在基础词典,解码为b,发现ab,可知256对应ab。
- 遇到98,在基础词典,解码为b,发现bb,可知257对应bb。
- 遇到256,解码为ab,发现ba,可知258对应ba。
已编码:a b b a b
- 遇到259,目前词典没有259,为什么会发生这种情况?
只有当码字刚加入词典就被用于编码时,才会出现这一情况,则下一个字符(这里的第六个字符)与上一个码字构成新加入词典的码字,并与后面字符共同编码为这一码字。故而这一字符,与上一个码字的头个字符相同。 - 259解码为aba(“ab”+“a”)。
- 遇到99,在基础词典,解码为c。
- 解码结束。
已解码:a b b a b a b a c
三、LZW编码算法实现
1、数据结构
struct {
//词典节点结构体 int suffix; //后缀字符 int parent, firstchild, nextsibling;//母节点、第一个孩子节点、兄弟节点 } dictionary[MAX_CODE+1]; //数组下标为编码
typedef struct{
//二进制文件结构体 FILE *fp;//输出文件指针 unsigned char mask;//掩码 int rack;//缓存,每写入8位,写入rack }BITFILE;
2、编码代码分析
(1)编码总流程
void LZWEncode( FILE *fp, BITFILE *bf){
int character;//字符 int string_code;//前缀 int index;//编码 unsigned long file_length;//文件大小 fseek( fp, 0, SEEK_END);//文件指针定位到文件最后 file_length = ftell( fp);//输入文件大小 fseek( fp, 0, SEEK_SET);//文件指针定位到文件起始 BitsOutput( bf, file_length, 4*8);//将输入文件大小写入输出文件。32位表示文件大小 InitDictionary();//词典初始化 string_code = -1;//前缀初始化 while( EOF!=(character=fgetc( fp))){
//依次扫描输入文件,取出各字符 index = InDictionary( character, string_code);//判断码字string+character是否在词典中 if( 0<=index){
// string+character在词典中 string_code = index;//将string+character对应编码作为前缀 }else{
// string+character不在词典中 output( bf, string_code);//输出前缀 if( MAX_CODE > next_code){
// 若词典有剩余空间 // 将string+character加入词典 AddToDictionary( character, string_code); } string_code = character;//将新字符作为新的前缀 } } output( bf, string_code);//文件扫描完毕,将最后未输出的前缀输出 }
(2)词典初始化
void InitDictionary( void){
//词典初始化即将0-255根节点初始化 int i; for( i=0; i<256; i++){
//下标为ASCII码值 dictionary[i].suffix = i;//根的后缀字符为对应ASCII码 dictionary[i].parent = -1;//没有母节点 dictionary[i].firstchild = -1;//暂时没有第一个孩子节点 dictionary[i].nextsibling = i+1;//下一个兄弟节点下标为下一个ASCII码值 } dictionary[255].nextsibling = -1;//最后一个根节点没有下一个兄弟节点 next_code = 256;//下一个编码为256 }
(3)判断码字是否在词典中
int InDictionary( int character, int string_code){
//判断码字string+character是否在词典中 string_code前缀 character后缀 int sibling; if( 0>string_code) return character;//文件第一个字符,故而编码为character的ASCII码值 /*自左向右遍历string_code节点的所有孩子(第一个孩子的所有兄弟)*/ sibling = dictionary[string_code].firstchild;//string_code节点的第一个孩子 while( -1<sibling){
//sibling=-1时说明所有兄弟遍历结束 if( character == dictionary[sibling].suffix) return sibling;//若找到兄弟节点的后缀是character,则返回此节点的编码即下标sibling sibling = dictionary[sibling].nextsibling;//若该兄弟节点后缀不是character,则寻找下一个兄弟节点 } return -1;//若遍历所有兄弟节点的后缀后,都找不到该字符,说明string+character不在字典中,返回-1 }
(4)将码字添加到词典中
void AddToDictionary( int character, int string_code){
//码字不在词典,添加进词典中,并编码为next_code int firstsibling, nextsibling; if( 0>string_code) return; dictionary[next_code].suffix = character;//新节点的后缀为该字符 dictionary[next_code].parent = string_code;//新节点的母亲节点为该前缀 dictionary[next_code].nextsibling = -1;//新节点下一个兄弟节点暂不存在 dictionary[next_code].firstchild = -1;//新节点的第一个孩子节点暂不存在 firstsibling = dictionary[string_code].firstchild;//新节点的母亲节点的第一个孩子 /*设置新节点的兄弟关系*/ if( -1<firstsibling){
// 若新节点的母亲节点原本有孩子 nextsibling = firstsibling; while( -1<dictionary[nextsibling].nextsibling ) //循环找到该母亲节点的最后一个孩子即新节点的最后一个兄弟 nextsibling = dictionary[nextsibling].nextsibling; dictionary[nextsibling].nextsibling = next_code;//将新节点设为最后一个兄弟的下一个兄弟 }else{
// 若新节点的母亲节点原本没有孩子 dictionary[string_code].firstchild = next_code;//则新节点是母亲节点的第一个孩子 } next_code ++;//下一个编码增加1 }
(5)打开二进制文件
BITFILE *OpenBitFileOutput( char *filename){
//输出文件名 BITFILE *bf; bf = (BITFILE *)malloc( sizeof(BITFILE)); if( NULL == bf) return NULL; if( NULL == filename) bf->fp = stdout;//如果参数为NULL,则指向屏幕 else bf->fp = fopen( filename, "wb");//以二进制只写的方式打开文件 if( NULL == bf->fp) return NULL; bf->mask = 0x80;//掩码为 bf->rack = 0;//缓存为0 return bf; }
(6)按位输出数据到输出文件
void BitsOutput( BITFILE *bf, unsigned long code, int count){
unsigned long mask; /*计算掩码值,位数为count的数值且最高位为1,例如若count为16,则mask=1000 0000 0000 0000*/ mask = 1L << (count-1); while( 0 != mask){
//mask为0时,说明code的count位数字输出完毕,注意:LZW是等长码 BitOutput( bf, (int)(0==(code&mask)?0:1));//按位输出code mask >>= 1;//掩码向右移位 } }
void BitOutput( BITFILE *bf, int bit){
/*若bite=1,则本code输出结束,此时mask为1,rack为0,则缓存rack变为1 若bit=0,则尚未输出结束,此时mask向右移位,为0,rack不变*/ if( 0 != bit) bf->rack |= bf->mask; bf->mask >>= 1;//mask向右移1位 /*若mask溢出为0,则表示成功累计写入8位,则直接输出rack, 并将rack初始化为0,mask初始化为1000 0000*/ if( 0 == bf->mask){
// eight bits in rack fputc( bf->rack, bf->fp); bf->rack = 0; bf->mask = 0x80; } }
(7)关闭二进制输出文件
void CloseBitFileOutput( BITFILE *bf){
// 输出剩余的二进制数 if( 0x80 != bf->mask) fputc( bf->rack, bf->fp); fclose( bf->fp);//关闭二进制输出文件 free( bf);//释放内存 }
3、以文本文件为输入,得到输出的LZW编码文件
四、LZW解码算法实现
1、解码代码编写与分析
(1)得到输入文件数据
以count位为单位得到输入文件中的数据
unsigned long BitsInput( BITFILE *bf, int count){
unsigned long mask;//掩码 unsigned long value;// /*计算掩码值,位数为count的数值且最高位为1,例如若count为16,则mask=1000 0000 0000 0000*/ mask = 1L << (count-1); value = 0L;//初始化value为0 /*mask由1000 0000 0000 0000向右移位至溢出为0时循环结束,取得16位的等长码*/ while( 0!=mask){
/*输出文件下一位为1时,value相应位变为1,为0时不作变化,遍历下一位*/ if( 1 == BitInput( bf)) value |= mask;//value在mask对应位置的二进制数字变为1 mask >>= 1;//mask向右移动一位 } return value; }
逐位得到输出文件中的二进制数字(从右向左)
int BitInput( BITFILE *bf){
int value; if( 0x80 == bf->mask){
//当mask为1000 0000时,从文件中得到一字节数据 bf->rack = fgetc( bf->fp); if( EOF == bf->rack){
//若成功得到数据 fprintf(stderr, "Read after the end of file reached\n");//输出 exit( -1); } } value = bf->mask & bf->rack;//取得mask掩码所表示的位置所在处的二进制数字 bf->mask >>= 1;//mask向右移动一位 if( 0==bf->mask) bf->mask = 0x80;//若溢出为0,则mask重新初始化为1000 0000 return( (0==value)?0:1);//value为0则返回0,为其它数字则返回1 }
(2)解码总流程
void LZWDecode( BITFILE *bf, FILE *fp){
//需填写 int character;//新/旧编码首字母 int new_code;//新编码 int last_code = -1;//起初旧编码空缺,初始化为-1 int string_length;//输出字符串长度 unsigned long file_length;//输出文件大小 file_length = BitsInput(bf, 4*8);//输入文件起始处为输出文件大小,共32位 if (-1 == file_length) file_length = 0;//若文件无内容,大小为0 InitDictionary();//词典初始化,设置0-255的根节点 while (file_length > 0) {
//若读取到最后一个编码,结束循环 new_code = input(bf);//16位读取新编码 if (new_code >= next_code) {
//若字典中没有新编码 d_stack[0] = character;//character为旧编码last_code的首字母,将其放置在数组d_stack[0]首位 /*遍历旧编码last_code所在树,将last_code对应字符串放入d_stack,前缀在栈底,后缀在d_stack[1]。返回字符串长度*/ string_length = DecodeString(1, last_code); } else {
//若字典中已有新编码 /*遍历新编码new_code所在树,将new_code对应字符串放入d_stack,前缀在栈底,后缀在d_stack[0]。返回字符串长度*/ string_length = DecodeString(0, new_code); } /*若新读取的编码不存在于词典中,character为旧编码last_code的首字母 若新读取的编码存在于词典中,character为新编码new_code的首字母*/ character = d_stack[string_length - 1]; /*若新读取的编码不存在于词典中,输出旧编码last_code对应字符串+last_code的首字母 若新读取的编码存在于词典中,输出新编码new_code对应字符串*/ while (string_length > 0) {
string_length--; fputc(d_stack[string_length], fp); file_length--; } /*若新读取的编码不存在于词典中,将last_code对应字符串+last_code的首字母添加到词典 若新读取的编码存在于词典中,将last_code对应字符串+new_code的首字母添加到词典*/ if (MAX_CODE > next_code) {
AddToDictionary(character, last_code); } last_code = new_code;//新编码变为旧编码 } }
(3)将字符串放入d_stack,得到解码后字符串长度
int DecodeString( int start, int code){
//需填充 int count = start;//d_stack下标 while (code >= 0) {
//code=-1时到达根节点循环结束 d_stack[count] = dictionary[code].suffix;//下标为code的节点中的后缀字母放置数组对应位置,这里是d_stack[0/1] code = dictionary[code].parent;//节点上移至母节点 count++;//字符数增加1 } return count;//返回字符串长度 }
(4)当前码字在词典中不存在时应如何处理
2、以之前得到的LZW编码文件作为输入,得到输出的解码文件
五、LZW压缩效率
附件:完整代码
1、lzw.c
#include <stdlib.h> #include <stdio.h> #include "bitio.h" #define MAX_CODE 65535 struct {
//词典节点结构体 int suffix; //后缀字符 int parent, firstchild, nextsibling;//母节点、第一个孩子节点、兄弟节点 } dictionary[MAX_CODE+1]; //数组下标为编码 int next_code; int d_stack[MAX_CODE]; // 解码时每一个字符串可存放在d_stack #define input(f) ((int)BitsInput( f, 16))//16位写入 #define output(f, x) BitsOutput( f, (unsigned long)(x), 16)//16位输出 int DecodeString( int start, int code); void InitDictionary( void); void PrintDictionary( void){
//输出词典 int n; int count; for( n=256; n<next_code; n++){
count = DecodeString( 0, n); printf( "%4d->", n); while( 0<count--) printf("%c", (char)(d_stack[count])); printf( "\n"); } } int DecodeString( int start, int code){
//需填充 int count = start;//d_stack下标 while (code >= 0) {
//code=-1时到达根节点循环结束 d_stack[count] = dictionary[code].suffix;//下标为code的节点中的后缀字母放置数组对应位置,这里是d_stack[0/1] code = dictionary[code].parent;//节点上移至母节点 count++;//字符数增加1 } return count;//返回字符串长度 } void InitDictionary( void){
//词典初始化即将0-255根节点初始化 int i; for( i=0; i<256; i++){
//下标为ASCII码值 dictionary[i].suffix = i;//根的后缀字符为对应ASCII码 dictionary[i].parent = -1;//没有母节点 dictionary[i].firstchild = -1;//暂时没有第一个孩子节点 dictionary[i].nextsibling = i+1;//下一个兄弟节点下标为下一个ASCII码值 } dictionary[255].nextsibling = -1;//最后一个根节点没有下一个兄弟节点 next_code = 256;//下一个编码为256 } /* * Input: string represented by string_code in dictionary, * Output: the index of character+string in the dictionary * index = -1 if not found */ int InDictionary( int character, int string_code){
//判断码字string+character是否在词典中 string_code前缀 character后缀 int sibling; if( 0>string_code) return character;//文件第一个字符,故而编码为character的ASCII码值 /*自左向右遍历string_code节点的所有孩子(第一个孩子的所有兄弟)*/ sibling = dictionary[string_code].firstchild;//string_code节点的第一个孩子 while( -1<sibling){
//sibling=-1时说明所有兄弟遍历结束 if( character == dictionary[sibling].suffix) return sibling;//若找到兄弟节点的后缀是character,则返回此节点的编码即下标sibling sibling = dictionary[sibling].nextsibling;//若该兄弟节点后缀不是character,则寻找下一个兄弟节点 } return -1;//若遍历所有兄弟节点的后缀后,都找不到该字符,说明string+character不在字典中,返回-1 } void AddToDictionary( int character, int string_code){
//码字不在词典,添加进词典中,并编码为next_code int firstsibling, nextsibling; if( 0>string_code) return; dictionary[next_code].suffix = character;//新节点的后缀为该字符 dictionary[next_code].parent = string_code;//新节点的母亲节点为该前缀 dictionary[next_code].nextsibling = -1;//新节点下一个兄弟节点暂不存在 dictionary[next_code].firstchild = -1;//新节点的第一个孩子节点暂不存在 firstsibling = dictionary[string_code].firstchild;//新节点的母亲节点的第一个孩子 /*设置新节点的兄弟关系*/ if( -1<firstsibling){
// 若新节点的母亲节点原本有孩子 nextsibling = firstsibling; while( -1<dictionary[nextsibling].nextsibling ) //循环找到该母亲节点的最后一个孩子即新节点的最后一个兄弟 nextsibling = dictionary[nextsibling].nextsibling; dictionary[nextsibling].nextsibling = next_code;//将新节点设为最后一个兄弟的下一个兄弟 }else{
// 若新节点的母亲节点原本没有孩子 dictionary[string_code].firstchild = next_code;//则新节点是母亲节点的第一个孩子 } next_code ++;//下一个编码增加1 } void LZWEncode( FILE *fp, BITFILE *bf){
int character;//字符 int string_code;//前缀 int index;//编码 unsigned long file_length;//文件大小 fseek( fp, 0, SEEK_END);//文件指针定位到文件最后 file_length = ftell( fp);//输入文件大小 fseek( fp, 0, SEEK_SET);//文件指针定位到文件起始 BitsOutput( bf, file_length, 4*8);//将输入文件大小写入输出文件。32位表示文件大小 InitDictionary();//词典初始化 string_code = -1;//前缀初始化 while( EOF!=(character=fgetc( fp))){
//依次扫描输入文件,取出各字符 index = InDictionary( character, string_code);//判断码字string+character是否在词典中 if( 0<=index){
// string+character在词典中 string_code = index;//将string+character对应编码作为前缀 }else{
// string+character不在词典中 output( bf, string_code);//输出前缀 if( MAX_CODE > next_code){
// 若词典有剩余空间 // 将string+character加入词典 AddToDictionary( character, string_code); } string_code = character;//将新字符作为新的前缀 } } output( bf, string_code);//文件扫描完毕,将最后未输出的前缀输出 } void LZWDecode( BITFILE *bf, FILE *fp){
//需填写 int character;//新/旧编码首字母 int new_code;//新编码 int last_code = -1;//起初旧编码空缺,初始化为-1 int string_length;//输出字符串长度 unsigned long file_length;//输出文件大小 file_length = BitsInput(bf, 4*8);//输入文件起始处为输出文件大小,共32位 if (-1 == file_length) file_length = 0;//若文件无内容,大小为0 InitDictionary();//词典初始化,设置0-255的根节点 while (file_length > 0) {
//若读取到最后一个编码,结束循环 new_code = input(bf);//16位读取新编码 if (new_code >= next_code) {
//若字典中没有新编码 d_stack[0] = character;//character为旧编码last_code的首字母,将其放置在数组d_stack[0]首位 /*遍历旧编码last_code所在树,将last_code对应字符串放入d_stack,前缀在栈底,后缀在d_stack[1]。返回字符串长度*/ string_length = DecodeString(1, last_code); } else {
//若字典中已有新编码 /*遍历新编码new_code所在树,将new_code对应字符串放入d_stack,前缀在栈底,后缀在d_stack[0]。返回字符串长度*/ string_length = DecodeString(0, new_code); } /*若新读取的编码不存在于词典中,character为旧编码last_code的首字母 若新读取的编码存在于词典中,character为新编码new_code的首字母*/ character = d_stack[string_length - 1]; /*若新读取的编码不存在于词典中,输出旧编码last_code对应字符串+last_code的首字母 若新读取的编码存在于词典中,输出新编码new_code对应字符串*/ while (string_length > 0) {
string_length--; fputc(d_stack[string_length], fp); file_length--; } /*若新读取的编码不存在于词典中,将last_code对应字符串+last_code的首字母添加到词典 若新读取的编码存在于词典中,将last_code对应字符串+new_code的首字母添加到词典*/ if (MAX_CODE > next_code) {
AddToDictionary(character, last_code); } last_code = new_code;//新编码变为旧编码 } } int main( int argc, char **argv){
FILE *fp; BITFILE *bf; if( 4>argc){
fprintf( stdout, "usage: \n%s <o> <ifile> <ofile>\n", argv[0]); fprintf( stdout, "\t<o>: E or D reffers encode or decode\n"); fprintf( stdout, "\t<ifile>: input file name\n"); fprintf( stdout, "\t<ofile>: output file name\n"); return -1; } if( 'E' == argv[1][0]){
// LZW编码 fp = fopen( argv[2], "rb");//输入文件名 bf = OpenBitFileOutput( argv[3]);//输出文件名 if( NULL!=fp && NULL!=bf){
LZWEncode( fp, bf); fclose( fp); CloseBitFileOutput( bf); fprintf( stdout, "encoding done\n"); } }else if( 'D' == argv[1][0]){
// LZW解码 bf = OpenBitFileInput( argv[2]);//输入文件名 fp = fopen( argv[3], "wb");//输出文件名 if( NULL!=fp && NULL!=bf){
LZWDecode( bf, fp); fclose( fp); CloseBitFileInput( bf); fprintf( stdout, "decoding done\n"); } }else{
fprintf( stderr, "not supported operation\n"); } return 0; }
2、bitio.h
#ifndef __BITIO__ #define __BITIO__ #include <stdio.h> typedef struct{
//二进制文件结构体 FILE *fp;//输出文件指针 unsigned char mask;//掩码 int rack;//缓存,每写入8位,写入rack }BITFILE; BITFILE *OpenBitFileInput( char *filename); BITFILE *OpenBitFileOutput( char *filename); void CloseBitFileInput( BITFILE *bf); void CloseBitFileOutput( BITFILE *bf); int BitInput( BITFILE *bf); unsigned long BitsInput( BITFILE *bf, int count); void BitOutput( BITFILE *bf, int bit); void BitsOutput( BITFILE *bf, unsigned long code, int count); #endif // __BITIO__
3、bitio.c
#include <stdlib.h> #include <stdio.h> #include "bitio.h" BITFILE *OpenBitFileInput( char *filename){
BITFILE *bf; bf = (BITFILE *)malloc( sizeof(BITFILE)); if( NULL == bf) return NULL; if( NULL == filename) bf->fp = stdin; else bf->fp = fopen( filename, "rb"); if( NULL == bf->fp) return NULL; bf->mask = 0x80; bf->rack = 0; return bf; } BITFILE *OpenBitFileOutput( char *filename){
//输出文件名 BITFILE *bf; bf = (BITFILE *)malloc( sizeof(BITFILE)); if( NULL == bf) return NULL; if( NULL == filename) bf->fp = stdout;//如果参数为NULL,则指向屏幕 else bf->fp = fopen( filename, "wb");//以二进制只写的方式打开文件 if( NULL == bf->fp) return NULL; bf->mask = 0x80;//掩码为 bf->rack = 0;//缓存为0 return bf; } void CloseBitFileInput( BITFILE *bf){
fclose( bf->fp); free( bf); } void CloseBitFileOutput( BITFILE *bf){
// 输出剩余的二进制数 if( 0x80 != bf->mask) fputc( bf->rack, bf->fp); fclose( bf->fp);//关闭二进制输出文件 free( bf);//释放内存 } int BitInput( BITFILE *bf){
int value; if( 0x80 == bf->mask){
//当mask为1000 0000时,从文件中得到一字节数据 bf->rack = fgetc( bf->fp); if( EOF == bf->rack){
//若成功得到数据 fprintf(stderr, "Read after the end of file reached\n");//输出 exit( -1);//非正常运行导致退出程序 } } value = bf->mask & bf->rack;//取得mask掩码所表示的位置所在处的二进制数字 bf->mask >>= 1;//mask向右移动一位 if( 0==bf->mask) bf->mask = 0x80;//若溢出为0,则mask重新初始化为1000 0000 return( (0==value)?0:1);//value为0则返回0,为其它数字则返回1 } unsigned long BitsInput( BITFILE *bf, int count){
unsigned long mask;//掩码 unsigned long value;// /*计算掩码值,位数为count的数值且最高位为1,例如若count为16,则mask=1000 0000 0000 0000*/ mask = 1L << (count-1); value = 0L;//初始化value为0 /*mask由1000 0000 0000 0000向右移位至溢出为0时循环结束,取得16位的等长码*/ while( 0!=mask){
/*输出文件下一位为1时,value相应位变为1,为0时不作变化,遍历下一位*/ if( 1 == BitInput( bf)) value |= mask;//value在mask对应位置的二进制数字变为1 mask >>= 1;//mask向右移动一位 } return value; } void BitOutput( BITFILE *bf, int bit){
/*若bite=1,则本code输出结束,此时mask为1,rack为0,则缓存rack变为1 若bit=0,则尚未输出结束,此时mask向右移位,为0,rack不变*/ if( 0 != bit) bf->rack |= bf->mask; bf->mask >>= 1;//mask向右移1位 /*若mask溢出为0,则表示成功累计写入8位,则直接输出rack, 并将rack初始化为0,mask初始化为1000 0000*/ if( 0 == bf->mask){
// eight bits in rack fputc( bf->rack, bf->fp); bf->rack = 0; bf->mask = 0x80; } } void BitsOutput( BITFILE *bf, unsigned long code, int count){
unsigned long mask; /*计算掩码值,位数为count的数值且最高位为1,例如若count为16,则mask=1000 0000 0000 0000*/ mask = 1L << (count-1); while( 0 != mask){
//mask为0时,说明code的count位数字输出完毕,注意:LZW是等长码 BitOutput( bf, (int)(0==(code&mask)?0:1));//按位输出code mask >>= 1;//掩码向右移位 } }
参考
免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://haidsoft.com/119625.html