草庐IT

数据结构:链式二叉树初阶

摆烂小青菜 2023-04-14 原文

目录

一.链式二叉树的逻辑结构

1.链式二叉树的结点结构体定义

2.链式二叉树逻辑结构

二.链式二叉树的遍历算法

1.前序遍历

2.中序遍历

3.后序遍历 

4.层序遍历(二叉树非递归遍历算法)

层序遍历概念:

层序遍历算法实现思路: 

层序遍历代码实现:

三.链式二叉树遍历算法的运用

1.前序遍历算法的运用

相关练习: 

2.后序遍历算法的运用

3.层序遍历算法的运用

问题来源:

四.链式二叉树其他操作接口的实现

1. 计算二叉树结点个数的接口

2.计算二叉树叶子结点的个数的接口

3.计算二叉树第k层结点个数的接口

4.二叉树的结点查找接口(在二叉树中查找值为x的结点)


一.链式二叉树的逻辑结构

1.链式二叉树的结点结构体定义

  • 树结点结构体定义:
    ​
    typedef int BTDataType;
    typedef struct BinaryTreeNode
    {
    	BTDataType data;                //数据域
    	struct BinaryTreeNode* left;    //指向结点左孩子的指针
    	struct BinaryTreeNode* right;   //指向结点右孩子的指针
    }BTNode;
    
    ​

2.链式二叉树逻辑结构

  • 这里先暴力"创建"一颗二叉树:
    //在内存堆区上申请结点的接口
    BTNode* BuyNode( BTDataType x)
    {
    	BTNode* tem = (BTNode*)malloc(sizeof(BTNode));
    	assert(tem);
    	tem->data = x;
    	tem->left = NULL;
    	tem->right = NULL;
    	return tem;
    }
    int main ()
    {
        BTNode* node1 = BuyNode(1);
    	BTNode* node2 = BuyNode(2);
    	BTNode* node3 = BuyNode(3);
    	BTNode* node4 = BuyNode(4);
    	BTNode* node5 = BuyNode(5);
    	BTNode* node6 = BuyNode(6);
    	node1->left = node2;
    	node1->right = node4;
    	node2->left = node3;
    	node4->left = node5;
    	node4->right = node6;
    
        //其他操作
        return 0;
    }
  • 树的逻辑结构图示:

  1. 每个树结点都有其左子树和右子树

  2. 因此关于链式二叉树的递归算法的设计思路是:关于树T的问题可以分解为其左子树和右子树的问题,树T的左子树和右子树的问题又可以以同样的方式进行分解,因此就形成了递归

  3. 关于链式二叉树递归算法的核心思维:左右子树分治思维

二.链式二叉树的遍历算法

遍历二叉树的递归框架:

  1. 函数的抽象意义完成整棵树的遍历
  2. 为了完成整棵树的遍历我们先要完成该树的左子树的遍历右子树的遍历
  3. 左子树和右子树的遍历又可以以相同的方式进行问题拆分,于是便形成了递归(数学上的递推迭代)
//递归遍历二叉树的基本函数结构
void Order(BTNode* root)
{
	if (NULL == root)
	{
		return;
	}

	对当前结点进行某种处理的语句位置1(前序);
	Order(root->left);  //遍历左子树的递归语句
	对当前结点进行某种处理的语句位置2(中序);
	Order(root->right); //遍历右子树的递归语句
	对当前结点进行某种处理的语句位置3(后序);
}
  • 通过这个递归函数,我们可以遍历二叉树的每一个结点:(假设根结点的地址为root)
  • 根据函数中对当前的root结点进行某种处理的代码语句位置可将遍历二叉树的方式分为前序遍历(位置1),中序遍历(位置2),后序遍历(位置3)三种

1.前序遍历

