[TOC]

tree和graph不再是线性的数据结构

貌似课件里默认root是level0 同理,树的的height也会那个一点

Binary Tree

显而易见

Full Binary Tree(满二叉树)

height = h 的树刚好有 2^h+1^ - 1 elements

Complete Binary Tree(完全二叉树)

一个最后一层可以不被填满,但是元素一定都从左向右依次排好的,其余层和full binary tree一样的树

也就是说它的height如果是h,那它就会2^h+1^-i(1<=i<=k)个节点

节点编号

我们需要给每个节点编个号,如何去映射呢?

if 0 <= i <= n - 1

i = 0 => root

i > 0 => i的parent在Math.floor((i - 1)/2)

2 * i + 1 => i 的 left child

2 * i+ 2 => i 的 right child

Representation

Array (Formula-Based Representation)

Linked(L-R linked storage)

Class node{
    ....Leftchild
    .....data
    .....Rightchild
    ................
}

Cursor

Common binary tree operations

其他不是特别复杂,主要在于遍历顺序

preorder inorder postorder

 /**
     * 使用递归方法,先序遍历二叉树
     * @param root  当前树
     * @return  遍历后的序列
     * @Example
     *      BinTree.PreOrderTraversal(root)
     */
    public static StringBuilder PreOrderTraversal(BinTree root) {
        // please enter your code here...
        StringBuilder sb = new StringBuilder();
        sb.append(root.element);
        if(root.left != null){
            sb.append(PreOrderTraversal(root.left));
        }
        if(root.right != null){
            sb.append(PreOrderTraversal(root.right));
        }
        return sb;
    }

    /**
     * 使用递归方法,中序遍历二叉树
     * @param root  当前树
     * @return  遍历后的序列
     * @Example
     *      BinTree.InOrderTraversal(root)
     */
    public static StringBuilder InOrderTraversal(BinTree root) {
        // please enter your code here...
        StringBuilder sb = new StringBuilder();
        if(root.left != null){
            sb.append(InOrderTraversal(root.left));
        }
        sb.append(root.element);
        if(root.right != null){
            sb.append(InOrderTraversal(root.right));
        }
        return sb;
    }

    /**
     * 使用递归方法,后序遍历二叉树
     * @param root  当前树
     * @return  遍历后的序列
     * @Example
     *      BinTree.PostOrderTraversal(root)
     */
    public static StringBuilder PostOrderTraversal(BinTree root) {
        // please enter your code here...
        StringBuilder sb = new StringBuilder();
        if(root.left != null){
            sb.append(PostOrderTraversal(root.left));
        }
        if(root.right != null){
            sb.append(PostOrderTraversal(root.right));
        }
        sb.append(root.element);
        return sb;
    }


//上面三个还可以用非递归实现 仅用inorder举个例子吧
	public staticStringBuilder InOrder(BinaryTree root){
        stack[] a;
        StringBuilder sb = new Stringbuilder();
        Binarynode p = root;
        while(1){
            while(p != null){
                a.push(p);
                p = p.left;
            }
            if(!a.IsEmpty()){
                p = a.pop();
                sb.append(p.element);
                p = p.right;
            }
            else{
                return sb;
            }
        }
    }
    public static void preorderTraversal(TreeNode root) {
        if (root == null) return;

        Stack<TreeNode> stack = new Stack<>();
        stack.push(root);

        while (!stack.isEmpty()) {
            TreeNode current = stack.pop();
            System.out.print(current.val + " ");

            // 先右后左,保证左子树先处理
            if (current.right != null) {
                stack.push(current.right);
            }
            if (current.left != null) {
                stack.push(current.left);
            }
        }
    }
	Class StkNode{
        BinaryNode ptr;
        int tag;
    }
    public static StringBuilder Postorder(BinaryTree root){
        Stack<StkNode> s = new stack<StkNode>();
        StringBuilder sb = new StringBuilder();
        Binarynode p = root;
        StkNode Cnode;
        while(1){
            while(p != null){
                Cnode.ptr = p;
                Cnode,tag = 0;
                s.push(Cnode);
                p = p,left;
            }
            Cnode = s.pop();
            p = Cnode.ptr;
            
            //下面处理从右子树回来
            while(Cnode.tag == 1){
                sb.append(p.element);
                if(!s.IsEmpty()){
                    Cnode = s.pop();
                    p = Cnode.ptr;
                }
                else{
                    return;
                }
            }
            
            //下面处理从左子树回来
            Cnode.tag = 1;
            s.push(Cnode);
            p = p.right;
        }
    }//太复杂了    其实是这么做的,先把最左边的压进栈,然后一通操作下来你会发现,虽然中间节点会比右节点先tag变成1,但是他会在栈里面比右节点排的低
       

    public static StringBuilder LevelOrder(BinTree root){
        StringBuilder sb = new StringBuilder();
        Queue[] a...........;
        while(root){
            vistit(root);
            if(t->left){
                a.add(root->left);
            }
            if(t->right){
                a.add(root->right);
            }
            try{
                (a.Delete(root));//删除队列第一个节点,并赋值给root
            }
            catch(OutOfBounds){
                return;
            }
        }
    }

