小肥柴慢慢手写数据结构(C篇)(5-5 Huffuman编码)

小肥柴慢慢学习数据结构笔记(C篇)(5-5 Huffman编码)

  • 目录
    • 5-16 编码案例
    • 5-17 Huffman编码原理
    • 5-18 Huffman编码/解码实现
      • 5-18-1 大致思路
      • 5-18-2 编码实现
      • 5-18-3 解码实现
      • 5-18-4 测试
    • 5-19 实际案例
    • 总结
    • 参考文献

目录

5-16 编码案例

咱们引用一个常见的案例,一步步带着大家理解Huffman编码的出现。

【问题】给定A、B、C、D四个字母组成的字符串(ABAACDC),要求使用数字0、1对其进行二进制编码

方案1:不等长编码

字符二进制编码
A0
B1
C10
D11

编码结果:0100101110

但是这个编码结果在译码阶段会产生歧义,因为:A(0)和B(1)分别是C(10)和D(11)的前缀,即“0100101110”可以被翻译为多种结果:
(1)ABBAACDC or (2)ACACDC
这显然是我们不想看到的,于是有人提出了一种等长编码方案。

方案2:等长编码

字符二进制编码
A00
B01
C10
D11

很容易得到编码结果:000100001011,对比不等长编码的结果“0100101110”,明显长了许多。

小结

(1)等长编码不会产生译码歧义,但是编码长度相对较长,不符合尽量节省传输带宽的通信设计原则。
(2)不等长编码容易产生译码歧义,但能有效缩短编码长度,在传输上是理想的形态。

【注】我们仅站在计算机专业初学者的视角去看待这个问题,我自己是通信/信号类专业出身,知道这样的引入和讨论会造成非议,但为了帮助初学者理解该问题,这样的简化描述是很有必要的,见谅。

那么,有没有其他的编码方式,使得:“在不出现译码歧义的情况下,使得编码长度最短”。

===> 有,就是本帖简要介绍的Huffman编码,且这种编码要借助于二叉树类的数据结构。

5-17 Huffman编码原理

【核心思想】让出现次数最多的字符编码长度最短。(次数也可称为:频率、频次)

【案例】

还是使用开篇的问题来介绍Huffman编码:给定A、B、C、D四个字母组成的字符串(ABAACDC),要求使用数字0、1对其进行二进制编码。
step1:首先统计各个字符出现的次数,并作为节点,各字符节点拥有一个权重(weight)来表示字符串中该字符出现的次数。
step2:(每次)挑选weight最小的两个节点进行合并,即为这两个节点生成一个父节点,且父节点的weight为两个子节点weight之和。
step3:从剩下的节点(包含还未合并的原始字符节点和生成的父节点)循环执行上述操作,不断地合并最小的两个节点,最终只剩下一个根节点为止。

具体过程如下图所示:
在这里插入图片描述

最后得到编码表:

字符二进制编码
A1
B000
C001
D01

编码结果:100011001000001

【观察/性质】

  1. 标记所有左枝路径为0,所有右枝路径为1,则可以得到如下编码,称为Huffman编码
  2. 按照Huffman编码规则得到的编码结果,一定是在不出现歧义的条件下输出的码长度最短的编码。 ⇒ 后续给出相关学习链接
  3. 有关Huffman编码的唯一性,可以绕开数学推导直观地给出证明:
    因为所有的叶子结点都是被编码的字符,对树形数据结构来讲,从根节点出发(编码)到叶节点的路径是唯一的,不是吗? ⇒ 与本章第一节介绍树的基础术语那节对上了!
  4. Huffman编码不是唯一的,因为每次被选中的两个作业节点,总有两种排列方式(且互为镜像)形成新的父节点。
  5. 而出现频次高(weight大)的叶子结点排在后面被合并,相反出现频次低(weight小)的叶子结点排在前面被合并,自然而然使得出现频次高的字符的Huffman编码端,从而使得整体编码长度缩短。

关于 带权路径长度,WPL

  1. WPL——Weighted Path Length of Tree, 简记为 WPL

