【ZZULI數據結構實驗】壓縮與解碼的鑰匙:赫夫曼編碼應用

在這里插入圖片描述

📃博客主頁: 小鎮敲碼人
💚代碼倉庫,歡迎訪問
🚀 歡迎關注:👍點贊 👂🏽留言 😍收藏
🌏 任爾江湖滿血骨,我自踏雪尋梅香。 萬千浮云遮碧月,獨傲天下百堅強。 男兒應有龍騰志,蓋世一意轉洪荒。 莫使此生無痕度,終歸人間一捧黃。🍎🍎🍎
?? 什么?你問我答案,少年你看,下一個十年又來了 💞 💞 💞

【ZZULI數據結構實驗】壓縮與解碼的鑰匙:赫夫曼編碼應用

  • 🏆 實驗目的和要求
  • 🏆 實驗前的準備工作
    • 🔑 確定漢字編碼
    • 🔑 在文件中實現漢字的讀和寫
      • 🍸 知道漢字的高位和低位在屏幕上打印漢字
        • 👘 字符串形式(%s)打印
        • 👘 字符形式(%c)打印
      • 🍸 在文件中寫入漢字(以GBK編碼的形式)
      • 🍸 在文件中讀入中文,并以GBK編碼的形式來輸出
  • 🏆 赫夫曼樹的結構設計
    • 🔑 知識點介紹
    • 🔑 結構設計
  • 🏆 赫夫曼樹函數的具體實現
    • 🔑 List_Init(鏈表初始化)和 Node_Init(節點初始化)
    • 🔑 鏈表的銷毀和赫夫曼樹的銷毀
    • 🔑 鏈表的插入
    • 🔑 buildHuffmanTree(構造赫夫曼樹)
    • 🔑 assignCodes(編碼)
      • 🍸 strdup函數介紹
      • 🍸 編碼函數實現
    • 🔑 打印字符出現的頻次
    • 🔑 打印字符的編碼
  • 🏆 最終效果演示
    • 🔑 菜單及調用函數實現
    • 🔑 效果展示

前言:上篇博客,博主分享了多項式的運算實驗,今天我們繼續來看實驗二——赫夫曼編碼及應用。相關代碼在博主的代碼倉庫自行查看。

🏆 實驗目的和要求

在這里插入圖片描述

🏆 實驗前的準備工作

🔑 確定漢字編碼

我們這次實驗采用GBK編碼來編碼漢字,該編碼標準兼容GB2312(ANSI),由兩個字節來編碼確定一個漢字,而且高位和低位為了和英文字符做區分,都是大于128的。我們可以看一下編碼表。

  • 也可以使用UTF-8編碼,但是該編碼會出現3個乃至4個字節編碼一個漢字的情況,控制起來太復雜,所以我們不采用這個。

🔑 在文件中實現漢字的讀和寫

在學習如何在漢字中編碼前,我們先來學習一下如何在屏幕上(標準輸出流stdout)上打印一個用GBK編碼的漢字。

🍸 知道漢字的高位和低位在屏幕上打印漢字

👘 字符串形式(%s)打印

我們通過查閱資料,知道了中文阿的高位和低位是0xB0、0xA2。

#include <stdio.h>  int main()
{// 定義GBK編碼的高位字節  int high = 0xB0;// 定義GBK編碼的低位字節  int low = 0xA2;// 創建一個字符數組來存儲GBK編碼的漢字unsigned char s[3] = { (unsigned char)high, (unsigned char)low, '\0' };printf("%s", s);return 0;
}

運行結果:

在這里插入圖片描述

👘 字符形式(%c)打印
#include <stdio.h>  int main()
{// 定義GBK編碼的高位字節  int high = 0xB0;// 定義GBK編碼的低位字節  int low = 0xA2;printf("%c%c",high,low);return 0;
}

運行結果:

在這里插入圖片描述

  • 這里高位字節和低位字節用大小為1字節的類型也是可以的,但是要注意,應該使用unsigned char無符號類型,這樣就不會出現負數的情況(GBK編碼高位和低位第一位都為1),便于我們的判斷。的如果4字節的會發生截斷。

🍸 在文件中寫入漢字(以GBK編碼的形式)