递归简单,非递归太难了

Create BinaryTree

/**
     * 使用递归方法,根据先序遍历和中序遍历结果,创建一棵二叉树
     * @param pres 先序遍历字符数组
     * @param ins 中序遍历字符数组
     * @Example
     *      BinTree root = BinTree.CreateBT("ABDCEGFHI", "DBAEGCHFI");
     */
    public static BinTree CreateBT(char[] pres, char[] ins){
        // please enter your code here...
        if(pres.length == 0 || ins.length == 0){
            return null;
        }
        BinTree root = new BinTree(pres[0], null, null);
        int i = 0;
        for(; i < ins.length; i++){
            if(ins[i] == pres[0]){
                break;
            }
        }
        char[] leftPres = new char[i];
        char[] leftIns = new char[i];
        char[] rightPres = new char[pres.length - i - 1];
        char[] rightIns = new char[pres.length - i - 1];
        for(int j = 0; j < i; j++){
            leftPres[j] = pres[j + 1];
            leftIns[j] = ins[j];
        }
        for(int j = 0; j < pres.length - i - 1; j++){
            rightPres[j] = pres[j + i + 1];
            rightIns[j] = ins[j + i + 1];
        }
        root.left = CreateBT(leftPres, leftIns);
        root.right = CreateBT(rightPres, rightIns);
        return root;
    }

关键在于找到root,左分支,右分支,找到这些就可以递归求解了.

Application

Binary-Tree Representation of a Tree

森林->二叉树

深度遍历->先序\后序\中序

广度遍历->分层访问

Thread Tree

关键在于这里面的前驱和后继是哪个 这得根据前/中/后序线索树来判断

构造中序线索树如下

这其实挺好理解的,用pre记录前驱,这样就可以在每次循环记录pre的后继和p的前驱

Huffman Tree(霍尔曼树)

Huffman算法

其实有种类似并查集的感觉,每次取两个最小的union,意味着她两是兄弟,他们的parent的他们的合

Binary Search Tree

进阶定义Indexed binary search tree:比正常的二叉搜索树多一个leftSize用来标记该节点的 左子树元素数量+1

Find Operation

private BinaryNode find( Comparable x, BinaryNode t ){
    if( t = = null )
        return null;
    if( x. compareTo( t.element ) < 0 )
        return find( x, t.left );
    else if( x.compareTo( t.element ) > 0 )
        return find( x, t.right );
    else
        return t; //Match
}

这个很简单吧

Insertion Operation

public BinaryNode insert(Comparable x, BinaryNode t){
    if(t == null){
        t = new BinaryNode(x, null, null);
    }
    if(x.compareTo(t.element) < 0){
        t.left = insert(x, t.left);
    }
    else if(x.compareTo(t.element) > 0){
        t,right = insert(x, t.right);
    }
    else{
        //do nothing 我要插入的已经在里面了
    }
    return t;
}

这个也不复杂吧

Deletion Operation

public BinaryNode delete(Comparable x, Binary t){
    if(t == null){
        return t;
    }
    if(x.compareTo(t.element) > 0){
        t.right = delete(x, t.right);
    }
    else if(x.compareTo(t.element) < 0){
        t.left = delete(x, t.left);
    }
    else{
        if(t.left != null && t.right != null){
            t.element = t,finMin(t.right).element;
            delete(t.findMin(t.right).element, t.right);
        }
        else{
            t = (t.left != null)? t.left: t.right;
        }
    }
    return t;
}

