目录
在前几篇博文中,我们学习的都是完全二叉树或满二叉树,而这两个都是可以用数组来实现的,但是如果不是完全二叉树呢?回顾下曾经学过的知识点:
由上图得知,普通二叉树也可以使用数组来存储,但是会存在大量的空间浪费,而完全二叉树就不会这样,因为其空间利用率是%100的。既然这样,那普通二叉树该如何进行存储呢?答案是使用链式结构进行存储。
- 链式结构分为两种:二叉链和三叉链
先看下代码结构:
typedef int BTDataType; // 二叉链 struct BinaryTreeNode { struct BinTreeNode* _pLeft; // 指向当前节点左孩子 struct BinTreeNode* _pRight; // 指向当前节点右孩子 BTDataType _data; // 当前节点值域 }; // 三叉链 struct BinaryTreeNode { struct BinTreeNode* _pParent; // 指向当前节点的双亲 struct BinTreeNode* _pLeft; // 指向当前节点左孩子 struct BinTreeNode* _pRight; // 指向当前节点右孩子 BTDataType _data; // 当前节点值域 };
- 画图演示:
- 注意:
链式二叉树和我们之前的学习略有差别。以前我们学习的数据结构无非就是增删查改这些东西,而链式二叉树不太关注这块的增删查改。因为普通二叉树的增删查改没有意义。如下的二叉树:
链式二叉树是要比之前链表啥的更加复杂的,如果只是单纯的让链式二叉树存储数据的话,价值就不大了,不如使用线性表。接下来,我将通过其遍历方式,结点个数……为大家展开讨论。此节内容是为了后续学习更复杂的搜索二叉树打基础,具体是啥后面再谈。
- 在具体讲解之前,再回顾下二叉树,二叉树是:
- 空树
- 非空:根节点,根节点的左子树、根节点的右子树组成的。
从概念中可以看出,二叉树定义是递归式的,因此后序基本操作中基本都是按照该概念实现的。
创建二叉链结构其实就很easy了,也就是创建一个结构体罢了,这种在先前已经写过很多,咱就是说直接上代码:
//创建二叉链结构 typedef int BTDataType; //本文以int整型为例 typedef struct BinaryTreeNode { struct BinaryTreeNode* left; struct BinaryTreeNode* right; int data; }BTNode;
- 思路:
其实构建一棵树的思想还是挺简单的,按照图示创建6个节点,并根据图中的样子将节点顺次链接起来
- 代码演示:
//创建二叉链结构 typedef int BTDataType; //本文以int整型为例 typedef struct BinaryTreeNode { struct BinaryTreeNode* left; struct BinaryTreeNode* right; int data; }BTNode; //创建结点 BTNode* BuyBTNode(BTDataType x) { BTNode* node = (BTNode*)malloc(sizeof(BTNode)); if (node == NULL) { printf("malloc fail\n"); exit(-1); } node->data = x; node->left = node->right = NULL; return node; } //构建树 BTNode* CreatBinaryTree() { //创建6个结点 BTNode* node1 = BuyBTNode(1); BTNode* node2 = BuyBTNode(2); BTNode* node3 = BuyBTNode(3); BTNode* node4 = BuyBTNode(4); BTNode* node5 = BuyBTNode(5); BTNode* node6 = BuyBTNode(6); //将结点连接起来,构成自己想要的树 node1->left = node2; node1->right = node4; node2->left = node3; node4->left = node5; node4->right = node6; //返回根结点 return node1; } int main() { BTNode* tree = CreatBinaryTree(); return 0; }
- 以一颗二叉树为例:
后续的遍历均是建立在次二叉树基础上展开。
- 遍历规则:
前序遍历,也叫先根遍历
遍历顺序:根 -> 左子树 -> 右子树
- 思路:
既然先从根走,根就是1,接下来访问1的左子树,此时又要先访问其左子树的根为2,接着再访问2的左子树->根:3,接着访问其左子树和右子树,不过均为空,递归返回,此时3作为2的左子树访问完毕,访问2的右子树为NULL,再递归返回此时1的左子树就访问完毕了,访问其右子树,同理访问左树4,再访问左树5,接着左右子树NULL,递归返回访问4的右树,……类似的
- 图示:
- 代码演示:
前序遍历的代码非常简洁,短短几行即可操作,先看代码:
//前序遍历 void PrevOrder(BTNode*root) { if (root == NULL) { printf("NULL "); //如果为空,就打印空 return; } printf("%d ", root->data); PrevOrder(root->left); PrevOrder(root->right); }
- 效果如下:
跟我们先前画的图一模一样,让我们通过一副递归图来深刻理解其原理:
- 递归图:
- 遍历规则:
中序遍历,也叫中根遍历
遍历顺序:左子树 -> 根结点 -> 右子树
- 思路:
根据遍历顺序,我们得知,如若想访问1,得先访问其左子树2,访问2还得先访问其左子树3,类似的,再访问其左子树为NULL,递归返回访问根结点3,再访问右子树NULL,递归返回访问根结点2,再访问右子树NULL,递归返回访问根结点1,再访问其右子树,1的右子树访问规律同1的左子树,这里不过多赘述。
- 画图演示:
- 代码演示:
中序遍历的代码和前序遍历一样,看起来都非常简洁:
//中序遍历 void InOrder(BTNode* root) { if (root == NULL) { printf("NULL "); //如果为空,就打印空 return; } InOrder(root->left); printf("%d ", root->data); InOrder(root->right); }
- 效果如下:
单纯看代码看不出啥头绪,还得是画递归图。
- 递归图:
- 遍历规则:
后续遍历,也叫后根遍历
遍历顺序:左子树 -> 右子树 -> 根结点
- 思路:
要访问1得先访问1的左子树2,继而得先访问2的左子树3,再先访问3的左子树NULL,右子树NULL,根3,递归返回访问2的右子树NULL,根2,再递归返回访问1的右子树……类似的
- 画图演示:
- 代码演示:
后续的代码也非常简单,有了前文前序遍历和中序遍历的基础,后续遍历只需要把打印放后面即可,代码如下:
//后序遍历 void PosOrder(BTNode* root) { if (root == NULL) { printf("NULL "); //如果为空,就打印空 return; } PosOrder(root->left); PosOrder(root->right); printf("%d ", root->data); }
- 效果如下:
- 画图演示递归过程:
- 遍历规则:
层序遍历听名字就很直白,直接一层一层按顺序遍历呗。
我这里直接给出答案:1、2、4、3、5、6
- 思路:
细心的童鞋可能已经发现,先前的遍历思想都是通过递归来完成的,而层序的遍历则是通过队列来实现的。
首先,把根节点1的结点指针先入队列,队列此时不为空,出队头的数据,把队头数据的孩子2的结点指针和4的结点指针入进去,队列不为空,出2,入孩子3,队列不为空,再出4,把孩子5和6入进去,然后再出,没有孩子继续出,出到最后队列为空。总结如下两句话:
- 先把根入队列,借助队列性质:先进先出
- 上一层的节点出的时候,带下一层的节点进去
- 图示:
- 代码演示:
//层序遍历 void LevelOrder(BTNode* root) { Queue q; QueueInit(&q); if (root) { QueuePush(&q, root); //先把根结点入进去 } while (!QueueEmpty(&q)) { BTNode* front = QueueFront(&q); //取队头 QueuePop(&q); //出队头 printf("%d ", front->data); //打印队头数据 if (front->left) { QueuePush(&q, front->left); //front左孩子不为空,就入 } if (front->right) { QueuePush(&q, front->right);//front右孩子不为空,就入 } } printf("\n"); QueueDestory(&q); }
- 效果如下:
- 思想:
求结点个数,这里我将提供如下几种方法,但不都是可行的,要对比着看,本质都是递归的思想:
- 法一:遍历
在前文中,我们已经学习了如何遍历链式二叉树,现在想求结点的个数,那么只需要随便采用一种遍历方式,并把打印换成计数++来求个数即可,听起来非常容易,先看代码:
//节点个数 void BTreeSize(BTNode* root) { int count = 0; //局部变量 if (root == NULL) //如果为空 return; ++count; BTreeSize(root->left); BTreeSize(root->right); }难道上述代码能够准确求出结点个数吗?其实不然,根本求不出来。
具体解释起来需要借用栈帧的思想,因为这里用的是递归,而递归是每递归一次在栈帧里头都会开辟一块空间,每一块栈帧都会有一个count,而我希望的是只需要有一个count,然后所有的count均加在一起,可是现在每递归一次,重新开辟一个count,count即局部变量。递归完就销毁,同形参的改变不会影响实参一样,一个道理。所有此法根本就行不通,得换。
- 法二:定义局部静态变量count
在法一中,我们定义的是局部变量count,会导致每递归一次就开辟栈帧,并创建count,每次递归结束返回就销毁栈帧。那如果可以把count放在静态区里头,不久可以保留住count吗
//节点个数 int BTreeSize(BTNode* root) { static int count = 0; //局部静态变量 if (root == NULL) //如果为空 return count; ++count; BTreeSize(root->left); BTreeSize(root->right); return count; }
- 效果如下:
看似好像是成功了,确实结点个数为6,但真的就是成功了吗?当然不是,如果我们现在想多打印几次呢?
什么鬼?怎么size还呈现等差数列递增呢?就是因为这里运用了static关键字,将count扣在静态区,导致多次调用没办法初始化为0,使其每次递归调用累计加加,但是当你再重新调用自己时,count不会重新置为0,会依旧保留为曾经++的结果。局部的静态变量有一个好处,它的生命周期在全局,但是只能在局部去访问。它的初始化为0只有第一次调用会访问,其余均不会。由此可见,局部的静态也是不行的,还得再优化。
- 法三:定义全局变量count
法二的局部静态变量行不通,那就把count设定为全局变量。要知道全局变量是存在静态区的。虽然也在静态区,但是其初始化为0是可以重复访问的。
//节点个数 int count = 0; void BTreeSize(BTNode* root) { if (root == NULL) //如果为空 return; ++count; BTreeSize(root->left); BTreeSize(root->right); }
- 效果如下:
确实可以求出结点个数,并且也不会出现像法二一样的问题。但是其实定义全局变量也会存在一个小问题:线程安全的问题,这个等以后学到Linux再来讨论,我们这边考虑再换一种更优解。
- 法四:最优解
我们这里可以考虑多套一层,可以考虑把变量的地址传过去。这样操作不会存在任何问题,上代码:
//节点个数 void BTreeSize(BTNode* root, int* pCount) { if (root == NULL) //如果为空 return; ++(*pCount); BTreeSize(root->left, pCount); BTreeSize(root->right, pCount); }
- 法五:新思路
直接利用子问题的思想来写,返回当root为空为0,不是就递归左树+右树+1。
- 空树,最小规模子问题,结点个数返回0
- 非空,左子树结点个数+右子树结点个数 + 1(自己)
int BTreeSize(BTNode* root) { return root == NULL ? 0 : BTreeSize(root->left) + BTreeSize(root->right) + 1; }此法非常巧妙,很灵活的运用了递归的思想,我们通过递归图来深刻理解下:
- 递归图:
- 总结:
上述算法思想其实就是分治思想。
把复杂的问题分成更小规模的子问题,子问题再分成更小规模的子问题……直到子问题不可再分割,直接能出结果。
此思想纯纯是在套娃。接下来的多个问题都将会用到分治思想。
- 思路1:遍历+计数
在遍历的基础上如果结点的左右子树均为空则count++。但是此题我们依旧采用分治思想。
- 思路2:分治思想
首先,如果为空,直接返回0,如若结点的左子树和右子树均为空,则为叶节点,此时返回1,其它的继续分治递归。
- 代码演示:
//叶结点个数 int BTreeLeafSize(BTNode* root) { if (root == NULL) return 0; //为空,返回0 if (root->left == NULL && root->right == NULL) return 1; //如果左右子树均为空,则为叶结点,返回1 return BTreeLeafSize(root->left) + BTreeLeafSize(root->right); //继续分治递归 }
- 递归图:
- 思路:
假设K=3
- 空树,返回0
- 非空,且K == 1,返回1
- 非空,且K>1,转换成左子树K-1层节点个数 + 右子树K-1层节点个数
- 代码演示:
//第K层节点个数,K>=1 int BTreeKLevelSize(BTNode* root, int k) { assert(k >= 1); if (root == NULL) return 0; if (k == 1) return 1; return BTreeKLevelSize(root->left, k - 1) + BTreeKLevelSize(root->right, k - 1); }
- 递归图:
- 思路:
此题同样是运用分治的思想来解决,要比较左子树的高度和右子树的高度,大的那个就+1,因为还有根结点也算1个高度。
- 代码演示:
//求树的深度 int BTreeDepth(BTNode* root) { if (root == NULL) return 0; int leftDepth = BTreeDepth(root->left); //左子树高度 int rightDepth = BTreeDepth(root->right);//右子树高度 return leftDepth > rightDepth ? leftDepth + 1 : rightDepth + 1; }
- 递归图:
- 思路:
还是利用分治的思想,将其递归化成子问题去解决
- 先去根结点寻找,是就返回此节点
- 此时去左子树查找有无节点x
- 最后再去右子数去查找有无节点x
- 若左右子树均找不到节点x,直接返回空
- 代码演示:
//二叉树查找值为x的节点 BTNode* BTreeFind(BTNode* root, BTDataType x) { //如果根节点为空,直接返回空 if (root == NULL) return NULL; //如果找到节点,直接返回节点位置 if (root->data == x) return root; //若没找到,去左子树找 BTNode* ret1 = BTreeFind(root->left, x); if (ret1) return ret1; //此时左子树没找到,去右子树找 BTNode* ret2 = BTreeFind(root->right, x); if (ret2) return ret2; //若左子树和右子树都每找到,直接返回空 return NULL; } int main() { BTNode* tree = CreatBinaryTree(); for (int i = 0; i <= 7; i++) { printf("Find:%d,%p\n", i, BTreeFind(tree, i)); } return 0; }
- 效果如下:
- 递归图:
假设我们寻找的是数字5
- 思路:
销毁的思想和遍历类似,如若我挨个遍历的同时,没遍历一次就销毁一次,岂不能达到效果,但是又会存在一个问题,那就是你要采用什么样的遍历方式?倘若你采用前序遍历,刚开始就把根销毁了,那么后面的子树还怎么销毁呢?因为此时根没了,子树找不到了就,所以要采用倒着销毁的规则,也就是后续的思想
- 代码演示:
//二叉树的销毁 void BTreeDestory(BTNode* root) { if (root == NULL) return; BTreeDestory(root->left); BTreeDestory(root->right); free(root); root = NULL; }思想和后续遍历类似,不做递归图演示。
在做提前,再来回顾下完全二叉树的概念:前k-1层是满的,最后一层是连续的。
- 来看一幅图:
在这三幅图中,很明显肉眼得知第二幅和第三幅图是完全二叉树,只有第一幅不是,现在如何用代码的方式表明出来呢?
- 思路:层序遍历+变形
通过上图,不难发现,如果是完全二叉树的话,在层序遍历的时候是不会出现间隔的NULL。例如第一幅图就不是完全二叉树,因为层序遍历到第三层的时候会出现间隔NULL,因为3 -> NULL -> 5 -> 6,而剩余两幅图均不会出现这样的问题,接下来,我将利用类似的思想解决这道题。
层序遍历明确指出,当其中一个结点pop出来时,要把它的孩子给push进队列里,但前提是把不为空的孩子给push进去,现在规矩变了,不管你是否为空,都给push进去,也就是说出一个结点,push两个孩子结点,使其停止的条件是当我pop出来的结点为NULL时,此时停止push,一直pop到队列为空,如果全是空,就是完全二叉树,如果有非空,就不是。
- 画图演示:
- 代码演示:
//判断一颗二叉树是否是完全二叉树 bool BTreeComplete(BTNode* root) { Queue q; QueueInit(&q); if (root) { QueuePush(&q, root); //根结点不为空,入队列 } while (!QueueEmpty(&q)) { BTNode* front = QueueFront(&q); QueuePop(&q); //删除队头数据,方便后续取队头数据 if (front == NULL) //如果取队头为空,停止,接下来进入下一个while循环判断是否为完全二叉树 break; QueuePush(&q, front->left); QueuePush(&q, front->right); } while (!QueueEmpty(&q)) { BTNode* front = QueueFront(&q); QueuePop(&q); //如果空出到非空,那么说明不是完全二叉树 if (front) { QueueDestory(&q); return false; } } QueueDestory(&q); return true; //全是空,此时返回true,为完全二叉树 }
- 效果如下:
#define _CRT_SECURE_NO_WARNINGS 1 #include<stdio.h> #include<stdlib.h> #include<assert.h> #include<stdbool.h> #include"Queue.h" //创建二叉链结构 typedef int BTDataType; //本文以int整型为例 typedef struct BinaryTreeNode { struct BinaryTreeNode* left; struct BinaryTreeNode* right; int data; }BTNode; //创建结点 BTNode* BuyBTNode(BTDataType x) { BTNode* node = (BTNode*)malloc(sizeof(BTNode)); if (node == NULL) { printf("malloc fail\n"); exit(-1); } node->data = x; node->left = node->right = NULL; return node; } //构建树 BTNode* CreatBinaryTree() { //创建6个结点 BTNode* node1 = BuyBTNode(1); BTNode* node2 = BuyBTNode(2); BTNode* node3 = BuyBTNode(3); BTNode* node4 = BuyBTNode(4); BTNode* node5 = BuyBTNode(5); BTNode* node6 = BuyBTNode(6); //将结点连接起来,构成自己想要的树 node1->left = node2; node1->right = node4; node2->left = node3; node4->left = node5; node4->right = node6; //返回根结点 return node1; } //前序遍历 void PrevOrder(BTNode* root) { if (root == NULL) { printf("NULL "); //如果为空,就打印空 return; } printf("%d ", root->data); PrevOrder(root->left); PrevOrder(root->right); } //中序遍历 void InOrder(BTNode* root) { if (root == NULL) { printf("NULL "); //如果为空,就打印空 return; } InOrder(root->left); printf("%d ", root->data); InOrder(root->right); } //后序遍历 void PosOrder(BTNode* root) { if (root == NULL) { printf("NULL "); //如果为空,就打印空 return; } PosOrder(root->left); PosOrder(root->right); printf("%d ", root->data); } /* 全局变量 节点个数 int count = 0; void BTreeSize(BTNode* root) { if (root == NULL) //如果为空 return; ++count; BTreeSize(root->left); BTreeSize(root->right); }*/ /*节点个数 多套一层 void BTreeSize(BTNode* root, int* pCount) { if (root == NULL) //如果为空 return; ++(*pCount); BTreeSize(root->left, pCount); BTreeSize(root->right, pCount); } */ //结点个数 -- > 子问题法 int BTreeSize(BTNode* root) { return root == NULL ? 0 : BTreeSize(root->left) + BTreeSize(root->right) + 1; } //叶结点个数 int BTreeLeafSize(BTNode* root) { if (root == NULL) return 0; //为空,返回0 if (root->left == NULL && root->right == NULL) return 1; //如果左右子树均为空,则为叶结点,返回1 return BTreeLeafSize(root->left) + BTreeLeafSize(root->right); //继续分治递归 } //第K层节点个数,K>=1 int BTreeKLevelSize(BTNode* root, int k) { assert(k >= 1); if (root == NULL) return 0; if (k == 1) return 1; return BTreeKLevelSize(root->left, k - 1) + BTreeKLevelSize(root->right, k - 1); } //求树的深度 int BTreeDepth(BTNode* root) { if (root == NULL) return 0; int leftDepth = BTreeDepth(root->left); int rightDepth = BTreeDepth(root->right); return leftDepth > rightDepth ? leftDepth + 1 : rightDepth + 1; } //二叉树查找值为x的节点 BTNode* BTreeFind(BTNode* root, BTDataType x) { //如果根节点为空,直接返回空 if (root == NULL) return NULL; //如果找到节点,直接返回节点位置 if (root->data == x) return root; //若没找到,去左子树找 BTNode* ret1 = BTreeFind(root->left, x); if (ret1) return ret1; //此时左子树没找到,去右子树找 BTNode* ret2 = BTreeFind(root->right, x); if (ret2) return ret2; //若左子树和右子树都每找到,直接返回空 return NULL; } //二叉树的销毁 void BTreeDestory(BTNode* root) { if (root == NULL) return; BTreeDestory(root->left); BTreeDestory(root->right); free(root); root = NULL; } //层序遍历 void LevelOrder(BTNode* root) { Queue q; QueueInit(&q); if (root) { QueuePush(&q, root); //先把根结点入进去 } while (!QueueEmpty(&q)) { BTNode* front = QueueFront(&q); //取队头 QueuePop(&q); //出队头 printf("%d ", front->data); //打印队头数据 if (front->left) { QueuePush(&q, front->left); //front左孩子不为空,就入 } if (front->right) { QueuePush(&q, front->right);//front右孩子不为空,就入 } } printf("\n"); QueueDestory(&q); } //判断一颗二叉树是否是完全二叉树 bool BTreeComplete(BTNode* root) { Queue q; QueueInit(&q); if (root) { QueuePush(&q, root); //根结点不为空,入队列 } while (!QueueEmpty(&q)) { BTNode* front = QueueFront(&q); QueuePop(&q); //删除队头数据,方便后续取队头数据 if (front == NULL) //如果取队头为空,停止,接下来进入下一个while循环判断是否为完全二叉树 break; QueuePush(&q, front->left); QueuePush(&q, front->right); } while (!QueueEmpty(&q)) { BTNode* front = QueueFront(&q); QueuePop(&q); //如果空出到非空,那么说明不是完全二叉树 if (front) return false; } return true; //全是空,此时返回true,为完全二叉树 } int main() { BTNode* tree = CreatBinaryTree(); //PrevOrder(tree); //printf("\n"); //InOrder(tree); //printf("\n"); //PosOrder(tree); //printf("\n"); //printf("size: %d", BTreeSize(tree)); for (int i = 0; i <= 7; i++) { printf("Find:%d,%p\n", i, BTreeFind(tree, i)); } LevelOrder(tree); printf("完全二叉树:%d\n", BTreeComplete(tree)); BTreeDestory(tree); tree = NULL; return 0; }
我想将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
有时我需要处理键/值数据。我不喜欢使用数组,因为它们在大小上没有限制(很容易不小心添加超过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
本教程将在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
我正在尝试在Rails上安装ruby,到目前为止一切都已安装,但是当我尝试使用rakedb:create创建数据库时,我收到一个奇怪的错误:dyld:lazysymbolbindingfailed:Symbolnotfound:_mysql_get_client_infoReferencedfrom:/Library/Ruby/Gems/1.8/gems/mysql2-0.3.11/lib/mysql2/mysql2.bundleExpectedin:flatnamespacedyld:Symbolnotfound:_mysql_get_client_infoReferencedf
文章目录1.开发板选择*用到的资源2.串口通信(个人理解)3.代码分析(注释比较详细)1.主函数2.串口1配置3.串口2配置以及中断函数4.注意问题5.源码链接1.开发板选择我用的是STM32F103RCT6的板子,不过代码大概在F103系列的板子上都可以运行,我试过在野火103的霸道板上也可以,主要看一下串口对应的引脚一不一样就行了,不一样的就更改一下。*用到的资源keil5软件这里用到了两个串口资源,采集数据一个,串口通信一个,板子对应引脚如下:串口1,TX:PA9,RX:PA10串口2,TX:PA2,RX:PA32.串口通信(个人理解)我就从串口采集传感器数据这个过程说一下我自己的理解,