首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >数据结构与算法(十一)——线索化二叉树&哈夫曼树

数据结构与算法(十一)——线索化二叉树&哈夫曼树

作者头像
拉维
发布于 2022-04-19 02:16:57
发布于 2022-04-19 02:16:57
1.6K00
代码可运行
举报
文章被收录于专栏:iOS小生活iOS小生活
运行总次数:0
代码可运行

一、线索化二叉树

1,产生的背景

如上图所示,是一个二叉树。可以看到,每一个节点都有三个元素:左子指针域、右子指针域、值域。对于存在左右子树的节点,其左右指针域指向的分别是各自的左右子节点;而对于未存在左子树,或者未存在右子树,或者左右子树均未存在的节点,该节点的左子指针域、右子指针域、左右指针域就会指向为空,此时就会存在指针域空间浪费的情况。而线索化二叉树就可以将这些浪费的指针域空间给利用起来,这是第一个背景。

第二个背景就是,可以更加快速和便捷地查找到某个节点的前驱和后继节点

2,对二叉树进行线索化的逻辑

二叉树的线索化,实际上就是将某个节点的未利用到的指针域空间指向某种遍历次序(前序、中序、后序)下的前驱或者后继节点。如下图所示,实线指向的就是子节点,虚线指向的就是前驱结点或者后序节点:

需要注意的是,前驱或者后继的指向是跟遍历次序有关的,某一个节点的前驱和后继在不同的遍历次序下是不一样的。比如,在前序、中序以及后序遍历的次序下,某个节点的前驱或者后继的指向都是不一样的。

与一般的二叉树相比,线索化的二叉树无非就是将空余的指针域给利用了起来,然后将这些空余的指针域指向某种遍历次序下的前驱或者后继。线索化的目的其实就是为了能够快速找到某一个节点的前驱和后继节点

并不是所有的二叉树节点都可以线索化的,只有当前节点的左子节点或者右子节点为空的时候,当前节点才可以被线索化。若某节点的左子树为空,则该节点的左子指针域指向前驱节点;若某节点的右子树为空,则该节点的右子指针域指向后继节点。

如上图所示,是中序遍历次序下的二叉树中各个前驱后继指针的指向。实际上,我们在对二叉树进行线索化的时候,肯定是需要进行一次遍历的,分析遍历到的每一个节点,如果有无用的指针域,那么就为其设置前驱或者后继。

3,如何判断某节点的左子节点指针指向的是左子节点还是前驱结点

线索化之后,二叉树的某一个节点的左子指针域和右子指针域就都指向了某一个节点,那么我们该如何区分左子指针域指向的是该节点的左子节点还是前驱结点呢?

可以通过在节点结构中加一个左标识(右标识)来判断左子指针域(右子指针域)指向的是左子节点(右子节点)还是前驱节点(后继节点)

如下图所示,就是加了标识位的线索化了的二叉树:

可以看到,此时的节点结构中包含五个元素:值域、左右子节点指针域、左右子节点指针类型

4,代码

(1)二叉树的结构

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
#include <stdio.h>
#include "string.h"
#include "stdio.h"
#include "stdlib.h"


#include "math.h"
#include "time.h"
#include <stdbool.h>


// 操作的状态
typedef enum : int {
  Success,
  Error,
} Status;


// 指针域指向的类型
typedef enum : int {
  link, // 左子节点、右子节点
  thread, // 线索化,前驱节点、后继节点
} PointerType;


// 节点值的类型
typedef char ElementValueType;
#define Nil '#' // 定义元素的空值


// 节点构造
typedef struct BinaryTreeNode {
  // 值域
  ElementValueType data;

  // 指针域
  struct BinaryTreeNode *leftChild;
  struct BinaryTreeNode *rightChild;

  // 指针域类型
  PointerType leftChildType;
  PointerType rightChildType;
} BinaryTreeNode;


// 二叉树类型
typedef struct BinaryTreeNode * BinaryTree;

