数据结构总结-- 树和二叉树
2016-09-03 20:25
225 查看
树的性质
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;
线索化二叉树
若节点的左右子节点为空,可以利用节点的空指针指向,该节点的直接前去和直接后继。得到的线索化二叉树,根据不同的遍历方式不一样,
相关文章推荐
- 数据结构学习之二叉树(性质总结)
- 【数据结构】二叉树的原理及实现学习总结
- 数据结构---二叉树总结
- 数据结构二叉树个人总结
- 数据结构二叉树知识点总结
- 基本数据结构学习总结: 二叉树的遍历
- 数据结构二叉树知识点总结
- 基本数据结构学习总结: 二叉树的基本操作
- 【数据结构】面试搞定二叉树大总结-15道二叉树题
- 《数据结构》第五章 树和二叉树 知识总结导图
- 【数据结构】二叉树面试题总结
- 数据结构复习:几种排序算法的C++实现和二叉树的相关算法实现
- 数据结构 排序 总结
- 常用的算法和数据结构分析(二叉树)
- 网络编程数据结构 及函数总结
- 第一天总结(信号与系统&数据结构)
- 二叉树(C#数据结构四)
- 二叉树 遍历 算法总结
- 数据结构C语言实现系列——二叉树
- 数据结构类型定义及基本操作汇总(二)-- 二叉树及其遍历