目录
8.逐个删除堆顶数据过程的时间复杂度分析(删堆的时间复杂度分析)
关于数据结构的文章:写博客的时候我感觉在表达层面上有点费劲,如果文章整体在表述上让人感觉不够清楚,恳请读者原谅,本菜狗已经尽力了.
关于二叉树的图论基础参见:http://t.csdn.cn/Nv325
http://t.csdn.cn/Nv325青菜友情提示:基础概念对于理解堆的实现与运用非常重要
堆的本质是映射在内存中顺序存储结构(数组)上的完全二叉树(完全二叉树是堆的逻辑结构).
堆的图解:
- 结点中的数据存储在相应下标的数组元素空间中
堆的两个类型:
- 大根堆:在大根堆中任何子结构的父结点中存储的值大于其左右孩子结点中存储的值(即任何一条连通路径上从上往下满足降序排列)
- 小根堆:在小根堆中任何子结构的父结点中存储的值小于其左右孩子结点中存储的值(即任何一条连通路径上从上往下满足升序排列)
- 堆的任何一个父结点的编号parent与其左孩子结点的编号leftchild满足如下关系式:
- 堆的任何一个父结点的编号parent与其右孩子结点的编号rightchild满足如下关系式:
这两组重要结论的推导和分析参见青菜的博客:http://t.csdn.cn/Nv325
http://t.csdn.cn/Nv325
堆是存储在数组上的(大小根)完全二叉树,因此在实现堆之前我们先构建一个简单的动态数组作为物理存储结构框架:
- 维护动态数组的结构体:
typedef int HPDataType; //堆元素类型 typedef struct Heap { HPDataType* arry; //堆区内存指针 size_t size; //堆元素个数 size_t capacity; //数组的空间容量 }HP;- 堆的基础操作接口声明:
//维护堆的结构体的初始化接口 void HeapInit(HP* php); //销毁堆的接口 void HeapDestroy(HP* php); //堆元素的打印接口 void HeapPrint(HP* php); //判断堆是否为空(堆中有无元素)的接口 bool HeapEmpty(HP* php); //返回堆中元素个数的接口 size_t HeapSize(HP* php); //返回堆顶元素(即编号为0的元素)的接口 HPDataType HeapTop(HP* php);- 基础接口的实现:
//堆结构体的初始化接口 void HeapInit(HP* ptrheap) { assert(ptrheap); ptrheap->arry = NULL; ptrheap->capacity = 0; ptrheap->size = 0; } //销毁堆的接口 void HeapDestroy(HP* ptrheap) { assert(ptrheap); free(ptrheap->arry); ptrheap->arry = NULL; ptrheap->capacity = 0; ptrheap->size = 0; } //堆的打印接口 void HeapPrint(HP* ptrheap) { assert(ptrheap); size_t tem = 0; for (tem = 0; tem < ptrheap->size; ++tem) { printf("%d->", ptrheap->arry[tem]); } printf("END\n"); } //判断堆是否为空的接口 bool HeapEmpty(HP* ptrheap) { assert(ptrheap); return (0 == ptrheap->size); } //返回堆元素个数的接口 size_t HeapSize(HP* ptrheap) { assert(ptrheap); return ptrheap->size; } //返回堆顶元素的接口 HPDataType HeapTop(HP* ptrheap) { assert(!HeapEmpty(ptrheap)); return ptrheap->arry[0]; }主函数建堆时的内存布局简单图示:
堆区上的数组是在堆元素插入接口中调用malloc申请出来的(我们暂时还没有实现堆元素插入的接口)
以上是堆在内存中的存储框架的构建,然而堆的核心接口是堆元素的插入和删除接口
- 堆的构建过程概述:向堆尾逐个插入元素(将元素尾插进数组中),每次插入一个元素后都通过堆的调整算法逐层向上(二叉树意义上的层)(子结点和父结点进行大小比较,若不满足小根堆的性质就进行数值交换)调整该元素在堆中的位置以保持小根堆的数据结构.
- 算法过程简图:
- 从0个结点的堆开始,每尾插一个元素我们都按照小根堆的性质(通过元素向上调整算法)调整该元素在堆中的位置来保持小根堆的数据结构,我们就是以这样的基本思想来建堆的.
- 建堆的思路细解:
- 显然在堆尾插入元素6后破坏了小根堆的结构(6小于其父结点20不满足小根堆的性质)(将任意一个子结构中子结点与父结点进行大小比较就可以知道堆的结构有没有被破坏),因此我们就需要将6这个元素逐层向二叉树的上层调整(每个调整的子步骤都是从子结点向父结点调整)
- 通过树的子结构中子结点与父结点的编号关系可以找到堆尾8号结点(元素6)的父结点(元素20,结点编号为3)
- 接下来将元素逐层向上调整(子结点和父结点进行数值交换)来恢复小根堆的数据结构:
向上调整的第一步:将尾插进堆尾的结点(8号结点,值为6)与其父结点(3号结点,值为20)进行比较,父结点大于子结点,交换父结点与子结点的值(下图中的Swap是元素数值交换接口):
向上调整的第二步:交换了8号结点和3号结点的值后,我们发现小堆结构依然不成立,于是重复上述的调整过程,将元素6继续向上层的父结点位置进行调整,不断重复迭代直到小堆结构恢复或者元素6被调到堆顶为止:
- 可见经过再一次调整后,元素6来到了1号结点,此时1号结点大于0号结点,小堆数据结构得到恢复,调整过程终止。
同时不难分析出,任何堆尾元素向上调整的过程都是在堆尾到堆顶元素的连通路径上进行的(该路径长度数量级为logk)(k为当前树结点总个数)):
- 根据上面的分析我们可以设计出一个堆尾元素向上调整的算法接口:
接口首部: arry是指向数组首地址的指针,child是堆尾元素的编号(结点编号同时也是该元素的数组下标)
void AdjustUp(HPDataType* arry, size_t child);堆尾元素向上调整的算法接口:
//元素交换接口 void Swap(HPDataType* e1, HPDataType* e2) { assert(e1 && e2); HPDataType tem = *e1; *e1 = *e2; *e2 = tem; } //小堆元素的向上调整接口 void AdjustUp(HPDataType* arry, size_t child) //child表示孩子结点的编号 { assert(arry); size_t parent = (child - 1) / 2; //算出父结点的数组下标(堆编号) while (child > 0) //child减小到0时则调整结束(堆尾元素已调到堆顶) { if (arry[child] < arry[parent]) //父结点大于子结点,则子结点需要上调以保持小堆的结构 { Swap(arry + child, arry+parent); child = parent; //将原父结点作为新的子结点继续迭代过程 parent = (child - 1) / 2; //算出父结点的数组下标(堆编号) } else { break; //父结点不大于子结点,则堆结构恢复,无需再调整 } } }堆尾元素向上调整的算法接口中需要注意的细节:
- 调整循环的终止条件有两个:
- 一个是被调整元素被调到了堆顶(即child=0的时候)
- 当被调整元素大于其父结点时小堆的数据结构得到恢复,break终止循环
有了该接口,我们就可以设计堆元素插入接口:
- HP * ptrheap是指向堆结构体的结构体指针,x是要插入的元素的数值
void HeapPush(HP* ptrheap, HPDataType x)// 插入一个小堆元素的接口 // 插入x以后,保持其数据结构依旧是小堆 void HeapPush(HP* ptrheap, HPDataType x) //ptrheap是指向堆结构体的结构体指针 { assert(ptrheap); if (ptrheap->capacity == ptrheap->size) //数组容量检查,容量不够则扩容 { size_t newcapacity = (0 == ptrheap->capacity) ? 4 : 2 * ptrheap->capacity; //注意容量为0则将堆区数组容量调整为4 HPDataType* tem = (HPDataType*)realloc(ptrheap->arry, newcapacity*sizeof(HPDataType)); //空间不够则扩容 assert(tem); //检查扩容是否成功 ptrheap->arry = tem; //将堆区地址赋给结构体中的指针成员 ptrheap->capacity = newcapacity; //容量标记更新 } ptrheap->arry[ptrheap->size] = x; //元素尾插入堆 ptrheap->size++; //将尾插的元素进行向上调整以保持堆的数据结构(复用元素向上调整接口) AdjustUp(ptrheap->arry, ptrheap->size - 1); //根据完全二叉树的结构特点可知堆尾元素 //在二叉树中编号为size-1 }
- 该接口可以完成一个堆元素的插入(而且每次完成插入后小根堆的数据结构都可以得到保持)
- 如果我们想要创建一个n个元素小根堆,则n次调用HeapPush接口即可
现在我们试着用HeapPush接口来构建六个元素的小堆,并将堆打印出来观察其逻辑结构:
- 主函数代码:
int main () { HP hp; //创建一个堆结构体 HeapInit(&hp); //结构体初始化 HeapPush(&hp, 1); //调用堆元素插入接口建堆 HeapPush(&hp, 5); HeapPush(&hp, 0); HeapPush(&hp, 8); HeapPush(&hp, 3); HeapPush(&hp, 9); HeapPrint(&hp); //打印堆元素 }
我们来观察一下打印出来的小根堆的逻辑结构:
- 假设现在我们要调用HeapPush接口来创建一个N个元素的小堆(调用N次HeapPush接口)
- 注意:我们只关注时间复杂度最坏的情况(即假设每个插入堆尾的元素都沿着连通路径向上调整到了堆顶)
- 分析建堆的时间复杂度时我们将堆看成满二叉树(满二叉树是特殊的完全二叉树),也就是说当堆有N个元素时,堆的层数h=log(N+1)(该对数以2为底,计算机科学中我们一般不关注对数的底数)
- 接下来我们将所有元素的向上调整次数进行求和,即可得出用HeapPush接口建堆的时间复杂度:
- 用错位相减法可对上面公式进行求和计算:
- 因此可以得到用堆元素插入接口(堆元素向上调整算法)建堆(建立N个元素的小根堆)的时间复杂度为:O(NlogN);
我们讨论的堆元素删除指的是:删除堆顶数据
删除堆顶的元素的基本思路:
- 先将堆顶元素与堆尾元素进行交换,然后令维护堆的结构体中的size(记录堆元素个数的成员变量)减一(相当于移除堆尾元素)
- 然而原来的堆尾元素被交换到堆顶位置后,小根堆的数据结构会被破坏,因此我们需要将堆顶元素进行向下调整操作:
- 堆顶元素向下调整图解演示:
- 同样不难分析出,任何堆顶元素向下调整的过程都是在堆顶到堆尾元素的连通路径上进行的(该路径长度数量级为logk)(k为当前树结点总个数)):
- 通过算法分析我们可以设计一个堆元素向下调整算法接口:
arry是指向内存堆区数组首地址的指针,size是堆的结点总个数,parent是待调整结点在堆中的编号;
void AdjustDown(HPDataType* arry,size_t size,size_t parent)堆元素向下调整算法接口实现:
//元素交换接口 void Swap(HPDataType* e1, HPDataType* e2) { assert(e1 && e2); HPDataType tem = *e1; *e1 = *e2; *e2 = tem; } //小堆元素的向下调整接口 void AdjustDown(HPDataType* arry,size_t size,size_t parent) { assert(arry); size_t child = 2 * parent + 1; //确定父结点的左孩子的编号 while (child < size) //child增加到大于或等于size时说明被调整元素被调整到了叶结点上,调整过程结束 { if (child + 1 < size && arry[child + 1] < arry[child])//确定左右孩子中较小的孩子结点 { ++child; //条件成立说明右孩子较小,child更新为右孩子编号 } if ( arry[child] < arry[parent])//父结点大于子结点,则父结点需要下调以保持小堆的结构 { Swap(arry + parent, arry + child);//父子结点进行值交换 parent = child; //将原来的子结点作为新的父结点继续迭代过程 child = 2 * parent + 1; //继续向下找另外一个子结点 } else { break; //父结点不大于子结点,则小堆结构成立,无需继续调整 } } }
- 注意一下算法接口的一些细节和边界条件:
- child<size说明被调整元素已经被调整到叶结点位置,结束调整过程
- 算法接口中我们只设计了一个child变量来记录当前父结点的孩子结点的编号,接着通过arry[child + 1] < arry[child]来比较左右孩子大小来确定child到底是取左孩子的编号还是取右孩子的编号(注意child+1<size判断语句是为了确定右孩子是否存在)
- 有了堆元素向下调整算法接口我们就可以实现堆顶数据删除接口:
//删除堆顶元素 void HeapPop(HP* ptrheap) { assert(ptrheap); Swap(ptrheap->arry, ptrheap->arry + ptrheap->size - 1); //交换堆顶与堆尾元素 ptrheap->size--; //删除堆尾元素 AdjustDown(ptrheap->arry, ptrheap->size, 0); //将堆顶元素进行向下调整以保持堆的数据结构 }注意:AdjustDown接口的传参中,ptrheadp->size指的是堆元素的总个数,由于我们要将堆顶元素向下调整,因此传入的被调整结点编号为0
该接口每调用一次就可以删除一个堆顶数据(并保持小根堆的数据结构):
我们先6次调用HeapPush接口创建一个六个元素的小堆,然后再6次调用HeapPop接口逐个删除堆顶元素(每删除一个堆顶数据打印一次堆,借此观察每次删除堆顶数据后堆的数据结构).
int main () { HP hp; HeapInit(&hp); HeapPush(&hp, 1); HeapPush(&hp, 5); HeapPush(&hp, 0); HeapPush(&hp, 8); HeapPush(&hp, 3); HeapPush(&hp, 9); HeapPrint(&hp); HeapPop(&hp); HeapPrint(&hp); HeapPop(&hp); HeapPrint(&hp); HeapPop(&hp); HeapPrint(&hp); HeapPop(&hp); HeapPrint(&hp); HeapPop(&hp); HeapPrint(&hp); HeapDestroy(&hp); }
观察小根堆的数据结构(逻辑结构):
- 可见逐个删除堆顶数据的过程中小根堆的数据结构得到了保持
假设现在有一个N个元素的小根堆,我们逐个删除堆顶数据直到将堆删空。
现在我们来分析这个过程的时间复杂度(我们同样将二叉树看成满二叉树来分析):
- 同样我们考虑的是时间复杂度最坏的情况:即每次调用堆元素删除接口后,被交换到堆顶的元素都沿着连通路径向下被调整到叶结点
- 分析:假设堆的高度为h,则满树的总结点个数N为 : 2^h - 1
需要注意的是满树的第h层(最后一层)的结点个数为: 2^(h-1)所以满树的最后一层的结点个数占树的总结点个数的一半
因此如果我们删去满树的前h-1层的所有结点,则由最后一层结点所构成的新树的高度大致为h-1:
这也就意味着,在删除前h-1层结点的过程中树的总高度几乎不会发生变化:
因此可以得到删除满树的前h-1层所有结点过程中,每个被交换到堆顶的元素都要被向下调整log(N+1)次,则删除满树的前h-1层所有结点过程中,向下调整算法中循环语句执行总次数约为:
接着,对于由原树的最后一层(第h层)结点构成的新树(层数为h-1层)我们可以作类似的分析(即先删除其前h-2层结点,得到由其第h-1层结点构成的新树),以这种方式递推下去直到树被删空为止:
- 由此可得, 逐个删除堆顶数据直到将堆删空过程中,堆元素的向下调整算法中循环语句总的执行次数估算为:
该多项式的和化为大O阶渐进表示法结果为O(NlogN).
- 逐个删除堆顶数据直到将堆删空的过程的时间复杂度分析为:O(NlogN)
接口和结构类型声明的头文件:
#pragma once #include <stdio.h> #include <assert.h> #include <stdlib.h> #include <stdbool.h> #include <time.h> //实现小堆 typedef int HPDataType; typedef struct Heap { HPDataType* arry; size_t size; size_t capacity; }HP; //堆的初始化接口 void HeapInit(HP* php); //销毁堆的接口 void HeapDestroy(HP* php); //堆的打印接口 void HeapPrint(HP* php); //判断堆是否为空的接口 bool HeapEmpty(HP* php); //返回堆元素个数的接口 size_t HeapSize(HP* php); //返回堆顶元素的接口 HPDataType HeapTop(HP* php); void Swap(HPDataType* pa, HPDataType* pb); //堆元素向上调整算法接口 void AdjustUp(HPDataType* a, size_t child); //堆元素向下调整算法接口 void AdjustDown(HPDataType* a, size_t size,size_t parent); // 插入元素x以后,保持数据结构是(大/小)堆 void HeapPush(HP* php, HPDataType x); // 删除堆顶的数据后,保持数据结构是(大/小)堆 void HeapPop(HP* php);各个堆操作接口的实现:
#include "heap.h" //堆的初始化接口 void HeapInit(HP* ptrheap) { assert(ptrheap); ptrheap->arry = NULL; ptrheap->capacity = 0; ptrheap->size = 0; } //销毁堆的接口 void HeapDestroy(HP* ptrheap) { assert(ptrheap); free(ptrheap->arry); ptrheap->arry = NULL; ptrheap->capacity = 0; ptrheap->size = 0; } //堆的打印接口 void HeapPrint(HP* ptrheap) { assert(ptrheap); size_t tem = 0; for (tem = 0; tem < ptrheap->size; ++tem) { printf("%d->", ptrheap->arry[tem]); } printf("END\n"); } //判断堆是否为空的接口 bool HeapEmpty(HP* ptrheap) { assert(ptrheap); return (0 == ptrheap->size); } //返回堆元素个数的接口 size_t HeapSize(HP* ptrheap) { assert(ptrheap); return ptrheap->size; } //返回堆顶元素的接口 HPDataType HeapTop(HP* ptrheap) { assert(!HeapEmpty(ptrheap)); return ptrheap->arry[0]; } //元素交换接口 void Swap(HPDataType* e1, HPDataType* e2) { assert(e1 && e2); HPDataType tem = *e1; *e1 = *e2; *e2 = tem; } //小堆元素的向上调整接口 void AdjustUp(HPDataType* arry, size_t child) //child表示孩子结点的编号 { assert(arry); size_t parent = (child - 1) / 2; while (child > 0) //child减小到0时则调整结束 { if (arry[child] < arry[parent]) //父结点大于子结点,则子结点需要上调以保持小堆的结构 { Swap(arry + child, arry+parent); child = parent; //将原父结点作为新的子结点继续迭代过程 parent = (child - 1) / 2; //继续向上找另外一个父结点 } else { break; //父结点不大于子结点,则堆结构任然成立,无需调整 } } } // 插入一个小堆元素的接口 // 插入x以后,保持其数据结构依旧是小堆 void HeapPush(HP* ptrheap, HPDataType x) { assert(ptrheap); if (ptrheap->capacity == ptrheap->size) //容量检查,容量不够则扩容 { size_t newcapacity = (0 == ptrheap->capacity) ? 4 : 2 * ptrheap->capacity; HPDataType* tem = (HPDataType*)realloc(ptrheap->arry, newcapacity * sizeof(HPDataType)); assert(tem); ptrheap->arry = tem; ptrheap->capacity = newcapacity; } ptrheap->arry[ptrheap->size] = x; //先尾插一个元素 ptrheap->size++; //将尾插的元素进行向上调整以保持堆的数据结构 AdjustUp(ptrheap->arry, ptrheap->size - 1); //根据完全二叉树的结构特点可知尾插进数组的元素在二叉树中编号为size-1 } //小堆元素的向下调整接口 void AdjustDown(HPDataType* arry,size_t size,size_t parent) { assert(arry); size_t child = 2 * parent + 1; //确定父结点的左孩子的编号 while (child < size) //child增加到大于或等于size时则调整结束 { if (child + 1 < size && arry[child + 1] < arry[child]) //确定左右孩子中较小的孩子结点 { ++child; } if ( arry[child] < arry[parent])//父结点大于子结点,则子结点需要上调以保持小堆的结构 { Swap(arry + parent, arry + child); parent = child; //将原子结点作为新的父结点继续迭代过程 child = 2 * parent + 1; //继续向下找另外一个子结点 } else { break; //父结点不大于子结点,则堆结构任然成立,无需调整 } } } //删除堆顶元素 void HeapPop(HP* ptrheap) { assert(ptrheap); Swap(ptrheap->arry, ptrheap->arry + ptrheap->size - 1); //交换堆顶与堆尾元素 ptrheap->size--; //删除堆尾元素 AdjustDown(ptrheap->arry, ptrheap->size, 0); //将堆顶元素进行向下调整以保持堆的数据结构 }
我们只需将小根堆的堆元素上下调整算法接口中的子父结点值大小比较符号改为大于号即可实现大根堆
- 本文的核心:通过堆元素的上下调整算法接口完成堆的构建和删空(并分析过程的时间复杂度)
- 堆元素上下调整算法接口在后续的堆排序和TopK问题中还会直接用到
- 堆的高效性来源于如下数学思想:利用指数级展开的数据结构模型实现对数级连通路径遍历算法,从而降低了排序和查找的时间复杂度(不得不说前人的创造真的是无可比拟)

