考研数据结构--树和二叉树(2)

news2024/9/29 5:30:54

文章目录

  • 二叉树的遍历
    • 前序遍历
    • 中序遍历
    • 后序遍历
    • 层次遍历
  • 不用栈的二叉树中序遍历算法
  • Morris
    • 代码分析
  • 二叉树的构造
    • 概述
    • 如何完成二叉树的构造
    • **回顾**
    • **思考**
    • 各种遍历序列提供的信息
    • 二叉树遍历性质
      • 性质1
      • 性质2
  • 线索化二叉树
    • 引入
    • 定义
    • 构造
    • 堆的定义
    • 堆的性质
    • 堆的建立
      • 堆的元素插入(代码)
      • 堆的元素删除(代码)
  • 霍夫曼树(哈夫曼树)
    • 来源
    • 基本概念
      • 1. 路径和路径长度
      • 2. 结点的权及结点的带权路径长度
      • 3. 树的带权路径长度
    • 引出霍夫曼树(定义)
    • 构造霍夫曼树
      • 原则
      • 如何构建即过程
      • 特点
    • 霍夫曼编码
    • 为什么要使用霍夫曼编码
    • 霍夫曼编码的特点

二叉树的遍历

在这里插入图片描述

在这里插入图片描述

前序遍历

在这里插入图片描述

二叉树先序遍历是指先访问根结点,再访问左子树,最后访问右子树。先序遍历的特点是每个结点都在其左右子树之前被访问,因此也称为根左右遍历。

二叉树先序遍历的应用场景有:

  • 输出二叉树的结构,方便观察和调试。
  • 复制二叉树,因为先序遍历可以保证每个结点在其子树之前被复制。
  • 序列化和反序列化二叉树,因为先序遍历可以保证每个结点在其子树之前被存储或读取。
  • 求二叉树的深度,因为先序遍历可以保证每个结点在其子树之前被计数。

在这里插入图片描述

// 先序遍历(递归)
void preOrder(BTree root) {
    if (root == NULL) return; // 空树直接返回
    printf("%c ", root->data); // 输出根结点
    preOrder(root->lChild); // 递归遍历左子树
    preOrder(root->rChild); // 递归遍历右子树
}

// 先序遍历(非递归)
void preOrderWithoutRecursion(BTree root) {
    if (root == NULL) return; // 空树直接返回
    BTree stack[100]; // 定义一个数组作为辅助栈
    int top = -1; // 栈顶指针初始化为-1
    BTree cur = root; // 当前结点指针初始化为根结点
    while (cur != NULL || top != -1) { // 当前结点不为空或者栈不空时循环
        while (cur != NULL) { // 当前结点不为空时循环
            printf("%c ", cur->data); // 输出当前结点
            stack[++top] = cur; // 将当前结点入栈
            cur = cur->lChild; // 将当前结点更新为左孩子(继续向左走)
        }
        if (top != -1) { // 如果栈不空
            cur = stack[top--]; // 将栈顶元素出栈并赋值给当前结点
            cur = cur->rChild; // 将当前结点更新为右孩子(继续向右走)
        }
    }
}

中序遍历

在这里插入图片描述

二叉树中序遍历是指先访问左子树,再访问根结点,最后访问右子树。中序遍历的特点是每个结点都在其左右子树之间被访问,因此也称为左根右遍历。

二叉树中序遍历的应用场景有:

  • 输出二叉搜索树的有序序列,因为二叉搜索树的中序遍历是升序或降序的。
  • 求二叉树的第k小或第k大的元素,因为二叉搜索树的中序遍历可以按顺序找到第k个元素。
  • 判断二叉搜索树是否合法,因为二叉搜索树的中序遍历应该是单调递增或递减的。
  • 求二叉搜索树中两个结点的最近公共祖先,因为二叉搜索树的中序遍历可以确定两个结点的相对位置。

在这里插入图片描述

// 中序遍历(递归)
void inOrder(BTree root) {
    if (root == NULL) return; // 空树直接返回
    inOrder(root->lChild); // 递归遍历左子树
    printf("%c ", root->data); // 输出根结点
    inOrder(root->rChild); // 递归遍历右子树
}