//二叉树前序遍历
void PreOrder(BTNode* root)
{
	if (NULL == root)
	{
		printf("NULL->");//为了方便观察我们将空结点打印出来
		return;
	}
	printf("%d->", root->data); //当前结点处理语句
	PreOrder(root->left);       //向左子树递归
	PreOrder(root->right);      //向右子树递归
}

  • 以图中的树为例调用前序遍历函数:
    PreOrder(root); //root为树根地址

    递归函数执行过程中函数栈帧的逻辑结构分布以及代码语句执行次序图示:

    递归函数执行过程中函数栈帧的物理结构分布:

  1. 当递归执行到图中的第4步是函数栈帧的物理结构:

  2. 当递归执行到图中的第10步是函数栈帧的物理结构: 

  3. 当递归执行到图中的第14步是函数栈帧的物理结构: 

  4. 当递归执行到图中的第22步是函数栈帧的物理结构: 

  • 递归函数执行过程中同一时刻开辟的函数栈帧的最大个数取决于树的高度:因此递归遍历二叉树的空间复杂度为:O(logN);

2.中序遍历

//二叉树中序遍历
void InOrder(BTNode* root)
{
	if (NULL == root)
	{
		printf("NULL->");   //为了方便观察我们打印出空结点
		return;
	}
	InOrder(root->left);        //向左子树递归
	printf("%d->", root->data); //打印结点值(root处理语句)
	InOrder(root->right);       //向右子树递归
}
  •  递归函数执行过程分析方法与前序遍历类似
  • 对于图中的二叉树,中序遍历结点值打印次序为:

3.后序遍历 

//二叉树后序遍历
void BackOrder(BTNode* root)
{
	if (NULL == root)
	{
		printf("NULL->");       //为了方便观察我们打印出空结点
		return;
	}
	BackOrder(root->left);      //向左子树递归
	BackOrder(root->right);     //向右子树递归
	printf("%d->", root->data); //打印结点值(root处理语句)
}
  •  递归函数执行过程分析方法与前序遍历类似
  • 对于图中的二叉树,后序遍历结点值打印次序为:

4.层序遍历(二叉树非递归遍历算法)

层序遍历概念:

  • 二叉树的层序遍历借助结点指针队列循环实现的二叉树遍历算法

  • 树结点的遍历次序从低层到高层,同一层从左到右进行遍历的:

层序遍历算法实现思路: 

  • 创建一个队列,将树的根结点地址插入队列中;(队列中的数据先进先出,数据从队尾入队从队头出队)
  • 接着开始执行循环语句
  • 每一次循环,取出队头的一个指针,对其进行处理(打印数值等等处理),若队头指针不为空指针,则将队头指针指向的结点左右孩子指针插入队列中,重复循环直到队列为空为止.
  • 树结点指针入队次序图示:
  • 经过分析可知:队列中的树结点地址就是按照树结点的层序逻辑顺序进行排列的(从根结点开始,前一层结点指针出队后带入下一层结点的指针),因此该算法可以完成二叉树的层序遍历
  • 算法gif图示:

层序遍历代码实现:

  • 为了方便我们直接使用C++STL的queue类模板 (VS2022的queue对象数据入队(尾插)接口为push,数据出队(头删)接口为pop)(不同编译器接口命名可能不同)
  • 如果要使用C语言则需要自己手动实现队列
void LevelOrder(BTNode* root)
{
	queue<BTNode*> NodeStore;			//创建一个队列用于存储结点指针
	NodeStore.push(root);				//将根结点指针插入队列中

	while (!NodeStore.empty())			//若队列不为空则循环继续
	{
		BTNode* tem = NodeStore.front();//保存队头指针
		NodeStore.pop();				//队头指针出队
		if (nullptr != tem)
		{
			cout << (tem->data)<<' ';	//处理出队的结点(这里是打印其结点值)
		}
		if (nullptr != tem)				//若出队结点不为空,则将其孩子结点指针带入队列
		{
			NodeStore.push(tem->left);  //将出队结点的左孩子指针带入队列
			NodeStore.push(tem->right); //将出队结点的右孩子指针带入队列
		}
	}
	cout << endl;
}


三.链式二叉树遍历算法的运用

1.前序遍历算法的运用

前序遍历递归:

  1. 先处理根
  2. 向左子树递归
  3. 向右子树递归
  • 由于根处理语句前序递归函数中是首先执行的,因此我们可以利用前序遍历递归框架来实现链式二叉树的递归构建(在前序递归的框架下我们可以实现先创建根结点,再创建根结点的左右子树,中序和后序递归无法完成树的递归构建)

相关练习: 