(2)二叉树的构造

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// 1,二叉树的构造
char *initialString = "ABDH##I##EJ###CF##G##";
int initialIndex = 0;
void createBinaryTree(BinaryTree *tree) {
  // 递归结束条件
  ElementValueType currentValue = initialString[initialIndex];
  if (currentValue == Nil) { // 当前节点值为空值
    return;
  }

  // 为当前节点开辟空间
  *tree = malloc(sizeof(BinaryTreeNode));

  // 给当前节点值域赋值
  (*tree)->data = initialString[initialIndex++];

  // 递归构造左子节点
  createBinaryTree(&((*tree)->leftChild));
  (*tree)->leftChildType = (*tree)->leftChild ? link : thread;

  // 递归构造右子节点
  createBinaryTree(&((*tree)->rightChild));
  (*tree)->rightChildType = (*tree)->rightChild ? link : thread;
}

(3)二叉树的线索化

我们这里采用中序遍历的次序进行线索化。

使用preNode来记录当前遍历到的节点的上一个节点。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// 2,中序遍历二叉树,将其线索化
struct BinaryTreeNode *preNode;
void threadBinaryTree(BinaryTree *tree) {
  // 递归结束的条件
  if (!(*tree)) {
    return;
  }

  // 处理左子树
  threadBinaryTree(&((*tree)->leftChild));

  // 左子节点指针域
  if (!(*tree)->leftChild) {
    // 前驱结点
    (*tree)->leftChild = preNode;
    (*tree)->leftChildType = thread;
  } else {
    (*tree)->leftChildType = link;
  }

  // 前驱节点的右子节点指针域
  if (preNode && !preNode->rightChild) {
    preNode->rightChild = *tree;
    preNode->rightChildType = thread;
  } else if (preNode) {
    preNode->rightChildType = link;
  }

  // 更新上一个节点
  preNode = *tree;

  // 处理右子树
  threadBinaryTree(&((*tree)->rightChild));
}

(4)中序遍历二叉树,将其循环线索化

上面👆第(3)步中线索化好了的二叉树,在遍历的时候,实际上就类似于去操作一个双向链表结构。此时我们可以考虑一下,将这个“双向链表”优化成“双向循环链表”,也就是说,我们可以在二叉树根节点的头上再增加一个头结点。关于这个头结点相关的各个指针指向,说明如下:

  • ①头结点的左子节点设置为二叉树的根节点
  • ②头结点的右子节点指针线索化指向为二叉树的最后一个叶子节点
  • ③二叉树的第一个遍历到的节点的左子节点指针线索化为头结点
  • ④二叉树的最后一个叶子节点指针线索化为头结点

图示如下:

这样做的一个好处就是,我可以从第一个节点开始沿着后继节点进行遍历,也可以从最后一个节点开始沿着前驱节点进行遍历

代码如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// 3,中序遍历二叉树,将其循环线索化
void circleThreadBinaryTree(BinaryTree *circleThreadedTree, BinaryTree *tree) {
  // 创建头结点
  *circleThreadedTree = malloc(sizeof(BinaryTreeNode));

  // 头结点的左子节点设置为根节点
  (*circleThreadedTree)->leftChild = *tree;
  (*circleThreadedTree)->leftChildType = link;

  // 在线索化二叉树之前设置preNode的初始值,以将二叉树的第一个遍历到的节点的前驱设置为叶子节点
  preNode = *circleThreadedTree;

  // 对二叉树进行线索化
  threadBinaryTree(tree);

  // 头结点的右子节点线索化为二叉树的最后一个叶子节点
  (*circleThreadedTree)->rightChild = preNode;
  (*circleThreadedTree)->rightChildType = thread;

  // 二叉树的最后一个叶子节点的后继设置为头结点
  preNode->rightChild = *circleThreadedTree;
  preNode->rightChildType = thread;
}

(5)遍历循环线索化之后的二叉树

之前我们在遍历未经线索化的二叉树的时候,使用的是递归操作;二叉树经过线索化之后,再对其进行遍历就没必要使用递归了,而是直接通过后继节点和前驱节点进行迭代就可以了。

