一.树中的节点关系和一些概念node
1.基本概念数组
树中节点数可使用n来表示this
空树:n为0的树spa
节点的度:指节点的子节点数目,如上图中B节点为一度,D节点为三度3d
父子兄弟关系:如上图中D是G的父节点,H是D的子节点,G是H的兄弟节点,I是J的堂兄弟节点(这个概念不重要)code
树的层次:仍是如上图,A节点为第一层,B、C节点为第二层,D、E、F节点为第三层,G、H、I、J节点为第四层blog
树的深度:仍是上图中,总共有四个层次的节点,称树的深度为4排序
树中的一些注意事项:1)一个子节点只能有一个父节点,可是一个父节点的子节点数目没有限制递归
2)子树之间不能相交ci
3)树只能有一个根节点
2.树的存储
树能够采用链式存储,能够采用如下的一些方式存储:
1)在节点中记录父节点的位置,即父节点存储法。这个方式能够很方便找到父节点甚至父节点的父节点,可是寻找子节点时却须要遍历查找;
2)在节点中记录全部子节点的位置,即子节点存储法。这个方式一样方便找子节点,可是寻找父节点一样须要遍历;
3)在节点中记录第一个子节点和其余兄弟节点的位置,即孩子兄弟存储法。这个方法找子节点和兄弟节点很方便,可是找父节点一样要遍历。
二.二叉树
二叉树中每一个节点的子节点最多只能有两个,称为左右子节点,左右子节点下方的树称为左右子树。
1.一些特殊的二叉树
左斜树:全部节点只有左子节点的树。
右斜树:全部节点只有右子节点的树。
满二叉树:对于一个k层的二叉树,k-1层的节点都有两个子节点,第k层的节点都有0个子节点,这样的二叉树称为满二叉树。
彻底二叉树:对一个有k层的满二叉树,第k层的节点有2k-1个,若是将这些节点从右向左连续删除0-2k-1个后就获得一个彻底二叉树。满二叉树是特殊的彻底二叉树。下图分别是4层的满树从右向左连续删除6个节点和3个节点获得的两个彻底二叉树。
2.二叉树的存储
对一个k层的满二叉树,每一层的节点数分别是20、21、......、2k-1,总节点数是2k-1。以下图,将这个满二叉树的全部节点一层一层从左到右编号,能够发现一个有趣的现象:每一个节点的父节点编号都是这个节点编号除以2的商,每一个节点的子节点编号都是这个节点编号乘以2的积(左子节点)或者乘以2加1的和(右子节点)。
根据这个特性可使用顺序的方式存储二叉树,给每一个节点编号,每一个节点的子节点和父节点均可以经过编号运算获得。对于非彻底二叉树,编号时将空缺的位置一同编号便可。下面是三个非彻底二叉树的编号方式:
所以二叉树的存储可使用数组进行顺序存储,空缺的节点置为空便可。同时也因为空缺的节点都置为空,所以像右斜树等不彻底二叉树会形成严重的空间浪费,所以链表存储的方式也能够采用。
3.二叉树的遍历
1)前序遍历:从根节点开始,先输出当前节点的数据,再依次遍历输出左节点和右节点。
2)中序遍历:从根节点开始,先输出当前左节点的数据,再输出当前节点的数据,最后输出当前的右节点的数据。
3)后序遍历:从根节点开始,先输出当前节点左节点的数据,再输出当前节点右节点的数据,最后输出当前节点的数据。
4)层序遍历:从树的第一层开始,从上到下逐层遍历,每一层从左到右依次输出。
4.二叉树的代码实现(C#),这里使用顺序存储实现了二叉树的数值添加和几种遍历,没有实现删除的方法。
class BiTree<T> { private T[] data; private int count = 0; //当前二叉树存储的数据量 public BiTree(int capacity) //当前二叉树的数据容量 { data = new T[capacity]; } /// <summary> /// 向二叉树中添加数据或者修改二叉树中的数据 /// </summary> /// <param name="item"></param>须要存储的数据 /// <param name="index"></param>须要存储的数据的编号 /// <returns></returns> public bool Add(T item,int index) { //校验二叉树是否存满 if (count >= data.Length) return false; data[index - 1] = item; count++; return true; } public void Traversal() { FirstTravalsal(1); Console.WriteLine(); MiddleTravalsal(1); Console.WriteLine(); LastTravalsal(1); Console.WriteLine(); LayerTravalsal(); } /// <summary> /// 前序遍历 /// </summary> /// <param name="index"></param>遍历的数据的编号 private void FirstTravalsal(int index) { //校验编号是否存在 if (index > data.Length) return; //校验数据是否存在,当前位置没有数据则存储为-1 if (data[index - 1].Equals(-1)) return; //输出当前数据 Console.Write(data[index - 1] + " "); //计算左右子节点的下标 int leftNumber = index * 2; int rightNumber = index * 2 + 1; //递归遍历左右子节点 FirstTravalsal(leftNumber); FirstTravalsal(rightNumber); } /// <summary> /// 中序遍历 /// </summary> /// <param name="index"></param>遍历的数据的编号 private void MiddleTravalsal(int index) { //校验编号是否存在 if (index > data.Length) return; //校验数据是否存在,当前位置没有数据则存储为-1 if (data[index - 1].Equals(-1)) return; //计算左右子节点的下标 int leftNumber = index * 2; int rightNumber = index * 2 + 1; //递归遍历左子节点 FirstTravalsal(leftNumber); //输出当前数据 Console.Write(data[index - 1] + " "); //递归遍历右子节点 FirstTravalsal(rightNumber); } /// <summary> /// 后序遍历 /// </summary> /// <param name="index"></param>遍历的数据的编号 private void LastTravalsal(int index) { //校验编号是否存在 if (index > data.Length) return; //校验数据是否存在,当前位置没有数据则存储为-1 if (data[index - 1].Equals(-1)) return; //计算左右子节点的下标 int leftNumber = index * 2; int rightNumber = index * 2 + 1; //递归遍历左子节点 FirstTravalsal(leftNumber); //递归遍历右子节点 FirstTravalsal(rightNumber); //输出当前数据 Console.Write(data[index - 1] + " "); } /// <summary> /// 层序遍历 /// </summary> private void LayerTravalsal() { for(int i = 0;i < data.Length;i++) { //校验当前数据是否为空 if (data[i].Equals(-1)) continue; //输出遍历的数据 Console.Write(data[i] + " "); } } }
三.二叉排序树
二叉排序树的节点位置和节点的大小有关,从根节点开始判断,比当前节点小就往当前节点的左子树上移动,反之往当前节点的右子树上移动,一直判断直到移动到的位置没有节点,这个位置就是节点的放置位置。以下图所示,链接线上的数字是节点的放置顺序:
能够看到,一样的数据,最后存储出来的二叉树形状和数据的放置顺序有关。
下面是二叉排序树的实现(C#),这里使用链表实现了二叉排序树的节点添、查找和删除。
class BSNode { public BSNode LeftChild{get;set;} public BSNode RightChild { get; set; } public BSNode Parent { get; set; } public int Data { get; set; } public BSNode() { } public BSNode(int item) { this.Data = item; } }
class BSTree { //记录根节点位置 public BSNode Root { get; set; } /// <summary> /// 添加数据 /// </summary> /// <param name="item"></param>要添加的数据,以int为例,也能够拓展为一个泛型 public void AddNode(int item) { //新建一个node BSNode newNode = new BSNode(item); //判断树中有没有节点,没有节点当前节点做为根节点,有节点将数据放入节点中 if (Root == null) Root = newNode; else { //定义一个临时节点记录当前正在访问的节点位置 BSNode temp = Root; //死循环,须要不断判断节点应该往当前节点左边仍是右边放置,且不知道要循环判断多少次 while (true) { //若是要放置的数据大于当前节点,说明要放置的节点应该往右边放 if(item >= temp.Data) { //判断当前节点的右边子节点位置是否已经有节点,若是没有直接放置而后跳出循环 if (temp.RightChild == null) { temp.RightChild = newNode; newNode.Parent = temp; break; } //当前节点右节点位置有数据的状况下,将临时节点置为当前节点的右节点,继续循环判断应该往这个节点的哪一边放置 else { temp = temp.RightChild; } } //若是要放置的数据不是大于当前节点,说明要放置的节点应该往左边放 else { if (temp.LeftChild == null) { temp.LeftChild = newNode; newNode.Parent = temp; break; } else { temp = temp.LeftChild; } } } } } /// <summary> /// 采用中序遍历能够实现数据由小到大输出 /// </summary> /// <param name="node"></param>遍历输出node节点及其子节点 public void MiddleTraversal(BSNode node) { if (node == null) return; MiddleTraversal(node.LeftChild); Console.Write(node.Data + " "); MiddleTraversal(node.RightChild); } /// <summary> /// 查找数据是否在树中(递归方式) /// </summary> /// <param name="item"></param>要查找的数据 /// <param name="node"></param>在node节点及其子孙节点中查找 /// <returns></returns> public bool Find1(int item,BSNode node) { //校验当前节点是否为空 if (node == null) return false; //判断节点的数据是否和要查找的数据相同 else if (item == node.Data) return true; //判断要查找的数据和当前数据的大小,决定是继续在左子树中查找仍是在右子树中查找 else if (item > node.Data) return Find1(item, node.RightChild); else return Find1(item, node.LeftChild); } /// <summary> /// 查找数据是否在树中(循环方式) /// </summary> /// <param name="item"></param>要查找的数据 /// <param name="node"></param>在node节点及其子孙节点中查找 /// <returns></returns> public bool Find2(int item, BSNode node) { BSNode temp = node; while (true) { //校验当前节点是否为空 if (temp == null) return false; //判断节点的数据是否和要查找的数据相同 else if (item == temp.Data) return true; //判断要查找的数据和当前数据的大小,决定是继续在左子树中查找仍是在右子树中查找 else if (item > temp.Data) temp = temp.RightChild; else temp = temp.LeftChild; } } /// <summary> /// 根据数据查找并删除存储数据的节点 /// </summary> /// <param name="item"></param>要删除的数据 /// <returns></returns> public bool DeleteNode(int item) { //首先须要查找要删除的节点是否存在,使用临时节点temp记录,若是存在才能删除,不然不能删除 BSNode temp = Root; while (true) { //校验当前节点是否为空 if (temp == null) return false; //判断节点的数据是否和要查找的数据相同,相同就须要删除这个节点 else if (item == temp.Data) { DeleteNode(temp); return true; } //判断要查找的数据和当前数据的大小,决定是继续在左子树中查找仍是在右子树中查找 else if (item > temp.Data) temp = temp.RightChild; else temp = temp.LeftChild; } } /// <summary> /// 删除指定的节点 /// </summary> /// <param name="node"></param>要删除的节点 private void DeleteNode(BSNode node) { //判断当前节点是否为根节点,不是根节点删除时须要修改当前节点的父节点的引用指向,是根节点须要修改Root的值 if (node.Parent != null) { //分为四种状况,分别是这个节点没有子节点、只用左子节点、只有右子节点和有左右两个子节点 //没有子节点直接修改父节点的引用,并删除节点 if (node.LeftChild == null && node.RightChild == null) { if (node.Parent.RightChild == node) node.Parent.RightChild = null; else node.Parent.LeftChild = null; node = null; } //只有左子节点或者右子节点须要更改父节点的引用,并更改相应子节点的父节点引用 else if (node.LeftChild == null && node.RightChild != null) { if (node.Parent.RightChild == node) node.Parent.RightChild = node.RightChild; else node.Parent.LeftChild = node.RightChild; node.RightChild.Parent = node.Parent; node = null; } else if (node.LeftChild != null && node.RightChild == null) { if (node.Parent.RightChild == node) node.Parent.RightChild = node.LeftChild; else node.Parent.LeftChild = node.LeftChild; node.LeftChild.Parent = node.Parent; node = null; } //左右子节点都有的状况下将右子树的最小值的数据复制到当前节点,而后去删除右子树的最小值 else { BSNode temp = node.RightChild; while (true) { if (temp.LeftChild != null) temp = temp.LeftChild; else break; } node.Data = temp.Data; DeleteNode(temp); } } //当前节点是根节点的状况下和不是根节点的删除相似,也是四种状况,不过须要修改的是Root的引用和子节点的父节点引用,不用管父节点 else { if (node.LeftChild == null && node.RightChild == null) { node = null; Root = null; } else if (node.LeftChild == null && node.RightChild != null) { node.RightChild.Parent = null; Root = node.RightChild; node = null; } else if (node.LeftChild != null && node.RightChild == null) { node.LeftChild.Parent = null; Root = node.LeftChild; node = null; } else { BSNode temp = node.RightChild; while (true) { if (temp.LeftChild != null) temp = temp.LeftChild; else break; } node.Data = temp.Data; DeleteNode(temp); } } } }