探索数据结构:分支的世界之二叉树与堆

简介: 探索数据结构:分支的世界之二叉树与堆

一、什么是树

1.1 什么是树

树是一种非线性的数据结构,它是由n(n>=0)个有限结点组成一个具有层次关系的集合。把它叫做树是因为它看起来像一棵倒挂的树,也就是说它是根朝上,而叶朝下的。


  • 根结点:根节点没有前驱结点。
  • 除根节点外,其余结点被分成是一棵结构与树类似的子树。每棵子树的根结点有且只有一个前驱,可以有0个或多个后继。
  • 因此,树是递归定义的



1.2 树的相关概念

  • 节点的度:一个节点含有的子树的个数称为该节点的度; 如上图:A的为2
  • 叶节点:度为0的节点称为叶节点; 如上图:G、H、I节点为叶节点
  • 非终端节点或分支节点:度不为0的节点; 如上图:B、D、C、E、F节点为分支节点
  • 双亲节点或父节点:若一个节点含有子节点,则这个节点称为其子节点的父节点; 如上图:A是B的父节点孩子节点或子节点:一个节点含有的子树的根节点称为该节点的子节点; 如上图:B是A的孩子节点
  • 兄弟节点:具有相同父节点的节点互称为兄弟节点; 如上图:B、C是兄弟节点
  • 树的度:一棵树中,最大的节点的度称为树的度; 如上图:树的度为2
  • 节点的层次:从根开始定义起,根为第1层,根的子节点为第2层,以此类推;
  • 树的高度或深度:树中节点的最大层次; 如上图:树的高度为4
  • 堂兄弟节点:双亲在同一层的节点互为堂兄弟;如上图:H、I互为兄弟节点
  • 节点的祖先:从根到该节点所经分支上的所有节点;如上图:A是所有节点的祖先
  • 子孙:以某节点为根的子树中任一节点都称为该节点的子孙。如上图:所有节点都是A的子孙
  • 森林:由m棵互不相交的树的集合称为森林;


1.3 树的表示方法

树结构相对线性表就比较复杂了,要存储表示起来就比较麻烦了,实际中树有很多种表示方式,如:双亲表示法,孩子表示法、孩子兄弟表示法等等。我们这里就简单的了解其中最常用的孩子兄弟表示法。

代码如下所示:

typedef int DataType;
 
struct  TreeNode
{
  DataType val;
 
  TreeNode* FirstChild;
  TreeNode* FirstBrother;
};


二、特殊的树之二叉树

那么树的结构如此多样我们要怎么用呢,所以设计出了一种名为二叉树的数据结构来使用。


2.1 二叉树的概念

一棵二叉树是结点的一个有限集合,该集合或者为空,或者是由一个根节点加上两棵别称为左子树和右子树的二叉树组成。

二叉树的特点:

  1. 每个结点最多有两棵子树,即二叉树不存在度大于2的结点。
  2. 二叉树的子树有左右之分,其子树的次序不能颠倒。


2.2 数据结构中的二叉树

对于任意二叉树都是由以下几种结构复合形成的:


2.3 特殊的二叉树

有两种特殊的二叉树,能让我们更好地利用空间


  1. 满二叉树:一个二叉树,如果每一个层的结点数都达到最大值,则这个二叉树就是满二叉树。也就是说,如果一个二叉树的层数为K,且结点总数是(2^k) -1 ,则它就是满二叉树。
  2. 完全二叉树:完全二叉树是效率很高的数据结构,完全二叉树是由满二叉树而引出来的。对于深度为K的,有n个结点的二叉树,当且仅当其每一个结点都与深度为K的满二叉树中编号从1至n的结点一一对应时称之为完全二叉树。 要注意的是满二叉树是一种特殊的完全二叉树。


2.4 二叉树的性质


  1. 若规定根节点的层数为1,则一棵非空二叉树的第i层上最多有2^(i-1) 个结点。
  2. 若规定根节点的层数为1,则深度为h的二叉树的最大结点数是2^h- 1。
  3. 对任何一棵二叉树, 如果度为0其叶结点个数为 n0, 度为2的分支结点个数为 n2,则有n0=n2+1。
  4. 若规定根节点的层数为1,具有n个结点的满二叉树的深度,h=Log2(n+1)。
  5. 对于具有n个结点的完全二叉树,如果按照从上至下从左至右的数组顺序对所有节点从0开始编号,则对于序号为 i 的结点有:
  1. 若i > 0,i 位置节点的双亲序号:(i - 1) / 2(计算机计算保留整数部分);若 i = 0,i 为根节点编号,无双亲节点
  2. 若2i + 1 < n,左孩子序号:2i + 1,2i + 1 >= n 否则无左孩子
  3. 若2i + 2 < n,右孩子序号:2i + 2,2i + 2 >= n 否则无右孩子