代码如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// 4,遍历线索化后的二叉树
void printBinaryNodeValue(ElementValueType element) {
  printf("%d", element);
}


void traverseCircleThreadedBinaryTree(BinaryTree tree) {
  struct BinaryTreeNode *tempNode = tree->leftChild; // 一开始指向根节点,自根节点开始遍历

  while (tempNode != tree) { // 经过一轮遍历之后最终会重新回到头结点,此时结束遍历
    // 找到当前未遍历到的最前面的节点
    while (tempNode->leftChildType == link) {
      tempNode = tempNode->leftChild;
    }

    // 打印当前节点信息
    printBinaryNodeValue(tempNode->data);

    // 通过后继指针依次找到接下来的节点
    while (tempNode->rightChildType == thread && tempNode->rightChild != tree) { // 有后继节点的时候则打印后继节点
      tempNode = tempNode->rightChild;
      printBinaryNodeValue(tempNode->data);
    }

    // 有右子节点的时候,则进行新一轮遍历
    tempNode = tempNode->rightChild;
  }
}

逻辑思路图示如下:

二、哈夫曼树 & 哈夫曼编码

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

哈夫曼编码是一种用于无损数据压缩的权编码算法。

1,哈夫曼树的必要性

如上图所示,学生分五类:优秀(90~100)、良好(80~90)、中等(70~80)、及格(60~70)、不及格(60分以下),这里我是按照成绩依次递增的顺序进行条件判断的。各个成绩区间的学生比例分布如下:

可以看到,有70%的学生是位于70~90的分数区间之内,但这些学生需要经历3~4次判断才可以确定其最终成绩,这样就会多出来很多初期的判断。如果数据量很大,那么就会造成效率问题。接下来我们就将树的路径长度作为指标来分析一下该效率问题。

节点的路径长度指的是,从根节点到该节点的路径上所包括的边的数目。

树的路径长度指的是,该树中所有节点的路径长度之和。

如上图所示,节点D的路径长度是4。

上面二叉树的路径长度是1+1+2+2+3+3+4+4=20。

接下来我按照权重占比,来调整一下条件判断的次序,如下图所示:

此时,占比较高的良好(D)和中等(C)的路径长度都是2,而不是之前的4和3了。

再计算一下二叉树的路径长度,是1+1+2+2+2+2+3+3=16。

可以看到,根据权重占比对二叉树进行改造之后,占比较高的节点的路径长度就变小了。

上面👆我只是计算了二叉树改造前后的路径长度,该路径长度并没有将权重计算进去,此时我们是没有办法据此来判断一个树的效率高低的。接下来我们就来计算一下WPL(Weighted Path Length of Tree),即树的带权重的路径长度,它是树的所有叶子节点的带权路径长度之和,它是可以判断树的效率高低的

首先来看一下改造之前的树的WPL:

一定要注意哦,一棵树的WPL指的是该树所有的叶子节点的加权路径长度之和。因此这里的WPL=315。

接下来再看一下根据权重改造之后的二叉树的WPL:

可以看到,按权重改造优化之后,树的WPL变为了220,对比之前的350,说明效率大大提高了。

2,哈夫曼树的构建思路

假设A、B、C、D、E五个节点的权重值分别是5、15、40、30、10,那么如何来构建一个哈夫曼树呢?

首先,取出两个最小权重节点A和E,小的放左边,大的放右边,并将这俩节点的值相加构建一个新的节点N1:

此时N1的权重值是15。

然后找到剩余节点中权重最小的节点B,此时发现N1和B节点的权重值都是15,所以将B节点放到N1节点的右侧,并生成一个新的节点N2:

此时,N2的权重值是30。

然后找到剩余节点中权重最小的节点D,此时发现N2和D节点的权重值都是30,所以将D节点放到N2节点的右侧,并生成一个新的节点N3:

此时,N3节点的权重值是60。

然后找到剩余节点中权重最小的节点C,其权重值是40,比N3的权重值60要小,所以将C节点放到N3节点的左侧,并生成一个新的节点T:

这里的T节点就是该哈夫曼树的根节点。

