数据结构总结-- 树和二叉树

树的性质

1.树的结点数 = 结点度数和 + 1;(n0 + n1 + nm ….+1)
2.度为m的数第i层最多m^(i-1)次方个结点
3.高度为h的m次树,最多(m^h)/(m-1)
4.n个结点的m次树,最小高度logm(n(m-1)+1)

二叉树的定义

结点定义,根节点,左孩子,右孩子
java代码:

/**
     * 结点类
     * 
     * @author Administrator
     *
     * @param <T>
     */
    static class BTNode<T> {
        T value;
        BTNode<T> leftChild;
        BTNode<T> rightChild;

        /**
         * 结点构造方法
         * 
         * @param value
         */
        public BTNode(T value) {
            this.value = value;
            leftChild = null;
            rightChild = null;
        }

        public BTNode() {
            value = null;
            leftChild = rightChild = null;
        }

    }

二叉树类:

public class BinaryTree<T> {

    /**
     * 结点类
     * 
     * @author Administrator
     *
     * @param <T>
     */
    static class BTNode<T> {
        T value;
        BTNode<T> leftChild;
        BTNode<T> rightChild;

        /**
         * 结点构造方法
         * 
         * @param value
         */
        public BTNode(T value) {
            this.value = value;
            leftChild = null;
            rightChild = null;
        }

        public BTNode() {
            value = null;
            leftChild = rightChild = null;
        }

    }

    /**
     * 根节点
     */
    BTNode<T> root;