2.5 二叉树的结构

2.5.1 顺序结构

普通的二叉树是不适合用数组来存储的,因为可能会存在大量的空间浪费。而完全二叉树更适合使用顺序结构存储。现实中我们通常把堆(一种二叉树)使用顺序结构的数组来存储,需要注意的是这里的堆和操作系统虚拟进程地址空间中的堆是两回事,一个是数据结构,一个是操作系统中管理内存的一块区域分段。




这种实现二叉树的数据结构我们称之为堆

2.5.2 链式结构

二叉树的链式存储结构是指,用链表来表示一棵二叉树,即用链来指示元素的逻辑关系。 通常的方法是链表中每个结点由三个域组成,数据域和左右指针域,左右指针分别用来给出该结点左孩子和右孩子所在的链结点的存储地址 。链式结构又分为二叉链和三叉链,当前我们学习中一般都是二叉链,后面课程学到高阶数据结构如红黑树等会用到三叉链。


三、堆的概念及其结构

如果有一个数据的集合K = {k0,k1, k2,…,kn-1},把它的所有元素按完全二叉树的顺序存储方式存储在一个一维数组中,并满足:每一个孩子都比它的父亲小(或大),则称为小堆(或大堆)。将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆。



四、堆的实现

4.1 堆的向下和向上调整算法

现在我们给出一个数组,逻辑上看做一颗完全二叉树。

我们通过从根节点开始的向下调整算法可以把它调整成一个堆。

向下调整算法:


  • 大堆:父节点比孩子节点小,往下换,换孩子中大的那个
  • 小堆:父节点比孩子结点大,往下换,换孩子中小的那个
int array[] = {27,15,19,18,28,34,65,49,25,37};


下列代码实现的是建立一个大堆的示例:

void AdjustDown(HPDataType* a, HPDataType size, HPDataType parent)
{
  int child = parent * 2 + 1;
  while (child < size)
  {
    if (child + 1 < size && a[child] < a[child + 1])//建大堆
    {
      child++;
    }
 
    if (a[parent] < a[child])//建大堆
    {
      Swap(&a[child], &a[parent]);
      parent = child;
      child = parent * 2 + 1;
    }
    else
    {
      break;
    }
  }
}


向上调整算法则是从最后一个节点开始向上调整:

  • 大堆:父节点比孩子结点小,往上换
  • 小堆:父节点比孩子节点大,往上换


代码示例如下:

void AdjustUp(HPDataType* a, int child)
{
  int parent = (child - 1) / 2;
  while (child > 0)
  {
    if (a[child] > a[parent])//建大堆
    {
      Swap(&a[child], &a[parent]);
      child = parent;
      parent = (child - 1) / 2;
    }
    else
    {
      break;
    }
  }
}


4.2 堆的定义

1. typedef struct Heap//顺序表
2. {
3.  HPDataType* a;     //动态开辟的数组
4.  int size;    //数据个数
5.  int capacity;   //容量大小
6. }HP;


4.3 堆的销毁

void HPDeseroy(HP* php)
{
  assert(php);
  if (php->a != NULL)
  {
    free(php->a);
    php->a = NULL;
    php->size = php->capacity = 0; 
  }
}


4.4 堆的初始化

void HPInit(HP* php)
{
  assert(php);
  php->a = NULL;
  php->size = 0;
  php->capacity = 0;
}


4.5 堆的插入

先插入一个10到数组的尾上,再进行向上调整算法,直到满足堆。

void HPPush(HP* php, HPDataType x)
{
  assert(php);
 
  if (php->size == php->capacity)
  {
    int newCapacity = php->capacity == 0 ? 4 : php->capacity * 2;
    HPDataType* tmp = (HPDataType*)realloc(php->a, sizeof(HPDataType) * newCapacity);
    if (tmp == NULL)//防止原先地址被覆盖
    {
      perror("realloc fail");
      return;
    }
    php->a = tmp;
    php->capacity = newCapacity;
  }
 
  php->a[php->size] = x;
  php->size++;
 
  AdjustUp(php->a, php->size - 1);
}


4.6 堆的删除

