二叉树的原理和实现

来源:互联网 发布:数据库的规范化设计 编辑:程序博客网 时间:2024/05/29 09:13
    在计算机科学中,二叉树(Binary Tree)是每个节点最多有两个子树的树结构。通常子树被称作“左子树”(left subtree)和“右子树”(right subtree)。二叉树常被用于实现二叉查找树和二叉堆。
    二叉树的每个结点至多只有二棵子树(不存在度大于2的结点),二叉树的子树有左右之分,次序不能颠倒。二叉树的第i层至多有2^{i-1}个结点;深度为k的二叉树至多有2^k-1个结点;对任何一棵二叉树T,如果其终端结点数为n_0,度为2的结点数为n_2,则n_0=n_2+1。
一棵深度为k,且有2^k-1个节点称之为满二叉树;深度为k,有n个节点的二叉树,当且仅当其每一个节点都与深度为k的满二叉树中,序号为1至n的节点对应时,称之为完全二叉树。

定义

二叉树在图论中是这样定义的:二叉树是一个连通的无环图,并且每一个顶点的度不大于3。有根二叉树还要满足根结点的度不大于2。有了根结点之后,每个顶点定义了唯一的父结点,和最多2个子结点。然而,没有足够的信息来区分左结点和右结点。如果不考虑连通性,允许图中有多个连通分量,这样的结构叫做森林。
二叉树是递归定义的,其结点有左右子树之分,逻辑上二叉树有五种基本形态:
(1)空二叉树——如图(a);
(2)只有一个根结点的二叉树——如图(b);
(3)只有左子树——如图(c);
(4)只有右子树——如图(d);
(5)完全二叉树——如图(e)。
注意:尽管二叉树与树有许多相似之处,但二叉树不是树的特殊情形。

类型

(1)完全二叉树——若设二叉树的高度为h,除第 h 层外,其它各层 (1~h-1) 的结点数都达到最大个数,第h层有叶子结点,并且叶子结点都是从左到右依次排布,这就是完全二叉树。
(2)满二叉树——除了叶结点外每一个结点都有左右子叶且叶子结点都处在最底层的二叉树。
(3)平衡二叉树——平衡二叉树又被称为AVL树(区别于AVL算法),它是一棵二叉排序树,且具有以下性质:它是一棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。

相关术语

树的结点:包含一个数据元素及若干指向子树的分支;
孩子结点:结点的子树的根称为该结点的孩子;
双亲结点:B 结点是A 结点的孩子,则A结点是B 结点的双亲;
兄弟结点:同一双亲的孩子结点; 堂兄结点:同一层上结点;
祖先结点: 从根到该结点的所经分支上的所有结点子孙结点:以某结点为根的子树中任一结点都称为该结点的子孙
结点层:根结点的层定义为1;根的孩子为第二层结点,依此类推;
树的深度:树中最大的结点层
结点的度:结点子树的个数
树的度: 树中最大的结点度。
叶子结点:也叫终端结点,是度为 0 的结点;
分枝结点:度不为0的结点;
有序树:子树有序的树,如:家族树;
无序树:不考虑子树的顺序;

二叉树性质

(1) 在非空二叉树中,第i层的结点总数不超过
, i>=1;
(2) 深度为h的二叉树最多有
个结点(h>=1),最少有h个结点;
(3) 对于任意一棵二叉树,如果其叶结点数为N0,而度数为2的结点总数为N2,则N0=N2+1;
(4) 具有n个结点的完全二叉树的深度为
(5)有N个结点的完全二叉树各结点如果用顺序方式存储,则结点之间有如下关系:
若I为结点编号则 如果I>1,则其父结点的编号为I/2;
如果2*I<=N,则其左儿子(即左子树的根结点)的编号为2*I;若2*I>N,则无左儿子;
如果2*I+1<=N,则其右儿子的结点编号为2*I+1;若2*I+1>N,则无右儿子。
(6)给定N个节点,能构成h(N)种不同的二叉树。
h(N)为卡特兰数的第N项。h(n)=C(2*n,n)/(n+1)。
(7)设有i个枝点,I为所有枝点的道路长度总和,J为叶的道路长度总和J=I+2i

遍历顺序

遍历是对树的一种最基本的运算,所谓遍历二叉树,就是按一定的规则和顺序走遍二叉树的所有结点,使每一个结点都被访问一次,而且只被访问一次。由于二叉树是非线性结构,因此,树的遍历实质上是将二叉树的各个结点转换成为一个线性序列来表示。
设L、D、R分别表示遍历左子树、访问根结点和遍历右子树, 则对一棵二叉树的遍历有三种情况:DLR(称为先根次序遍历),LDR(称为中根次序遍历),LRD (称为后根次序遍历)。

先序遍历

