數據結構第三次實驗報告

實驗三:哈夫曼編/譯碼器

實驗目的: 掌握哈夫曼樹

實驗內容: 利用哈夫曼編碼進行通信可以大大提高信道利用率,縮短信息傳輸時間,降低傳輸成本。但是,這要求在發送端通過一個編碼系統對待傳數據預先編碼,在接收端將傳來的數據進行譯碼(復原)。對於雙工信道(即可以雙向傳輸信息的信道),每端都需要一個完整的編/譯碼系統。試爲這樣的信息收發站寫一個哈夫曼碼的編/譯碼系統。

實驗要求: 編寫完整的系統,要求具有以下功能:
I:初始化(Initialization)。從終端讀入字符集大小n,以及n個字符和n個權值,建立哈夫曼樹,並將它存入文件hfmTree中。
E:編碼(Encoding)。利用以建好的哈夫曼樹(如不在內存,則從文件hfmTree中讀入),對文件ToBeTran中的正文進行編碼,然後將結果存入文件CodeFile中。
D:譯碼(Decoding)。利用已建好的哈夫曼樹將文件CodeFile中的代碼進行譯碼,結果存入文件TextFile中。
P:印代碼文件(Print)。將文件CodeFile以緊湊格式顯示在終端上,每行50個代碼。同時將此字符形式的編碼文件寫入文件CodePrin中。
T:印哈夫曼樹(Tree printing)。將已在內存中的哈夫曼樹以直觀的方式(樹或凹入表形式)顯示在終端上,同時將此字符形式的哈夫曼樹寫入文件TreePrint中。

