草庐IT

数据结构与算法(四):树结构

鹤冲天Pro 2023-09-27 原文


前面讲到的 顺序表栈和队列都是一对一的线性结构,这节讲一对多的线性结构——树。「一对多」就是指一个元素只能有一个前驱,但可以有多个后继。

一、基本概念

树(tree)是n(n>=0)个结点的有穷集。n=0时称为空树。在任意一个非空树中:(1)每个元素称为结点(node);(2)仅有一个特定的结点被称为根结点或树根(root)。(3)当n>1时,其余结点可分为m(m≥0)个互不相交的集合T1,T2,……Tm,其中每一个集合Ti(1<=i<=m)本身也是一棵树,被称作根的子树(subtree)。

注意:

  • n>0时,根节点是唯一的。
  • m>0时,子树的个数没有限制,但它们一定是互不相交的。

结点拥有的子树数被称为结点的度(Degree)。度为0的结点称为叶节点(Leaf)或终端结点,度不为0的结点称为分支结点。除根结点外,分支结点也被称为内部结点。结点的子树的根称为该结点的孩子(Child),该结点称为孩子的双亲或父结点。同一个双亲的孩子之间互称为兄弟。树的度是树中各个结点度的最大值。

结点的层次(Level)从根开始定义起,根为第一层,根的孩子为第二层。双亲在同一层的结点互为堂兄弟。树中结点的最大层次称为树的深度(Depth)或高度。如果将树中结点的各个子树看成从左到右是有次序的,不能互换的,则称该树为有序树,否则称为无序树。森林是m(m>=0)棵互不相交的树的集合。

树的定义:

二、树的存储结构

由于树中每个结点的孩子可以有多个,所以简单的顺序存储结构无法满足树的实现要求。下面介绍三种常用的表示树的方法:双亲表示法、孩子表示法和孩子兄弟表示法。

1、双亲表示法

由于树中每个结点都仅有一个双亲结点(根节点没有),我们可以使用指向双亲结点的指针来表示树中结点的关系。这种表示法有点类似于前面介绍的静态链表的表示方法。具体做法是以一组连续空间存储树的结点,同时在每个结点中,设一个「游标」指向其双亲结点在数组中的位置。代码如下:

public class PTree<E> {
    private static final int DEFAULT_CAPACITY = 100;
    private int size;
    private Node[] nodes;

    private class Node() {
        E data;
        int parent;

        Node(E data, int parent) {
            this.data = data;
            this.parent = parent;
        }
    }

    public PTree() {
        nodes = new PTree.Node[DEFAULT_CAPACITY];
    }
}

由于根结点没有双亲结点,我们约定根节点的parent域值为-1。树的双亲表示法如下所示:

这样的存储结构,我们可以根据结点的parent域在O(1)的时间找到其双亲结点,但是只能通过遍历整棵树才能找到它的孩子结点。一种解决办法是在结点结构中增加其孩子结点的域,但若结点的孩子结点很多,结点结构将会变的很复杂。

2、孩子表示法

由于树中每个结点可能有多个孩子,可以考虑用多重链表,即每个结点有多个指针域,每个指针指向一个孩子结点,我们把这种方法叫多重链表表示法。它有两种设计方案:

方案一:指针域的个数等于树的度。其结点结构可以表示为:

class Node() {
    E data;
    Node child1;
    Node child2;
    ...
    Node childn;
}

对于上一节中的树,树的度为3,其实现为:

显然,当树中各结点的度相差很大时,这种方法对空间有很大的浪费。

方案二,每个结点指针域的个数等于该结点的度,取一个位置来存储结点指针的个数。其结点结构可以表示为:

class Node() {
    E data;
    int degree;
    Node[] nodes;
    Node(int degree) {
        this.degree = degree;
        nodes = new Node[degree];
    }
}

对于上一节中的树,这种方法的实现为:

这种方法克服了浪费空间的缺点,但由于各结点结构不同,在运算上会带来时间上的损耗。