    /**
     * 构造方法
     */
    public BinaryTree() {
        root = new BTNode<T>(null);
    }

二叉树的性质

1.叶子节点数= 双分支结点+1,分支数 = 总结点数-1;
2.编号为i (i>=0,i<=n)的结点,左孩子为2i + 1,右孩子为2i+2,一般使用数组存储时使用。如果起始i ==1,则左孩子 2i,右孩子2i+1

二叉树的存储结构

1.顺序存储

优点:结构紧凑,没有额外的指针空间,随机存取能力强,
缺点:插入删除耗时,所需空间较大时,较难找到连续的内存空间,不利于碎片空间利用。

2.链式存储结构

优点:碎片利用率高,不产生空结点,
缺点:不能随机存取,必须从头遍历

二叉树的基本操作

由括号表示法创建二叉树

括号表示法:兄弟结点用“,”分割,父节点后()的内表示其子节点

Java实现

/**
     * 使用括号表示法创建二叉树
     * @param kuohao
     * @return
     */
    public BTNode<Character> createBTree(String kuohao){

        BTNode<Character> [] stack= (BTNode<Character>[]) new Object[maxSize]; 
        int top =-1;
        //是否是左子结点
        boolean isLeft = true;

        BTNode<Character> tempRoot = null ;

        for (int i = 1; i < kuohao.length(); i++) {
            switch (kuohao.charAt(i)) {
            case '(':
                isLeft = true;
                stack[++top] = tempRoot;

                break;
            case ')':
                top--;
                break;
            case ',':
                isLeft = false;
                break;
            default:
                BTNode<Character> tempNode = new BTNode<>(kuohao.charAt(i));
                if(tempRoot == null){
                    tempRoot = tempNode;
                }else{
                    if(isLeft){
                        stack[top].leftChild = tempNode;
                    }else{
                        stack[top].rightChild = tempNode;
                    }
                }
                break;
            }
        }
        return tempRoot;
    }

二叉树的遍历,递归实现

所谓遍历就是找到结点并对其进行一定的操作

先序遍历

二叉树是递归结构,遍历很显然也是递归的,遍历顺序:根节点–》左子结点–》右子节点

/**
     * 先序遍历
     */
    public void preOrder(BTNode<T> root) {
        // 对根节点进操作
        System.out.println(root.value.toString());
        preOrder(root.leftChild);
        preOrder(root.rightChild);
    }

中序遍历

遍历顺序:左子结点–》根节点–》右子节点


/**
     * 中序遍历
     * 
     * @param root
     */
    public void inOrder(BTNode<T> root) {
        inOrder(root.leftChild);
        System.out.println(root.value.toString());
        inOrder(root.rightChild);
    }

后序遍历

遍历顺序:左子节点–》右子节点–》根节点

/**
     * 后序遍历
     * 
     * @param root
     */
    public void postOrder(BTNode<T> root) {
        postOrder(root.leftChild);
        postOrder(root.rightChild);
        System.out.println(root.value.toString());
    }

二叉树的非递归遍历

非递归遍历使用栈保存需要遍历的结点,出栈表示遍历,因为栈的后入先出,适合回溯。

栈需要维护栈顶指针top,初始为top=-1,入栈top++,出栈top–;

先序遍历

先序遍历,根节点进栈,栈不为空时循环出栈,根节点出战,将根节点孩子进栈,注意先将右孩子进栈,再将左孩子进栈,

java 实现:

/**
     * 非递归先序遍历
     * @param root
     */
    public void preOrderNoRecur(BTNode<T> root){
        if(root == null){
            return;
        }
        int top =-1;
        BTNode<T> [] stack= (BTNode<T>[]) new Object[maxSize];
        top ++;
        stack[top] = root;//根节点入栈

        while(top>-1){
            BTNode<T> current = stack[top];//出栈
            top --;

            System.out.println(current.value.toString());//访问根节点,下次循环访问左子结点(如果存在)

            if(current.rightChild !=null){
                stack[top] = current.rightChild;//右子结点入栈
                top ++;
            }
            if(current.leftChild != null){//右子节点入栈
                stack[top] = current.leftChild;
                top++;
            }
        }
    }

中序遍历

中序遍历,开始访问的结点是根节点的最左子节点,在该节点之前的节点都进栈,出栈最左子节点访问之,将其右子节点入栈,重复上面的步骤。直到节点为null 或者栈空。两重循环,外循环判是否栈空结束,内循环入栈左子树。

java实现:

/**
     * 非递归中序遍历
     * 
     * @param root
     */
    public void inOrderNoRecur(BTNode<T> root) {
        if (root == null) {
            return;
        }
        int top = -1;
        BTNode<T>[] stack = (BTNode<T>[]) new Object[maxSize];
        //
        top++;
        stack[top] = root;
        BTNode<T> current = root;// 根节点入栈
        while (top > -1 || current != null) {// ||
            while (current.leftChild != null) {
                stack[top] = current.leftChild;
                top++;
            }
            // current 为根的左子结点都入栈了
            if (top > -1) {
                current = stack[top];
                top--;
                //
                System.out.println(current.value.toString());
                // stack[top] = current.rightChild;//入栈不是在这,在前面的循环
                // top++;
                current = current.rightChild;// 这里处理右子节点,下一步将右子树的左子树入栈
            }
        }
    }

后序遍历

难点在于如何判断一个节点的右子树已经访问过了,需要一个标志刚刚访问过的节点 previous,如果current->rightChild == previous 那么说明current的右子树已经访问过,可以访问current。还要一个标志左子树是否全访问过。

后序遍历是先访问左子树,在访问右子树,最后访问根节点。先将左子树全部入栈,再判断栈顶的右子树是否访问过(即判断刚才访问的是否是栈顶的右子节点),是则访问栈顶出栈,否则重复上面步骤访问右子树的左子树,直到栈空。

三次循环。外部循环判断是否栈为空结束,内部1将左子树并入栈,内部2判断栈顶是否可以访问,可以则出栈,不可以则将右子树的左子树入栈。

栈中保存的是当前节点current的所有祖先节点

java实现:

/**
     * 非递归后序遍历
     */
    public void postOrderNoRecur(BTNode<T> root) {
        if (root == null) {
            return;
        }
        int top = -1;
        BTNode<T>[] stack = (BTNode<T>[]) new Object[maxSize];
        stack[++top] = root;
        while (top > -1) {
            BTNode<T> current = stack[top];
            while (current != null) {
                top++;
                stack[top] = current;
                current = current.leftChild;
            }
            // 左子结点都入栈了,开始判断栈顶是否能访问,
            boolean leftCompeleted = true;
            // 上一次访问的节点
            BTNode<T> previous = null;
            while (leftCompeleted && top > -1) {
                BTNode<T> stackTop = stack[top];
                if (stackTop.rightChild == previous) {// previous 说明没有右子节点
                    top--;
                    previous = stackTop;// !! 将stacktop设为之前访问过的节点。
                    System.out.println(stackTop.value.toString());
                } else {
                    current = current.rightChild;
                    leftCompeleted = false;
                }
            }
        }
    }

层次遍历

层次遍历也叫广度优先遍历,从根节点开始,第一层遍历完遍历第二层,借助队列入队保存每层遍历过的结点,每次出队遍历其子节点,同时将子节点入队。队列先进先出,适合按层遍历。

java实现

/**
     * 使用数组实现队列管理结点访问顺序
     * 
     * @param root
     */
    @SuppressWarnings("unchecked")
    public void layOrder(BTNode<T> root) {
        if (root == null) {
            return;
        }
        int max = 50;
        int front = 0, rear = 0;
        int count = 0;

        //创建object数组转为泛型数组
        BTNode<T>[] queue = (BTNode<T>[]) new Object[max];

        // 入队
        queue[rear] = root;
        rear = (rear + 1) % max;
        count++;

        while (count > 0) {
            //出队
            BTNode<T> out = queue[front];
            front = (front + 1) % max;
            count --;

            //遍历出队结点的左右结点
            //入队
            if(out.leftChild != null){
                queue[rear] = out.leftChild;
                rear = (rear+1)%max;
                count ++;
            }
            if(out.rightChild != null){
                queue[rear] = out.rightChild;
                rear = (rear+1)%max;
                count ++;
            }

        }

    }

查找节点的父节点

利用层次遍历法,在队列出队时,会遍历其左右子节点,此时比较待查找的结点和左右子节点是否相等,相等则返回刚刚出队的父节点。
特殊情况,待查结点是根节点,结点为null,结点不在树上

Java实现

public BTNode<T> findParentNode(BTNode<T> child){
        if (child == null || child == root) {
            return null;
        }
        int max = 50;
        int front = 0, rear = 0;
        int count = 0;

        //创建object数组转为泛型数组
        BTNode<T>[] queue = (BTNode<T>[]) new Object[max];

        // 入队
        queue[rear] = root;
        rear = (rear + 1) % max;
        count++;

        BTNode<T> out;
        while (count > 0) {
            //出队
            out = queue[front];
            front = (front + 1) % max;
            count --;

            //遍历出队结点的左右结点
            //入队
            if(out.leftChild != null){
                //判断是否是待查的子节点,是则返回刚刚出列的节点,为待求的父节点
                if(out.leftChild.value == child.value){
                    return out;
                }
                queue[rear] = out.leftChild;
                rear = (rear+1)%max;
                count ++;
            }
            if(out.rightChild != null){
                if(out.rightChild.value == child.value){
                    return out;
                }
                queue[rear] = out.rightChild;
                rear = (rear+1)%max;
                count ++;
            }

        }
        //所有遍历完了没找到,则返回null,不在树上
        return null;
    }

求结点的左右兄弟结点

由上面的求父节点的方法,求得父节点后,再求该节点的兄弟结点即可

public BTNode<T> getBrotherNode(BTNode<T> child) {

        BTNode<T> parent = findParentNode(child);
        // 没有父节点
        if (parent == null || child == null) {
            return null;
        }
        if (parent.rightChild == child) {// 返回做兄弟
            return parent.leftChild;
        } else if (parent.leftChild == child) {
            return parent.rightChild;
        } else {
            return null;
        }

    }

求树的高度

求树高度就是后序遍历树,取左子树和右子树较大者+1。
递归实现:

/**
     * 求树的高度,递归后序遍历
     * @param root
     * @return
     */
    public int getTreeHeight(BTNode<T> root) {
        if (root == null) {
            return 0;
        }
        int leftHeight = getTreeHeight(root.leftChild);
        int rightHeight = getTreeHeight(root.rightChild);
        return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;
    }

非递归实现主要是非递归后序遍历的修改。
判断是否是叶子节点,是则更新最大高度。

求某一节点所在的层次,或者某一节点的高度

/**
     * 求任意节点的高度
     * 
     * @param root
     * @param node
     * @param h,初始值为1,递归了多少次
     * @return
     */
    public int nodeHeight(BTNode<T> root, T node, int h) {
        if (node == null) {
            return 0;
        }
        if (root == null) {
            return 0;
        }
        if (root.value == node) {
            return h;
        }
        int leftHeight = nodeHeight(root.leftChild, node, h + 1);
        if (leftHeight == 0) {
            return nodeHeight(root.rightChild, node, h + 1);
        }
        return leftHeight;
    }

将一个二叉树插入到另一棵树

找到待插入的节点,将其左子树改为要插入的二叉树。

/**
     * 插入子树
     * 
     * @param subTree
     * @param node
     */
    public void insertSubTree(BinaryTree<T> subTree, T node) {
        if (node == null) {
            return;
        }
        if (subTree.root == null) {
            return;
        }
        BTNode<T> found = findNode(root, node);
        if (found.leftChild == null) {
            found.leftChild = subTree.root;
        }
    }

查找节点

递归查找,先序遍历