實驗過程

  1. 需求分析
    首先要確定n個字符及其權值。一篇英語短文包括字母的大小寫以及基本的符號,從網上查找資料獲得各個字母出現的平均概率,作爲各個符號的權值。由於符號衆多,我們選擇將其保存到文件裏面進行讀取。
    其次,關於文件的寫入操作。我們直接將屏幕上打印出來的內容保存到相應的txt文件中去。
    最後樹的打印,我們採取中序遍歷樹的方式,將一棵樹橫向的打印到文件中去。

  2. 概要設計
    相關的數據結構設計:
    哈夫曼樹的節點

    typedef struct Node {
    int weight;//權值
    int parent;//父節點的序號,爲0表示根節點
    int lchild, rchild;//左右孩子節點的序號,爲0的是葉子節點
    }HTNode, *HuffmanTree;
    

    編譯結果的保存

    struct Ans {
    char ch; // 保存字符
    int wet; // 保存權值
    }ans[maxn];
    

    主程序的流程:

    1. 初始化:從文件中讀取權值和字符,建立一棵哈夫曼樹。
    2. 編碼:編寫函數HuffmanCoding確定每個字符的哈夫曼編碼。
    3. 譯碼:利用事先準備好的一篇文章,打印出文章結果。
    4. 打印部分:打印包括代碼和哈夫曼樹。
  3. 詳細設計

    1. 建立哈夫曼樹的過程
    HuffmanTree create_HuffmanTree(struct Ans *p, int n)
    {
    
    int t = 2 * n - 1;
    HuffmanTree HT = (HuffmanTree)malloc(total * sizeof(HTNode));
    int i;
    for (i = 0; i < n; ++i) {//先構造n個葉節點
    	HT[i].parent = 0;//parent後面會改變
    	HT[i].lchild = 0;
    	HT[i].rchild = 0;
    	HT[i].weight = p->wet;
    	p++;
    }
    for (; i < total; ++i) {
    	HT[i].parent = 0;
    	HT[i].lchild = 0;
    	HT[i].rchild = 0;
    	HT[i].weight = 0;
    }
    
    int min1, min2;//用來保存每一輪選出的兩個weight最小且parent爲0的節點下標
    
    //每一輪比較後選擇出min1和min2構成一課二叉樹,最後構成一棵哈夫曼樹
    for (i = n; i < t; ++i) {
    	select_min(HT, i, min1, min2); //另外編寫的一個函數,用來尋找i之前的結點
    	HT[min1].parent = i;
    	HT[min2].parent = i;
    	//兩個最小權值的結點構成子樹
    
    
    	HT[i].lchild = min1;
    	HT[i].rchild = min2;
    	HT[i].weight = HT[min1].weight + HT[min2].weight;
    }
    /*
    for(int i=0;i<total;i++)
    {
        printf("%d-%d-%d\n",HT[i].lchild,HT[i].rchild,HT[i].weight);
        printf("%c\n",ans[i].ch);
    }*/
    return HT;
    }
    
    1. 譯碼過程
    void HuffmanCoding(HuffmanTree HT, int n)
    {
    char temp[maxn + 5];//臨時用來保存每次求得的一個哈夫曼編碼串
    temp[maxn] = '\0';//結束符
    
    for (int i = 0; i < n; ++i) {
    	int current = i;
    	int fa = HT[i].parent;
    	int start = maxn;  //初始爲編碼結束符的位置
    	
    	while (fa != 0)
    	{
    		if (HT[fa].lchild == current)
    			temp[--start] = '0';
    		else temp[--start] = '1';
    		current = fa;
    		fa = HT[current].parent;
    	}
    	strcpy(HC[i], temp + start);//將編碼串從最後的start編碼~\0複製到HC上
        }
    }
    
    1. 初始化(從文件中讀入權值)
    int n;//需要編碼的字符數
    void Initialzation()
    {
    FILE *fp1,*fpx;
    fp1 = fopen("./DataFile.data", "r");
    printf("從文件DataFile.txt中讀取編碼的字符種類個數\n");
    fscanf(fp1, "%d", &n);
    
    printf("以及這%d個字符及其權值(整型)\n", n);
    for (int i = 0; i < n; ++i) {
    	fscanf(fp1, " %c %d", &ans[i].ch, &ans[i].wet);
    }
    HuffmanTree HT = create_HuffmanTree(ans, n);
    HuffmanCoding(HT, n);//求解每個字符的編碼
    
    printf("各字符對應權值的哈夫曼編碼爲(按照輸入順序):\n");
    for (int i = 0; i < n; ++i) {
    	printf("%c :", ans[i].ch);
    	puts(HC[i]);
    }
    printf("將它們存入htmtree.txt中去:\n");
    fpx=fopen("./htmtree.txt","w+");
    for(int i=0;i<n;i++)
    {
        fprintf(fpx,"%c %s\n",ans[i].ch,HC[i]);
    }
    fclose(fp1);
    fclose(fpx);
    printf("\n******************************************************\n\n");
    }
    
    1. 編碼
    void EnCoding()
    {
    //char ch[maxn*maxn],temp;
    printf("下面對文件ToBeTran.data中的文本進行編碼形成報文\n");
    printf("文章寫在文件CodeFile.txt中\n");
    FILE *fp2, *fp3;
    fp2 = fopen("ToBeTran.txt", "rb");
    fp3 = fopen("CodeFile.txt", "w");
    //fscanf(fp2,"%s",temp);
    char ch;
    do
    {
    	ch = fgetc(fp2);
    	/* read a char from the file */
    	if (ch == ' ') fprintf(fp3, " ");
    	else if (ch == '\n') fprintf(fp3, "\n");
    	else if(ch-'a'>=0)
            fprintf(fp3, "%s", HC[ch - 'a']);
        else if(ch-'A'>=0)
            fprintf(fp3, "%s", HC[ch - 'A'+26]);
        else if(ch-'0'>=0&&'9'-ch>=0)
            fprintf(fp3, "%s", HC[ch - '0'+52]);
        else if(ch==',')
            fprintf(fp3, "%s", HC[62]);
        else if(ch=='.')
            fprintf(fp3, "%s", HC[63]);
    
    
    } while (ch != EOF);
    fclose(fp2);
    fclose(fp3);
    
    for(int i=0;i<n;i++)
        printf("%s\n",HC[i]);
    }
    
    1. 譯碼
    void Decoding()
    {
    printf("下面對文件CodeFile.data中的代碼進行解碼形成原文\n結果存入文件Textfile.txt中\n");
    FILE *fp4, *fp5;
    fp4 = fopen("CodeFile.txt", "rb");
    fp5 = fopen("Textfile.txt", "w");
    char ch;
    char temp[maxn];
    char *s = temp;
    memset(temp, 0, sizeof(temp));
    //while(fgets(temp,maxn*maxn,fp4))//讀到換行符,並加上\0,存入temp
    do
    {
    	ch = fgetc(fp4);
    	if (ch == '\n') memset(temp, 0, sizeof(temp)), s = temp, fprintf(fp5, "\n");
    	else if (ch == ' ') memset(temp, 0, sizeof(temp)), s = temp, fprintf(fp5, " ");
    	else {
    		*s++ = ch;
    		if (fun(temp) >= 26&&fun(temp)<52) fprintf(fp5, "%c", 'A' + fun(temp)%26), memset(temp, 0, sizeof(temp)), s = temp;
    		else if(fun(temp)>=0&&fun(temp)<26) fprintf(fp5, "%c", 'a' + fun(temp)), memset(temp, 0, sizeof(temp)), s = temp;
    		else if(fun(temp)>=52&&fun(temp)<62) fprintf(fp5, "%c", '0' + fun(temp)%52), memset(temp, 0, sizeof(temp)), s = temp;
    		else if(fun(temp)==62) fprintf(fp5, "%c", ','), memset(temp, 0, sizeof(temp)), s = temp;
            else if(fun(temp)==63) fprintf(fp5, "%c", '.'), memset(temp, 0, sizeof(temp)), s = temp;
    		else continue;
    	}
    } while (ch != EOF);
    fclose(fp4);
    fclose(fp5);
    }
    
    1. 打印
    void print_tree(HuffmanTree HT,int t,int step)//橫向打印哈夫曼樹
    {
    
    int i;
    if(HT[t].rchild)
        print_tree(HT, HT[t].rchild, step + 1);
    for (i = 0; i < step; i++)
    {
        printf("       ");
        fprintf(pt,"       ");
    }
    printf("   -%d-▏\n", HT[t].weight);
    fprintf(pt,"   -%d-▏\n",HT[t].weight);
    if(HT[t].lchild)
        print_tree(HT, HT[t].lchild, step + 1);
    }
    void Output()
    {
    FILE *fp6;
    fp6=fopen("CodePrin.txt","w");
    for (int i = 0; i < n; ++i) {
    	printf("%c :%lf\n", ans[i].ch, ans[i].wet*1.0 / sum);
    }
    printf("\n******************************************************\n\n");
    printf("下面輸出輸出ToBeTran.data及其報文CodeFile.txt\n\n");
    FILE *fp2, *fp3;
    fp2 = fopen("ToBeTran.txt", "r");
    fp3 = fopen("CodeFile.txt", "r");
    char ch;
    printf("ToBeTran.data :\n");
    do
    {
    	ch = fgetc(fp2);
    	printf("%c", ch);
    } while (ch != EOF);
    printf("\n\n\n");
    printf("CodeFile.txt :\n");
    int cnt=0;
    do
    {
    	ch = fgetc(fp3);
    	printf("%c", ch);
    	fprintf(fp6,"%c",ch);
    	cnt+=1;
    	if(cnt%50==0)
        {
            printf("\n");
            fprintf(fp6,"\n");
        }
    
    } while (ch != EOF);
    printf("\n\n\n");
    
    printf("\n******************************************************\n\n");
    printf("下面輸出CodeFile.data及其原文Textfile.txt\n\n");
    FILE *fp4, *fp5;
    fp4 = fopen("CodeFile.txt", "r");
    fp5 = fopen("Textfile.txt", "r");
    
    printf("CodeFile.data :\n");
    do
    {
    	ch = fgetc(fp4);
    	printf("%c", ch);
    } while (ch != EOF);
    printf("\n\n\n");
    printf("Textfile.txt :\n");
    do
    {
    	ch = fgetc(fp5);
    	printf("%c", ch);
    } while (ch != EOF);
    printf("\n\n\n");
    fclose(fp6);
    }
    
  4. 調試分析
    (1)調試過程中遇到的問題

    1. 大小寫以及符號的問題。如果只有小寫字母,那我們可以根據Ascii碼-'a'來確定每個字符的相對大小,但是引入了大寫和符號,所以我們就需要多增加幾個條件分支來保存他們的權值。
    2. 如何打印樹。如果直接選擇常規的方法去遍歷樹就會像得十分的麻煩,所以我選擇了橫向打印樹的方式,類似下圖。
      在這裏插入圖片描述

    (2) 算法的時空複雜度。
    假設編碼的文本字符串長度爲n,字母爲k個符號。
    由於我們的權值讀入是無序的,因此需要先對權值進行排序,時間複雜度O(k*log(k))

    對於每個編碼符號,必須遍歷樹以解碼該符號。樹包含k個節點,並且平均需要O(log k)個節點訪問來解碼符號。所以時間複雜度爲O(n log k)。

    空間複雜度爲樹的O(k)和解碼文本的O(n)。