#include <stdio.h>  // 引入標準輸入輸出庫,用于文件操作和輸入/輸出函數  int main() // 主函數入口  
{  // 聲明一個文件指針pf,并嘗試以寫入("w")模式打開名為"詩.txt"的文件  FILE* pf = fopen("詩.txt", "w");  // 檢查文件是否成功打開  if (NULL == pf)  {  // 如果文件打開失敗,則輸出錯誤信息(來自perror函數)  perror("fopen");  // 并返回錯誤代碼1  return 1;  }  // 定義一個無符號字符數組s,用于存儲用戶輸入的字符串,并初始化為全0  unsigned char s[600] = { 0 };  // 使用scanf函數從標準輸入(通常是鍵盤)讀取一個字符串,并存儲在s中  // 注意:這里使用%s可能會引發緩沖區溢出問題,因為scanf不會檢查目標數組的大小  // 更好的做法是使用fgets函數或者限制scanf讀取的字符數  scanf("%s", s);  // 初始化一個循環計數器i,用于遍歷字符串s  int i = 0;  // 循環遍歷字符串s,直到遇到字符串結束符'\0'  while(s[i] != '\0')  {  // 聲明兩個無符號字符變量high和low,用于存儲GBK編碼的漢字的高位和低位字節  // 假設字符串s中包含GBK編碼的漢字,但實際上這種假設可能不正確  unsigned char high = '\0';  unsigned char low = '\0';  // 將s中的當前字符賦值給high  high = s[i++];  // 如果high的值大于128(通常表示這是一個非ASCII字符),則假設它是GBK編碼的漢字的高位字節  if(high > 128)  {  // 嘗試將s中的下一個字符賦值給low(假設它是GBK編碼的漢字的低位字節)  low = s[i++];  }  // 將high寫入文件  fputc(high, pf);  // 如果low不為'\0'(即存在低位字節),則將其寫入文件  if (low != '\0')  fputc(low, pf);  }  // 關閉文件  fclose(pf);  // 將文件指針設置為NULL,避免野指針  pf = NULL;  // 程序正常結束,返回0  return 0;  
}

這是我們寫入的內容:

在這里插入圖片描述
看看文件中是否生成了對應內容:

在這里插入圖片描述

從右邊的預覽我們可以看見確實寫入了對應的內容,有小伙伴可以會好奇,為什么換行了呢?我們剛剛我們明明沒有換行呀,其實你如果點進這個文件會發現,其實并沒有換行,只是預覽這樣可能更方便我們閱讀:

在這里插入圖片描述

🍸 在文件中讀入中文,并以GBK編碼的形式來輸出

首先我們需要新建一個文件寫入內容后,另存選擇編碼為GBK或者是它兼容的,因為程序編碼格式和文件的編碼格式必須保持一致。

在這里插入圖片描述

選擇GB類型的編碼或者ANSI都是,因為ANSI也是GB類型的一種,GBK都是兼容的他們的。這里我們選擇ANSI編碼。
文件中的內容如下:

在這里插入圖片描述

下面我們用代碼來以GBK的形式讀一下文件中的內容并輸出到屏幕上。

#include <stdio.h>  // 引入標準輸入輸出庫  int main()  
{  // 聲明一個文件指針pf,并嘗試以只讀("r")模式打開名為"十年.txt"的文件  FILE* pf = fopen("十年.txt", "r");  // 檢查文件是否成功打開  if (NULL == pf)  {  // 如果文件打開失敗,則輸出錯誤信息  perror("fopen");  // 并返回錯誤代碼1  return 1;  }  // 初始化兩個變量high和low,high用于存儲從文件中讀取的字符,low用于存儲漢字的低字節(如果存在)  int high = 0, low = '\0';  // 使用while循環從文件中逐個字符地讀取,直到遇到文件結束符EOF  while ((high = fgetc(pf)) != EOF)  {  // 如果讀取到的字符(存在于high中)大于128(假設是GBK或其他多字節編碼的漢字的高字節)  if (high > 128)  {  // 讀取下一個字符作為漢字的低字節(如果存在)  low = fgetc(pf);  // 輸出高字節和低字節,但由于low可能不是漢字的低字節(例如遇到非漢字字符),  // 直接輸出可能會導致亂碼或不正確的輸出。  printf("%c%c", high, low);  }  else  {  // 如果不是漢字的高字節,則只輸出該字符  printf("%c", high);  }  }  // 關閉文件  fclose(pf);  // 將文件指針設置為NULL,避免野指針  pf = NULL;  // 程序正常結束,返回0  return 0;  
}

