数据结构--树结构的相关总结

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/weixin_40042143/article/details/85036893

 

关于树的几个概念

  • 树:非线性结构,数据元素之间的逻辑关系时前驱唯一而后续不唯一,即数据元素之间是一对多关系。
  • 二叉树:每个结点最多有两个子树;左右子树是有序的,次序不能颠倒;即使树中只有一个结点,也要区分左右子树。
  • 满二叉树:所有分支结点都存在左右子树,并且所有叶子都在同一层上。
  • 完全二叉树:对一棵具有n个结点的二叉树按层序编号,如果编号为i的结点与同样深度的满二叉树中编号为i的结点再二叉树中位置完全相同。---叶子结点只能出现在最下两层;最下层的叶子一定集中在左部连续位置;倒数第二层若有叶子,一定在右部连续位置;

二叉树的存储结构

  1. 顺序存储结构:用一维数组存储二叉树的结点,并且结点的存储位置(数组下标)要能体现结点的逻辑关系,如双亲与孩子关系,左右兄弟等------堆排序
  2. 二叉链表存储结构

 class Node {
        private Object data;//数据域
        private Node lchild;//左指针
        private Node rchild;//右指针


    }

遍历二叉树

前序遍历规则:

           若二叉树为空,则返回空操作,否则先访问根结点,然后前序遍历左子树,再前序遍历右子树

public void preOrder(Node tree) {
        if (tree == null) {
            return;
        }
        System.out.println(tree.data);
        preOrder(tree.lchild);//先序遍历左子树
        preOrder(tree.rchild);//最后遍历右子树

    }

中序遍历规则:

         若二叉树为空,则返回空操作,否则先中序遍历左子树,然后根结点,最后中序遍历右子树

 public void InOrder(Node tree) {
        if (tree == null) {
            return;
        }
        InOrder(tree.lchild);//中序遍历左子树
        System.out.println(tree.data);
        InOrder(tree.rchild);//最后中序遍历右子树

    }

后序遍历规则:

          若二叉树为空,则返回空操作,否则先后序遍历左子树,再后序遍历右子树,最后遍历根结点

    public void PostOrder(Node tree) {
        if (tree == null) {
            return;
        }
        PostOrder(tree.lchild);
        preOrder(tree.rchild);
        System.out.println(tree.data);


    }

已知前序和中序,可以唯一确定一棵二叉树

已知后序和中序,可以唯一确定一棵二叉树


线索二叉树

二叉树的每个结点加上指向前驱和后驱的指针,结点结构如下

 class BiThrNode {
        private Object data;
        //左右指针
        private BiThrNode lchild;
        private BiThrNode rchilid;
        //左右标志
        private Tag ltag;
        private Tag rtag;

    }

    enum Tag {Link, Thread}
    /**
     * Link==0,指向左右孩子指针
     * Thread==1指向前驱或后继线索
     * */

线索二叉树遍历---双向链表结构,线索化的实质:将二叉链表中的空指针改为指向前驱或后驱的线索

 BiThrNode pre;//一直指向刚刚访问过的结点

    //中序遍历进行中序线索化
    public void InThreading(BiThrNode p) {
        if (p != null) {
            InThreading(p.lchild);//递归左子树线索化
            if (p.lchild == null) {//没有左孩子
                p.ltag = Tag.T;//lchild变为前驱或后驱指针
                p.lchild = pre;//左孩子指针指向前驱
            }
            if (pre.rchilid == null)//前驱没有右指针
            {
                pre.rtag = Tag.T;//pre的rchild变为前驱或后驱指针
                pre.rchilid = p;//pre右孩子指针指向后继

            }
            pre = p;
            InThreading(p.rchilid);//递归中序遍历右子树
        }

    }

在二叉线索链表上添加一个头结点,如图

头结点的lchild指针指向二叉树的根结点,rchild指向中序遍历时最后一个访问的结点,令二叉树中序遍历时第一个访问的lchild指针和最后一个访问的rchlid指针都指向添加的这个头结点。 

 /**
     * T指向头结点,头结点的lchild指向二叉树根结点,头结点的rchild指向中序遍历的最后一个结点
     */
    public void InOrder_Thr(BiThrNode T) {

        BiThrNode p;
        p = T.lchild;//p指向二叉链表树的根结点
        while (p != T) {
            while (p.ltag == Tag.L)//当ltag==Tag.L时,循环遍历到中序第一个结点
            {
                p = p.lchild;
            }
            System.out.println(p.data);//H

            while (p.rtag == Tag.T && p.rchilid != T) {//没有指向头节点
                p = p.rchilid;
                System.out.println(p.data);
            }
            p = p.rchilid;//p进入右子树
        }

    }

整个操作相当于遍历了一次链表,时间复杂度O(n)


树的查找方法