删除堆是删除堆顶的数据

堆的删除不能往前挪动覆盖,因为首先数组的挪动覆盖 比较慢,而后,堆的关系会乱。

删最后一个就不会改变堆的关系所以,将堆顶的数据根最后一个数据一换,然后删除数组最后一个数据,再进行向下调整算法。

void HPPop(HP* php)
{
  assert(php);
  assert(php->size > 0);
 
  Swap(&php->a[0], &php->a[php->size - 1]);
  php->size--;
 
  AdjustDown(php->a, php->size, 0);
}


4.7 取堆顶元素

1. HPDataType HPTop(HP* php)
2. {
3.  assert(php);
4.  assert(php->size > 0);
5. 
6.  return php->a[0];
7. }


4.8 堆的判空

1. bool HPEmpty(HP* php)
2. {
3.  assert(php);
4. 
5.  return php->size == 0;
6. }


五、堆排序

5.1 堆的创建

下面我们给出一个数组,这个数组逻辑上可以看做一颗完全二叉树,但是还不是一个堆,现在我们通过算法,把它构建成一个堆。

5.1.1 向上调整建堆

从根节点开始一个个向上调整

1. for (int i = 1; i < size; i++)
2. {
3.  AdjustUp(a, i);
4. }


5.1.2 向下调整建堆

我们可以将二叉树的每一个非终端节点,也就是非叶节点看成一个小二叉树,将所有小二叉树排成堆,从每一个这样的节点向下调整

当根节点之下的两个子树都为堆的时候,只需要从根开始向下调整

代码示例如下:

1. for (int i = ((size - 1) - 1) / 2; i >= 0; i--)
2. {
3.  AdjustDown(a, size, i);
4. }


5.2 建堆的时间复杂度

因为堆是完全二叉树,而满二叉树也是完全二叉树,此处为了简化使用满二叉树来证明

(时间复杂度本来看的 就是近似值,多几个节点不影响最终结果)

向上调整建堆的时间复杂度为O(N*logN)。

向下调整建堆的时间复杂度为O(N)。


所以堆的创建一般选取像下调整建堆

证明:

设数组元素有N个,则二叉树的高度为


1. 向上调整算法:

这种算法证明过程较为简单

向上调整算法相当于,从头开始将元素一个个插入在尾部,再进行向上调整,由于从底部到顶部高度为 log(N),则最坏情况要调整 log(N) 次,由于有N个元素,整体建堆的时间复杂度为O(N*logN)

2. 向下调整算法:

设时间复杂度为O( F(h) ),则我们可以得到向下调整算法最坏情况的调整次数:


      ①



由高中数列知识可化简得:

      ②

② - ①可得:


由等差数列求和公式可得


已知


因此向下调整建堆的时间复杂度为O(N)


5.2 排序

1. for (int i = 1; i < size; i++)
2. {
3.  Swap(&a[0], &a[size - i]);
4.  AdjustDown(a, size - i, 0);
5. }


建堆之后最大的元素在堆顶,然后将堆顶与堆末尾的元素位置交换,再在前N-1从堆顶开始向下调整算法,此时最大的元素在数组的末尾,

以此类推排成了递减的数列

5.3 完整代码

void HPSort(int* a, int size)//升序建大堆,降序建小堆
{
  //效率为Nlog(N)
  /*for (int i = 1; i < size; i++)
  {
    AdjustUp(a, i);
  }*/
  //效率为log(N)
  for (int i = ((size - 1) - 1) / 2; i >= 0; i--)
  {
    AdjustDown(a, size, i);
  }
 
  for (int i = 1; i < size; i++)
  {
    Swap(&a[0], &a[size - i]);
    AdjustDown(a, size - i, 0);
  }
}


六、TOP-K问题

TOP-K问题:即求数据结合中前K个最大的元素或者最小的元素,一般情况下数据量都比较大。比如:专业前10名、世界500强、富豪榜、游戏中前100的活跃玩家等。


对于Top-K问题,能想到的最简单直接的方式就是排序,但是:如果数据量非常大,排序就不太可取了(可能数据都不能一下子全部加载到内存中)。最佳的方式就是用堆来解决,基本思路如下:


  1. 用数据集合中前K个元素建堆
  1. 前k个最小的元素,则建大堆
  2. 前k个最大的元素,则建小堆
  1. 用剩余 N - k 个元素依次与堆顶元素(最大的)进行比较,比最大项要小就替换再向下调整