運行結果:

在這里插入圖片描述

這里還是用int來保存低位和高位較好,因為既要與128作比較來區分因為字符和中文字符,不能讓系統把首位的1當作負號位,又要做判斷文件結束的判斷,因為EOF是-1,無符號數沒有負數,所以如果使用無符號數,程序會陷入死循環。
所以接下來的實驗中我們會以int類型保存字符的高位和低位。最終系統會發生截斷的,所以我們不用擔心intchar不匹配的問題。

🏆 赫夫曼樹的結構設計

🔑 知識點介紹

在這里插入圖片描述

🔑 結構設計

赫夫曼樹是一種特殊的二叉樹,WPL最小的二叉樹,所以赫夫曼樹又叫最優二叉樹。
首先就是哈夫曼樹的節點類型,我們需要在這個類型里面放5個數據,節點的左孩子、右孩子、還有這個節點保存的字符即它的低位和高位,還有這個字符的字符串編碼(char*類型,動態開辟內存按需申請)。

typedef struct HuffmanNode* NodeP;
typedef struct HuffmanNode {unsigned int freq;//出現的頻率NodeP left, right;//節點的左孩子和右孩子int low;//低位int high;//高位char* code; // 編碼,在構造樹后分配  
}Node;

然后我們還需要一個線性表的結構,這個線性表用來保存每種字符的頻率,可以使用鏈表或者線性表,這里我們使用的是鏈表。因為鏈表無需我們考慮申請空間的問題,省事很多。

typedef struct HuffmanList* ListP;//創建一個雙向循環鏈表,存儲節點的頻度
typedef struct HuffmanList{NodeP data;//哈夫曼樹節點ListP next;
}List;

這里我們把鏈表指針和樹的節點指針類型取了一下別名,因為后面要多次使用,這樣做可以少寫一個*,指針的英文是Pointer,所以我們后面加了P代表這個是指針類型。

函數接口:

void List_insert(ListP Head,ListP newnode);//插入新的節點ListP List_Init(NodeP data);//初始化鏈表void Print_freq(ListP Head);//打印各個詞出現的頻率void Destroy_List(ListP newnode);//銷毀鏈表NodeP Node_Init(int freq, int low, int high);//節點初始化NodeP buildHuffmanTree(ListP* Head);//構造哈夫曼樹void assignCodes(NodeP root, char* code);//編碼void decode(NodeP root, FILE* encodedFile, FILE* decodedFile);//解碼void dfs(NodeP root);//前序遍歷,打印節點和其對應的編碼

🏆 赫夫曼樹函數的具體實現

🔑 List_Init(鏈表初始化)和 Node_Init(節點初始化)

過于簡單不過多敘述。

ListP List_Init(NodeP data)//鏈表節點初始化
{ListP newL = (ListP)malloc(sizeof(List));//為鏈表節點申請空間if (NULL == newL){printf("malloc Failed\n");exit(-1);}//初始化newL->data = data;//初始化數據節點newL->next = NULL;//初始化next為空
}NodeP Node_Init(int freq, int low, int high)//赫夫曼樹節點初始化
{NodeP NewN = (NodeP)malloc(sizeof(Node));//為赫夫曼樹節點申請空間if (NULL == NewN){printf("malloc Failed\n");exit(-1);}//初始化NewN->freq = freq;NewN->high = high;NewN->low = low;NewN->left = NULL;NewN->right = NULL;NewN->code = NULL;
}

🔑 鏈表的銷毀和赫夫曼樹的銷毀

  • 注意:雖然鏈表里面存的有赫夫曼樹的節點指針,但是節點的內存并不是和鏈表節點一起申請的,鏈表節點只是有一個4字節的變量也指向那片空間而以,而且鏈表里有的節點在赫夫曼樹中肯定是存在的,節點的內存在赫夫曼樹中走一個后序就可以釋放,但是如果你在鏈表中就釋放了,在釋放赫夫曼樹的時候,釋放葉子節點時還需要特判一下,因為葉子節點已經釋放過了(重復釋放程序會崩潰),而且非法訪問也會出問題,所以我們統一走后序在樹中釋放節點的內存。