二叉树遍历_牛客题霸_牛客网 (nowcoder.com)https://www.nowcoder.com/practice/4b91205483694f449f94c179883c1fef?tpId=60&&tqId=29483&rp=1&ru=/activity/oj&qru=/ta/tsing-kaoyan/question-ranking(本题源自清华大学OJ)

问题描述:

编一个程序,读入用户输入一串先序遍历字符串,根据此字符串建立一个二叉树(以指针方式存储)。

例如:先序遍历字符串: ABC##DE#G##F### 其中“#”表示的是空格空格字符代表空树。建立起此二叉树以后,再对二叉树进行中序遍历,输出遍历结果。

(输入的字符串,长度不超过100.)

示例:

  • 输入:abc##de#g##f###
  • c b e g d f a 

问题分析:

  • 以字符串"abc##de#g##f###"为例,先分析一下在先序遍历的逻辑顺序下字符串所对应的二叉树结构:如果按照中序逻辑顺序打印这颗树结果为:c-->b-->e-->g-->d-->f-->a;下面给出各个#所代表的空树在树中的逻辑位置:
  • 递归构建分析:
  • 进一步分析可知:给定的字符串序列必须满足二叉树的前序序列逻辑,不然该字符串序列便无法被转换成一颗完整的二叉树
  • 借助递归分析我们可以实现二叉树的递归构建代码
  1. 为了方便理解, 先给出主函数测试代码,熟悉递归函数调用方式:
    int main() 
    {
        char arr[101] = {0};
        scanf("%s",arr);                        //输入待转换的字符串
        int ptr = 0;                            //用于遍历字符串的下标变量
        TreeNode * root = CreateTree(arr,&ptr); //调用建树函数
        InOrder(root);                          //对树进行中序遍历
      
        return 0;
    }
  2. 建树递归函数首部:

    TreeNode * CreateTree(char * input,int* ptr)

  • input是待转换的字符串的首地址

  • ptr是用于遍历字符串的下标变量的地址,这里一定要传址,因为我们要在不同的函数栈帧中修改同一个字符数组下标变量

  • 函数实现:

    TreeNode * CreateTree(char * input,int* ptr)
    {
        if('#'==input[*ptr])            //#代表空树,直接将空指针返回给上层结点即可
        {
            ++*ptr;
            return nullptr;
        }
        TreeNode * root = new TreeNode;     //申请新结点
        root->word = input[*ptr];           //结点赋值 
        ++*ptr;                             //字符数组下标加一处理下一个字符
        root->left = CreateTree(input,ptr); //向左子树递归
        root->right = CreateTree(input,ptr);//向右子树递归
        return root;                        //将本层结点地址返回给上一层结点(或返回给根指针变量)
    }
  • 整体题解代码:

    #include <iostream>
    #include <string>
    using namespace std;
    
    
    struct TreeNode  //树结点结构体
    {
        char word;
        TreeNode * left;
        TreeNode * right;
    };
    
    //ptr是字符串数组下标的地址,这里一定要传址,因为我们要在不同的函数栈帧中修改同一个字符数组下标变量
    TreeNode * CreateTree(char * input,int* ptr)
    {
        if('#'==input[*ptr])            //#代表空树,直接将空指针返回给上层结点即可
        {
            ++*ptr;
            return nullptr;
        }
        TreeNode * root = new TreeNode;     //申请新结点
        root->word = input[*ptr];           //结点赋值 
        ++*ptr;                             //字符数组下标加一处理下一个字符
        root->left = CreateTree(input,ptr); //向左子树递归
        root->right = CreateTree(input,ptr);//向右子树递归
        return root;                        //将本层
    }
    
    void InOrder(TreeNode * root)           //中序遍历递归
    {
        if(nullptr == root)
        {
            return;
        }
        InOrder(root->left);
        printf("%c ",root->word);
        InOrder(root->right);
    }
    
    int main() 
    {
        char arr[101] = {0};
        scanf("%s",arr);
        int ptr = 0;
        TreeNode * root = CreateTree(arr,&ptr);
        InOrder(root);
      
        return 0;
    }

2.后序遍历算法的运用

后序遍历算法:

  1. 先向左子树递归
  2. 再向右子树递归
  3. 完成左右子树的处理后再处理根