首先访问根,再先序遍历左(右)子树,最后先序遍历右(左)子树,C语言代码如下:
1
2
3
4
5
6
7
voidXXBL(tree*root){
//DoSomethingwithroot
if(root->lchild!=NULL)
XXBL(root->lchild);
if(root->rchild!=NULL)
XXBL(root->rchild);
}

中序遍历

首先中序遍历左(右)子树,再访问根,最后中序遍历右(左)子树,C语言代码如下
1
2
3
4
5
6
7
voidZXBL(tree*root)
{
if(root->lchild!=NULL)
ZXBL(root->lchild);//DoSomethingwithroot
if(root->rchild!=NULL)
ZXBL(root->rchild);
}

后序遍历

首先后序遍历左(右)子树,再后序遍历右(左)子树,最后访问根,C语言代码如下
1
2
3
4
5
6
voidHXBL(tree*root){
if(root->lchild!=NULL)
HXBL(root->lchild);
if(root->rchild!=NULL)
HXBL(root->rchild);//DoSomethingwithroot
}

层次遍历

即按照层次访问,通常用队列来做。访问根,访问子女,再访问子女的子女(越往后的层次越低)(两个子女的级别相同)

线索二叉树

线索二叉树(保留遍历时结点在任一序列的前驱和后继的信息):若结点有左子树,则其lchild域指示其左孩子,否则令lchild域指示其前驱;若结点有右子树,则其rchild域指示其右孩子,否则令rchild指示其后继。还需在结点结构中增加两个标志域LTag和RTag。LTag=0时,lchild域指示结点的左孩子,LTag=1时,lchild域指示结点的前驱;RTag=0时,rchild域指示结点的右孩子,RTag=1时,rchild域指示结点的后继。以这种结点结构构成的二叉线索链表,链表作为二叉树的存储结构,叫做其中指向结点前驱和后继的指针叫做线索,加上线索的二叉树称为线索二叉树。对二叉树以某种次序遍历使其变为线索二叉树的过程叫做线索化。若对二叉树进行中序遍历,则所得的线索二叉树称为中序线索二叉树,线索链表称为为中序线索链表。线索二叉树是一种物理结构。
线索二叉树的存储结构线索二叉树的存储结构
在中序线索树找结点后继的规律是:若其右标志为1,则右链为线索,指示其后继,否则遍历其右子树时访问的第一个结点(右子树最左下的结点)为其后继;找结点前驱的规律是:若其左标志为1,则左链为线索,指示其前驱,否则遍历左子树时最后访问的一个结点(左子树中最右下的结点)为其前驱。
在后序线索树中找到结点的后继分三种情况:
若结点是二叉树的根,则其后继为空;若结点是其双亲的右孩子,或是其双亲的左孩子且其双亲没有右子树,则其后继即为双亲结点;若结点是其双亲的左孩子,且其双亲有右子树,则其后继为双亲右子树上按后序遍历列出的第一个结点。
数据结构定义为:
/*二叉线索存储表示*/typedefenum{Link,Thread}PointerTag;/* Link(0):指针,Thread(1):线索*/typedefstruct BiThrNode{ TElemType data;struct BiThrNode *lchild,*rchild;/*左右孩子指针*/PointerTag LTag,RTag;/* 左右标志 */}BiThrNode,*BiThrTree;

Java实现