该方法的时间复杂度为:

代码示例如下:

void TestTopk()
{
  int n = 10000;
  int* a = (int*)malloc(sizeof(int) * n);
  srand(time(0));
  for (size_t i = 0; i < n; ++i)
  {
    a[i] = rand() % 1000000;
  }
  a[5] = 1000000 + 1;
  a[1231] = 1000000 + 2;
  a[531] = 1000000 + 3;
  a[5121] = 1000000 + 4;
  a[115] = 1000000 + 5;
  a[2335] = 1000000 + 6;
  a[9999] = 1000000 + 7;
  a[76] = 1000000 + 8;
  a[423] = 1000000 + 9;
  a[3144] = 1000000 + 10;
  PrintTopK(a, n, 10);
}


void PrintTopK(HPDataType* a, int size, int k)
{
  for (int i = (k - 1 - 1) / 2; i >= 0; i--)
  {
    AdjustDown(a, k, i);
  }
 
  for (int i = k; i < size; i++)
  {
 
    if (a[0] < a[i])
    {
      a[0] = a[i];
      AdjustDown(a, k, 0);
    }
  }
 
  cout << "最大的十个为: ";
  for (int i = 1; i < 10; i++)
  {
    cout << a[0] << " ";
    Swap(&a[0], &a[k - i]);
    AdjustDown(a, k - i, 0);
  }
  cout << a[0] << endl;
}

用文件读取的方式完成示例如下:

void TestTopk()
{
  int n = 10000;
  srand(time(NULL));
  const char* file = "data.txt";
  FILE* fin = fopen(file, "w");
  if (fin == NULL)
  {
    perror("fopen error");
    return;
  }
  for (int i = 0; i < n; i++)
  {
    int x = rand() + i;
    fprintf(fin, "%d\n", x);
  }
  fclose(fin);
 
  int* a = (int*)malloc(sizeof(int) * 10);
  PrintTopK(a, n, 10);
}


void PrintTopK(HPDataType* a, int size, int k)
{
  const char* file = "data.txt";
  FILE* fout = fopen(file, "r");
  if (fout == NULL)
  {
    perror("fopen error");
    return;
  }
  for (int i = 0; i < k; i++)
  {
    fscanf(fout, "%d\n", &a[i]);
  }
 
  for (int i = (k - 1 - 1) / 2; i >= 0; i--)
  {
    AdjustDown(a, k, i);
  }
 
  int x = 0;
  for (int i = k; i < size; i++)
  {
    fscanf(fout, "%d\n", &x);
    if (a[0] < x)
    {
      a[0] = x;
      AdjustDown(a, k, 0);
    }
  }
  fclose(fout);
 
  cout << "最大的十个为: ";
  for (int i = 1; i < 10; i++)
  {
    cout << a[0] << " ";
    Swap(&a[0], &a[k - i]);
    AdjustDown(a, k - i, 0);
  }
  cout << a[0] << endl;
}


七、链式二叉树

7.1 二叉树的遍历

7.1.1 二叉树的前、中、后序遍历

所谓遍历(Traversal)是指沿着某条搜索路线,依次对树中每个结点均做一次且仅做一次访问。访问结点所做的操作依赖于具体的应用问题。遍历是二叉树上最重要的运算之一,是二叉树上进行其它运算之基础。

具体分为:前序、中序、后序,它们的递归结构遍历:是根据访问结点操作发生位置命名。

前序遍历(Preorder Traversal 亦称先序遍历)——访问根结点的操作发生在遍历其左右子树之前
  • 中序遍历(Inorder Traversal)——访问根结点的操作发生在遍历其左右子树之中(间)。
  • 后序遍历(Postorder Traversal)——访问根结点的操作发生在遍历其左右子树之后。

代码实现如下:

//前序
void PrevOrder(BTNode* root)
{
  if (root == NULL)
  {
    cout << "N ";
    return;
  }
 
  cout << root->data << " ";
  PrevOrder(root->left);
  PrevOrder(root->right);
}
 
//中序
void InOrder(BTNode* root)
{
  if (root == NULL)
  {
    cout << "N ";
    return;
  }
 
  InOrder(root->left);
  cout << root->data << " ";
  InOrder(root->right);
}
 
//后序
void PostOrder(BTNode* root)
{
  if (root == NULL)
  {
    cout << "N ";
    return;
  }
 
  PostOrder(root->left);
  PostOrder(root->right);
  cout << root->data << " ";
}


7.1.2 层序遍历