此时该树的加权路径长度WPL为40*1+30*2+15*3+5*4+10*4=205。

3,哈夫曼编码思考

现在需要将26个英文字母转换成二进制,假设A是000,后面依次累加,则ABCDEFG…转换成二进制如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
000 001 010 011 100 101 110……

按照这样的规则,字符串BADCADFEED转换成二进制为:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
001 000 011 010 000 011 101 100 100 011

接下来我们看一下如何对这些英文字母进行哈夫曼编码

我们知道,在英文语境中,肯定有一些字母是出现频率比较高的,有些是出现频率比较低的。这里我们假设ABCDEF的权重分别为27、8、15、15、30、5,如下:

接下来我按照权重对这些字母进行排序,如下:

然后我们就可以按照权重来构建一个哈夫曼树

首先找到权重最小的两个节点F和B,权重较小的F放在左边,二者生成一个父节点N1,如下:

此时N1的权重值是13。

接下来找到剩余节点中权重最小的那一个C节点,其权重是15,比N1的权重13要大,所以放在N1的右边,如下图所示:

N2是N1和C的双亲节点,其权重值是28。

接着找到余下来的权重最小的节点D,其权重值是15。这里的这个D放在哪里呢,是放在节点N2的右侧吗?不要这样想当然哦~此时需要重新再构建一个子树,与当前权重倒数第二小的节点A(27)分别作为左右子节点,然后生成一个双亲结点N3,如下图所示:

此时,N3的权重值是42,N2的权重值是28。

现在还剩最后一个节点E,其权重值是30,30比28大比42小,所以将节点E放在N2的右侧,并且生成双亲结点N4:

此时,N3的权重值是42,N4的权重值是58。

最后,以N3、N4分别作为左、右子节点,生成一个双亲结点T:

这里的T就是哈夫曼二叉树的根节点,至此,一个哈夫曼二叉树就构建出来了。

可以看到,在哈夫曼二叉树中,很重要的一个原则就是保证左右子树的权重平衡

好,现在已经生成了一个哈夫曼二叉树,接下来我将二叉树中所有的左子树路径全部标记为0,所有的右子树路径全部标记为1,如下图所示:

这样的话,ABCDEF的二进制表示如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
A——01
B——1001
C——101
D——00
E——11
F——1000

然后我们在比较一下BADCADFEED这个字符串的纯二进制表示和哈弗曼编码表示:

可以看到,使用哈夫曼编码表示比使用纯二进制表示要节省了5个字符空间

4,哈夫曼树的构建代码

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
#include <stdio.h>
#include "string.h"
#include "stdlib.h"
#include <stdbool.h>

// 权重最大值
#define MaxWeight 10000

// 哈夫曼节点结构
typedef struct HaffTreeNode {
  int weight; // 权重值
  int parentIndex; // 父节点坐标
  int leftChildIndex; // 左子节点坐标
  int rightChildIndex; // 右子节点坐标
  bool isAddedIntoTree; // 是否已经加入进了二叉树中
} HaffTreeNode;

// 哈夫曼树结构类型
typedef struct HaffTreeNode * HaffTree;

// 构建哈夫曼树
/*
 ①采用顺序存储的方式
 ②需要从外界传入权重数组相关信息
 */