// 中序遍历(非递归)
void inOrderWithoutRecursion(BTree root) {
    if (root == NULL) return; // 空树直接返回
    BTree stack[100]; // 定义一个数组作为辅助栈
    int top = -1; // 栈顶指针初始化为-1
    BTree cur = root; // 当前结点指针初始化为根结点
    while (cur != NULL || top != -1) { // 当前结点不为空或者栈不空时循环
        while (cur != NULL) { // 当前结点不为空时循环
            stack[++top] = cur; // 将当前结点入栈
            cur = cur->lChild; // 将当前结点更新为左孩子(继续向左走)
        }
        if (top != -1) { // 如果栈不空
            cur = stack[top--]; // 将栈顶元素出栈并赋值给当前结点
            printf("%c ", cur->data); // 输出当前结点
            cur = cur->rChild; // 将当前结点更新为右孩子(继续向右走)
        }
    }
}

后序遍历

在这里插入图片描述

二叉树后序遍历是指先访问左子树,再访问右子树,最后访问根结点。后序遍历的特点是每个结点都在其左右子树之后被访问,因此也称为左右根遍历。

二叉树后序遍历的应用场景有:

  • 删除二叉树,因为后序遍历可以保证每个结点在其子树之后被删除,避免内存泄漏。
  • 求二叉树的高度,因为后序遍历可以保证每个结点在其子树之后被计算,从下往上递推高度。
  • 求二叉树的平衡因子,因为后序遍历可以保证每个结点在其子树之后被计算,从下往上递推平衡因子。
  • 求二叉树中两个结点的最近公共祖先,因为后序遍历可以保证每个结点在其子树之后被访问,从下往上找到第一个包含两个结点的祖先。

在这里插入图片描述

// 后序遍历(递归)
void postOrder(BTree root) {
    if (root == NULL) return; // 空树直接返回
    postOrder(root->lChild); // 递归遍历左子树
    postOrder(root->rChild); // 递归遍历右子树
    printf("%c ", root->data); // 输出根结点
}

// 后序遍历(非递归)
void postOrderWithoutRecursion(BTree root) {
    if (root == NULL) return; // 空树直接返回
    BTree stack[100]; // 定义一个数组作为辅助栈
    int top = -1; // 栈顶指针初始化为-1
    BTree cur = root; // 当前结点指针初始化为根结点
    BTree pre = NULL; // 上一个访问过的结点指针初始化为NULL
    while (cur != NULL || top != -1) { // 当前结点不为空或者栈不空时循环
        while (cur != NULL) { // 当前结点不为空时循环
            stack[++top] = cur; // 将当前结点入栈
            cur = cur->lChild; // 将当前结点更新为左孩子(继续向左走)
        }
        if (top != -1) { // 如果栈不空
            cur = stack[top]; // 将当前结点更新为栈顶元素(但不出栈)
            if (cur->rChild == NULL || cur->rChild == pre) { // 如果当前结点没有右孩子或者右孩子已经访问过了(即当前结点的左右子树都已经访问过了)
                printf("%c ", cur->data); // 输出当前结点
                pre = cur; // 将上一个访问过的结点更新为当前结点
                top--; // 将当前结点出栈
                cur = NULL; // 将当前结点置空(继续向上回溯)
            } else { // 如果当前结点有右孩子且右孩子还没有访问过了(即当前结点的左子树已经访问过了,右子树还没有访问过了)
                cur = cur->rChild; // 将当前结点更新为右孩子(继续向右走)
            }
        }
    }
}

思考

在这里插入图片描述

层次遍历

在这里插入图片描述

二叉树层次遍历是指按照从上到下、从左到右的顺序访问二叉树中的每个结点。层次遍历的特点是每个结点都在其所在层次的顺序被访问,因此也称为广度优先遍历。

二叉树层次遍历的应用场景有:

  • 输出二叉树的结构,方便观察和调试。
  • 求二叉树的最小深度,因为层次遍历可以保证第一个遇到的叶子结点就是最小深度所在的层。
  • 求二叉树的最大宽度,因为层次遍历可以保证每一层的结点都被连续访问,从而统计每一层的宽度。
  • 判断二叉树是否为完全二叉树,因为层次遍历可以保证第一个空孩子之后不应该再出现非空孩子。

在这里插入图片描述