层序遍历:除了先序遍历、中序遍历、后序遍历外,还可以对二叉树进行层序遍历。设二叉树的根节点所在层数为1,层序遍历就是从所在二叉树的根节点出发,首先访问第一层的树根节点,然后从左到右访问第2层上的节点,接着是第三层的节点,以此类推,自上而下,自左至右逐层访问树的结点的过程就是层序遍历。

思路:


  1. 创建一个队列,并将根节点入队。
  2. 当队列不为空时,执行以下步骤:
  1. 从队列标记队首节点并取出
  2. 将该节点的所有子节点(如果存在)依次入队。
  1. 如果队列为空,则表示遍历结束。

由于队列的特性,首先入队的节点会先被访问,保证了按照层级顺序遍历节点。


代码实现:

void LevelOrder(BTNode* root)
{
  Queue q;
  QueueInit(&q);
  if (root)
    QueuePush(&q, root);
 
  while (!QueueEmpty(&q))
  {
    BTNode* front = QueueFront(&q);
    cout << front->data << " ";
    QueuePop(&q);
    if(front->left)
      QueuePush(&q, front->left);
    if(front->right)
      QueuePush(&q, front->right);
  }
  cout << endl;
}


7.2 二叉树的相关操作

7.2.1 基本操作

// 二叉树结点个数
int BTSize(BTNode* root)
{
  /*if (root == NULL)
  {
    return 0;
  }
  int size1 = BTSize(root->left);
  int size2 = BTSize(root->right);
  return size1 + size2 + 1;*/
  return root == NULL ? 0 : BTSize(root->left) + BTSize(root->right) + 1;
}  
 
// 二叉树叶子结点个数
int BTLSize(BTNode* root)
{
  /*if (root->left == NULL && root->right == NULL)
    return 1;
  else if (root->left == NULL)
    return BTLSize(root->right);
  else if (root->right == NULL)
    return BTLSize(root->left);
  else
    return BTLSize(root->right) + BTLSize(root->left);*/
  if (root == NULL)
    return 0;
  if (root->left == NULL && root->right == NULL)
    return 1;
 
  return BTLSize(root->right) + BTLSize(root->left);
}
 
//二叉树的高度
int BTHight(BTNode* root)
{
  if (root == NULL)
    return 0;
 
  return max(BTHight(root->left), BTHight(root->right)) + 1;
}
 
// 二叉树第k层结点个数
int BTKSize(BTNode* root, int k)
{
  if (root == NULL)
    return 0;
  if (k == 1)
    return 1;
  return BTKSize(root->left, k - 1) + BTKSize(root->right, k - 1);
}
 
// 二叉树查找值为x的结点
BTNode* BTFind(BTNode* root, BTDataType x)
{
  if (root == NULL)
    return NULL;
  if (root->data == x)
    return root;
  
  BTNode* node = BTFind(root->left, x);
  if (node != NULL)
    return node;
  node = BTFind(root->right, x);
  if (node != NULL)
    return node;
  return NULL;
  //return BTFind(root->right, x);
}
 
// 二叉树销毁
void BTDestory(BTNode** root)
{
  if (*root == NULL)
    return;
 
  BTDestory(&((*root)->left));
  BTDestory(&((*root)->left));
  free(*root);
  *root = NULL;
}


7.2.2 判断一个二叉树是否是完全二叉树

一颗完全二叉树:

一颗非完全二叉树:


思路分析:


  • 创建一个队列,并将根节点入队
  • 通过层序遍历来遍历树中的每一个结点,有以下两种情况
  1. 当遍历节点不为空时,执行以下步骤:
  1. 从队列标记队首节点并取出
  2. 将该节点的所有子节点(包括空节点)依次入队
  1. 当取的结点为空结点时,若要为完全二叉树,后续所有结点必须都为空(用 i 标记出来)
  • 当列表为空节点,退出循环,二叉树为完全二叉树


代码实现如下:

// 判断二叉树是否是完全二叉树
bool BTComplete(BTNode* root)
{
  Queue q;
  QueueInit(&q);
  if (root)
    QueuePush(&q, root);
 
  int i = 0;
  while (!QueueEmpty(&q))
  {
    BTNode* front = QueueFront(&q);
    if (front == NULL)
      i++;
    if (i == 1 && front != NULL)
      return false;
 
    QueuePop(&q);
    if (front != NULL)
    {
      QueuePush(&q, front->left);
      QueuePush(&q, front->right);    
    }
  }
  QueueDestroy(&q);
  return true;
}
相关文章
|
2天前
|
存储 缓存 关系型数据库
MySQL事务日志-Redo Log工作原理分析
事务的隔离性和原子性分别通过锁和事务日志实现,而持久性则依赖于事务日志中的`Redo Log`。在MySQL中,`Redo Log`确保已提交事务的数据能持久保存,即使系统崩溃也能通过重做日志恢复数据。其工作原理是记录数据在内存中的更改,待事务提交时写入磁盘。此外,`Redo Log`采用简单的物理日志格式和高效的顺序IO,确保快速提交。通过不同的落盘策略,可在性能和安全性之间做出权衡。
1519 4
|
29天前
|
弹性计算 人工智能 架构师
阿里云携手Altair共拓云上工业仿真新机遇
2024年9月12日,「2024 Altair 技术大会杭州站」成功召开,阿里云弹性计算产品运营与生态负责人何川,与Altair中国技术总监赵阳在会上联合发布了最新的“云上CAE一体机”。
阿里云携手Altair共拓云上工业仿真新机遇
|
5天前
|
人工智能 Rust Java
10月更文挑战赛火热启动,坚持热爱坚持创作!
开发者社区10月更文挑战,寻找热爱技术内容创作的你,欢迎来创作!
503 19
|
2天前
|
存储 SQL 关系型数据库
彻底搞懂InnoDB的MVCC多版本并发控制
本文详细介绍了InnoDB存储引擎中的两种并发控制方法:MVCC(多版本并发控制)和LBCC(基于锁的并发控制)。MVCC通过记录版本信息和使用快照读取机制,实现了高并发下的读写操作,而LBCC则通过加锁机制控制并发访问。文章深入探讨了MVCC的工作原理,包括插入、删除、修改流程及查询过程中的快照读取机制。通过多个案例演示了不同隔离级别下MVCC的具体表现,并解释了事务ID的分配和管理方式。最后,对比了四种隔离级别的性能特点,帮助读者理解如何根据具体需求选择合适的隔离级别以优化数据库性能。
179 1
|
8天前
|
JSON 自然语言处理 数据管理
阿里云百炼产品月刊【2024年9月】
阿里云百炼产品月刊【2024年9月】,涵盖本月产品和功能发布、活动,应用实践等内容,帮助您快速了解阿里云百炼产品的最新动态。
阿里云百炼产品月刊【2024年9月】
|
21天前
|
存储 关系型数据库 分布式数据库
GraphRAG:基于PolarDB+通义千问+LangChain的知识图谱+大模型最佳实践
本文介绍了如何使用PolarDB、通义千问和LangChain搭建GraphRAG系统,结合知识图谱和向量检索提升问答质量。通过实例展示了单独使用向量检索和图检索的局限性,并通过图+向量联合搜索增强了问答准确性。PolarDB支持AGE图引擎和pgvector插件,实现图数据和向量数据的统一存储与检索,提升了RAG系统的性能和效果。
|
9天前
|
Linux 虚拟化 开发者
一键将CentOs的yum源更换为国内阿里yum源
一键将CentOs的yum源更换为国内阿里yum源
457 5
|
7天前
|
存储 人工智能 搜索推荐
数据治理,是时候打破刻板印象了
瓴羊智能数据建设与治理产品Datapin全面升级,可演进扩展的数据架构体系为企业数据治理预留发展空间,推出敏捷版用以解决企业数据量不大但需构建数据的场景问题,基于大模型打造的DataAgent更是为企业用好数据资产提供了便利。
314 2
|
23天前
|
人工智能 IDE 程序员
期盼已久!通义灵码 AI 程序员开启邀测,全流程开发仅用几分钟
在云栖大会上,阿里云云原生应用平台负责人丁宇宣布,「通义灵码」完成全面升级,并正式发布 AI 程序员。
|
25天前
|
机器学习/深度学习 算法 大数据
【BetterBench博士】2024 “华为杯”第二十一届中国研究生数学建模竞赛 选题分析
2024“华为杯”数学建模竞赛,对ABCDEF每个题进行详细的分析,涵盖风电场功率优化、WLAN网络吞吐量、磁性元件损耗建模、地理环境问题、高速公路应急车道启用和X射线脉冲星建模等多领域问题,解析了问题类型、专业和技能的需要。
2608 22
【BetterBench博士】2024 “华为杯”第二十一届中国研究生数学建模竞赛 选题分析