(1)WPL表示树的所有叶结点的带权路径长度之和。

(2)WPL可用于衡量一颗带权二叉树的优劣。

(3)具体公式为: W P L = ∑ i = 0 N w i p i WPL=\sum_{i=0}^{ N}w_ip_i WPL=i=0Nwipi,其中 w i w_i wi为权重 p i p_i pi为路径长度,以案例说明
字符A,权重=3,路径长=1;字符B,权重=1,路径长=2…
WPL = 31 + 13 + 22 + 13 = 13
这也是考研/面试/考试经常问到的没有营养的问题。

(4)简单观察这个公式,明显可以看到若能让权重大的字符对应的路径短,则可以减小WPL的值,这与Huffman编码的设计思路是一致的

  1. 平均码长于WPL的关系

L = L ( C ) = ∑ i = 0 N p i l i L=L(C)=\sum_{i=0}^{ N}p_il_i L=L(C)=i=0Npili,其中 l i l_i li为编码长度, p i p_i pi为编号为 i i i的码出现的概率

(1)编码长度 l i l_i li正好对应WPL中的路径长度(path)

(2)编号为 i i i的码出现的概率: p i = w i ∑ i = 0 N w i p_i=\frac{w_i}{\sum_{i=0}^{N}w_i} pi=i=0Nwiwi,和WPL中的权重对应起来

(3)其实这个公式本质和WPL一致,仅在定义和变量的命名方式上不同 ⇒ 请看(2)中的定义,秒懂。

至于WPL相关的数学讨论,偏向通信方向和密码学方向(已经超出多数学习数据结构与算法的普通同学的理解了,咱们先挖个坑,有机会慢慢补齐),参考链接 [1]、[2]。

5-18 Huffman编码/解码实现

5-18-1 大致思路

接下来我们会模仿原理部分介绍的Huffman编码/解码操作步骤,尽量降低理解难度。大致思路如下:

(1)编码

step1 统计字符权重
step2 构建Huffman树
step3 对照Huffman树进行编码

需要注意的点:
(1)考虑到编解码的对象是文本字符,可以实用char对应的ASII码作为存储编码列表的索引(index~i),减轻另外实现一套映射算法的额外工作。
(2)为了构建Huffman树(buildHuffTree),用节点数组模拟“森林”(pNode forest[ ], 对应操作addToForest),然后不断从森林中挑选权重最小的两个节点/子树进行合并(getMinNode,mergeNode),在实际挑选时采用每次挑选最小的一个并标记,连续挑选两次的策略,感觉还有改进的空间。
(3)使用递归函数genHuffCode来生成Huffman编码表,借用strcpy和strcat两个API拼接编码字符串。
(4)在判断当前节点是否为字符节点时,可以不做标记,直接判定是否为叶结点即可(isLeaf)。
(5)打印Huffman编码表,采用中序遍历递归实现(printHuffTreeCode)。

(2)解码

step1 拿到之前编码得到的Huffman树
step2 遍历传入的待解码数据(char* / char[]),对照Huffman树找到叶节点,得到一段数据的解码结果,并重置游标(curNode)为Huffman树根节点,循环往复,直到所有数据使用完毕。

5-18-2 编码实现

(1)头文件 HuffmanTree.h

#ifndef _Huffman_Tree_H
#define _Huffman_Tree_H

#define LIST_SIZE 256  					//数据列表长度 
#define FOREST_SIZE (LIST_SIZE * 2 - 1) //构建Huffman树需要产生的森林长度
#define CODE_MAX 512                    //每个字符Huffman编码长度 
#define TEXT_MAX 4028                   //解码文本长度 

struct TreeNode {
    char val;
    int weight;
    char code[CODE_MAX];
    struct TreeNode* left;
    struct TreeNode* right;
};
typedef struct TreeNode* pNode;

char* Encode(char *orgData, int orgLen, pNode root);    //编码,返回编码结果 
char* Decode(char *codeData, int codeLen, pNode root);  //解码,返回解码结果 
void releaseTree(pNode root);                           //递归释放节点 
#endif