  /**
     * 查找节点
     * 
     * @param root
     * @param node
     * @return
     */
    private BTNode<T> findNode(BTNode<T> root, T node) {
        if (root == null) {
            return null;
        }
        if (root.value == node) {
            return root;
        }
        BTNode<T> found = null;
        found = findNode(root.leftChild, node);
        if (found != null)
            return found;

        return findNode(root.rightChild, node);
    }

删除子树

删除子树即删除某一节点的左子树或者右子树的所有节点。

递归删除节点,后序遍历树,先删除该节点的左子树,再删右子树,最后删根节点

javaj实现

/**
     * 删除所有节点
     * 
     * @param node
     */
    public void deleteNodes(BTNode<T> node) {
        if (node == null) {
            return;
        }
        if (root == null) {
            return;
        }
        deleteNodes(root.leftChild);
        deleteNodes(root.rightChild);
        node = null;// 置为null
    }

    /**
     * 删除左子树
     * @param node
     */
    public void deleteSubTree(BTNode<T> node) {
        if (node == null)
            return;
        deleteNodes(node.leftChild);
    }

重建树

有先序遍历和中序遍历可以重建树,同理由后序遍历和中序遍历实现方式一样,都是先在中序序列上找到根节点,在重建左右子树。

/**
     * 有前序和中序序列,重建二叉树
     * @param preStr
     * @param inStr
     * @param nodesNum
     * @param preStart
     * @param inStart
     * @return
     */
    public BTNode<T> rebuildBinaryTree(T[] preStr, T[] inStr, int nodesNum, int preStart, int inStart) {
        if (preStr == null || inStr == null || nodesNum <= 0) {
            return null;
        }
        BTNode<T> tempRoot = new BTNode<>();
        tempRoot.value = preStr[preStart];// 先序根节点为第一个
        int i;
        // 在中序中找到根节点
        for (i = inStart; i < nodesNum; i++) {
            if (inStr[i] == preStr[preStart]) {
                break;
            }
        }
        // 递归创建左右子树
        //调整先序和中序开始构建的下标
        tempRoot.leftChild = rebuildBinaryTree(preStr, inStr, i, preStart + 1, inStart);
        tempRoot.rightChild = rebuildBinaryTree(preStr, inStr, nodesNum - i - 1, preStart + i + 1,  i + 1);
        return tempRoot;

线索化二叉树

若节点的左右子节点为空,可以利用节点的空指针指向,该节点的直接前去和直接后继。

得到的线索化二叉树,根据不同的遍历方式不一样,

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
好的,下面是数据结构二叉树的实验报告。 一、实验目的 1. 了解二叉树的概念、性质和基本操作; 2. 掌握二叉树的遍历算法; 3. 实现二叉树的建立、遍历和查找等基本操作。 二、实验原理 二叉树是一种形结构,它的每个节点最多有两个子节点。二叉树的性质如下: 1. 每个节点最多有两个子节点,分别称为左子节点和右子节点; 2. 左子和右子都是二叉树; 3. 空也是一棵二叉树二叉树的遍历有三种方式: 1. 先序遍历:先访问根节点,然后遍历左子,最后遍历右子; 2. 中序遍历:先遍历左子,然后访问根节点,最后遍历右子; 3. 后序遍历:先遍历左子,然后遍历右子,最后访问根节点。 三、实验步骤 本次实验我们将实现二叉树的建立、遍历和查找等基本操作。 1. 定义二叉树结构体。 ```c typedef struct Node { int data; struct Node *left; struct Node *right; } Node, *pNode; ``` 2. 实现二叉树的创建函数。 ```c pNode createTree() { int data; scanf("%d", &data); if(data == -1) { return NULL; } else { pNode node = (pNode)malloc(sizeof(Node)); node->data = data; node->left = createTree(); node->right = createTree(); return node; } } ``` 3. 实现二叉树的先序遍历函数。 ```c void preOrder(pNode node) { if(node != NULL) { printf("%d ", node->data); preOrder(node->left); preOrder(node->right); } } ``` 4. 实现二叉树的中序遍历函数。 ```c void inOrder(pNode node) { if(node != NULL) { inOrder(node->left); printf("%d ", node->data); inOrder(node->right); } } ``` 5. 实现二叉树的后序遍历函数。 ```c void postOrder(pNode node) { if(node != NULL) { postOrder(node->left); postOrder(node->right); printf("%d ", node->data); } } ``` 6. 实现二叉树的查找函数。 ```c pNode search(pNode node, int data) { if(node == NULL) { return NULL; } else if(node->data == data) { return node; } else if(node->data > data) { return search(node->left, data); } else { return search(node->right, data); } } ``` 7. 编写主函数进行测试。 ```c int main() { pNode root = createTree(); printf("先序遍历结果:"); preOrder(root); printf("\n中序遍历结果:"); inOrder(root); printf("\n后序遍历结果:"); postOrder(root); printf("\n请输入要查找的节点值:"); int data; scanf("%d", &data); pNode node = search(root, data); if(node == NULL) { printf("未找到该节点!"); } else { printf("已找到该节点,节点值为:%d", node->data); } return 0; } ``` 四、实验结果 经过测试,程序能够正确地实现二叉树的建立、遍历和查找等基本操作。 五、实验总结 通过本次实验,我们深入理解了二叉树的概念、性质和基本操作,并实现了二叉树的建立、遍历和查找等基本操作。同时,也加深了对指针和动态内存分配的理解。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值