void createHaffTree(int *weights, int weighttCount, HaffTree tree) {
  // 1,首先初始化所有的节点
  /*
   ①如果叶子节点的个数是n,则该哈夫曼树中就会有2n-1个节点
   ②将n个叶子节点都存放在数组的前n个位置
   ③将所有的叶子节点都赋予指定的权重值,所有的非叶子节点的权重值都赋值为0
   ④将所有节点的父节点坐标都初始化为0,子节点坐标都初始化为-1(代表没有子节点),isAddedIntoTree均设置为false代表未加入到二叉树中
   */
  for (int i = 0; i < 2*weighttCount-1; i++) {
    if (i < weighttCount) { // 当是叶子节点的时候
      tree[i].weight = weights[i];
    } else { // 当是非叶子节点的时候
      tree[i].weight = 0;
    }
    tree[i].parentIndex = 0;
    tree[i].leftChildIndex = -1; // 默认左右子节点都不存在
    tree[i].rightChildIndex = -1;
    tree[i].isAddedIntoTree = false; // 默认未加入到二叉树中
  }

  // 2,循环遍历所有节点,处理其相互关系
  int theMinWeight = MaxWeight; // 最小权重值
  int theSecondToTheMinWeight = MaxWeight; // 倒数第二小权重值
  int indexOfMin = 0; // 最小权重的节点坐标
  int indexOfSecondToMin = 0; // 倒数第二小权重的节点坐标
  /*
   如叶子节点有n个,则总共会有2n-1个节点,所以非叶子节点的个数为n-1
   */
  for (int i = 0; i < weighttCount - 1; i++) {
    // (1)寻找权重最小的俩节点
    /*
     寻找的区间是0~weighttCount+i之间。
     一定要记住,每一次都是找到俩权重最小的节点,然后构建一个新的双亲结点并加入到节点数组中。
     */
    for (int j = 0; j < weighttCount + i; j++) {
      if (tree[j].weight < theMinWeight && !tree[j].isAddedIntoTree) {
        theSecondToTheMinWeight = theMinWeight;
        indexOfSecondToMin = indexOfMin;

        theMinWeight = tree[j].weight;
        indexOfMin = j;
      } else if (tree[j].weight < theSecondToTheMinWeight && !tree[j].isAddedIntoTree) {
        theSecondToTheMinWeight = tree[j].weight;
        indexOfSecondToMin = j;
      }
    }

    // (2)将权重最小的俩节点加入到二叉树中
    /*
     找到权重最小的俩节点之后,将这俩节点组合成一个小树,所以需要生成一个双亲结点,这个双亲结点就存储在节点数组的接下来的位置上。
     一定要注意哦,这里的双亲结点的存储位置是我自己定义的哦,这跟我们之前说的按照层序遍历的次序进行存储是不一样的哦~
     */
    // 处理俩子节点的双亲结点指针指向
    tree[indexOfMin].parentIndex = weighttCount + i;
    tree[indexOfSecondToMin].parentIndex = weighttCount + i;

    // 将俩子节点标记为已经加入到二叉树中
    tree[indexOfMin].isAddedIntoTree = true;
    tree[indexOfSecondToMin].isAddedIntoTree = true;

    // 给父节点权重赋值,并且设置父节点的俩子节点指针
    tree[weighttCount + i].weight = tree[indexOfMin].weight + tree[indexOfSecondToMin].weight;
    tree[weighttCount + i].leftChildIndex = indexOfMin;
    tree[weighttCount + i].rightChildIndex = indexOfSecondToMin;
  }
}

5,哈夫曼编码

哈夫曼编码,指的就是通过哈夫曼树来获取编码。其代码如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// 哈弗曼编码的最大位数
#define MaxHaffCodeSize 100

// 哈夫曼编码结构
typedef struct HaffCode {
  int bits[MaxHaffCodeSize]; // 承载哈弗曼编码的数组
  int finalBitIndex; // 哈弗曼编码在bits数组中占用的结束位置
  int weight; // 当前字母的权重
} HaffCode;