(2)编码部分
核心代码

char* Encode(char *orgData, int orgLen, pNode root){
	if(orgData == NULL || orgLen == 0){
		printf("编码入参错误!\n");
		return NULL;
	}
	
	if(orgLen == 1){
		printf("仅有一个字符,不用编码!\n");
		return NULL;
	}
	
	int i;
	printf("输入数据:%s\n", orgData);
	//(1)统计权重 
	int freq[LIST_SIZE];
	memset(freq, 0, sizeof(int) * LIST_SIZE);
	for(i=0; i<orgLen; i++){
		freq[orgData[i]]++;
	}
	
	//(2)构建Huffman树
	//C的传参比较繁琐,我不想为了代码看起来漂亮,给buildHuffTree再添加一个引用参数
	//所以采用了类似深拷贝的做法 
	pNode tmpTree = buildHuffTree(freq, LIST_SIZE);
	root->val = tmpTree->val;
	root->weight = tmpTree->weight;
	root->left = tmpTree->left;
	root->right = tmpTree->right;
	free(tmpTree);
	printf("\n字符的霍夫曼编码信息如下:\n");
	printHuffTreeCode(root); 

	//(3)得到编码结果 
	char* ret = doEncode(orgData, orgLen, root);
	
	return ret;
}

干活函数

=========================================

static pNode buildHuffTree(int freq[], int codeListlen){
	pNode forest[FOREST_SIZE] = {NULL};
	pNode root = NULL;
	
	int i;
	for(i=0; i<codeListlen; i++){
		if(freq[i]>0)
			addToForest(forest, FOREST_SIZE, creatLeafNode(i, freq[i]));
	}
	
	while(1){
		pNode left = getMinNode(forest, FOREST_SIZE);
		pNode right = getMinNode(forest, FOREST_SIZE);
		
		if(right == NULL) {//仅有一个节点,合并结束 
			root = left;
			break; 
		} else {
			pNode pathNode = mergeNode(left->weight + right->weight);
			pathNode->left = left;
			pathNode->right = right;
			addToForest(forest, FOREST_SIZE, pathNode);
		}
	}
	
	genHuffCode(root);
	return root;
}
static void addToForest(pNode forest[], int size, pNode node){
	int i;
	for(i=0; i<size; i++){
		if(forest[i] == NULL){
			forest[i] = node;
			break;
		}
	}
}
static pNode creatLeafNode(int val, int weight){
	pNode node = (pNode)malloc(sizeof(struct TreeNode));
	memset(node, 0, sizeof(struct TreeNode));
	node->val = val;
	node->weight = weight;
	return node;
}
static pNode getMinNode(pNode forest[], int size){
	pNode node = NULL;
	
	int min = -1;
	int i;
	for(i=0; i<size; i++){
		if(forest[i] && (min == -1 || forest[min]->weight > forest[i]->weight))
				min = i;				
	}
	
	if(min != -1){
		node = forest[min];
		forest[min] = NULL;
	}
	
	return node;
}

static pNode mergeNode(int weight){
	pNode node = (pNode)malloc(sizeof(struct TreeNode));
	memset(node, 0, sizeof(struct TreeNode));
	node->weight = weight;
	return node;
}
static void genHuffCode(pNode curNode){
	if(curNode){
		if(curNode->left){
			strcpy(curNode->left->code, curNode->code);
            strcat(curNode->left->code, "0");
            genHuffCode(curNode->left);
		}
		if(curNode->right){
			strcpy(curNode->right->code, curNode->code);
            strcat(curNode->right->code, "1");
            genHuffCode(curNode->right);
		}
	}
}

=============================================================

static int isLeaf(pNode node){
	return node->left == NULL && node->right == NULL;
}

static void inOrder(pNode curNode){
	if(curNode){
		inOrder(curNode->left);
		
		if(isLeaf(curNode))
			printf("字符:%c  权重:%d   编码:%s \n", curNode->val, curNode->weight, curNode->code);
			
		inOrder(curNode->right);
	}
}

static void printHuffTreeCode(pNode node){
	inOrder(node);
}