// 层次遍历
void levelOrder(BTree root) {
    if (root == NULL) return; // 空树直接返回
    BTree queue[100]; // 定义一个数组作为辅助队列
    int front = 0; // 队首指针初始化为0
    int rear = 0; // 队尾指针初始化为0
    queue[rear++] = root; // 将根结点入队
    while (front != rear) { // 队列不空时循环
        BTree cur = queue[front++]; // 将队首元素出队并赋值给当前结点
        printf("%c ", cur->data); // 输出当前结点
        if (cur->lChild != NULL) { // 如果当前结点有左孩子
            queue[rear++] = cur->lChild; // 将左孩子入队
        }
        if (cur->rChild != NULL) { // 如果当前结点有右孩子
            queue[rear++] = cur->rChild; // 将右孩子入队
        }
    }
}

不用栈的二叉树中序遍历算法

不用栈的二叉树中序遍历算法是指利用二叉树的空指针域来存放后继结点的地址,从而实现不用辅助空间的中序遍历。这种算法也称为线索化二叉树或Morris遍历算法。

不用栈的二叉树中序遍历算法的优点是节省了空间复杂度,只需要O(1)的额外空间;缺点是修改了原来的二叉树结构,虽然最后可以恢复,但是可能会影响其他操作。

Morris

// Morris Inorder遍历
void MorrisIn(BTree root){
    BTree cur = root; // 定义当前节点
    BTree mostRight = NULL; // 定义最右节点
    while (cur != NULL){ // 当前节点不为空时循环
        mostRight = cur->lChild; // 找到左孩子节点
        if (mostRight != NULL){ // 当前节点有左孩子节点
            while (mostRight->rChild != NULL && mostRight->rChild != cur){ // 找到左子树中最右下方的节点
                mostRight = mostRight->rChild;
            }
            if (mostRight->rChild == NULL){ // 第一次到达时
                mostRight->rChild = cur; // 将最右下方节点的右孩子指向当前节点,建立线索
                cur = cur->lChild; // 当前节点更新为其左孩子
                continue; // 继续循环
            } else{ // 第二次到达时
                mostRight->rChild = NULL; // 将线索断开
            }
        }
        printf("%c ", cur->data); // 输出当前节点
        cur = cur->rChild; // 当前节点更新为其右孩子
    }
}

代码分析

  1. 定义当前节点和最右节点
BTree cur = root; // 定义当前节点
BTree mostRight = NULL; // 定义最右节点

这里定义了两个变量,分别代表当前节点和最右节点,并将当前节点初始化为根节点,最右节点初始化为NULL。

  1. 遍历二叉树
while (cur != NULL){ // 当前节点不为空时循环
// ...省略代码...
printf("%c ", cur->data); // 输出当前节点
cur = cur->rChild; // 当前节点更新为其右孩子
}

通过while循环来遍历二叉树,在当前节点不为空的情况下执行循环,输出当前节点的值,然后将当前节点更新为其右孩子(前提是如果当前节点没有右孩子,则继续向上返回,直到找到有右孩子的节点)。

  1. 找到左子树中的最右下方节点
mostRight = cur->lChild; // 找到左孩子节点
if (mostRight != NULL){ // 当前节点有左孩子节点
while (mostRight->rChild != NULL && mostRight->rChild != cur){ // 找到左子树中最右下方的节点
    mostRight = mostRight->rChild;
	}
}

在当前节点存在左孩子节点的情况下,通过循环找到左子树中的最右下方节点并将其赋值给mostRight变量,如果当前节点的左子树中没有右孩子,则mostRight为当前节点的左孩子节点;如果当前节点的左子树中有右孩子,则mostRight为左子树中最右下方节点。

  1. 新建或删除线索
if (mostRight->rChild == NULL){ // 第一次到达时
	mostRight->rChild = cur; // 将最右下方节点的右孩子指向当前节点,建立线索
	cur = cur->lChild; // 当前节点更新为其左孩子
	continue; // 继续循环
} else{ // 第二次到达时
	mostRight->rChild = NULL; // 将线索断开
}

在找到左子树中的最右下方节点后,如果该节点的右孩子为NULL,则说明第一次到达该节点,此时将该节点的右孩子指向当前节点,相当于建立线索,然后将当前节点更新为其左孩子,继续遍历;如果该节点的右孩子不为NULL且不是当前节点,则说明第二次到达该节点,此时将该节点的右孩子置为NULL,相当于断开线索。

  1. 总结
    综上,MorrisIn函数实现了对二叉树的中序遍历。在循环中,不断更新当前节点为其右孩子,同时通过mostRight变量找到左子树中的最右下方节点,新建或删除线索,实现了在不借助栈的情况下对二叉树的中序遍历。