为了减少空指针的浪费,同时又使结点相同。我们可以将顺序存储结构和链式存储结构相结合。具体做法是:把每个结点的孩子结点以单链表的形式链接起来,若是叶子结点则此单链表为空。然后将所有链表存放进一个一维数组中。这种表示方法被称为孩子表示法。其结构为:

代码表示:

public class CTree<E> {
    private static final int DEFAULT_CAPACITY = 100;
    private int size;
    private Node[] nodes;

    private class Node() {
        E data;
        ChildNode firstChild;
    }
    
    //链表结点
    private class ChildNode() {
        int cur; //存放结点在nodes数组中的下标
        ChildNode next;
    }

    public CTree() {
        nodes = new CTree.Node[DEFAULT_CAPACITY];
    }
}

这种结构对于查找某个结点的孩子结点比较容易,但若想要查找它的双亲或兄弟,则需要遍历整棵树,比较麻烦。可以将双亲表示法和孩子表示法相结合,这种方法被称为双亲孩子表示法。其结构如下:

其代码和孩子表示法的基本相同,只需在Node结点中增加parent域即可。

3、孩子兄弟表示法

任意一棵树,它的结点的第一个孩子如果存在则是唯一的,它的右兄弟如果存在也是唯一的。因此,我们可以使用两个分别指向该结点的第一个孩子和右兄弟的指针来表示一颗树。其结点结构为:

class Node() {
    E data;
    Node firstChild;
    Node rightSib;
}

其结构如下:

这个方法,可以方便的查找到某个结点的孩子,只需先通过firstChild找到它的第一个孩子,然后通过rightSib找到它的第二个孩子,接着一直下去,直到找到想要的孩子。若要查找某个结点的双亲和左兄弟,使用这个方法则比较麻烦。

这个方法最大的好处是将一颗复杂的树变成了一颗二叉树。这样就可以使用二叉树的一些特性和算法了。

三、二叉树

1、基本概念

二叉树(Binary Tree)是每个节点最多有两个子树的树结构。通常子树被称作“左子树”(left subtree)和“右子树”(right subtree)。

二叉树的特点:

二叉树不存在度大于2的结点。
二叉树的子树有左右之分,次序不能颠倒。
如下图中,树1和树2是同一棵树,但它们是不同的二叉树。

1)、斜树

所有的结点都只有左子树的二叉树叫左斜树。所有的结点都只有右子树的二叉树叫右斜树。这两者统称为斜树。

斜树每一层只有一个结点,结点的个数与二叉树的深度相同。其实斜树就是线性表结构。

2)、满二叉树

在一棵二叉树中,如果所有分支结点都存在左子树和右子树,并且所有叶子都在同一层上,这样的二叉树称为满二叉树。

满二叉树具有如下特点:

  • 叶子只能出现在最下一层
  • 非叶子结点的度一定是2
  • 同样深度的二叉树中,满二叉树的结点个数最多,叶子数最多。

3)、完全二叉树

若设二叉树的高度为h,除第 h 层外,其它各层 (1~h-1) 的结点数都达到最大个数,第h层有叶子结点,并且叶子结点都是从左到右依次排布,这就是完全二叉树。

完全二叉树的特点:

  • 叶子结点只能出现在最下两层
  • 最下层叶子在左部并且连续
  • 同样结点数的二叉树,完全二叉树的深度最小

4)、平衡二叉树

平衡二叉树又被称为AVL树(区别于AVL算法),它是一棵二叉排序树,且具有以下性质:它是一棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树

2、二叉树的性质

在二叉树的第i层上至多有2^i-1个结点(i>=1)。

深度为k的二叉树至多有2^k-1个结点(k>=1)。

对任何一棵二叉树T,如果其终端结点个数为n0,度为2的结点数为n2,则n0 = n2 + 1。

具有n个结点的完全二叉树的深度为「log2n」+ 1(「x」表示不大于x的最大整数)。

如果对一棵有n个结点的完全二叉树的结点按层序编号(从第一层到第「log2n」+ 1层,每层从左到右),对任一结点i(1≤i≤n)有:

若i=1,则结点i是二叉树的根,无双亲;如i>1,则其双亲是结点「i/2」。
如2i>n,则结点i无左孩子(结点i为叶子结点);否则其左孩子是结点2i。
若2i+1>n,则结点i无右孩子;否则其右孩子是结点2i+1。

3、二叉树的实现

二叉树是一种特殊的树,它的存储结构相对于前面谈到的一般树的存储结构要简单一些。

1)、顺序存储

二叉树的顺序存储结构就是用一维数组来存储二叉树中的结点。不使用数组的第一个位置。结点的存储位置反映了它们之间的逻辑关系:位置k的结点的双亲结点的位置为「k/2」,它的两个孩子结点的位置分别为2k和2k+1。

代码实现:

public class ArrayBinaryTree<E> {

    private static final int DEFAULT_DEPTH = 5;

    private int size = 0;
    private E[] datas;

    ArrayBinaryTree() {
        this(DEFAULT_DEPTH);
    }

    @SuppressWarnings("unchecked")
    ArrayBinaryTree(int depth) {
        datas = (E[]) new Object[(int)Math.pow(2, depth)];
    }

    public boolean isEmpty() { return size == 0; }

    public int size(){ return size; }

    public E getRoot() { return datas[1]; }

    // 返回指定节点的父节点    
    public E getParent(int index) {  
        checkIndex(index);  
        if (index == 1) {    
            throw new RuntimeException("根节点不存在父节点!");    
        }    
        return datas[index/2];    
    }    
        
    //获取右子节点    
    public E getRight(int index){    
        checkIndex(index*2 + 1);  
        return datas[index * 2 + 1];    
    }    
        
    //获取左子节点    
    public E getLeft(int index){    
        checkIndex(index*2);    
        return datas[index * 2];    
    }     
        
    //返回指定数据的位置    
    public int indexOf(E data){    
       if(data==null){   
         throw new NullPointerException();  
       } else {  
           for(int i=0;i<datas.length;i++) {  
               if(data.equals(datas[i])) {  
                   return i;  
               }  
           }  
       }  
        return -1;    
    }

    //顺序添加元素
    public void add(E element) {
        checkIndex(size + 1);
        datas[size + 1] = element;
        size++;
    }

    //在指定位置添加元素
    public void add(E element, int parent, boolean isLeft) {

        if(datas[parent] == null) {  
            throw new RuntimeException("index["+parent+"] is not Exist!");  
        }  
        if(element == null) {  
            throw new NullPointerException();  
        } 

        if(isLeft) {
            checkIndex(2*parent);
            if(datas[parent*2] != null) {  
                throw new RuntimeException("index["+parent*2+"] is  Exist!");  
            }
            datas[2*parent] = element;
        }else {
            checkIndex(2*parent + 1);
            if(datas[(parent+1)*2]!=null) {  
                throw new RuntimeException("index["+ parent*2+1 +"] is  Exist!");  
            } 
            datas[2*parent + 1] = element;
        }
        size++;
    }

    //检查下标是否越界
    private void checkIndex(int index) {  
        if(index <= 0 || index >= datas.length) {  
            throw new IndexOutOfBoundsException();  
        }  
    } 
    public static void main(String[] args) {
        char[] data = {'A','B','C','D','E','F','G','H','I','J'};
        ArrayBinaryTree<Character> abt = new ArrayBinaryTree<>();
        for(int i=0; i<data.length; i++) {
            abt.add(data[i]);
        }
        System.out.print(abt.getParent(abt.indexOf('J')));
    }
}

一棵深度为k的右斜树,只有k个结点,但却需要分配2k-1个顺序存储空间。所以顺序存储结构一般只用于完全二叉树。

2)、链式存储

二叉树每个结点最多有两个孩子,所以为它设计一个数据域和两个指针域即可。我们称这样的链表为二叉链表。其结构如下图:

代码如下:

import java.util.*;
public class LinkedBinaryTree<E> {    
    private List<Node> nodeList = null;  
   
    private class Node {  
        Node leftChild;  
        Node rightChild;  
        E data;  
  
        Node(E data) {  
            this.data = data;  
        }  
    }  
  