如果左右都有节点就替补上左最大or右最小

稍微思考一下

AVL Tree(自平衡的二叉搜索树)

Insertion Operation

屏幕截图 2024-12-22 210149

屏幕截图 2024-12-22 210158

关键是找到最小不平衡子树

private AVLNode insert(Comparable x, AVLNode t){
    if(t == null){
        t = new AVLNode(x, null, null);
    }
    else if(x.compareTo(t.element) < 0){
        t.left = insert(x,t.left);//由于递归调用,所以其实只有最小不平衡子树的节点会进入到底下那个if里面
        if(height(t.left) - height(t.right) == 2){
            if(x.compareTo(t.left.element) < 0){
                t = rotateWithLeftChild(t);
            }
            else{
                t = doubleWithLeftChild(t);
            }
        }
        else if(x.compareTo(t.left.element) > 0){
            .............
        }
    }
    else{
        ......
    }
    t.height = max(height(t.left), height(t.right)) + 1;
    return t;
}

private static AVLNode rotateWithLeftChild(AVLNode k2){
    AVLNode k1 = k2.left;
    //k1就是即将替代k2位置的新的"根"
    //我们需要做几个调整 k2的右节点应该变成k1,而k2原来的右节点应该变成k1的左节点
    k2.left = k1.right;
    k1.right = k2;
    
    //下面更新高度
    k2.height = max(height(k2.left),height(k2.right)) + 1;
    k1.height = max(height(k1.left), height(k1.right)) + 1;
    return k1;
}//左单旋

private static AVLNode doubleWithLeftChild(AVLNode k3){
    //双旋主要就是需要先进行一次单旋转,使得原来不在一条直接上的节点变成一条直线
    k3.left = rotateWithRightChild(k3.left);
    k3 = rotateWithLeftChild(k3);
    return k3;
}
....................................................

处理好单旋和双旋的判断是关键

AVL树的插入:

  1. 首先要正确地插入
  2. 找到有可能发生的最小不平衡子树
  3. 判别插入在不平衡子树的外侧还是内侧
  4. 根据3的判别结果,再进行单旋还是双旋

Deletion Operation

ppt没给代码说明没那么重要,其实就是和insert差不多,把insert换成delete就好,也就是一个调整的问题

m-way search tree

注意m-way对应的是elements最多m-1个

Find Operation

find其实倒是没那么难,反正就是一顿比较递归下去

Insertion Operation

insert或许也没那么多讲究

Deletion Operation

像这种删了就删了,无所谓,它底下没子树

像这种删除就很有说法了,一般是左边的最大or右边最小来替代它

其他

值得注意的是levelh没有element

B-Tree(未完全掌握)

底下那些没有元素的节点必须在同一个level

root必须至少两个元素

Math.ceiling(m / 2) <= child数量 <= m

Insertion Operation

两种case

case1确实舒服

屏幕截图 2024-12-23 193355

case2较为复杂,有个分裂的过程,所谓分裂就是把一个超出数量的节点分成两半,然后把分裂出来的三个节点的根并到parent里面,然后看看parent是否需要进一步分裂,以次类推

Deletion Operation

第一个情况没啥好说的,直接删

这个是删了之后,节点数量不够,所以得找兄弟借,借的时候就有一个相当于单左旋/单右旋的过程.

兄弟借不了,那只能和兄弟合并了.

上左最大或者右最小 再删除对应节点

要是邻居也借不了该怎么办呢,这时候就需要和parent去合并了,然后再做调整

森林先根和后根

先根遍历若树非空先访问根结点再依次对每棵子树进行先根遍历
void PreOrder(TreeNode *R){
	if(R!=NULL){
		visit(R);	//访问根节点
		while(R还有下一个子树T)
			PreOrder(T);	//递归,先根遍历下一个子树
	}
}

后根遍历若树非空先依次对每棵子树进行后根遍历最后再访问根结点
void PostOrder(TreeNode *R){
	if(R!=NULL){
		while(R还有下一个子树T)
			PostOrder(T);	//递归,后根遍历下一个子树
		visit(R);	//访问根节点
	}
}

注意,树的先根后根和森林的不是一个东西