区别

不用栈的二叉树中序遍历算法有两种,一种是Morris遍历,另一种是用线索化遍历

这两种算法的思想都是利用线索(或链表)将二叉树的遍历顺序预先存储下来,然后直接根据线索遍历二叉树,避免使用栈,节省空间。

不过,Morris遍历算法是通过修改二叉树的指针来实现线索化的,而线索化遍历算法则是通过在二叉树上添加额外的线索(或链表)来实现线索化的。

优劣

Morris遍历的时间复杂度为O(n),空间复杂度为O(1),与线索化二叉树遍历的时间复杂度和空间复杂度相同。但是Morris遍历的实现更加简单,不需要像线索化二叉树遍历那样需要对二叉树进行线索化的预处理,而是在遍历过程中动态创建线索,因此Morris遍历更加方便。

Morris遍历算法的代码实现相对较简单,而且不需要额外的数据结构,所以比线索化遍历算法更加常用。但是,如果需要对二叉树进行多次遍历,或者需要同时实现前序、中序、后序遍历,而不想每次都要重新遍历树建立线索,则线索化遍历算法可能更加适合。

二叉树的构造

概述

在这里插入图片描述

如何完成二叉树的构造

回顾

在这里插入图片描述

结论

在这里插入图片描述

思考

在这里插入图片描述

各种遍历序列提供的信息

在这里插入图片描述

二叉树遍历性质

二叉树的遍历有以下性质:

  • 任意一种遍历序列都无法唯一确定一棵二叉树,因为可能存在多种结构的二叉树具有相同的遍历序列。
  • 如果知道了二叉树的中序遍历序列和任意一种其他遍历序列,就可以唯一确定一棵二叉树,因为可以根据中序遍历序列确定左右子树的范围,然后根据其他遍历序列确定根节点的位置,以此递归地构造二叉树。

性质1

在这里插入图片描述

例子

在这里插入图片描述

性质2

在这里插入图片描述

例子

在这里插入图片描述

例题

在这里插入图片描述

线索化二叉树

引入

设一棵二叉树有n个结点,则有n-1条边(指针连线),而n个结点共有2n个指针域(Lchild和Rchild),显然有n-1个空闲指针域未用。则可以利用这些空闲的指针域来存放结点的直接前驱直接后继信息。

在这里插入图片描述

  • 前驱:在某种遍历方式下,一个结点的前一个访问的结点就是其前驱;
  • 后继:在某种遍历方式下,一个结点的后一个访问的结点就是其后继。

定义

对结点的指针域做如下规定:

  • 若结点有左孩子,则Lchild指向其左孩子,否则,指向其直接前驱;

  • 若结点有右孩子,则Rchild指向其右孩子,否则,指向其直接后继;

为避免混淆,对结点结构加以改进,增加两个标志域,如图所示。

在这里插入图片描述

以中序为例

在这里插入图片描述

构造

线索化二叉树:二叉树的线索化指的是依照某种遍历次序使二叉树成为线索二叉树的过程
线索化的过程就是在遍历过程中修改空指针使其指向直接前驱或直接后继的过程。

图例

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

对于后序遍历的线索树中找结点的直接后继依然困难可分以下三种情况:

  • 若结点是二叉树的根结点:其直接后继为空;
  • 若结点是其父结点的左孩子或右孩子且其父结点没有右子树:直接后继为其父结点;
  • 若结点是其父结点的左孩子且其父结点有右子树:直接后继是对其父结点的右子树按后序遍历的第一个结点。

总结

在这里插入图片描述

堆的定义

堆结构是一种数组对象,它可以被视为一棵完全二叉树。树中每个结点与数组中存放该结点中值的那个元素相对应,如下图:

在这里插入图片描述

堆是一种基于完全二叉树的高效数据结构,通常分为大根堆小根堆两种类型。

大根堆的每个结点的值都不小于其子结点的值,而小根堆的每个结点的值都不大于其子结点的值。

堆的重要应用包括堆排序、优先队列等。