void Destroy_List(ListP Head)
{assert(Head);//斷言,頭節點不能為空ListP Cur = Head;while (Cur != NULL){ListP next = Cur->next;free(Cur);Cur = next;}
}void Destroy_HuffmanTree(NodeP root)//銷毀赫夫曼樹
{if (root == NULL)return;Destroy_HuffmanTree(root->left);//先去釋放根節點的左樹Destroy_HuffmanTree(root->right);//再去釋放根節點的右樹free(root);//最后釋放根節點
}

🔑 鏈表的插入

鏈表的插入就是用來統計每個字符出現的頻次的,具體邏輯是這樣的,我們在外面的函數只需要傳入字符的高位和低位即可,如果highlow已經出現了,就沒有構造鏈表節點和赫夫曼樹節點的必要,如果沒有出現,外面就需要依次構造赫夫曼樹節點和鏈表節點頭插進鏈表中

void List_insert(ListP Head,int high,int low)//插入節點
{ListP cur = Head->next;//循環遍歷,看是否該字符已經存在while (cur != NULL){if (cur->data->high == high && cur->data->low == low){cur->data->freq++;break;}cur = cur->next;}if (cur == NULL)//沒有找到,或者鏈表為空(只有一個頭節點){//構造新節點NodeP newHnode = Node_Init(1,low, high);ListP newLnode = List_Init(newHnode);//頭插進鏈表中newLnode->next = Head->next;//先把Head后面的節點和新節點鏈接Head->next = newLnode;//在把頭節點的next更新}
}

🔑 buildHuffmanTree(構造赫夫曼樹)

統計完文件中每個字符的頻次,我們得到對應的樹節點,也可以將它們視作森林。因為此時它們還沒有鏈接起來,因為每次我們需要依次取兩個頻次最小的節點,所以我們可以使用小堆(按照頻次來調整)這種數據結構,一共有N個節點,每次調整只需要logN,調整N次,時間復雜度的量級在O(N * logN),我們來看排序一次排序是NlogN,有N次,大概在O(N logN* N)的量級。如果你直接找兩個最小的,比排序還快一點N*N量級。如何構造我們不再詳細贅述,在之前的思維導圖中已經敘述過了。如果你對堆這種數據結構不太了解,可以看一下博主這篇博客。


NodeP buildHuffmanTree(ListP Head)//構造哈夫曼樹
{// 初始化最小堆,并將所有葉子節點加入堆中  Heap hp;HeapInit(&hp);ListP cur = Head->next;while (cur != Head){HeapPush(&hp, cur->data);cur = cur->next;}//開始構造赫夫曼樹while (HeapSize(&hp) > 1) {NodeP left = HeapTop(&hp);HeapPop(&hp);NodeP right = HeapTop(&hp);HeapPop(&hp);NodeP top = (NodeP)malloc(sizeof(Node));top->freq = left->freq + right->freq;top->left = left;top->right = right;top->high = -1;top->low = -1;top->code = NULL;// 暫時不分配編碼  HeapPush(&hp, top);//把新節點插入到堆中}// 堆中只剩一個節點,即根節點  NodeP root = HeapTop(&hp);HeapDestory(&hp);return root;
}

🔑 assignCodes(編碼)

編碼就是為葉子節點的code寫入相應的字符編碼,左孩子寫字符0,右孩子寫字符1,這是前綴編碼模式,可以保證我們的每個葉子節點的編碼都是唯一的,不存在二義性。我們先來隆重介紹一下一個非常棒的字符串函數strdup,如果你會使用這個函數,那簡直是太酷了!

🍸 strdup函數介紹

這個函數主要做兩件事,第一件事是拷貝字符串,第二件事是為這個字符串重新申請一片空間(在堆上),所以這個函數相當于是strcpymalloc函數的結合,它拷貝的結束條件是\0,并且這個函數會給\0開一個空間。它會返回新開空間的起始地址。

  1. 如果我們給普通的字符指針的\0位置賦值,是會報錯的:
#include<stdio.h>
int main()
{char* s = "11";s[2] = '\0';return 0;
}

運行結果:

在這里插入圖片描述
說我們非法訪問了,但是字符串的結束標志不就是\0嗎,如果你不把那一個字節的空間給我,該如何處理呢?這里我們先理解為是字符指針,系統會給它開這個空間但是不允許我們訪問,這也算是一種保護機制,因為字符串是以\0來判斷結束的,如果你隨意更改,就會打印亂碼。

  1. 如果我們把這個相同的字符串給_strdup函數,執行同樣的操作,系統不會報錯但是打印出來會亂碼。