后序遍历算法的思想刚好可以适用于二叉树的销毁过程. (前序和中序算法不能完成二叉树的销毁,因为一旦我们释放了当前结点便无法再向左右子树进行递归(地址丢失))

  • 二叉树销毁接口:
    // 二叉树销毁
    void BinaryTreeDestory(BTNode* root)
    {
    	if (NULL == root)
    	{
    		return;
    	}
    	BinaryTreeDestory(root->left);
    	BinaryTreeDestory(root->right);
    	free(root);						//释放到当前结点的内存空间
    
    }
  • 二叉树销毁过程图示:

3.层序遍历算法的运用

层序遍历算法可以用于检验一颗二叉树是否为完全二叉树;

问题来源:

958. 二叉树的完全性检验 - 力扣(Leetcode)https://leetcode.cn/problems/check-completeness-of-a-binary-tree/问题描述:

给定一个二叉树的 root ,确定它是否是一个完全二叉树 。

在一个 完全二叉树中,除了树的最后一层以外,其他层是完全被填满的,并且最后一层中的所有结点都是靠左排列的。

958. 二叉树的完全性检验 - 力扣(Leetcode)

题解接口:

class Solution 
{
public:
    bool isCompleteTree(TreeNode* root) 
    {

    }
};

 关于完全二叉树的结构特点参见青菜的博客:http://t.csdn.cn/6qErohttp://t.csdn.cn/6qEro

问题分析:

  • 完全二叉树的结构特点是各层结点连续排列(各结点编号是连续的)
  • 因此层序遍历完全二叉树,各结点指针在队列中都是连续排列的(即各结点指针之间不会出现空指针)
  • 根据完全二叉树的结构特点,一旦层序遍历完全二叉树的过程中遍历到了空指针,则说明已经遍历到了二叉树的最后一层
  • 我们可以利用层序遍历算法遍历二叉树,当遇到第一个出队的空指针时,检验后续出队的指针中是否还存在非空结点指针,如果后续出队的指针中存在非空指针说明该树不是完全二叉树,否则说明该树是一颗完全二叉树。
  • 算法图示:

 

题解代码: 

class Solution 
{
public:
    bool isCompleteTree(TreeNode* root) 
    {
        queue<TreeNode*> ans;
        ans.push(root);        //将根结点指针插入队列
        while(!ans.empty())    //队列不为空则循环继续
        {
            TreeNode* tem = ans.front();
            ans.pop();         //取出队头指针存入tem变量中
            if(nullptr == tem) //遇到第一个出队的空指针,停止循环进行下一步判断
            {
                break;
            }
            else
            {
                ans.push(tem->left);   //将出队指针的孩子结点指针带入队列
                ans.push(tem->right);
            }
        }
        while(!ans.empty())
        {
            TreeNode* tem = ans.front();
            ans.pop();         //取出队头指针存入tem变量中
            if(nullptr != tem) //第一个出队的空指针后续存在非空结点指针,说明该树不是完全二叉树
            {
                return false;
            }
        }
        return true;           //所有结点指针完成入队出队则验证了该树是完全二叉树
    }
};

 

四.链式二叉树其他操作接口的实现

链式二叉树的众多递归接口都是通过左右子树分治递归的思想来实现的

 树结点结构体定义:

typedef int BTDataType;
typedef struct BinaryTreeNode
{
	BTDataType data;                //数据域
	struct BinaryTreeNode* left;    //指向结点左孩子的指针
	struct BinaryTreeNode* right;   //指向结点右孩子的指针
}BTNode;

1. 计算二叉树结点个数的接口

函数首部:

int BinaryTreeSize(BTNode* root)
  • 将接口函数抽象为某颗树的结点个数A(T)
  • 抽象出递推公式: A(T) = A(T->right) + A(T->left) + 1;
  • 递推公式的含义是: 树T的结点总个数 = 树T左子树的结点总个数 + 树T右子树的结点总个数 + 1个树T的树根(本质上是左右子树分治思想)
  • 由此可以设计出递归函数:
    int BinaryTreeSize(BTNode* root)
    {
    	//空树则返回0
    	if (NULL == root)
    	{
    		return 0;
    	}
    
    	//化为子问题进行分治
    	return BinaryTreeSize(root->left) + BinaryTreeSize(root->right) + 1;
    }
    

    将下图中的树的根结点地址传入递归函数:

    递归函数执行过程的简单图示: 

     