在堆中,根结点存储着堆中最大(或最小)值,因此堆的建立过程就是不断将元素插入到树的叶子结点中,并使得新插入的结点上浮至合适的位置,以维护堆的性质。堆的插入和删除操作都要保证堆的结构仍然是完全二叉树,并且满足堆的性质。

堆的操作包括建立堆、插入元素、删除根结点、获取堆顶元素等。其中,堆排序是一种在大根堆或小根堆上进行排序的算法,它的核心思想是将待排序的数组构建成一个堆,并不断将堆顶元素取出放到有序数组中,直到堆为空。

堆的性质

设数组A的长度为len,堆的结点个数为size, size≤len,则A[i]存储编号为i的结点值(1≤i≤size),堆的根为A[1],并且利用完全二叉树的性质,我们很容易求第i个结点的父结点的下标为i/2,左孩子结点的下标为2i,右孩子结点的下标为2i+1;

堆具有这样一个性质,对除根以外的每个结点i,A[parent(i)]≥A[i]。即除根结点以外,所有结点的值都不得超过其父结点的值,这种堆又称为“大根堆”,这样就推出,堆中的最大元素存放在根结点中,且每一结点的子树中的结点值都小于等于该结点的值;反之,对除根以外的每个结点i,A[parent(i)]≤A[i]的堆,称为“小根堆”。

堆的建立

:建立一个小根堆,设n = 9,分别为:3 5 1 7 6 4 2 5 4。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

堆的元素插入(代码)

#include <cstdio>
#include <cstdlib>
#define MaxSize 50

int heap[MaxSize],siz = 0;

void swap(int arr[], int i, int j) {
    int temp = arr[i];
    arr[i] = arr[j];
    arr[j] = temp;
}

void insert(int x){
    heap[++siz] = x;      // 将新元素加入堆的最后一个位置
    int pos = siz;        // pos指向新加入元素的位置
    while (pos != 1){     // 自下而上维护堆的性质
        if (heap[pos]<heap[pos/2]) // 若新元素比其父节点小,交换两个元素的位置
            swap(heap,pos,pos/2);
        else     // 否则堆性质已经满足,结束循环
            break;
        pos = pos/2;    // pos指向其父节点,继续向上比较
    }
}

堆的元素删除(代码)

在这里插入图片描述

在这里插入图片描述

void del(){
    if (siz == 0) { // 如果堆为空,直接返回
        return;
    }
    printf("删除的堆顶最小元素是:%d\n",heap[1]); // 打印被删除的最小元素
    heap[1] = heap[len-1]; // 将堆的最后一个元素替换到根节点
    len--; // 堆的大小减1
    int now = 1;
    while (now*2<=len){ // 从堆顶开始向下调整堆
        int next = now*2; // next是now的子节点
        if (next+1<=len&&heap[next+1]<heap[next])next = next+1; // 找到两个子节点中更小的那个
        if (heap[next]<heap[now]){ // 如果子节点的值比父节点小,就交换两个节点
            swap(heap,now,next);
        }
        else break; // 否则停止调整
        now = next; // 继续向下调整
    }
}

全部代码

//
// Created by lenovo on 2023/5/14.
//
#include <cstdio>
#include <cstdlib>
#define MaxSize 50

int heap[MaxSize],siz = 0, len = 1;;

void swap(int arr[], int i, int j) {
    int temp = arr[i];
    arr[i] = arr[j];
    arr[j] = temp;
}

void insert(int x){
    heap[++siz] = x;      // 将新元素加入堆的最后一个位置
    int pos = siz;        // pos指向新加入元素的位置
    while (pos != 1){     // 自下而上维护堆的性质
        if (heap[pos]<heap[pos/2]) // 若新元素比其父节点小,交换两个元素的位置
            swap(heap,pos,pos/2);
        else     // 否则堆性质已经满足,结束循环
            break;
        pos = pos/2;    // pos指向其父节点,继续向上比较
    }
}

void del(){
    if (siz == 0) { // 如果堆为空,直接返回
        return;
    }
    printf("删除的堆顶最小元素是:%d\n",heap[1]); // 打印被删除的最小元素
    heap[1] = heap[len-1]; // 将堆的最后一个元素替换到根节点
    len--; // 堆的大小减1
    int now = 1;
    while (now*2<=len){ // 从堆顶开始向下调整堆
        int next = now*2; // next是now的子节点
        if (next+1<=len&&heap[next+1]<heap[next])next = next+1; // 找到两个子节点中更小的那个
        if (heap[next]<heap[now]){ // 如果子节点的值比父节点小,就交换两个节点
            swap(heap,now,next);
        }
        else break; // 否则停止调整
        now = next; // 继续向下调整
    }
}