    public Node getRoot() {
        return nodeList.get(0);
    }

    public void createBinTree(E[] array) {  
        nodeList = new LinkedList<Node>();  

        for (int i = 0; i < array.length; i++) {  
            nodeList.add(new Node(array[i]));  
        }  
        // 对前lasti-1个父节点按照父节点与孩子节点的数字关系建立二叉树  
        for (int i = 0; i < array.length / 2 - 1; i++) {   
            nodeList.get(i).leftChild = nodeList.get(i * 2 + 1);    
            nodeList.get(i).rightChild = nodeList.get(i * 2 + 2);  
        }  
        // 最后一个父节点:因为最后一个父节点可能没有右孩子,所以单独拿出来处理  
        int lastParent = array.length / 2 - 1;  
        nodeList.get(lastParent).leftChild = nodeList  .get(lastParent * 2 + 1);  

        // 右孩子,如果数组的长度为奇数才建立右孩子  
        if (array.length % 2 == 1) {  
            nodeList.get(lastParent).rightChild = nodeList.get(lastParent * 2 + 2);  
        }  
    } 

    public static void main(String[] args) {
        Character[] data = {'A','B','C','D','E','F','G','H','I','J'};
        LinkedBinaryTree<Character> ldt = new LinkedBinaryTree<>();
        ldt.createBinTree(data);
    }
}

4、二叉树的遍历

二叉树的遍历(traversing binary tree)是指从根结点出发,按照某种次序依次访问二叉树中所有结点,使得每个结点被访问一次且仅被访问一次。

二叉树的遍历主要有四种:前序遍历、中序遍历、后序遍历和层序遍历。

1)、前序遍历

先访问根结点,然后遍历左子树,最后遍历右子树。

代码:

//顺序存储
public void preOrderTraverse(int index) {  
    if (datas[index] == null)  
        return;  
    System.out.print(datas[index] + " ");  
    preOrderTraverse(index*2);  
    preOrderTraverse(index*2+1);  
} 

//链式存储
 public void preOrderTraverse(Node node) {  
    if (node == null)  
        return;  
    System.out.print(node.data + " ");  
    preOrderTraverse(node.leftChild);  
    preOrderTraverse(node.rightChild);  
} 

2)、中序遍历

先遍历左子树,然后遍历根结点,最后遍历右子树。

//链式存储
 public void inOrderTraverse(Node node) {  
    if (node == null)  
        return;  
    inOrderTraverse(node.leftChild);
    System.out.print(node.data + " ");  
    inOrderTraverse(node.rightChild);  
} 

3)、后序遍历

先遍历左子树,然后遍历右子树,最后遍历根结点。

//链式存储
 public void postOrderTraverse(Node node) {  
    if (node == null)  
        return;  
    postOrderTraverse(node.leftChild);
    postOrderTraverse(node.rightChild);  
    System.out.print(node.data + " ");  
} 

4)、层序遍历

从上到下逐层遍历,在同一层中,按从左到右的顺序遍历。如上一节中的二叉树层序遍历的结果为ABCDEFGHIJ。

注意:

  • 已知前序遍历和中序遍历,可以唯一确定一棵二叉树。
  • 已知后序遍历和中序遍历,可以唯一确定一棵二叉树。
  • 已知前序遍历和后序遍历,不能确定一棵二叉树。

如前序遍历是ABC,后序遍历是CBA的二叉树有:

四、线索二叉树

对于n个结点的二叉树,在二叉链存储结构中有n+1个空指针域,利用这些空指针域存放在某种遍历次序下该结点的前驱结点和后继结点的指针,这些指针被称为线索,加上线索的二叉树称为线索二叉树。

结点结构如下:

其中:

lTag为0时,lChild指向该结点的左孩子,为1时指向该结点的前驱
rTag为0时,rChild指向该结点的右孩子,为1时指向该结点的后继。
线索二叉树的结构图为:图中蓝色虚线为前驱,红色虚线为后继

代码如下:

public class ThreadedBinaryTree<E> {
    private TBTreeNode root;
    private int size;          // 大小  
    private TBTreeNode pre;   // 线索化的时候保存前驱  