2.计算二叉树叶子结点的个数的接口

函数首部:

int BinaryTreeLeafSize(BTNode* root)
  • 将接口函数抽象为某颗树的叶子结点的个数A(T)
  • 可以抽象出递推公式:A(T) = A(T->left) + A(T->right)
  • 递推公式的含义是: 树T的叶子总数 = T的左子树的叶子总数 + T的右子树的叶子总数
  • 由此可以设计出递归函数:
    int BinaryTreeLeafSize(BTNode* root)
    {
    	if (NULL == root)                                      //空树不计入个数
    	{
    		return 0;
    	}
    	else if (NULL == root->left && NULL == root->right)   //判断当前树是否为树叶(是树叶则返回1,计入叶子结点个数)
    	{
    		return 1;
    	}
    	//化为子问题进行分治
    	return BinaryTreeLeafSize(root->left) + BinaryTreeLeafSize(root->right);
    }

    将下图中的树的根结点地址传入递归函数:

     递归函数执行过程的简单图示: 

3.计算二叉树第k层结点个数的接口

函数首部:

int BinaryTreeLevelKSize(BTNode* root, int k)
  • 将接口函数抽象为某颗树的第k层结点个数A(T,k)
  • 可以抽象出递推公式: A(T,k) = A(T->left,k-1) + A(T->right,k-1)
  • 递推公式的含义是: 树T的第k层结点个数 = 树T左子树的第k-1层结点数 + 树T右子树的第k-1层结点数
  • 由此可以设计出递归函数:
    int BinaryTreeLevelKSize(BTNode* root, int k)
    {
    	assert(k >= 1);
    	//树为空返回0
    	if (NULL == root)
    	{
    		return 0;
    	}
    	else if (root && k == 1)	//问题分解到求子树的第一层结点(即求树根的个数,树根的个数为1)
    	{
    		return 1;
    	}
    	else
    	{
    		return BinaryTreeLevelKSize(root->left, k - 1) + BinaryTreeLevelKSize(root->right, k - 1);
    	}
    }

    将下图中的树的根结点地址传入递归函数:

    递归函数执行过程的简单图示: 

     

4.二叉树的结点查找接口(在二叉树中查找值为x的结点)

 函数首部:

BTNode* BinaryTreeFind(BTNode* root, BTDataType x)

递归的思路:

  • 若当前树不为空树(若为空树则直接返回空指针),先判断树根是不是要找的结点
  • 若树根不是要找的结点,则往左子树递归查找
  • 若左子树找不到最后再往右子树递归查找
  • 若最后左右子树中都找不到待找结点则返回空指针
  • 设计递归函数:
    BTNode* BinaryTreeFind(BTNode* root, BTDataType x)
    {
    	//寻找到空树则返回空(说明在某条连通路径上不存在待找结点)
    	if (NULL == root)
    	{
    		return NULL;
    	}
    	//判断树根是否为待找结点,若判断为真则返回该结点地址
    	else if (root->data == x)
    	{
    		return root;
    	}
    	//若树根不是要找的结点, 则往左子树找
    	BTNode* leftret = BinaryTreeFind(root->left, x);
    	if (leftret)
    	{
    		return leftret;
    	}
    	//若左子树中不存在待找结点,则往右子树找
    	BTNode* rightret = BinaryTreeFind(root->right, x);
    	if (rightret)
    	{
    		return rightret;
    	}
    	//若左右子树中都找不到则说明不存在待找结点,返回空
    	return NULL;
    }
    

    将下图中的树的根结点地址传入递归函数:

    递归函数执行过程的简单图示: 

分治递归的思想是核心哦!!!! 