// 根据哈夫曼树来求出哈夫曼编码
void haffCode(HaffTree tree, int count, HaffCode *haffCodes) {
  struct HaffCode *tempHaffCode = malloc(sizeof(HaffCode));
  int childIndex;
  int parentsIndex;

  // 依次遍历各个叶子节点,以获取到每一个叶子节点的哈弗曼编码,并保存在haffCodes中
  for (int i = 0; i < count; i++) {
    // 1,获取当前遍历到的叶子节点的哈弗曼编码,并通过临时变量tempHaffCode来记录
    tempHaffCode->finalBitIndex = 0;
    tempHaffCode->weight = tree[i].weight;

    childIndex = i;
    parentsIndex = tree[childIndex].parentIndex;
    // 自叶子节点一直遍历到根节点
    while (parentsIndex != 0) {
      // 左孩子节点编码为0,右孩子节点编码为1
      tempHaffCode->bits[tempHaffCode->finalBitIndex] = tree[parentsIndex].leftChildIndex == childIndex ? 0 : 1;

      // 将节点在二叉树中位置上移一层
      tempHaffCode->finalBitIndex++;
      childIndex = parentsIndex;
      parentsIndex = tree[childIndex].parentIndex;
    }

    // 2,将临时编码变量tempHaffCode的数据赋值到haffCodes[i]中
    // (1)tempHaffCode中记录的编码是逆序的,因此需要给正序过来
    for (int j = 0; j < tempHaffCode->finalBitIndex - 1; j++) {
      haffCodes[i].bits[j] = tempHaffCode->bits[tempHaffCode->finalBitIndex-j-1];
    }
    // (2)将tempHaffCode中的编码结束位置和权重赋值给haffCodes[i]
    haffCodes[i].finalBitIndex = tempHaffCode->finalBitIndex;
    haffCodes[i].weight = tempHaffCode->weight;
  }
}