====================================================

static void transHuffTable(pNode HuffTable[], pNode curNode){
	if(curNode){
		if(isLeaf(curNode))
			HuffTable[curNode->val] = curNode;
		
		transHuffTable(HuffTable, curNode->left);
		transHuffTable(HuffTable, curNode->right);
	}
}

static char* doEncode(char *orgData, int orgLen, const pNode root){

	pNode HuffTable[LIST_SIZE] = {NULL};
	transHuffTable(HuffTable, root);
	
	//这里写的不好,为了节省空间采取了一个笨办法 
	int i;
	int totalSize = 0;
	for(i=0; i<orgLen; i++)
		totalSize += strlen(HuffTable[orgData[i]]->code);
	printf("\n霍夫曼编码长度:%d", totalSize);
	
	char* HuffCode = (char *)malloc(sizeof(char) * (totalSize+1));
	memset(HuffCode, 0, totalSize+1);
	for(i=0; i<orgLen; i++)
		strcat(HuffCode, HuffTable[orgData[i]]->code);
	
	return HuffCode;
}

5-18-3 解码实现

char* Decode(char *codeData, int codeLen, pNode root){
	if(codeData == NULL || codeLen == 0 || root == NULL){
		printf("\n解码入参错误!\n");
		return NULL;
	}
	
	if(codeLen == 1){
		printf("仅有一个字符,不用解码!\n");
		return NULL;
	}
	
	//此处也是一个笨办法,应该去看论文估计一下长度 
	char* text = (char *)malloc(sizeof(char) * (TEXT_MAX));
	memset(text, 0, TEXT_MAX);
	int i,j;
	pNode curNode = root;
	//按照之前思路模拟实现,虽然贴近人的自然思维,但是效率不高 
	for(i=0, j=0; i<codeLen; i++){
		curNode = codeData[i] == '0' ? curNode->left : curNode->right;
		if(isLeaf(curNode)){
			text[j++] = curNode->val;
			curNode = root;
		}
	}
	return text;
}

=======================================================
完整的 HuffmanTree.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "HuffmanTree.h"


static pNode getMinNode(pNode forest[], int size){
	pNode node = NULL;
	
	int min = -1;
	int i;
	for(i=0; i<size; i++){
		if(forest[i] && (min == -1 || forest[min]->weight > forest[i]->weight))
				min = i;				
	}
	
	if(min != -1){
		node = forest[min];
		forest[min] = NULL;
	}
	
	return node;
}

static pNode mergeNode(int weight){
	pNode node = (pNode)malloc(sizeof(struct TreeNode));
	memset(node, 0, sizeof(struct TreeNode));
	node->weight = weight;
	return node;
}

static pNode creatLeafNode(int val, int weight){
	pNode node = (pNode)malloc(sizeof(struct TreeNode));
	memset(node, 0, sizeof(struct TreeNode));
	node->val = val;
	node->weight = weight;
	return node;
}

static void addToForest(pNode forest[], int size, pNode node){
	int i;
	for(i=0; i<size; i++){
		if(forest[i] == NULL){
			forest[i] = node;
			break;
		}
	}
}

static void genHuffCode(pNode curNode){
	if(curNode){
		if(curNode->left){
			strcpy(curNode->left->code, curNode->code);
            strcat(curNode->left->code, "0");
            genHuffCode(curNode->left);
		}
		if(curNode->right){
			strcpy(curNode->right->code, curNode->code);
            strcat(curNode->right->code, "1");
            genHuffCode(curNode->right);
		}
	}
}

static pNode buildHuffTree(int freq[], int codeListlen){
	pNode forest[FOREST_SIZE] = {NULL};
	pNode root = NULL;
	
	int i;
	for(i=0; i<codeListlen; i++){
		if(freq[i]>0)
			addToForest(forest, FOREST_SIZE, creatLeafNode(i, freq[i]));
	}
	
	while(1){
		pNode left = getMinNode(forest, FOREST_SIZE);
		pNode right = getMinNode(forest, FOREST_SIZE);
		
		if(right == NULL) {//仅有一个节点,合并结束 
			root = left;
			break; 
		} else {
			pNode pathNode = mergeNode(left->weight + right->weight);
			pathNode->left = left;
			pathNode->right = right;
			addToForest(forest, FOREST_SIZE, pathNode);
		}
	}
	
	genHuffCode(root);
	return root;
}