void input(){
    char x;
    while(true){
        scanf("%s",&x);
        if (x == '#') break;  // 输入#时停止添加元素
        insert(x-'0');            // 将输入的元素加入堆中
        len++;
    }
}

void printArray(int arr[], int size) {
    for (int i = 1; i < size; i++) {
        printf("%d ", arr[i]);
    }
    printf("\n");
}

int main(){
    input();
    printArray(heap,len);
    del();
    printArray(heap,len);
}

霍夫曼树(哈夫曼树)

来源

在这里插入图片描述

基本概念

1. 路径和路径长度

  • 路径:在一棵树中,从一个结点到另一个结点之间的通路,称为路径;
  • 路径长度:路径上的分支数目,称为路径长度;

在这里插入图片描述

2. 结点的权及结点的带权路径长度

  • 结点的权是指给结点赋予的一个有着某种含义的数值,称为该结点的权。
  • 带权路径长度:从根结点到某个结点的路径长度与该结点的权值的乘积,称为带权路径长度;

在这里插入图片描述

3. 树的带权路径长度

  • 树的带权路径长度:所有叶子结点的带权路径长度之和,记为 WPL。

在这里插入图片描述

例题

在这里插入图片描述

引出霍夫曼树(定义)

霍夫曼树是一种带权路径长度最短的二叉树,也称为最优二叉树。

它是根据一组给定的权值构造出来的,每个权值对应一个叶子结点。

霍夫曼树的特点是它的带权路径长度最小,也就是说,它能用最少的分支数来表示所有的权值。

这样就可以节省存储空间和传输时间,因此霍夫曼树常用于数据压缩和编码。😊

在这里插入图片描述

构造霍夫曼树

原则

在这里插入图片描述

如何构建即过程

构造霍夫曼树的基本思路是:

  • 首先,将给定的一组权值作为叶子结点,构成一个森林,每棵树只有一个结点;
  • 然后,从森林中选出两个权值最小的树,作为一棵新树的左右子树,新树的权值为两个子树的权值之和;
  • 接着,从森林中删除选出的两棵树,并将新树加入森林;
  • 重复上述步骤,直到森林中只剩一棵树为止,这棵树就是霍夫曼树。

在这里插入图片描述

示例

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

特点

霍夫曼树的特点有以下几点 :

  • 霍夫曼树是一种带权路径长度最短的二叉树,也称为最优二叉树;
  • 霍夫曼树是一种满二叉树,只有度为 0 或 2 的结点,没有度为 1 的结点;
  • 霍夫曼树的权值较大的结点离根结点较近,权值较小的结点离根结点较远;
  • 霍夫曼树的任意非叶子结点的左右子树交换后仍然是一棵霍夫曼树;
  • 霍夫曼树的带权路径长度与权值的分布有关,与权值的排列顺序无关;

在这里插入图片描述

霍夫曼编码

为什么要使用霍夫曼编码

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

霍夫曼编码的特点

霍夫曼编码的特点有以下几点:

  • 霍夫曼编码是一种基于霍夫曼树的编码方式,它可以给不同的字符分配不同长度的二进制编码,使得出现频率高的字符用较短的编码,出现频率低的字符用较长的编码,从而实现数据压缩的目的;

  • 霍夫曼编码是一种无损压缩编码,它可以保证原始数据在压缩和解压缩后不会发生任何变化,保证数据的完整性和可靠性;

  • 霍夫曼编码是一种前缀编码,它可以保证任何一个字符的编码都不是另一个字符的编码的前缀,这样就可以方便地进行识别和解码,避免歧义和错误;

    • 这句话的意思是,如果用霍夫曼编码给不同的字符分配二进制编码,那么就不会出现这样的情况:一个字符的编码是另一个字符的编码的一部分,而且在前面。例如,如果有两个字符 A 和 B ,它们的编码分别是 01 和 011 ,那么就不符合这个条件,因为 A 的编码 01 是 B 的编码 011 的前缀。这样就会造成解码时的混乱和错误,因为无法区分 01 是 A 还是 B 的一部分。但是如果用霍夫曼编码,就不会出现这样的问题,因为它可以保证任何一个字符的编码都不是另一个字符的编码的前缀。

  • 霍夫曼编码是一种最优编码,它可以使得数据压缩后的平均长度达到最小,也就是说,它可以使得数据压缩率达到最大。

