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

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

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

目录

5-16 编码案例

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

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

方案1:不等长编码

字符 二进制编码
A 0
B 1
C 10
D 11

编码结果:0100101110

但是这个编码结果在译码阶段会产生歧义,因为:A(0)和B(1)分别是C(10)和D(11)的前缀,即"0100101110"可以被翻译为多种结果:

(1)ABBAACDC or (2)ACACDC

这显然是我们不想看到的,于是有人提出了一种等长编码方案。

方案2:等长编码

字符 二进制编码
A 00
B 01
C 10
D 11

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

小结

(1)等长编码不会产生译码歧义,但是编码长度相对较长,不符合尽量节省传输带宽的通信设计原则。

(2)不等长编码容易产生译码歧义,但能有效缩短编码长度,在传输上是理想的形态。

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

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

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

5-17 Huffman编码原理

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

还是使用开篇的问题来介绍Huffman编码:给定A、B、C、D四个字母组成的字符串(ABAACDC),要求使用数字0、1对其进行二进制编码。

step1:首先统计各个字符出现的次数,并作为节点,各字符节点拥有一个权重(weight)来表示字符串中该字符出现的次数。

step2:(每次)挑选weight最小的两个节点进行合并,即为这两个节点生成一个父节点,且父节点的weight为两个子节点weight之和。

step3:从剩下的节点(包含还未合并的原始字符节点和生成的父节点)循环执行上述操作,不断地合并最小的两个节点,最终只剩下一个根节点为止。

具体过程如下图所示:

最后得到编码表:

字符 二进制编码
A 1
B 000
C 001
D 01

编码结果: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 + 1 3 + 22 + 1 3 = 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

c 复制代码
#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)编码部分

核心代码

c 复制代码
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;
}

干活函数

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

c 复制代码
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;
}
c 复制代码
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;
		}
	}
}
c 复制代码
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;
}
c 复制代码
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;
}
c 复制代码
static pNode mergeNode(int weight){
	pNode node = (pNode)malloc(sizeof(struct TreeNode));
	memset(node, 0, sizeof(struct TreeNode));
	node->weight = weight;
	return node;
}
c 复制代码
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);
		}
	}
}

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

c 复制代码
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);
}

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

c 复制代码
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 解码实现

c 复制代码
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

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)测试代码

c 复制代码
#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]

cpp 复制代码
// 定义哈夫曼树节点
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.

相关推荐
没有理想的不伤心16 分钟前
责任链模式(大话设计模式)C/C++版本
c语言·设计模式·责任链模式
续亮~1 小时前
6、Redis系统-数据结构-05-整数
java·前端·数据结构·redis·算法
ningbaidexia2 小时前
java数据结构集合复习之ArrayList与顺序表
java·数据结构·windows
托尼沙滩裤4 小时前
【js面试题】js的数据结构
前端·javascript·数据结构
续亮~5 小时前
6、Redis系统-数据结构-03-压缩列表
数据结构·数据库·redis
akthwyl6 小时前
2024年【安全员-C证】考试及安全员-C证免费试题
c语言·开发语言
鸽鸽程序猿6 小时前
【数据结构】顺序表
java·开发语言·数据结构·学习·算法·intellij idea
取加若则_7 小时前
C++入门(C语言过渡)
c语言·开发语言·数据结构·c++·算法
真果粒wrdms7 小时前
【在线词典】项目实现
linux·c语言·嵌入式硬件·算法·udp·sqlite3
中草药z8 小时前
【Java算法】二分查找 上
数据结构·笔记·算法·leetcode·二分查找·学习方法