實驗結果
首先準備好n個權值和一篇英文文章。

I:初始化
67個權值
在這裏插入圖片描述
一篇英語文章
在這裏插入圖片描述

實驗終端打印
在這裏插入圖片描述
文件中打印
在這裏插入圖片描述
E:編碼

將編碼結果打印出來並且保存到CodeFile文件裏
在這裏插入圖片描述
文章原文
在這裏插入圖片描述
CodeFile文件
在這裏插入圖片描述
D:譯碼

將譯碼之後的結果保存到TextFile文件裏
在這裏插入圖片描述
在這裏插入圖片描述

P:打印

將文件CodeFile以緊湊格式顯示在終端上,每行50個代碼。同時將此字符形式的編碼文件寫入文件CodePrin中。
在這裏插入圖片描述
CodePrin文件
在這裏插入圖片描述

T:印哈夫曼樹

將哈夫曼樹打印出來
在這裏插入圖片描述
在這裏插入圖片描述

實驗總結
本次實驗花費時間比較長,有以下幾點感悟:

  1. 注意內存空間的管理,很有可能就出現內存泄漏的情況。
  2. 對於文件的打印,最後一定要記得把文件關掉。
  3. 關於遞歸問題的文件打印,文件指針最好是要設置成全局變量,不然文件很有可能在中途就關閉。
  4. 哈夫曼樹是一種效率很高的數據結構,如果輸入權值前就進行好排序,可能時間複雜度會更好。
  5. 橫向打印樹可以是一種輸出樹結構的好方式。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章