在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

GPT1解读:Improving Language Understanding by Generative Pre-Training

自然语言处理NLP是当代人工智能的关键领域&#xff0c;包含文本识别、智能问答等多个方向任务&#xff0c;通过监督学习方式一般需要大量带标签数据&#xff0c;而对某些特定任务&#xff0c;获取带标签数据成本非常高。GPT通过大量的未标记文本数据来学习一个通用预训练&#…

ZiKiT DICOM 存档(PACS)模态服务器 Crack

ZiKiT结合了DICOM存档&#xff08;PACS&#xff09;&#xff0c;模态工作列表服务器和HL7消息代理&#xff0c;它们共享相同的数据库并相互通信。 最新版本 – ZiKiT 2020 ZiKiT 提供动态映射规则和消息结构定义&#xff0c;同时保持合规性并遵守标准。该套件使非程序员能够在…

电动力学:电偶极辐射场

电磁辐射的产生条件 存在时变源&#xff08;时变的电荷源、时变的电流源&#xff0c;或时变的电磁场&#xff09;时变源的频率应足够高&#xff08;辐射系统的尺寸大小和电磁波波长差不多时&#xff0c;才有可能产生明显的辐射效应&#xff09;波源电路必须开放&#xff08;源电…

Android 如何获取有效的DeviceId

目录 前言官方唯一标识符建议使用广告 ID使用实例 ID 和 GUID不要使用 MAC 地址标识符特性常见用例和适用的标识符 解决方案DeviceIdANDROID_IDMac地址UUID补充 总结 前言 从 Android 10 开始&#xff0c;应用必须具有 READ_PRIVILEGED_PHONE_STATE 特许权限才能访问设备的不可…

新手建站:腾讯云轻量服务器安装宝塔镜像和使用方法

腾讯云轻量应用服务器宝塔面板怎么用&#xff1f;轻量应用服务器如何安装宝塔面板&#xff1f;在镜像中选择宝塔Linux面板腾讯云专享版&#xff0c;在轻量服务器防火墙中开启8888端口号&#xff0c;然后远程连接到轻量服务器执行宝塔面板账号密码查询命令&#xff0c;最后登录和…

Java内存模型介绍

Java作为一种面向对象的&#xff0c;跨平台语言&#xff0c;其对象、内存等一直是比较难的知识点。而且很多概念的名称看起来又那么相似&#xff0c;很多人会傻傻分不清楚。比如本文要讨论的JVM内存结构、Java内存模型和Java对象模型&#xff0c;这就是三个截然不同的概念&…

系列四、vue3 初始化项目(图形化界面方式)

一、启动UI界面 vue ui 二、创建项目 2.1、在此创建项目 2.2、创建新项目-详情配置 2.3、创建新项目-预设 2.4、创建新项目-功能 2.5、创建新项目-配置 2.6、运行项目 任务》serve》运行》启动app 2.7、首页 三、安装element-plus 3.1、步骤 ①、运行 vue ui 命令&#…

【C++初阶】想要编译器为你干活吗?来试试模板吧(模板初阶)

一.泛型编程 引入 我们之前都写过交换函数Swap&#xff0c;例如这样的&#xff1a; //交换两个整型 void Swap(int*x1, int *x2) {int tmp *x1;*x1 *x2;*x2 tmp;} 如果要交换其它的类型该怎么办呢&#xff1f; 那只能当个CV工程师了&#xff0c;然后再修修改改&#xff0c;…

java枚举enum

目录 一、概念二、声明枚举三、枚举类四、为枚举添加方法五、EnumMap 与 EnumSet 一、概念 枚举是一个被命名的整型常数的集合&#xff0c;用于声明一组带标识符的常数。枚举在曰常生活中很常见&#xff0c;例如一个人的性别只能是“男”或者“女”&#xff0c;一周的星期只能…

CAN总线通讯协议学习