以上。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2022-04-17,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 iOS小生活 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
​融合视觉语言模型 HPE-CogVLM | 基于LoRA层,利用 CogVLM 的视觉定位能力来增强 HPE 预测任务!
如今, Head 姿态估计(HPE)技术可应用于诸如注意力估计、面部识别、客户行为分析、驾驶员辅助系统以及人机交互[39]等各个领域。这项任务涉及从图像或视频中预测人类 Head 的欧拉角(偏航、俯仰和翻滚)。最近一些非大型语言模型(Non-LLMs)如6DRepNet[11]、HopeNet[36]和WHENet[57]在HPE上的研究努力,已经取得了显著的进展。
AIGC 先锋科技
2024/07/08
3030
​融合视觉语言模型 HPE-CogVLM |  基于LoRA层,利用 CogVLM 的视觉定位能力来增强 HPE 预测任务!
每周AI论文速递(2506202-250606)
我们提出了一种基于自我反思和强化学习的大语言模型性能提升方法。当模型回答错误时,通过激励其生成更高质量的反思内容,我们证明即使无法合成训练数据且仅能获得二元反馈信号,模型解决复杂可验证任务的能力仍能得到显著提升。该框架包含两个阶段:(1) 任务失败时,模型需生成分析先前尝试的反思性文本;(2) 模型在获得反思内容后重新尝试解决该任务。若重试成功,则对反思阶段生成的Token(词元)给予奖励。实验结果显示,该方法在不同架构模型上均取得显著效果提升,其中数学方程编写任务提升达34.7%,函数调用任务提升18.1%。特别值得注意的是,经过微调的中小规模模型(15亿至70亿参数)表现优于同架构下参数规模大10倍的基准模型。这一创新范式为开发具备有限反馈条件下自我提升能力的语言模型提供了新思路,有望推动构建更实用可靠的大语言模型系统。
叶子的技术碎碎念
2025/06/09
2590
每周AI论文速递(2506202-250606)
让视觉语言模型搞空间推理,谷歌又整新活了
视觉语言模型 (VLM) 已经在广泛的任务上取得了显著进展,包括图像描述、视觉问答 (VQA)、具身规划、动作识别等等。然而大多数视觉语言模型在空间推理方面仍然存在一些困难,比如需要理解目标在三维空间中的位置或空间关系的任务。
机器之心
2024/02/26
2470
让视觉语言模型搞空间推理,谷歌又整新活了
清华提出 VoCo-LLaMA | 使用LLMs 进行视觉压缩,FLOPs 减少 94.8%,推理时间加快 69.6% !
视觉语言模型的出现导致了视觉理解的显著进步。特别是,高分辨率图像编码[7; 8]和更多视频帧的融合[9; 10]分别提高了大型视觉语言模型和大型视频语言模型的能力。然而,大量的视觉标记占据了大型语言模型宝贵的上下文窗口的大部分,导致了高昂的计算成本,如图1(a)所示。例如,在使用LLaVA-1.6[7]中的高分辨率图像输入时,一个分辨率为672×672的单个图像被划分为四个较小的块,每个块以336×336的分辨率进行编码。这个过程产生了包含2304个视觉标记的图像表示,占据了超过一半的上下文长度。此外,随着输入图像数量的增加,文本的上下文窗口将进一步受限。例如,Vicuna-1.5[11]在其4k上下文长度内只能处理大约7帧(7×576=4032个标记),考虑到文本输入。[9, 10]研究了将上下文长度扩展到百万级以缓解这个问题的影响,但这需要昂贵的计算资源(例如,[9]需要超过1000个v4 TPU)以及数据准备和框架开发方面的工程努力。
AIGC 先锋科技
2024/07/08
5070
清华提出 VoCo-LLaMA | 使用LLMs 进行视觉压缩,FLOPs 减少 94.8%,推理时间加快 69.6% !
WalkVLM:如何通过VLM来辅助盲人行走?
这篇论文主要研究了如何利用视觉语言模型(Vision-Language Models,简称VLMs)来帮助视障人士行走。目前全球有大约两亿人患有不同程度的视力障碍,因此开发AI技术提供行走辅助变得尤为重要。
一点人工一点智能
2025/01/03
3660
WalkVLM:如何通过VLM来辅助盲人行走?
每日学术速递2.26
CV - 计算机视觉 | ML - 机器学习 | RL - 强化学习 | NLP 自然语言处理
AiCharm
2025/02/27
2331
每日学术速递2.26
每周AI论文速递(250113-250117)
尽管大语言模型 (LLMs) 表现卓越,但其发展面临一个关键挑战:在人类评估困难或 LLMs 超越人类的任务中,如何提供有效的反馈。尽管使用 LLMs 进行批评的兴趣日益增长,但当前的方法仍然依赖于人类注释或更强大的模型,这使得在没有外部监督的情况下增强批评能力的问题仍未解决。我们提出了 SCRIT (Self-evolving CRITic),这是一个能够实现批评能力真正自我进化的框架。从技术上讲,SCRIT 通过训练合成数据进行自我改进,这些数据由基于对比的自我批评者生成,该批评者使用参考解决方案进行逐步批评,并通过自我验证机制确保批评质量,该机制通过纠正结果来确保批评质量。使用 Qwen2.5-72B-Instruct(最强大的 LLMs 之一)实现,SCRIT 在批评纠正和错误识别基准测试中实现了高达 10.3% 的提升。我们的分析表明,SCRIT 的性能随着数据和模型规模的增加而正向扩展,优于其他方法,并且其自我验证组件对其性能至关重要。
叶子的技术碎碎念
2025/04/08
1120
每周AI论文速递(250113-250117)
每日学术速递12.27
CV - 计算机视觉 | ML - 机器学习 | RL - 强化学习 | NLP 自然语言处理
AiCharm
2024/12/27
1800
每日学术速递12.27
每周AI论文速递(250224-250228)
LLM-Microscope: 揭示 Transformer 上下文记忆中标点符号的隐藏作用
叶子的技术碎碎念
2025/04/08
1080
每周AI论文速递(250224-250228)
每日学术速递1.9
1.Automated Generation of Challenging Multiple-Choice Questions for Vision Language Model Evaluation
AiCharm
2025/01/09
1620
每日学术速递1.9
每日学术速递10.22
1.Dual Prototype Evolving for Test-Time Generalization of Vision-Language Models
AiCharm
2024/10/22
2100
每日学术速递10.22
每日学术速递12.19
CV - 计算机视觉 | ML - 机器学习 | RL - 强化学习 | NLP 自然语言处理
AiCharm
2024/12/19
2050
每日学术速递12.19
中科大、中兴提出新后训练范式:小尺寸多模态模型,成功复现R1推理
本文第一作者为邓慧琳,中国科学技术大学硕博连读四年级,研究方向为多模态模型视觉理解、推理增强(R1强化学习)、异常检测。在TAI、TASE、ICCV等期刊和顶会发表论文。
机器之心
2025/04/15
1640
中科大、中兴提出新后训练范式:小尺寸多模态模型,成功复现R1推理
利用大型语言模型和扩散模型大规模生成视觉最小变化数据,提升VLMs的细粒度理解能力 !
细粒度地理解目标、属性及其关系对于视觉-语言模型(VLMs)有效泛化到新的、未见过的场景和构图至关重要。以往的研究如ARO [40] 和 Sugarcrepe [8],强调了VLMs在这一领域的不足,主要关注于理解两个非常相似的标题之间的细粒度差异——一个人工编写的标题和自动生成的硬负例2标题,其中硬负例标题与原标题仅在目标、属性或两个目标之间的关系上有所不同。虽然可以通过基于规则的方法合成标题的硬负例,但为图像合成这样的硬负例则非常具有挑战性。
AIGC 先锋科技
2024/07/31
4520
利用大型语言模型和扩散模型大规模生成视觉最小变化数据,提升VLMs的细粒度理解能力 !
轻量级视频压缩(LVC):以最小成本迁移长视频理解能力,解决VLMs采样问题并提升多模型性能 !
大语言模型(LLMs)的快速发展推动了视频理解研究范式的转变,从传统的以视觉为中心的方法转向利用跨模态对齐能力的基于LLM的框架。这种由LLM驱动的革命体现在两种主要架构中:在视频-文本对齐数据上预训练的视频LLMs[3, 16, 23]和以图像-文本对齐[19, 25]为核心的视觉语言模型(VLMs)。
AIGC 先锋科技
2025/05/14
2430
轻量级视频压缩(LVC):以最小成本迁移长视频理解能力,解决VLMs采样问题并提升多模型性能 !
每日学术速递3.26 (New! 一图速览)
CV - 计算机视觉 | ML - 机器学习 | RL - 强化学习 | NLP 自然语言处理
AiCharm
2025/03/27
2360
每日学术速递3.26 (New! 一图速览)
SARChat-2M:首个SAR图像多模态对话数据集,验证VLMs能力,项目即将开源!
在人工智能(AI)研究领域,随着技术的不断进步和应用领域的拓展,研究者们对AI的认知和期望也在不断提升。本文旨在对当前AI技术的发展现状、挑战及其在各个领域的应用进行综述,以期为AI领域的进一步研究和发展提供参考。
未来先知
2025/03/24
6350
SARChat-2M:首个SAR图像多模态对话数据集,验证VLMs能力,项目即将开源!
每日学术速递2.20
1.Re-Align: Aligning Vision Language Models via Retrieval-Augmented Direct Preference Optimization
AiCharm
2025/02/21
2320
每日学术速递2.20
超越语义理解,VLMs通过像素值预测增强视觉细节感知能力 !
大型语言模型(LLMs)彻底改变了人工智能领域,使得机器能够以惊人的表现感知和生成人类般的文本。随着这一进步,基于LLM的视觉语言模型(VLMs)正在迅速发展,并在视觉和语言的跨领域内。最近的一些VLMs,如,在多个视觉语言任务上表现出色,包括视觉问答(VQA)和指代表达理解(REC)。通常,这些基于LLM的VLMs采用类似的建模设计:一个预训练的视觉编码器来提取视觉特征,一个映射模块将这些特征与语言空间对齐,以及一个LLM进行推理。
AIGC 先锋科技
2024/08/13
4290
超越语义理解,VLMs通过像素值预测增强视觉细节感知能力 !
2024年3月的计算机视觉论文推荐
从去年开始,针对LLM的研究成为了大家关注的焦点。但是其实针对于计算机视觉的研究领域也在快速的发展。每周都有计算机视觉领域的创新研究,包括图像识别、视觉模型优化、生成对抗网络(gan)、图像分割、视频分析等。
deephub
2024/03/20
3610
2024年3月的计算机视觉论文推荐
推荐阅读
相关推荐
​融合视觉语言模型 HPE-CogVLM | 基于LoRA层,利用 CogVLM 的视觉定位能力来增强 HPE 预测任务!
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档