tài liệu tham khảo thuật giải toán LZW
Website: http://www.docs.vn Email : lienhe@docs.vn Tel (: 0918.775.368 Lời nói đầu Ngày nay, cùng với sự phát triển không ngừng của khoa học và công nghệ thì máy tính đóng vai trò không thể thiếu trong cuộc sống xã hội loài ngời. Việc trao đổi thông tin của con ngời trong tất cả các ngành, các lĩnh vực của đời sống ngày càng trở nên cấp thiết và quan trọng, chính vì thế mà các thiết bị thông tin mới liên tục ra đời nhằm đáp ứng các yêu cầu này. Tuy nhiên, vì một số phần mềm đòi hỏi rất nhiều bộ nhớ để hoạt động trao đổi thông tin nên ngời ta đã nghĩ ra một phơng pháp nhằm giải quyết vấn đề này, đó là phơng pháp nén dữ liệu mà vẫn bảo toàn thông tin. Nén dữ liệu là một kỹ thuật quan trọng trong rất nhiều lĩnh vực khác nhau. Chính nhờ có kỹ thuật nén dữ liệu mà ngày nay chúng ta có những phơng tiện truyền thông hiện đại phục vụ cho cuộc sống nh truyền hình cáp, điện thoại, th điện tử . và rất nhiều khía cạnh khác. Do đó kỹ thuật nén dữ liệu ngày càng đợc quan tâm và phát triển nhiều hơn. ở Việt Nam, hầu hết các trờng Đại học đều quan tâm đến việc nén dữ liệu và điều này đợc thể hiện ở việc đa kỹ thuật nén trở thành môn học chính thức trong giai đoạn chuyên ngành . Trong phạm vi môn học Mã - mã nén . Tôi đa ra bài phân tích trình LZW 15 nhằm mô phỏng thuật toàn kỹ thuật nén dữ liệu. Tuy nhiên do trình độ còn hạn chế, thời gian và kinh nghiệm cha nhiều, nên bài phân tích này không thể tránh khỏi sự sai sót trong quá trình phân tích. Do vậy tôi rất mong đợc sự quan tâm tham gia góp ý Thầy Cô cũng nh cùng toàn thể các bạn Sinh Viên để bài phân tích này rõ dàng hơn. Cuối cùng Em xin chân thành cảm ơn thày Nguyễn Lê Anh đã hớng dẫn và giảng dạy Em trong thời gian qua. 2 Thuật toán nén LZW Bớc 1 Cắt văn bản mới thành các đoạn copy nếu bảng chữ cái có m chữ thì các chữ cái là m đoạn copy đầu tiên đợc đánh số từ 0 đến m-1. Bớc 2 Bỏ tất cả phần chữ thu đợc mã nén. Lu ý rằng các đoạn copy lần lợt đợc tạo ra và phần số của nó luôn nhỏ hơn số hiệu cột mà nó đứng. Thuật toán giải nén LZW. Bắt đầu là các cột đầu tiên (trong ví dụ là cột thứ 2) lặp lại thao tác sau cho đến hết. Lấy hai số liên tiếp của bản mã ví dụ là X, Y thay nó về dạng X+? và Y+$. Trong đó kí tự đầu tiên của Y+$ là kí tự cuối cùng của X+?. Dấu ? và $ là thay cho một kí tự cha biết. Vì X và Y không thể lớn hơn chỉ số cột mà nó đứng cho nên chúng ta hoàn toàn tìm đợc giá trị đoạn copy ứng với cột có chỉ số X, Y và thay đoạn copy vào X+? và Y+$ tơng ứng. Giá tri ? là kí tự đầu của Y+$ cho nên luôn luôn xác định. Nh thế chúng ta tìm đợc X+?. Ví dụ. Nén theo LZW Bớc 1 aabababaaababb thay a0 đợc 0abababaaababb từ điển đoạn copy mới aabababaaababb Bớc 2 aabababaaababb thay a0 đợc 00bababaaababb từ điển đoạn copy mới aabababaaababb Bớc 3 00bababaaababb thay b1 đợc 001ababaaababb từ điển đoạn copy mới aabababaaababb Bớc 4 001ababaaababb thay ab3 đợc 0013abaaababb 3 0 a 1 b 0 a 1 b 2 aa 0+a 0 a 1 b 2 aa 0+a 3 ab 0 a 1 b 2 aa 0+a 3 ab 0+b 4 ba từ điển đoạn copy mới aabababaaababb aabababaaababb Bớc 5 0013abaaababb thay aba5 đợc 00135aababb từ điển đoạn copy mới aabababaaababb Bớc 6 00135aababb thay aa2 đợc 001352babb từ điển đoạn copy mới aabababaaababb Bớc 7 001352babb thay ba4 đợc 0013524bb từ điển đoạn copy mới aabababaaababb Thuật toán LZW Trong LZW thì token chỉ có index. Để làm việc này, từ điển khi đợc khởi tạo đã gồm tất cả các ký tự đơn lẻ cho nên nó luôn đợc tìm thấy trong từ điển mặc dù có thể trớc đó cha xuất hiện trong văn bản. Thuật toán nén cho LZW: Khi mới bắt đầu, từ điển đã gồm tất cả các ký tự đơn lẻ. 4 0 a 1 b 2 aa 0+a 3 ab 0+b 4 ba 1+a 0 a 1 b 2 aa 0+a 3 ab 0+b 4 ba 1+a 0 a 1 b 2 aa 0+a 3 ab 0+b 4 ba 1+a 5 aba 3+a Xâu hiện tại bắt đầu có độ dài 1. Mỗi khi đọc thêm 1 ký tự thì nó đợc thêm vào xâu hiện tại. Nếu xâu hiện tại còn trùng với một trong các phrase đã có thì quá trình cứ tiếp tục. Khi không có phrase trong từ điển trùng với xâu hiện tại nữa thì : chúng ta cho ra index là chỉ số xâu trớc đó (không kể ký tự vừa đọc vào) trong từ điển. thêm xâu hiện tại (bao gồm cả ký tự vừa đọc vào) vào trong từ điển. bắt đầu một xâu mới bằng ký tự vừa đọc vào. Pseudocode nén của LZW: Hai lệnh cuối cùng là để xử lý khi hết file, lúc đó cha có phrase mới nên không có lệnh add_to_dictionary(). Cột đầu tiên là string1 (biến của lệnh add_to_dictionary) bỏ bớt đi ký tự đầu (ký tự này có do các lệnh string1[0]=character; string1[1]=\0; ở phần else {} của bớc trớc), đây thực chất là các ký tự đã đợc đọc bởi lệnh character=getc(input) . Cột thứ hai là index của phrase trong từ điển (ở đây khi phrase chỉ có 1 ký tự thì chúng ta sử dụng chính ký tự này thay cho index, đúng ra là phải dùng hàm ascii (ký tự)). Cột thứ 3 là biến (string1 + character) trong lệnh add_to_dictionary (string1 + character) kèm theo index của nó trong từ điển. Ví dụ: Mỗi dòng của bảng sẽ ứng với một lần thực hiện vòng lặp while (!feof(input)) (trừ dòng đầu tiên). Input String: WED WE WEE WEB WET Từ thuật toán nén ta thấy rằng: 5 string1[0]=getc(input); string1[1]=\0; while (!feof(input)) { character=getc(input); if in_dictionary(string1+character) {string1=string1+character;} else { code=look_up_dictionary(string1); output_code(code); add_to_dictionary(string1+character); string1[0]=character; string1[1]=\0; } code=look_up_dictionary(string1); output_code(code); - index của string1 ở bớc hiện tại đợc đa vào tệp nén. - phrase đợc thêm vào từ điển là string1 ở bớc hiện tại + character, trong đó character là ký tự đầu của string1 ở bớc sau Thuật toán giải nén nh sau: Đầu tiên, đọc một index vào và tìm phrase tơng ứng với nó trong từ điển. Đa phrase này ra tệp (string1 ở bớc trớc). Vòng lặp: đọc index tiếp theo, tra từ điển để tìm string1 ở bớc hiện tại, đa nó vào tệp giải nén thêm (string1 ở bớc trớc + ký tự đầu của string1 ở bớc hiện tại) vào từ điển Trong đoạn pseudocode sau thì string2 chính là string1 ở bớc trớc: Ví dụ của việc giải nén: Trong bảng sau đây, mỗi dòng sẽ tơng ứng với một lần thực hiện vòng lặp while{}, riêng dòng đầu tiên là do các lệnh string2[0]=input_bits(); string2[1]=\0; putc(string2[0], output); Cột thứ nhất (I) là kết quả của lệnh code =input_bits(); Cột thứ hai (II) là kết quả của các lệnh string1=dictionary_lookup(code); fputs(string1, output); Cột thứ ba (III) là biến string2 trong lệnh add_to_dictionary(string2+ string1[0]). Nó bằng với cột thứ hai (string1) ở bớc trớc do lệnh strcpy(string2, string1). 6 string2[0]=input_bits(); string2[1]=\0; putc(string2[0], output); while ((code=input_bits() )!=EOF) { string1=dictionary_lookup(code); fputs(string1, output); add_to_dictionary(string2+string1[0]); strcpy(string2, string1);} Cột thứ t (IV) là ký tự đầu của cột thứ II (string1[0]). Cột thứ năm (V) là biến (string2+string1[0]) trong lệnh add_to_dictionary(string2+ string1[0]). Input Codes: WED<256>E<260><261><257>B<260>T Một điều cần chú ý khi nén bằng LZW: nó thêm phrase vào từ điển trớc khi toàn bộ phrase này đợc nén, cụ thể là ký tự cuối của phrase chỉ đợc xử lý ở lần lặp sau. Nếu vì một lý do nào đó mà trình nén sử dụng ngay lập tức phrase này thì khi giải nén sẽ có vấn đề là gặp phải code của một phrase mà phrase này lại cha xuất hiện trong từ điển (phrase này còn thiếu một ký tự cuối là ký tự đầu của xâu đợc giải mã tiếp theo. Rất tiếc là điều này có thể xảy ra. Điều này chỉ xảy ra khi trong dữ liệu vào có đoạn CHARACTER.STRING.CHARACTER.STRING.CHARACTER. Trong ví dụ sau CHARACTER=I, STRING=WOMBAT. Giả sử xâu IWOMBAT đã có trong từ điển (mã là 300) còn IWOMBATI thì cha có. Nh vậy khi gặp đoạn dữ liệuIWOMBATIWOMBATI trình nén sẽ cho ra mã 300 và thêm IWOMBATI vào từ điển(với mã 400 chẳng hạn). Và sau đó nó sử dụng ngay mã 400 cho đoạn tiếp theo, tức là cả đoạn dữ liệu trên sẽ cho ra mã <300><400>. Bây giờ chúng ta hãy theo dõi quá trình giải nén. Sau khi thay <300> bằng IWOMBAT thì trình giải nén gặp mã 400. Nhng khi đó trong từ điển của trình giải nén còn cha có phrase IWOMBATI vì còn thiếu chữI ở cuối. Cho nên trong đoạn pseudocode trên, lệnh string1=dictionary_lookup(code) không phải lúc nào cũng thành công. Nhng nó cũng chỉ không thành công trong trờng hợp gặp phải dữ liệu có dạng nh vừa mô tả, vì để tạo thành pharase mới trong từ điển chúng ta cũng chỉ thiếu có 1ký tự cuối và ký tự này xuất hiện ở ngay đầu của đoạn dữ liệu tiếp theo. Cho nên chúng ta cần phải sửa lại nh sau: 7 old_string[0]=input_bits(); old_string[1]=\0; putc(old_string[0], output); while ((new_code=input_bits())!=EOF) { new_string=dictionary_lookup(new_code); if (new_string==NULL) { strcpy(new_string, old_string); append_character_to_string(new_string, new_string[0]); } fputs(new_string, output); append_character_to_string(old_string, new_string[0]); add_to_dictionary(old_string); strcpy(old_string, new_string); } Chơng trình LZW15: /* LZW15V.C : realization of Lempel-Ziv*/ #include "bitio.c" void usage_exit(char *prog_name); void CompressFile(FILE input, BIT_FILE *output, int argc, char *argv[]); void ExpandFile(BIT_FILE *input, FILE *output, int argc, char *argv[]); unsigned int decode_string(unsigned int count, unsigned int code); unsigned int find_child_node(int parent_code, int child_character); void InitializeStorage(void); void InitializeDictionary(void); char *CompressionName="LZW 15 Bit Variable Rate Encoder "; char *Usage="in-file out-file \n\n"; void usage_exit(char *prog_name) { char *short_name; char *extension; short_name = strrchr(prog_name,'\\'); if (short_name == NULL) short_name=strrchr(prog_name,':'); if (short_name!= NULL) short_name++; else short_name=prog_name; extension=strrchr(short_name,'.'); if (extension != NULL) *extension='\0'; printf("\nUsage : %s %s \n",short_name,Usage); exit(0); } /*===============================*/ #define BITS 15 #define MAX_CODE ((1<<BITS)-1) #define TABLE_SIZE 35023L #define TABLE_BANKS ((TABLE_SIZE >> 8)+1) #define END_OF_STREAM 256 #define BUMP_CODE 257 //Mã dánh khi nào thì tăng độ dài của từ mã #define FLUSH_CODE 258 #define FIRST_CODE 259 #define UNUSED -1 struct dictionary { int code_value; 8 int parent_code; char character; } *dict[TABLE_BANKS]; #define DICT(i) dict[i>>8][i & 0xff] char decode_stack[TABLE_SIZE]; unsigned int next_code; int current_code_bits; unsigned int next_bump_code; void InitializeDictionary(void) { unsigned int i; for(i=0;i<TABLE_SIZE;i++) DICT(i).code_value=UNUSED; next_code=FIRST_CODE; putc('F',stdout); //Báo hiệu khởi đầu hay khởi đầu lại từ điển. current_code_bits=9; //Mã hiện tại. next_bump_code=511; } /*===============================*/ void InitializeStorage(void) //Thủ tục cấp phát bộ nhớ. { int i; for(i=0;i<TABLE_BANKS;i++) { dict[i]=(struct dictionary *) calloc(256,sizeof(struct dictionary)); if (dict[i]==NULL) fatal_error("Error allocating dictionary space"); } } /*==============================*/ void CompressFile(FILE *input,BIT_FILE *output,int argc,char *argv[]) { int character; int string_code; unsigned int index; InitializeStorage(); InitializeDictionary(); //ký tự đầu tiên đợc đọc vào. if ((string_code=getc(input))==EOF) string_code=END_OF_STREAM; while((character=getc(input))!=EOF) //Bắt đầu vòng lặp. { index=find_child_node(string_code,character); if(DICT(index).code_value!=-1) string_code=DICT(index).code_value; 9 else { DICT(index).code_value=next_code++; DICT(index).parent_code=string_code; DICT(index).character=(char)character; OutputBits(output,(unsigned long)string_code,current_code_bits); string_code=character; if(next_code >MAX_CODE) { OutputBits(output,(unsignedlong)FLUSH_CODE,current_code_bits); InitializeDictionary(); } else if (next_code > next_bump_code) { OutputBits(output,(unsigned long)BUMP_CODE,current_code_bits); current_code_bits++; next_bump_code <<= 1; next_bump_code |= 1; putc('B',stdout); } } } OutputBits(output,(unsigned long)string_code,current_code_bits); OutputBits(output,(unsigned long)END_OF_STREAM,current_code_bits); while (argc-- >0) printf("Unknown argument :%s\n",*argv++); } /*======================*/ void ExpandFile(BIT_FILE *input,FILE *output,int argc,char *argv[]) { int new_code; int old_code; int character; unsigned int count; InitializeStorage(); while (argc-->0) printf("Unknown argument:%s",*argv); for(;;) { InitializeDictionary(); old_code=(unsigned int)InputBits(input,current_code_bits); if (old_code==END_OF_STREAM) return; character=old_code; putc(old_code,output); 10 Tạo thêm 1 đỉnh ứng với phrase mới trong từ điển và gán các giá trị cần thiết. Ghi string_code vừa tìm được ra tệp và bắt đầu việc tìm mới Phần xử lý khởi đầu lại từ điển. Phần thay đổi độ dài từ mã Thực hiện một số lệnh khởi tạo for(;;) { new_code=(unsigned int)InputBits(input,current_code_bits); if (new_code==END_OF_STREAM) return; //Nếu (new_code==END_OF_STREAM) thì gọi intializeDictionnary() để gán lại từ điển. if (new_code==FLUSH_CODE) break; if (new_code==BUMP_CODE) //Nếu (new_code==BUMP_CODE) thìđộ dài từ mã đợc tăng bằng lệnh: current_code_bits++ { current_code_bits++; putc('B',stdout); continue; } //Nếu gặp phải mã cha xuất hiện trong từ điển thì xử lý nh sau: if (new_code >= next_code) { decode_stack[0]=(char)character; count=decode_string(1,old_code); } else count=decode_string(0,new_code); character=decode_stack[count-1]; while(count>0) putc(decode_stack[--count],output); DICT(next_code).parent_code=old_code; DICT(next_code).character=(char)character; next_code++; old_code=new_code; } } } /*==============================*/ unsigned int find_child_node(int parent_code,int child_character) { unsigned int index; int offset; index=(child_character << (BITS-8)) ^ parent_code; if (index==0) offset=1; else offset=TABLE_SIZE-index; for (;;) { if (DICT(index).code_value==UNUSED) return((unsigned int)index); if (DICT(index).parent_code==parent_code && DICT(index).character==(char)child_character) return(index); if (index >= offset) index-=offset; 11 //Trong trờng hợp còn lại chúng ta gọi code_string() để xử lý.