package com.boonya.utils.algorithm;import java.util.LinkedList;import java.util.List;/** * Java二叉树遍历 *  * 功能:把一个数组的值存入二叉树中,然后进行3种方式的遍历 *  * 参考资料0:数据结构(C语言版)严蔚敏 *  * 参考资料1:http://zhidao.baidu.com/question/81938912.html *  * 参考资料2:http://cslibrary.stanford.edu/110/BinaryTrees.html#java *  * @packge com.boonya.utils.algorithm.BinaryTree * @date 2016年5月19日 下午2:50:04 * @authorocaicai@yeah.net @date: 2011-5-17 * @comment * @update */public class BinaryTree {private int[] array = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };private static List<Node> nodeList = null;/** * 内部类:节点 *  * @author ocaicai@yeah.net @date: 2011-5-17 *  */private static class Node {Node leftChild;Node rightChild;int data;Node(int newData) {leftChild = null;rightChild = null;data = newData;}}/** * 公开内部类:节点 *  * @author ocaicai@yeah.net @date: 2011-5-17 *  */public static class NodeObject {NodeObject leftChild;NodeObject rightChild;Object data;NodeObject(Object newData) {leftChild = null;rightChild = null;data = newData;}public NodeObject getLeftChild() {return leftChild;}public void setLeftChild(NodeObject leftChild) {this.leftChild = leftChild;}public NodeObject getRightChild() {return rightChild;}public void setRightChild(NodeObject rightChild) {this.rightChild = rightChild;}public Object getData() {return data;}public void setData(Object data) {this.data = data;}}/** * 创建Integer数组二叉树 *  * @MethodName: createBinTree * @Description: * @throws */public void createBinTree() {nodeList = new LinkedList<Node>();// 将一个数组的值依次转换为Node节点for (int nodeIndex = 0; nodeIndex < array.length; nodeIndex++) {nodeList.add(new Node(array[nodeIndex]));}// 对前lastParentIndex-1个父节点按照父节点与孩子节点的数字关系建立二叉树for (int parentIndex = 0; parentIndex < array.length / 2 - 1; parentIndex++) {// 左孩子nodeList.get(parentIndex).leftChild = nodeList.get(parentIndex * 2 + 1);// 右孩子nodeList.get(parentIndex).rightChild = nodeList.get(parentIndex * 2 + 2);}// 最后一个父节点:因为最后一个父节点可能没有右孩子,所以单独拿出来处理int lastParentIndex = array.length / 2 - 1;// 左孩子nodeList.get(lastParentIndex).leftChild = nodeList.get(lastParentIndex * 2 + 1);// 右孩子,如果数组的长度为奇数才建立右孩子if (array.length % 2 == 1) {nodeList.get(lastParentIndex).rightChild = nodeList.get(lastParentIndex * 2 + 2);}}/** * 创建Object数组二叉树 *  * @MethodName: createBinTree * @Description: * @throws */public void createBinTree(LinkedList<NodeObject> nodeList) {// 对前lastParentIndex-1个父节点按照父节点与孩子节点的数字关系建立二叉树for (int parentIndex = 0; parentIndex < array.length / 2 - 1; parentIndex++) {// 左孩子nodeList.get(parentIndex).leftChild = nodeList.get(parentIndex * 2 + 1);// 右孩子nodeList.get(parentIndex).rightChild = nodeList.get(parentIndex * 2 + 2);}// 最后一个父节点:因为最后一个父节点可能没有右孩子,所以单独拿出来处理int lastParentIndex = array.length / 2 - 1;// 左孩子nodeList.get(lastParentIndex).leftChild = nodeList.get(lastParentIndex * 2 + 1);// 右孩子,如果数组的长度为奇数才建立右孩子if (array.length % 2 == 1) {nodeList.get(lastParentIndex).rightChild = nodeList.get(lastParentIndex * 2 + 2);}}/** * 先序遍历 *  * 这三种不同的遍历结构都是一样的,只是先后顺序不一样而已 *  * @param node *            遍历的节点 */public static void preOrderTraverse(Node node) {if (node == null)return;System.out.print(node.data + " ");preOrderTraverse(node.leftChild);preOrderTraverse(node.rightChild);}/** * 中序遍历 *  * 这三种不同的遍历结构都是一样的,只是先后顺序不一样而已 *  * @param node *            遍历的节点 */public static void inOrderTraverse(Node node) {if (node == null)return;inOrderTraverse(node.leftChild);System.out.print(node.data + " ");inOrderTraverse(node.rightChild);}/** * 后序遍历 *  * 这三种不同的遍历结构都是一样的,只是先后顺序不一样而已 *  * @param node *            遍历的节点 */public static void postOrderTraverse(Node node) {if (node == null)return;postOrderTraverse(node.leftChild);postOrderTraverse(node.rightChild);System.out.print(node.data + " ");}/** * 先序遍历 *  * 这三种不同的遍历结构都是一样的,只是先后顺序不一样而已 *  * @param node *            遍历的节点 */public static void preOrderTraverseObj(NodeObject node) {if (node == null)return;System.out.print(node.data + " ");preOrderTraverseObj(node.leftChild);preOrderTraverseObj(node.rightChild);}/** * 中序遍历 *  * 这三种不同的遍历结构都是一样的,只是先后顺序不一样而已 *  * @param node *            遍历的节点 */public static void inOrderTraverseObj(NodeObject node) {if (node == null)return;inOrderTraverseObj(node.leftChild);System.out.print(node.data + " ");inOrderTraverseObj(node.rightChild);}/** * 后序遍历 *  * 这三种不同的遍历结构都是一样的,只是先后顺序不一样而已 *  * @param node *            遍历的节点 */public static void postOrderTraverseObj(NodeObject node) {if (node == null)return;postOrderTraverseObj(node.leftChild);postOrderTraverseObj(node.rightChild);System.out.print(node.data + " ");}public static void main(String[] args) {BinaryTree binTree = new BinaryTree();binTree.createBinTree();// nodeList中第0个索引处的值即为根节点Node root = nodeList.get(0);System.out.println("先序遍历:");preOrderTraverse(root);System.out.println();System.out.println("中序遍历:");inOrderTraverse(root);System.out.println();System.out.println("后序遍历:");postOrderTraverse(root);}}
参考资料:http://baike.baidu.com/link?url=F9EC9GF7jKulVjdf-4ML2nbVVvi8wm_IBj9VRIlLMgY0TjHlHI5KDx-pZl3oS290kh6yumea4yfFzdocrZcp8K




0 0
原创粉丝点击