static int isLeaf(pNode node){
	return node->left == NULL && node->right == NULL;
}

static void inOrder(pNode curNode){
	if(curNode){
		inOrder(curNode->left);
		
		if(isLeaf(curNode))
			printf("字符:%c  权重:%d   编码:%s \n", curNode->val, curNode->weight, curNode->code);
			
		inOrder(curNode->right);
	}
}

static void printHuffTreeCode(pNode node){
	inOrder(node);
}

static void transHuffTable(pNode HuffTable[], pNode curNode){
	if(curNode){
		if(isLeaf(curNode))
			HuffTable[curNode->val] = curNode;
		
		transHuffTable(HuffTable, curNode->left);
		transHuffTable(HuffTable, curNode->right);
	}
}

static char* doEncode(char *orgData, int orgLen, const pNode root){

	pNode HuffTable[LIST_SIZE] = {NULL};
	transHuffTable(HuffTable, root);
	
	//这里写的不好,为了节省空间采取了一个笨办法 
	int i;
	int totalSize = 0;
	for(i=0; i<orgLen; i++)
		totalSize += strlen(HuffTable[orgData[i]]->code);
	printf("\n霍夫曼编码长度:%d", totalSize);
	
	char* HuffCode = (char *)malloc(sizeof(char) * (totalSize+1));
	memset(HuffCode, 0, totalSize+1);
	for(i=0; i<orgLen; i++)
		strcat(HuffCode, HuffTable[orgData[i]]->code);
	
	return HuffCode;
}

static void doReleaseTree(pNode curNode){
	if(curNode){
		doReleaseTree(curNode->left);
		doReleaseTree(curNode->right);
	}
}

char* Encode(char *orgData, int orgLen, pNode root){
	if(orgData == NULL || orgLen == 0){
		printf("编码入参错误!\n");
		return NULL;
	}
	
	if(orgLen == 1){
		printf("仅有一个字符,不用编码!\n");
		return NULL;
	}
	
	int i;
	printf("输入数据:%s\n", orgData);
	//(1)统计权重 
	int freq[LIST_SIZE];
	memset(freq, 0, sizeof(int) * LIST_SIZE);
	for(i=0; i<orgLen; i++){
		freq[orgData[i]]++;
	}
	
	//(2)构建Huffman树
	//C的传参比较繁琐,我不想为了代码看起来漂亮,给buildHuffTree再添加一个引用参数
	//所以采用了类似深拷贝的做法 
	pNode tmpTree = buildHuffTree(freq, LIST_SIZE);
	root->val = tmpTree->val;
	root->weight = tmpTree->weight;
	root->left = tmpTree->left;
	root->right = tmpTree->right;
	free(tmpTree);
	printf("\n字符的霍夫曼编码信息如下:\n");
	printHuffTreeCode(root); 

	//(3)得到编码结果 
	char* ret = doEncode(orgData, orgLen, root);
	
	return ret;
}

char* Decode(char *codeData, int codeLen, pNode root){
	if(codeData == NULL || codeLen == 0 || root == NULL){
		printf("\n解码入参错误!\n");
		return NULL;
	}
	
	if(codeLen == 1){
		printf("仅有一个字符,不用解码!\n");
		return NULL;
	}
	
	//此处也是一个笨办法,应该去看论文估计一下长度 
	char* text = (char *)malloc(sizeof(char) * (TEXT_MAX));
	memset(text, 0, TEXT_MAX);
	int i,j;
	pNode curNode = root;
	//按照之前思路模拟实现,虽然贴近人的自然思维,但是效率不高 
	for(i=0, j=0; i<codeLen; i++){
		curNode = codeData[i] == '0' ? curNode->left : curNode->right;
		if(isLeaf(curNode)){
			text[j++] = curNode->val;
			curNode = root;
		}
	}
	return text;
}