#include<stdio.h>int main()
{char* s = _strdup("11");s[2] = '2';printf("%s", s);return 0;
}

運行結果:

在這里插入圖片描述

這是因為我們把原先的\0給修改了,這個函數變相的給了我們控制字符指針\0的權利,有好處也有壞處。

  1. strcatstrdup函數結合,恢復字符串特性(以\0)結尾。
    因為strcat函數會把源字符串的\0也拷貝進去,如果你不懂字符串拼接函數strcat,可以看一下博主這篇博客,這里正常應該會報錯因為那個字節的空間,并不是我們的。我們暫且認為這里是特殊處理,但是博主發現這個函數還是存在很大的不確定性,所以實際項目里面還是老老實實的使用malloc和循環去計算字符串長度。
#include<stdio.h>
int main()
{char* s = _strdup("11");strcat(s, "1");printf("%s", s);return 0;
}

運行結果:

在這里插入圖片描述

不再出現亂碼。

🍸 編碼函數實現

void assignCodes(NodeP root, char* code)//編碼
{if (root == NULL) return;if (root->left == NULL && root->right == NULL) {// 葉子節點,分配編碼  root->code = _strdup(code);}else {// 遞歸為左樹和右樹上的葉子節點分配編碼assignCodes(root->left, strcat(_strdup(code),"0"));assignCodes(root->right, strcat(_strdup(code),"1"));}
}

🔑 打印字符出現的頻次

// 打印各個詞及其出現頻度的函數  
// 參數:  
//     ListP Head - 指向鏈表頭部的指針,鏈表中的每個節點存儲了一個詞及其相關信息  void Print_freq(ListP Head) // 打印各個詞出現的頻度  
{assert(Head);//Head不能為空// cur 是一個臨時指針,用于遍歷鏈表  ListP cur = Head->next; // 從鏈表的第一個有效節點開始遍歷(假設Head是頭節點,不存儲數據)  // 當cur不為空時,說明還有節點未遍歷  while (cur != NULL){// 檢查當前節點的數據(詞)是否有'low'屬性(可能是指多字符的詞或某種特殊標識)  if (cur->data->low == -1){// 如果'low'為-1,說明只有一個字符(可能是單字符詞或特殊標識),直接打印該字符和它的頻度  char c = cur->data->high;if (c == '\n' || c == '\r')printf("\\n\\r: %d次\n", cur->data->freq);elseprintf("%c: %d次\n", cur->data->high, cur->data->freq);}else{// 如果'low'不為-1,說明是多字符的詞,打印兩個字符(或特殊標識)和它的頻度  printf("%c%c: %d次\n", cur->data->high, cur->data->low, cur->data->freq);}// 移動到下一個節點  cur = cur->next;}
}

🔑 打印字符的編碼

// 深度優先搜索函數,用于遍歷樹結構  
// 參數:  
//     NodeP root - 指向樹節點的指針,該節點是遍歷的起始點  
void dfs(NodeP root)
{// 如果當前節點為空(到達葉子節點的下一層或根節點之前就是空的),則直接返回  if (!root)return;// 如果當前節點是葉子節點(即沒有左孩子和右孩子)  if (root->left == NULL && root->right == NULL){// 檢查節點是否有'low'屬性(可能是某種輔助信息或鍵值)  if (root->low == -1){// 如果'low'為-1,說明只有一個字符(可能是單字符詞或特殊標識),直接打印該字符和它的頻度  char c = root->high;if (c == '\n' || c == '\r')//換行符特殊處理printf("\\n\\r: %s\n",root->code);elseprintf("%c: %s\n", root->high, root->code);}// 如果'low'不是-1(即存在'low'屬性),則按照指定格式打印  elseprintf("%c%c: %s\n", root->high, root->low, root->code);}// 遞歸遍歷左子樹  dfs(root->left);// 遞歸遍歷右子樹  dfs(root->right);
}

🏆 最終效果演示

🔑 菜單及調用函數實現

#define  _CRT_SECURE_NO_WARNINGS 1
#include"Huffman_tree.h"char file[200] = { 0 };//存儲原始文件路徑
ListP Head = NULL;//鏈表頭指針
NodeP root = NULL;//赫夫曼樹的根節點指針void Test1()//完成統計字符頻次的事情
{FILE* pf = fopen(file, "r");//打開原始文件if (NULL == pf)//如果打開失敗{perror("fopen");return 1;}int high = 0;int low = -1;Head = List_Init(NULL);//帶頭單鏈表,創建它的頭while ((high = fgetc(pf)) != EOF)//開始讀取文件的內容{if (high > 128)//如果是中文字符low = fgetc(pf);List_insert(Head,high,low);low = -1;//注意要及時置為-1,因為有時候不是中文字符}	
}void Test2()//完成打印字符頻次表的工作
{assert(Head != NULL);//Head不能為空Print_freq(Head);
}void Test3()//完成構建赫夫曼樹,并打印每個字符對應的01編碼的工作
{assert(Head != NULL);//Head不能為空root = buildHuffmanTree(Head);assignCodes(root, "");//編碼dfs(root);//遍歷打印
}void Test4()//完成寫入加密文件并打印加密文件的路徑的工作
{assert(Head != NULL);//Head不能為空assert(root != NULL);//root不能為空,保證已經加密過了FILE* pf = fopen(file, "r");//打開原始文件if (NULL == pf){perror("fopen");return 1;}FILE* pfw = fopen("encryption.txt", "w");//創建加密文件,相對路徑if (NULL == pfw){perror("fopen");return 1;}int high = -1, low = -1;while ((high = fgetc(pf)) != EOF)//讀取文件字符{if (high > 128)//判斷中文字符{low = fgetc(pf);}ListP cur = NULL;cur = Head->next;while (cur != NULL)//依次在表中找對應的字符并寫入它的編碼{if (cur->data->high == high && cur->data->low == low){fputs(cur->data->code, pfw);break;}cur = cur->next;}low = -1;//防止中文字符對英文字符產生干擾}printf("加密文件的路徑為:D:\\code_2023_5\\test_c\\數據結構\\c++\\哈夫曼樹編碼\\encryption.txt\n");//打印加密文件路徑,這個是自己事先就確定的//關閉對應的文件fclose(pfw);fclose(pf);pfw = NULL;pf = NULL;
}void Test5()//完成解密的工作,并打印解密的路徑
{assert(root != NULL);//root不為空FILE* pfw = fopen("encryption.txt", "r");//打開加密的文件if (NULL == pfw){perror("fopen");return 1;}FILE* pfD = fopen("Decoding_files.txt", "w");//創建解密文件if (NULL == pfD){perror("fopen");return 1;}decode(root, pfw, pfD);//調用解密函數printf("解密文件的路徑為:D:\\code_2023_5\\test_c\\數據結構\\c++\\哈夫曼樹編碼\\encryption.txt\n");//打印解密文件的絕對路徑//關閉文件fclose(pfw);fclose(pfD);pfw = NULL;pfD = NULL;
}void Test6()//完成清理資源的操作
{Destroy_HuffmanTree(root);//清理赫夫曼樹中的資源root = NULL;Destroy_List(Head);//清理鏈表中的資源Head = NULL;printf("清理資源成功<>\n");
}
void menu()//菜單函數
{int instructions = 0;printf("請輸入指令以執行操作<>\n: ");printf("***********************************************************************************************************\n");printf("****************************1.輸入要加密的文件路徑(絕對路徑和相對路徑均可)*******************************\n");printf("****************************2.打印字符頻次表***************************************************************\n");printf("****************************3.打印字符編碼*****************************************************************\n");printf("****************************4.輸出加密01文件路徑***********************************************************\n");printf("****************************5.輸出解碼文件路徑*************************************************************\n");printf("****************************6.清理相關資源*****************************************************************\n");printf("****************************7.刷新屏幕*********************************************************************\n");	printf("****************************8.結束程序*********************************************************************\n");scanf("%d", &instructions);switch(instructions){case 1: { printf("請輸入要加密的文件路徑<>:\n");  scanf("%199s", file); Test1(); }break;case 2: Test2();break;case 3: Test3();break;case 4: Test4();break;case 5: Test5();break;case 6: Test6();break;case 7: system("cls");break;case 8: exit(0);break;default: printf("指令不合法,重新輸入\n");break;}
}int main()
{while (1)//循環打印菜單{menu();}return 0;
}

🔑 效果展示

  1. 代碼運行結果:
    詞頻及字符串編碼打印:

在這里插入圖片描述
在這里插入圖片描述

  1. 加密01字符串文件

在這里插入圖片描述
3. 解密文件與原文件對比結果。

在這里插入圖片描述

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/10063.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/10063.shtml
英文地址,請注明出處:http://en.pswp.cn/web/10063.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Faiss原理及使用總結

Faiss&#xff08;Facebook AI Similarity Search&#xff09;是一個用于高效相似性搜索和密集向量聚類的庫。 一、原理 向量表示與相似度度量&#xff1a;在Faiss中&#xff0c;數據通常被表示為高維向量&#xff0c;這些向量可以來自深度學習模型的特征提取&#xff0c;也可…

2024-05-11 問AI:在深度學習中,學習率是什么

文心一言 在深度學習中&#xff0c;**學習率&#xff08;Learning Rate&#xff09;**是一個非常重要的超參數&#xff0c;它控制著模型在訓練過程中參數更新的步長。具體來說&#xff0c;學習率決定了在每次迭代&#xff08;或稱為一個epoch&#xff09;中&#xff0c;模型根…

前端工程化之包管理器

這里寫目錄標題 什么是包包管理工具常用的包管理工具npmnpm 基本使用初始化搜索工具包下載安裝包導入 npm 包基本流程生產依賴與開發依賴全局安裝安裝全部依賴安裝指定版本的包刪除依賴包 cnpm全局安裝nrm 的使用 yarn全局安裝yarn 常用命令 pnpm 【推薦使用】全局安裝 什么是包…

我國吻合器市場規模不斷擴大 國產化率有所增長

我國吻合器市場規模不斷擴大 國產化率有所增長 吻合器是替代手工切除或縫合的一種醫療器械&#xff0c;其工作原理與訂書機十分相似&#xff0c;可利用鈦釘對組織進行離斷或吻合。經過多年發展&#xff0c;吻合器種類逐漸增多&#xff0c;根據手術方式不同&#xff0c;吻合器大…

【JavaEE 初階(三)】多線程代碼案例

?博主主頁: 33的博客? ??文章專欄分類:JavaEE?? &#x1f69a;我的代碼倉庫: 33的代碼倉庫&#x1f69a; &#x1faf5;&#x1faf5;&#x1faf5;關注我帶你了解更多線程知識 目錄 1.前言2.單例模式2.1餓漢方式2.2餓漢方式 3.阻塞隊列3.1概念3.2實現 4.定時器4.1概念4.…

支付寶小程序如何去除頁面下拉回彈

描述&#xff1a;支付寶小程序頁面下拉時會產生回彈&#xff0c;如果頁面上有拖拽功能&#xff0c;會有影響 解決方法&#xff1a; 頁面xx.config.js中設置&#xff1a;allowsBounceVertical: “NO” 官方文檔&#xff1a;https://opensupport.alipay.com/support/FAQ/7110b5d…

WT32-ETH01作為TCP Client進行通訊

目錄 模塊簡介WT32-ETH01作為TCP Client設置電腦作為TCP Server設置連接并進行通訊總結 模塊簡介 WT32-ETH01網關主要功能特點: 采用雙核Xtensa⑧32-bit LX6 MCU.集成SPI flash 32Mbit\ SRAM 520KB 支持TCP Server. TCP Client, UDP Server. UDP Client工作模式 支持串口、wif…

鴻蒙OpenHarmony技術:【Docker編譯環境】

Docker環境介紹 OpenHarmony為開發者提供了兩種Docker環境&#xff0c;以幫助開發者快速完成復雜的開發環境準備工作。兩種Docker環境及適用場景如下&#xff1a; 獨立Docker環境&#xff1a;適用于直接基于Ubuntu、Windows操作系統平臺進行版本編譯的場景。基于HPM的Docker環…

其他編程語言中調用 Python 腳本,如何設置Python腳本的相對路徑

import os# 假設 script_directory 是你的腳本所在的目錄 script_directory os.path.dirname(os.path.abspath(__file__))# 使用 os.path.join 來構建相對路徑 relative_path_to_image os.path.join(script_directory, 合并/figure_pic2.png)# 現在你可以使用這個相對路徑來加…

uni-app+vue3 +uni.connectSocket 使用websocket

前言 最近在uni-appvue3websocket實現聊天功能&#xff0c;在使用websocket還是遇到很多問題 這次因為是app手機應用&#xff0c;就沒有使用websocket對象&#xff0c;使用的是uni-app的uni.connectSocket 為了方便測試這次用的是node.js一個簡單的dom&#xff0c;來聯調模擬…

Apache Flume Agent內部原理

Apache Flume Agent內部原理 Apache Flume 是一個可擴展的、分布式的日志收集、聚合和傳輸系統。在 Flume 中&#xff0c;Agent 是一個獨立的進程&#xff0c;負責接收、傳輸和處理數據。Agent 內部包含多個組件&#xff0c;每個組件都有不同的功能和責任。 1. Source&#xff…

5個 Elasticsearch 核心組件

Elasticsearch 是一個基于 Lucene 的搜索引擎&#xff0c;它提供了分布式、高可用、多租戶的能力。Elasticsearch 的核心組件包括節點&#xff08;Node&#xff09;、集群&#xff08;Cluster&#xff09;、索引&#xff08;Index&#xff09;、分片&#xff08;Shard&#xff…

三下鄉社會實踐投稿攻略在這里

在當今信息爆炸的時代&#xff0c;如何讓自己的聲音被更多人聽到&#xff0c;成為許多人和企業所關心的問題。其中&#xff0c;向各大媒體網站投稿&#xff0c;成為了一種常見的宣傳方式。但是&#xff0c;如何投稿各大媒體網站&#xff1f;新聞媒體發文策略又有哪些呢&#xf…

Flutter Clipboard實現復制功能

Flutter內置了Clipboard 功能,可以幫助我們完成復制粘貼的功能,比如我們想把“hello flutter”復制到粘貼板,代碼如下: TextButton(onPressed: () async {await Clipboard.setData(ClipboardData(text: hello flutter)

基于SpringBoot設計模式之開端

文章目錄 前言引言開始 前言 為了更好的在項目中&#xff0c;能更加優雅的使用設計模式&#xff0c;比較針對性的解決我們的問題。我將在這個專欄詳細的描述23種設計模式&#xff0c;為了與時俱進&#xff0c;我打算通過springboot的形式將23種設計模式全部擼完&#xff01; 引…

光耦推薦—高速風筒方案中用到哪些光耦型號

高速風筒是現代生活中常見的電器設備&#xff0c;廣泛應用于家庭、商業和工業領域&#xff1b;光耦是一種能夠將輸入信號轉換成輸出信號的元器件&#xff0c;其作用在于將電氣信號轉換成光信號&#xff0c;從而實現電路的隔離和保護&#xff1b;采用光耦可實現對風機轉速和溫度…

【管理咨詢寶藏99】離散制造智能工廠戰略規劃方案

本報告首發于公號“管理咨詢寶藏”&#xff0c;如需閱讀完整版報告內容&#xff0c;請查閱公號“管理咨詢寶藏”。 【管理咨詢寶藏99】離散制造智能工廠戰略規劃方案 【格式】PDF版本 【關鍵詞】智能制造、先進制造業轉型、數字化轉型 【核心觀點】 - 推進EHS、品質一致性、生…

【無標題】QCC 308x 518x 517x增加usb voice 32k采樣率

QCC 308x 518x 517x增加usb voice 32k采樣率 diff --git a/adk/src/domains/audio/kymera/kymera_usb_voice.c b/adk/src/domains/audio/kymera/kymera_usb_voice.c index 6dd82061..532c4ad8 100755 --- a/adk/src/domains/audio/kymera/kymera_usb_voice.c +++ b/adk/src/dom…

Failed to start tomcat.service: Unit is not loaded properly: Bad message 如何解決?

錯誤 “Failed to start tomcat.service: Unit is not loaded properly: Bad message” 通常意味著的 tomcat.service systemd 配置文件存在語法錯誤或配置不正確。為了解決這個問題&#xff0c;一步步檢查和修正這個服務文件。 1. 檢查 tomcat.service 文件 首先&#xff0c…

CSS文字描邊,文字間隔,div自定義形狀切割

clip-path: polygon( 0 0, 68% 0, 100% 32%, 100% 100%, 0 100% );//這里切割出來是少一角的正方形 letter-spacing: 1vw; //文字間隔 -webkit-text-stroke: 1px #fff; //文字描邊1px uniapp微信小程序頂部導航欄設置透明&#xff0c;下拉改變透明度 onP…