我想将html转换为纯文本。不过,我不想只删除标签,我想智能地保留尽可能多的格式。为插入换行符标签,检测段落并格式化它们等。输入非常简单,通常是格式良好的html(不是整个文档,只是一堆内容,通常没有anchor或图像)。我可以将几个正则表达式放在一起,让我达到80%,但我认为可能有一些现有的解决方案更智能。 最佳答案 首先,不要尝试为此使用正则表达式。很有可能你会想出一个脆弱/脆弱的解决方案,它会随着HTML的变化而崩溃,或者很难管理和维护。您可以使用Nokogiri快速解析HTML并提取文本:require'nokogiri'h
我主要使用Ruby来执行此操作,但到目前为止我的攻击计划如下:使用gemsrdf、rdf-rdfa和rdf-microdata或mida来解析给定任何URI的数据。我认为最好映射到像schema.org这样的统一模式,例如使用这个yaml文件,它试图描述数据词汇表和opengraph到schema.org之间的转换:#SchemaXtoschema.orgconversion#data-vocabularyDV:name:namestreet-address:streetAddressregion:addressRegionlocality:addressLocalityphoto:i
我有一个用户工厂。我希望默认情况下确认用户。但是鉴于unconfirmed特征,我不希望它们被确认。虽然我有一个基于实现细节而不是抽象的工作实现,但我想知道如何正确地做到这一点。factory:userdoafter(:create)do|user,evaluator|#unwantedimplementationdetailshereunlessFactoryGirl.factories[:user].defined_traits.map(&:name).include?(:unconfirmed)user.confirm!endendtrait:unconfirmeddoenden
有时我需要处理键/值数据。我不喜欢使用数组,因为它们在大小上没有限制(很容易不小心添加超过2个项目,而且您最终需要稍后验证大小)。此外,0和1的索引变成了魔数(MagicNumber),并且在传达含义方面做得很差(“当我说0时,我的意思是head...”)。散列也不合适,因为可能会不小心添加额外的条目。我写了下面的类来解决这个问题:classPairattr_accessor:head,:taildefinitialize(h,t)@head,@tail=h,tendend它工作得很好并且解决了问题,但我很想知道:Ruby标准库是否已经带有这样一个类? 最佳
给定一个复杂的对象层次结构,幸运的是它不包含循环引用,我如何实现支持各种格式的序列化?我不是来讨论实际实现的。相反,我正在寻找可能会派上用场的设计模式提示。更准确地说:我正在使用Ruby,我想解析XML和JSON数据以构建复杂的对象层次结构。此外,应该可以将该层次结构序列化为JSON、XML和可能的HTML。我可以为此使用Builder模式吗?在任何提到的情况下,我都有某种结构化数据-无论是在内存中还是文本中-我想用它来构建其他东西。我认为将序列化逻辑与实际业务逻辑分开会很好,这样我以后就可以轻松支持多种XML格式。 最佳答案 我最
我正在尝试使用Curbgem执行以下POST以解析云curl-XPOST\-H"X-Parse-Application-Id:PARSE_APP_ID"\-H"X-Parse-REST-API-Key:PARSE_API_KEY"\-H"Content-Type:image/jpeg"\--data-binary'@myPicture.jpg'\https://api.parse.com/1/files/pic.jpg用这个:curl=Curl::Easy.new("https://api.parse.com/1/files/lion.jpg")curl.multipart_form_
无论您是想搭建桌面端、WEB端或者移动端APP应用,HOOPSPlatform组件都可以为您提供弹性的3D集成架构,同时,由工业领域3D技术专家组成的HOOPS技术团队也能为您提供技术支持服务。如果您的客户期望有一种在多个平台(桌面/WEB/APP,而且某些客户端是“瘦”客户端)快速、方便地将数据接入到3D应用系统的解决方案,并且当访问数据时,在各个平台上的性能和用户体验保持一致,HOOPSPlatform将帮助您完成。利用HOOPSPlatform,您可以开发在任何环境下的3D基础应用架构。HOOPSPlatform可以帮您打造3D创新型产品,HOOPSSDK包含的技术有:快速且准确的CAD
华为OD机试题本篇题目:明明的随机数题目输入描述输出描述:示例1输入输出说明代码编写思路最近更新的博客华为od2023|什么是华为od,od薪资待遇,od机试题清单华为OD机试真题大全,用Python解华为机试题|机试宝典【华为OD机试】全流程解析+经验分享,题型分享,防作弊指南华为o
本教程将在Unity3D中混合Optitrack与数据手套的数据流,在人体运动的基础上,添加双手手指部分的运动。双手手背的角度仍由Optitrack提供,数据手套提供双手手指的角度。 01 客户端软件分别安装MotiveBody与MotionVenus并校准人体与数据手套。MotiveBodyMotionVenus数据手套使用、校准流程参照:https://gitee.com/foheart_1/foheart-h1-data-summary.git02 数据转发打开MotiveBody软件的Streaming,开始向Unity3D广播数据;MotionVenus中设置->选项选择Unit
文章目录一、概述简介原理模块二、配置Mysql使用版本环境要求1.操作系统2.mysql要求三、配置canal-server离线下载在线下载上传解压修改配置单机配置集群配置分库分表配置1.修改全局配置2.实例配置垂直分库水平分库3.修改group-instance.xml4.启动监听四、配置canal-adapter1修改启动配置2配置映射文件3启动ES数据同步查询所有订阅同步数据同步开关启动4.验证五、配置canal-admin一、概述简介canal是Alibaba旗下的一款开源项目,Java开发。基于数据库增量日志解析,提供增量数据订阅&消费。Git地址:https://github.co