void releaseTree(pNode root){
	doReleaseTree(root);
}

5-18-4 测试

(1)测试代码

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "HuffmanTree.h"

int main(int argc, char *argv[]) {
	char test1[] = {'A', 'B', 'A', 'A', 'C', 'D', 'C'}; //ABAACDC
	struct TreeNode huffTree1;
	char* huffEncode1 = Encode(test1, strlen(test1), &huffTree1);
	printf("\n\n霍夫曼编码结果: %s", huffEncode1);
	
	char* huffDecode1 = Decode(huffEncode1, strlen(huffEncode1), &huffTree1);
	printf("\n\n霍夫曼译码结果: %s", huffDecode1);
	
	printf("\n=======================================\n");
	
	//write file name
	char test2[] = {'w', 'r', 'i', 't', 'e', ' ', 'f', 'i', 'l', 'e', ' ', 'n', 'a', 'm', 'e'}; 
	struct TreeNode huffTree2;
	char* huffEncode2 = Encode(test2, strlen(test2), &huffTree2);
	printf("\n\n霍夫曼编码结果: %s", huffEncode2);
	
	char* huffDecode2 = Decode(huffEncode2, strlen(huffEncode2), &huffTree2); 
	printf("\n\n霍夫曼译码结果: %s", huffDecode2);
	printf("\n=======================================\n");
	return 0;
}

(2)测试结果
在这里插入图片描述

【遗憾】
(1)正如前面讨论的,这个程序是参考[3]修修补补完成的,当然也可以参考[3]和[4]去做成完整版的利用Huffman编码对文件进行压缩和解压的经典案例(重点在于能让大家观察到压缩/解压的操作的时间和空间效率,真正做到学以致用),
(2)对Huffman编解码的对应数学理论我们仅了解到皮毛,因此在具体编码中还有很多申请/释放空间的处理不够智慧,应该抽空研究一下真实开源案例,看看学习别人的实现手法。
(3)黑皮书里给出了相关论文,见[13],[14],[15],[16]。

以上问题,有空我们补一个帖子试试看。

【注】另外,很多Huffman教学贴中,编码实现阶段,为节点定义了一个指向父节点的指针,也不失为一种常见的解决方案,参看[4]

// 定义哈夫曼树节点
typedef struct {
	int weight;
	int parent;
	int l_child;
	int r_child;
	char data;
} HTNode, * HuffmanTree;
typedef char** HuffmanCode;

5-19 实际案例

(1)硬件编解码,参考 [6],[7],[8]
(2)其他参考[9],[10],[11],[12]

多媒体方向的水很深,看情况慢慢研究吧。

总结

(1)Huffman编码的应用非常广泛。
(2)Huffman编码是一种变长的编码,可配合类似树状的数据结构存储编码表。
(3)对于森林这个概念,我们没有介绍,直接在实践中学习。

【吐槽】C的传值解决方法不太优雅。

参考文献

[1] 信息与编码系列(一) 源码
[2] 信息与编码系列(二)最优码——Huffman码
[3] C语言实现Huffman的编码和解码 ==> 值得看,树形结构的打印不错,对文件的编解码也挺好
[4] C语言课程设计-文件的哈夫曼编码与解码 ==> 对时间和空间的统计展示值得借鉴
[5] 哈夫曼编码详细证明步骤
[6] 硬件huffman解码器(一):huffman编码原理
[7] 硬件huffman解码器(二):串行解码及其优化
[8] 硬件huffman解码器(三)-并行解码
[9] 语音处理:霍夫曼编码算法原理分析 ==> 提到了Jpeg
[10] MP3 和 AAC 中huffman解码原理,优化设计与参考代码中实现
[11] Android 性能优化 03—Bitmap优化02(huffman压缩)
[12] Android图片压缩原理分析(三)—— 哈夫曼压缩讲解 ==> Android Skia 图像引擎
[13] D. A. Huffman,“AMethod for the Construction of Minimum Redundancy Codes,” Proceedings of the IRE, 40 (1952),1098-1101. ==> 开山鼻祖
[14] D.E. Knuth, “Dynamic Huffman Coding,”Journal of Algorithms, 6 (1985),163-180.
[15] L.L. Larmore, “Height-Restricted Optimal Binary Trees,”SIAM Journal on Computing, 16 (1987),1115-1123.
[16] L. L. Larmoreand D.S. Hirschberg,“A Fast Algorithm for Optimal Length-Limited Huffman Codes,”Journal of the ACM, 37 (1990), 464-473.

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/539941.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Java 中文官方教程 2022 版(九)