    class TBTreeNode {
        E element;
        boolean lTag; //false表示指向孩子结点,true表示指向前驱或后继的线索
        boolean rTag;
        TBTreeNode lChild;
        TBTreeNode rChild;

        public TBTreeNode(E element) {
            this.element = element;
        }
    }
    
    public ThreadedBinaryTree(E[] data) {
        this.pre = null;  
        this.size = data.length;  
        this.root = createTBTree(data, 1);
    }

    //构建二叉树
    public TBTreeNode createTBTree(E[] data, int index) {  
        if (index > data.length){  
            return null;  
        }  
        TBTreeNode node = new TBTreeNode(data[index - 1]);  
        TBTreeNode left = createTBTree(data, 2 * index);  
        TBTreeNode right = createTBTree(data, 2 * index + 1);  
        node.lChild = left;  
        node.rChild = right;  
        return node;  
    } 

    /** 
     * 将二叉树线索化   
     */  
    public void inThreading(TBTreeNode node) {  
        if (node != null) {  
            inThreading(node.lChild);     // 线索化左孩子 

            if (node.lChild == null) {  // 左孩子为空  
                node.lTag = true;    // 将左孩子设置为线索  
                node.lChild = pre;  
            }  
            if (pre != null && pre.rChild == null) {  // 右孩子为空  
                pre.rTag = true;  
                pre.rChild = node;  
            }  
            pre = node;  

            inThreading(node.rChild);  // 线索化右孩子  
        }  
    }  
  
    /** 
     * 中序遍历线索二叉树 
     */  
    public void inOrderTraverseWithThread(TBTreeNode node) {

        while(node != null) {
            while(!node.lTag) { //找到中序遍历的第一个结点
                node = node.lChild;
            }
            System.out.print(node.element + " "); 
            while(node.rTag && node.rChild != null) { //若rTag为true,则打印后继结点
                node = node.rChild;
                System.out.print(node.element + " "); 
            }
            node = node.rChild;
        }
    }  
  
    /** 
     * 中序遍历,线索化后不能使用
     */  
    public void inOrderTraverse(TBTreeNode node) {  
        if(node == null)
            return;
        inOrderTraverse(node.lChild);  
        System.out.print(node.element + " ");  
        inOrderTraverse(node.rChild);  
    } 

    public TBTreeNode getRoot() { return root;}

    public static void main(String[] args) {
        Character[] data = {'A','B','C','D','E','F','G','H','I','J'};
        ThreadedBinaryTree<Character> tbt = new ThreadedBinaryTree<>(data);
        tbt.inOrderTraverse(tbt.getRoot());
        System.out.println();
        tbt.inThreading(tbt.getRoot());
        tbt.inOrderTraverseWithThread(tbt.getRoot());
    }
}

线索二叉树充分利用了空指针域的空间,提高了遍历二叉树的效率。

五、总结

到此,树的知识基本总结完了,这一节开头讲了树的一些基本概念,重点介绍了树的三种不同的存储方法:双亲表示法、孩子表示法和孩子兄弟表示法。由兄弟表示法引入了一种特殊的树:二叉树,并详细介绍了它的性质、不同结构的实现方法和遍历方法。最后介绍了线索二叉树的实现方法。

有关数据结构与算法(四):树结构的更多相关文章

  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. 区块链之加解密算法&数字证书 - 2

    目录一.加解密算法数字签名对称加密DES(DataEncryptionStandard)3DES(TripleDES)AES(AdvancedEncryptionStandard)RSA加密法DSA(DigitalSignatureAlgorithm)ECC(EllipticCurvesCryptography)非对称加密签名与加密过程非对称加密的应用对称加密与非对称加密的结合二.数字证书图解一.加解密算法加密简单而言就是通过一种算法将明文信息转换成密文信息,信息的的接收方能够通过密钥对密文信息进行解密获得明文信息的过程。根据加解密的密钥是否相同,算法可以分为对称加密、非对称加密、对称加密和非

  8. 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

  9. 使用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

  10. 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

随机推荐