二叉排序树(Binary Sort Tree),当其不为空时是具有以下的性质的二叉树:

  • 若左子树不空,则左子树上所有结点的值均小于它的根结点的值
  • 若右子树不为空,则右子树上所有结点的值均大于它的根结点的值
  • 左右子树也为二叉排序树

递归查找二叉排序树T中是否存在key

    public boolean searchBST(Node T, int key, Node f, Node p) {
        if (T != null) {
            p = f;
            return false;

        } else if (key == T.data) {//找到key
            p = T;
            return true;
        } else if (key < (Integer) T.data) {
            return searchBST(T.lchild, key, T, p);//在左子树继续找
        } else {
            return searchBST(T.rchild, key, T, p);//在右子树继续找
        }
    }

二叉排序树插入操作

当二叉排序树里面不存在关键字等于key的数据元素时,插入key

首先要先查找key---再插入

    public boolean InsertBST(Node T, int key) {
        Node p = new Node();
        Node s = new Node();
        if (!searchBST(T, key, null, p)) {
            s.data = key;
            s.lchild = s.rchild = null;
            if (p != null) {
                T = s;//s插入作为根结点
            } else if (key < (Integer) p.data) {
                p.lchild = s;//插入s 为左孩子
            } else {
                p.rchild = s;//插入s为右孩子
            }
            return true;

        } else {
            return false;
        }

    }

二叉排序树的删除操作

二叉排序树在删除过后,不能改变二叉排序树的特性。

有三种情况:

  1. 叶子结点,不影响其他结点的结构
  2. 仅有左子树或右子树的结点,删除结点后,将其左子树或右子树整个移动到删除结点的位置
  3. 左右子树都有结点,找到要删除的结点p的直接前驱或直接后继s,用s来替换结点p,然后再删除此结点

删除图中48结点

public boolean DeleteBST(Node T, int key) {
        if (T == null) {
            return false;
        } else {
            if (key == (Integer) T.data) {
                return Delete(T);
            } else if (key < (Integer) T.data) {
                return DeleteBST(T.lchild, key);
            } else {
                return DeleteBST(T.rchild, key);
            }
        }
    }

    public boolean Delete(Node p) {
        Node q = new Node();
        Node s = new Node();
        if (p.rchild == null) {
            //右子树为空,直接将其左子树接上去
            q = p;
            p = p.lchild;
        } else if (p.lchild == null) {
            //左子树为空,直接将其右子树接上去
            q = p;
            p = p.rchild;
        } else {//左右子树都不为空
            q = p;
            s = p.lchild;
            while (s.rchild != null) {
                q = s;
                s = s.rchild;//以删除的结点为根结点的子树中最大的那个值
            }
            p.data = s.data;
            if (q != p) {
                q.rchild = s.lchild;//重新街上q的右子树
            } else {
                q.lchild = s.lchild;
            }
            return true;
        }
        return false;
    }

对关键代码的分析

将要删除的p赋给变量q,p的左孩子lchild赋值给s,如下图

   q = p;
   s = p.lchild;

循环找左子树的右结点,知道右侧尽头,如图s指向的37,37之后没有右子树结点

 while (s.rchild != null) {
                q = s;
                s = s.rchild;
            }

将找到的37放到要删除的结点的位置上 

p.data = s.data;

 复杂度分析

对于平衡的二叉排序树,时间复杂度O(logn),近似于折半查找

最坏情况,时间复杂度为O(n)


平衡二叉树AVL

是一种二叉排序树,其中每个结点的左子树和右子树的高度最多相差1

BF平衡因子:左子树深度-右子树深度,BF={-1,0,1}只有这三个值

最小不平衡子树:距离插入结点最近的,且平衡隐私的绝对值大于1的结点为根的子树(逆向找回去)

如图,从插入结点37倒推回去

 平衡二叉树的构建:在二叉排序树构建的过程中,每当插入一个结点时,就先检查是否破坏了平衡性,若是,则找出最小不平衡子树,调整链接关系,使之成为新的平衡子树。

旋转操作:O(1)

左旋(逆时针):p点作为v的左子树根结点

     

右旋(顺时针):p点作为v的右子树根结点

 

结点的数据结构

   class Node{
        private Object data;
        private int bf;//平衡因子
        private Node lchild;
        private Node rchild;

    }

右旋操作

/**
     * 以p为根结点的二叉排序树右旋
     * 旋转之后p指向新的根结点:即旋转前p的左子树的根结点变成了现在p的根结点,p变成了该结点的右子树根结点
     */
    public void R_Rotate(Node p) {
        Node L;
        L = p.lchild;//旋转前L是p的左孩子
        p.lchild = L.rchild;
        L.rchild = p;
        p = L;//p指向新的根结点
    }

 左旋操作

  /**
     * 左旋操作
     */
    public void L_Rotate(Node p) {
        Node R;
        R = p.rchild;
        p.rchild = R.lchild;
        R.lchild = p;
        p = R;

    }

猜你喜欢

转载自blog.csdn.net/weixin_40042143/article/details/85036893