原文&#xff1a;docs.oracle.com/javase/tutorial/reallybigindex.html 链接&#xff0c;符号或其他 原文&#xff1a;docs.oracle.com/javase/tutorial/essential/io/links.html 如前所述&#xff0c;java.nio.file包&#xff0c;特别是Path类是“链接感知”的。每个Path方法…

PDF转TXT ChatGPT编程

1.目的 在Z-library找到一本书&#xff0c;只不过是PDF格式的&#xff0c;看的时候体验不好&#xff0c;还没有办法保存记录&#xff0c;就想着能不能转成txt格式放到手机自带的小说软件中看。 不想去网上找相关的软件&#xff0c;可以还需要付钱&#xff0c;所以尝试用ChatGP…

安卓焦点窗口切换

一、背景 我们经常会遇到一种Application does not hava focused window的ANR异常&#xff0c;这种异常一般是没有焦点窗口FocusedWindow导致,且这类异常只会发生在key事件的派发&#xff0c;因为key事件是需要找到一个焦点窗口然后再派发&#xff0c;而触摸事件只需要找到当前…

Python零基础从小白打怪升级中~~~~~~~FaskAPI中的请求和响应

第二节&#xff1a;FastAPI中请求数据 一、URL请求参数 url请求参数是通过url请求地址携带的&#xff0c;例如&#xff0c;在以下 url 中&#xff1a; http://127.0.0.1:8000/items/?skip0&limit10这些请求参数是键值对的集合&#xff0c;这些键值对位于 URL 的 &#…

php反序列化(2)

一.pop链 在反序列化中&#xff0c;我们能控制的数据就是对象中的属性值&#xff08;成员变量&#xff09;&#xff0c;所以在php反序列化中有一种漏洞利用方法叫“面向属性编程”&#xff0c;即pop&#xff08;property oriented programming&#xff09;。 pop链就是利用魔…

ES6基础(JavaScript基础)

本文用于检验学习效果&#xff0c;忘记知识就去文末的链接复习 1. ECMAScript介绍 ECMAScript是一种由Ecma国际&#xff08;前身为欧洲计算机制造商协会&#xff0c;英文名称是European Computer Manufacturers Association&#xff09;通过ECMA-262标准化的脚本程序设计语言…

python(使用循环显示四种模式)

代码&#xff1a; # 模式A n int(input("请输入三角形的层数")) for i in range(1,n 1):for j in range(1,i 1):print(f"{j}\t", end" ")print()# 模式B n int(input("请输入三角形的层数")) for i in range(1,n 1):for j in rang…

点击notify里面的通知,实现路由跳转

需求描述&#xff1a; 右上角有出来通知用户的有代办任务的消息框&#xff0c;点击消息框会跳转到代办路由页面。 duration:3000//弹窗显示时间, 毫秒 getElementsByClassName() – 获取所有指定类名的元素 效果展示&#xff1a;

32单片机入门持续更新中

配套资料为野火霸道V2 初识 STM32 4.1 什么是 STM32 STM32&#xff0c;从字面上来理解&#xff0c;ST 是意法半导体&#xff0c;M 是 Microelectronics 的缩写&#xff0c;32 表示 32 位&#xff0c;合起 来理解&#xff0c;STM32 就是指 ST 公司开发的 32 位微控制器。在如今…

7.基础乐理-重升重降号、等音扩展篇