s目录 CAN&#xff08;controller Area Network) 控制器局域网 CAN通讯 CAN总线的数据帧 解析 CAN&#xff08;controller Area Network) 控制器局域网 CAN总线应用最多的是汽车领域,这里的控制器在汽车领域的专业术语是ECU.(electronic control unit)电子控制单元。可以看成…

【计算机网络之HTTP篇】HTTP协议详解

目录 一、HTTP协议概念 二、HTTP 协议格式 三、HTTP请求详解 认识URL 认识HTTP方法 GET POST Host Content-Length Content-Type User-Agent (简称 UA) Referer Cookie 四、HTTP 响应详解 状态码 200 OK 404 Not Found 403 Forbidden 500 Internal Server E…

IMX6ULL裸机篇之DDR3初始化

一. DDR3L初始化简介 I.MX6U-ALPHA 开发板上带有一个 256MB/512MB 的 DDR3 内存芯片&#xff0c;16 位宽&#xff0c;型号为 NT5CC128M16JR/MT5CC256M16EP&#xff0c;nanya 公司出品的&#xff0c;分为对应 256MB 和 512MB 容量。 我自己用的开发板上 DDR3L内存芯片型号为…

【论文阅读】REPLUG: Retrieval-Augmented Black-Box Language Models

文章目录 前言REPLUGREPLUG LSR: Training the Dense RetrieverComputing Retrieval LikelihoodComputing LM likelihood 前言 原文地址&#xff1a;REPLUG: Retrieval-Augmented Black-Box Language Models 本文提出REPLUG&#xff0c;一个将语言模型视为黑盒检索增强的语言模…

45道SQL题目陆续更新

文章目录 学习视频配置环境第一天内连接 外连接第二天 学习视频 学习视频 配置环境 四张表 配置四张表的sql语句 #创建发据库 create database frogdata charsetutf8&#xff1b;use frogdata;# 学生表 Student create table Student( SId varchar(10), Sname varchar(1…

网易云音乐开发--SongDetail搭建

SongDetail静态页面搭建 我们再新建一个页面songDetail 先写结构 再写结构 然后在写样式&#xff0c;把这个图片放进去 这样就放进去了&#xff0c;这里有一个新的让元素居中的方式就是&#xff0c;子绝父相&#xff0c;然后 position: absolute;top: 0;left: 0;right: 0;bot…

MultipartFile来上传单个及多个文件代码示例

目录 一、MultipartFile上传单个文件代码示例1.1、MultipartFile上传单个文件,不包含其它参数1.2、MultipartFile上传单个文件,包含其它参数1.3、MultipartFile上传单个文件,包含其它请求实体 二、MultipartFile上传多个文件代码示例2.1、MultipartFile上传多个文件,不包含其它…

java(springboot+ssm)/python/php/nodejs/基于vue的景区门票预约管理系统

后端&#xff1a;java(springbootssm)/python/php/nodejs/ 开发运行&#xff1a;微信开发者/hbuilderx 后端:idea/eclipse/vscode/pycharm 模块划分&#xff1a;公告类型、公告信息、用户信息、用户咨询、地区信息、景区信息、景区开放、景区预约、统计信息 本技术是Java平台的…

国考省考行测:年均增长率,等速率增长率问题

国考省考行测&#xff1a;年均增长率&#xff0c;平均增长率 2022找工作是学历、能力和运气的超强结合体! 公务员特招重点就是专业技能&#xff0c;附带行测和申论&#xff0c;而常规国考省考最重要的还是申论和行测&#xff0c;所以大家认真准备吧&#xff0c;我讲一起屡屡申…

异常和中断

异常和中断机制 ​ 现代计算机中都配有完善的异常和中断处理系统&#xff0c;CPU的数据通路中有相应的异常检测和响应逻辑&#xff0c;外设接口中有相应的中断请求和控制逻辑&#xff0c;操作系统中有相应的中断服务程序。 异常和中断的基本概念 异常&#xff08;内中断&#…

数据结构之树,实现堆的增删改查接口及堆的应用

目录 一、树 1.树的概念及结构 1.1树的概念 1.2树的相关概念 1.3树的表示 2.二叉树的概念及结构 2.1二叉树的概念 2.2特殊的二叉树 2.3二叉树的性质 2.4二叉树的存储结构 3.二叉树的顺序结构及实现 3.1二叉树的顺序结构 3.2堆的概念及结构 二、堆的实现 0.定义堆…