有关数据结构:链式二叉树初阶的更多相关文章

  1. ruby - 使用 ruby​​ 将 HTML 转换为纯文本并维护结构/格式 - 2

    我想将html转换为纯文本。不过,我不想只删除标签,我想智能地保留尽可能多的格式。为插入换行符标签,检测段落并格式化它们等。输入非常简单,通常是格式良好的html(不是整个文档,只是一堆内容,通常没有anchor或图像)。我可以将几个正则表达式放在一起,让我达到80%,但我认为可能有一些现有的解决方案更智能。 最佳答案 首先,不要尝试为此使用正则表达式。很有可能你会想出一个脆弱/脆弱的解决方案,它会随着HTML的变化而崩溃,或者很难管理和维护。您可以使用Nokogiri快速解析HTML并提取文本:require'nokogiri'h

  2. ruby - 解析 RDFa、微数据等的最佳方式是什么,使用统一的模式/词汇(例如 schema.org)存储和显示信息 - 2

    我主要使用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

  3. ruby - Ruby 有 `Pair` 数据类型吗? - 2

    有时我需要处理键/值数据。我不喜欢使用数组,因为它们在大小上没有限制(很容易不小心添加超过2个项目,而且您最终需要稍后验证大小)。此外,0和1的索引变成了魔数(MagicNumber),并且在传达含义方面做得很差(“当我说0时,我的意思是head...”)。散列也不合适,因为可能会不小心添加额外的条目。我写了下面的类来解决这个问题:classPairattr_accessor:head,:taildefinitialize(h,t)@head,@tail=h,tendend它工作得很好并且解决了问题,但我很想知道:Ruby标准库是否已经带有这样一个类? 最佳

  4. ruby - 是否有用于序列化和反序列化各种格式的对象层次结构的模式? - 2

    给定一个复杂的对象层次结构,幸运的是它不包含循环引用,我如何实现支持各种格式的序列化?我不是来讨论实际实现的。相反,我正在寻找可能会派上用场的设计模式提示。更准确地说:我正在使用Ruby,我想解析XML和JSON数据以构建复杂的对象层次结构。此外,应该可以将该层次结构序列化为JSON、XML和可能的HTML。我可以为此使用Builder模式吗?在任何提到的情况下,我都有某种结构化数据-无论是在内存中还是文本中-我想用它来构建其他东西。我认为将序列化逻辑与实际业务逻辑分开会很好,这样我以后就可以轻松支持多种XML格式。 最佳答案 我最

  5. ruby - 我如何添加二进制数据来遏制 POST - 2

    我正在尝试使用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_

  6. 世界前沿3D开发引擎HOOPS全面讲解——集3D数据读取、3D图形渲染、3D数据发布于一体的全新3D应用开发工具 - 2

    无论您是想搭建桌面端、WEB端或者移动端APP应用,HOOPSPlatform组件都可以为您提供弹性的3D集成架构,同时,由工业领域3D技术专家组成的HOOPS技术团队也能为您提供技术支持服务。如果您的客户期望有一种在多个平台(桌面/WEB/APP,而且某些客户端是“瘦”客户端)快速、方便地将数据接入到3D应用系统的解决方案,并且当访问数据时,在各个平台上的性能和用户体验保持一致,HOOPSPlatform将帮助您完成。利用HOOPSPlatform,您可以开发在任何环境下的3D基础应用架构。HOOPSPlatform可以帮您打造3D创新型产品,HOOPSSDK包含的技术有:快速且准确的CAD

  7. FOHEART H1数据手套驱动Optitrack光学动捕双手运动(Unity3D) - 2

    本教程将在Unity3D中混合Optitrack与数据手套的数据流,在人体运动的基础上,添加双手手指部分的运动。双手手背的角度仍由Optitrack提供,数据手套提供双手手指的角度。 01  客户端软件分别安装MotiveBody与MotionVenus并校准人体与数据手套。MotiveBodyMotionVenus数据手套使用、校准流程参照:https://gitee.com/foheart_1/foheart-h1-data-summary.git02  数据转发打开MotiveBody软件的Streaming,开始向Unity3D广播数据;MotionVenus中设置->选项选择Unit

  8. 使用canal同步MySQL数据到ES - 2

    文章目录一、概述简介原理模块二、配置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

  9. ruby-on-rails - 创建 ruby​​ 数据库时惰性符号绑定(bind)失败 - 2

    我正在尝试在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

  10. STM32读取串口传感器数据(颗粒物传感器,主动上传) - 2

    文章目录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.串口通信(个人理解)我就从串口采集传感器数据这个过程说一下我自己的理解,

随机推荐