在 6.升降号、黑键的音名 这里知道了一个等音的概念&#xff0c;就是指的是同一个键&#xff0c;同一个音&#xff0c;拥有不同的名字&#xff0c;这些名字互相称为等音 在音乐中除了升降号&#xff0c;还有两个东西&#xff0c;一个长得像 x 叫重&#xff08;chong&#xff09…

React Router 5 vs 6:使用上的主要差异与升级指南

React Router 5 的一些API 在 React Router 6 上有时可能找不到&#xff0c;可能会看到如下画面&#xff1a;export ‘useHistory’ was not found in ‘react-router-dom’ … React Router目前有两个大的版本&#xff0c;即React Router 5、6。React Router 6 在设计上更加简…

分布式系统接口限流方案

Git地址&#xff1a;https://gitee.com/deepjava/test-api-limit.git 方案一、 Guava工具包 实现单机版限流 具体代码见git 方案二、Redis lua脚本 实现分布式系统的接口限流 具体代码见git

纯css实现左右拖拽改变盒子大小

效果&#xff1a; 代码 <!DOCTYPE html> <html><head><meta http-equiv"Content-Type" content"text/html;charsetutf-8"><title></title><style>body {background-color: black;color: white;}.column {ove…

Linux C柔性数组(零长数组)

零长数组&#xff0c;大小为0&#xff0c;一般用在结构体中&#xff08;网络通信&#xff0c;省流&#xff09;&#xff0c;节省空间&#xff0c;方便善后&#xff08;相对于指针类型&#xff09;&#xff0c;我们通过具体例子进行理解。 常规定长数组 #include <stdio.h&…

MongoDB的安装和使用

1.MongoDB 安装 1.1 基于Docker安装 docker run --restartalways -d --name mongo -v /opt/mongodb/data:/data/db -p 27017:27017 mongo:4.0.6 1.2 客户端工具使用 MongoDB Compass | MongoDB 2.MongoDB 使用 2.1 引用依赖包 <dependency><groupId>org.sprin…

软件无线电系列——抽取器的多相滤波和内插器的多相滤波

本节目录 一、抽取器的多相滤波结构 二、内插器的多相滤波结构 三、一个抽取器多相滤波器的设计本节内容 从前面文章中可以知道&#xff0c;抽取器模型中的低通滤波器在抽取算子D之前&#xff0c;是在降低速率之前实现的&#xff1b;内插器模型中的低通滤波器在内插算子I之后&…

DedeCMS 未授权远程命令执行漏洞分析

dedecms介绍 DedeCMS是国内专业的PHP网站内容管理系统-织梦内容管理系统&#xff0c;采用XML名字空间风格核心模板&#xff1a;模板全部使用文件形式保存&#xff0c;对用户设计模板、网站升级转移均提供很大的便利&#xff0c;健壮的模板标签为站长DIY自己的网站提供了强有力…

[数据结构]—二叉树基本概念

1.树概念及结构 1.树的概念 树是一种非线性的数据结构&#xff0c;它是由n&#xff08;n>0&#xff09;个有限结点组成一个具有层次关系的集合。把它叫做树是因为它看起来像一棵倒挂的树&#xff0c;也就是说它是根朝上&#xff0c;而叶朝下的。 有一个特殊的结点&#xff…

计算机网络 子网掩码与划分子网

一、实验要求与内容 1、需拓扑图和两个主机的IP配置截图。 2、设置网络A内的主机IP地址为“192.168.班内学号.2”&#xff0c;子网掩码为“255.255.255.128”&#xff0c;网关为“192.168.班内学号.1”&#xff1b;设置网络B内的主机IP地址为“192.168.班内学号100.2”&#…

【LeetCode】二叉树类题目详解

二叉树 二叉树的理论基础 二叉树是结点的度数之和不超过2的树&#xff0c;二叉树总共有五种基本形态 二叉树的种类主要有&#xff1a; 满二叉树完全二叉树 二叉树的存储方式 顺序存储链式存储 二叉树的遍历方式 先序遍历&#xff08;深度优先搜索&#xff09;中序遍历&…