树的定义

树,由边连接的一些列节点。树是一种非线性的数据结构。

根节点,树上最高的节点。

父节点,某个节点的上层节点。

子节点,某个节点的下层节点。

叶子,没有任何子节点。

二叉树

二叉树,子节点的数量不超过两个的树。

父节点的两个节点分别称为左节点和右节点。

二叉查找树,是一种较小数据值存储在左节点,较大数据值存储在有节点的二叉树。


遍历的概念

 所谓遍历(Traversal)是指沿着某条搜索路线,依次对树中每个结点均做一次且仅做一次访问。访问结点所做的操作依赖于具体的应用问题。

 遍历是二叉树上最重要的运算之一,是二叉树上进行其它运算之基础。

此部分内容转自:http://blog.163.com/klint_khl1/blog/static/28831538200952391021725/

遍历方案

1.遍历方案

 从二叉树的递归定义可知,一棵非空的二叉树由根结点及左、右子树这三个基本部分组成。因此,在任意给定结点上,可以按某种次序执行三个操作:

 (1)访问结点本身(N),

 (2)遍历该结点的左子树(L),

 (3)遍历该结点的右子树(R)。

以上三种操作有六种执行次序:

 NLR、LNR、LRN、NRL、RNL、RLN。

   注意:

 前三种次序与后三种次序对称,故只讨论先左后右的前三种次序。

 

2.三种遍历的命名

 根据访问结点操作发生位置命名:

  ① NLR:前序遍历(PreorderTraversal亦称(先序遍历))

      ——访问结点的操作发生在遍历其左右子树之前。

  ② LNR:中序遍历(InorderTraversal)

       ——访问结点的操作发生在遍历其左右子树之中(间)。

  ③ LRN:后序遍历(PostorderTraversal)

       ——访问结点的操作发生在遍历其左右子树之后。

   注意:

由于被访问的结点必是某子树的根,所以N(Node)、L(Left subtlee)和R(Right subtree)又可解释为根、根的左子树和根的右子树。NLR、LNR和LRN分别又称为先根遍历、中根遍历和后根遍历。

遍历算法

1.中序遍历的递归算法定义:

 若二叉树非空,则依次执行如下操作:

      (1)遍历左子树;

      (2)访问根结点;

      (3)遍历右子树。

 

2.先序遍历的递归算法定义:

 若二叉树非空,则依次执行如下操作:

      (1) 访问根结点;

      (2) 遍历左子树;

      (3) 遍历右子树。

 

3.后序遍历得递归算法定义:

 若二叉树非空,则依次执行如下操作:

      (1)遍历左子树;

      (2)遍历右子树;

      (3)访问根结点。

遍历序列

1.遍历二叉树的执行踪迹

 三种递归遍历算法的搜索路线相同(如下图虚线所示)。

具体线路为:

 从根结点出发,逆时针沿着二叉树外缘移动,对每个结点均途径三次,最后回到根结点。

                 A

          B              C

    D               E          F

 

2.遍历序列

(1) 中序序列

中序遍历按照节点键值的升序顺序访问树中所有的节点。先访问根节点下的左子节点,再访问根节点下的右子节点。


中序遍历二叉树时,对结点的访问次序为中序序列

 【例】中序遍历上图所示的二叉树时,得到的中序序列为:

            D B A E C F

(2) 先序序列

先访问根节点,左子节点,再右子节点。


先序遍历二叉树时,对结点的访问次序为先序序列

【例】先序遍历上图所示的二叉树时,得到的先序序列为:

            A B D C E F

(3) 后序序列

先访问左子节点,右子节点,再根节点。


后序遍历二叉树时,对结点的访问次序为后序序列

【例】后序遍历上图所示的二叉树时,得到的后序序列为:

            D B E F C A

注意:

(1) 在搜索路线中,若访问结点均是第一次经过结点时进行的,则是前序遍历;若访问结点均是在第二次(或第三次)经过结点时进行的,则是中序遍历(或后序遍历)。只要将搜索路线上所有在第一次、第二次和第三次经过的结点分别列表,即可分别得到该二叉树的前序序列、中序序列和后序序列。

(2) 上述三种序列都是线性序列,有且仅有一个开始结点和一个终端结点,其余结点都有且仅有一个前趋结点和一个后继结点。为了区别于树形结构中前趋(即双亲)结点和后继(即孩子)结点的概念,对上述三种线性序列,要在某结点的前趋和后继之前冠以其遍历次序名称。

【例】上图所示的二叉树中结点C,其前序前趋结点是D,前序后继结点是E;中序前趋结点是E,中序后继结点是F;后序前趋结点是F,后序后继结点是A。但是就该树的逻辑结构而言,C的前趋结点是A,后继结点是E和F。



从二叉查找树中移除叶子节点

把目标节点的父节点的每一个子节点设置为空。

        50                           50

  25         75          =>   25         75

1   49     51   99          1   49     51 

=>

删除带有一个子节点的节点

将被删除节点的父节点,其左(右)节点指向被删除节点的左(右)子节点。

        50                             50    

  25         75           =>    25         60    

10  30     60                  10   30          

_________________________________________________________

        50                             50

  25         75            =>    25         80

10  30          80             10   30         

_________________________________________________________

        50                              50           

  25         75            =>     10        75 

10         51  80                          51  80

_________________________________________________________

        50                              50           

  25         75            =>     30        75 

   30     51  80                          51  80

删除带有两个子节点的节点

删除带有两个子节点的节点比较复杂。替换被删除的节点的这个新节点,我们暂称为后继节点。要实现删除,关键是寻找后继节点和重新设置后继节点的左右子节点。

后继节点是被删除节点的右子节点下的最小节点(中序遍历很快能找到)。

后继节点的左节点是原先被删除节点的左子节点,后继节点的右节点是原先被删除节点的右子节点(内部需要去除掉后继节点)。


 

 

例:

...............50

........25....................75

....10.......30........60.......80

...1..14..28..40...55.65..78..90

 

删除75(是一个右子节点)

...............50

........25...............78

....10......30.......60.....80

...1..14..28..40...55.65......90

 

删除25(是一个左子节点)

...............50

........28.................75

....10......30.......60.....80

...1..14......40...55.65..78..90

 

例:

...............50

........25....................75

....10......30..........60......80

...1..14..28..40...55.65......90

删除75

...............50

........25..............80

....10......30.......60.....90

...1..14..28..40...55.65........




重点实例1:

构造二叉查找树

一个节点Node类和一个二叉查找树BinarySearchTree类。

Node类包含数据成员:数据值、左节点、右节点。

BinarySearchTree类包含数据成员:根节点。和一个插入节点的方法。

using System; 
  
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BinarySearchTree
{
   //二叉查找树的节点定义
   public class Node
{
       //节点本身的数据
       public int data;
       //左孩子
       public Node left;
       //右孩子
       public Node right;
       public Node rootNode;

       public int Data
{
           set { this.data = value; }
           get { return this.data; }
}
       public void DisplayData()
{
           Console.Write(data + " ");
}
   } //end class Node
   public class BinarySearchTree
{
       // 构建二叉树是通过向二叉树插入元素得以实现的,所有小于根节点的节点插入根节点的左子树,大于根节点的,插入右子树。
       // 依此类推进行递归。直到找到位置进行插入。二叉查找树的构建过程其实就是节点的插入过程。
       public Node root;
       public BinarySearchTree()
{
           root = null;
}
       public void Insert(int data)
{
           Node parent;
           //将所需插入的数据包装进节点
           Node newNode = new Node();
newNode.data = data;
           //如果为空树,则插入根节点
           if (root == null)
{
root = newNode;
}
           //否则找到合适叶子节点位置插入
           else
{
               //将根节点赋值给当前节点
               Node current = root;

               while (true)
{
                   //将根节点赋值给临时父节点
parent = current;
                   //如果插入的数据小于当前节点的值,则插入到左节点
                   if (data < current.data)
{
current = current.left;
                       //如果当前节点值为空,则插入当前的左节点
                       if (current == null)
{
parent.left = newNode;
                           break;
}
}
                   //如果插入的数据大于当前节点的值,则插入到右节点
                   else
{
current = current.right;
                       if (current == null)
{
parent.right = newNode;
                           break;
}
}
}
}
       }//end insert()
       //二叉树的遍历分为先序(PreOrder),中序(InOrder)和后序(PostOrder)。
       //先序首先遍历根节点,然后是左子树,然后是右子树。
       //中序首先遍历左子树,然后是根节点,最后是右子树。
       //后序首先遍历左子树,然后是右子树,最后是根节点。
       //中序
       public void InOrder(Node theRoot)
{
           if (theRoot != null)
{
InOrder(theRoot.left);
theRoot.DisplayData();
InOrder(theRoot.right);
}
}
       //先序
       public void PreOrder(Node theRoot)
{
           if (theRoot != null)
{
theRoot.DisplayData();
PreOrder(theRoot.left);
PreOrder(theRoot.right);
}
}
       //后序
       public void PostOrder(Node theRoot)
{
           if (theRoot != null)
{
PostOrder(theRoot.left);
PostOrder(theRoot.right);
theRoot.DisplayData();
}
}
       //找到二叉查找树中的最大值和最小值
       //二叉查找树因为已经有序,所以查找最大值和最小值非常简单,找最小值只需要找最左边的叶子节点即可。而找最大值也仅需要找最右边的叶子节点
       //找到最大节点
       public int FindMax()
{
           Node current = root;
           //找到最右边的节点即可
           while (current.right != null)
{
current = current.right;
}
           return current.data;
           //Console.WriteLine("\n The max node:" + current.data);
       } // end FindMax()
 
       //找到最小节点
       public int FindMin()
{
           Node current = root;
           //找到最左边的节点即可
           while (current.left != null)
{
current = current.left;
}
           return current.data;
           //Console.WriteLine("\n The min node:" + current.data);
       } //end FindMin()
       //二叉查找树的查找
       //因为二叉查找树已经有序,所以查找时只需要从根节点开始比较,如果小于根节点,则查左子树,如果大于根节点,则查右子树。如此递归
       //查找
       public Node Find(int key)
{
           Node current = root;
           //如果要搜索的值与当前的值不等,执行搜索操作
           while (current.data != key)
{
               //如果要搜索的值小于当前的值,继续搜索左边的节点
               if (key < current.data)
{
current = current.left;
}
               //如果要搜索的值大于当前的值,继续搜索右边的节点
               else
{
current = current.right;
}
               if (current == null)
                   return null;
}
           //如果要搜索的值与当前的值相等,直接返回当前值;
           return current;
       } //end Find()
 
       ///难点:二叉树的删除
       ///
       ///

       // 二叉树的删除是最麻烦的,需要考虑四种情况:
       //     被删节点是叶子节点
       //     被删节点有左孩子没右孩子
       //     被删节点有右孩子没左孩子
       //     被删节点有两个孩子
       //我们首先需要找到被删除的节点和其父节点,然后根据上述四种情况分别处理。如果遇到被删除元素是根节点时,还需要特殊处理
       //删除二叉查找树中的节点,最麻烦的操作
       public bool Delete(int key)
{
           //初始化:将根节点赋值给current, parent对象;
           Node current = root;
           Node parent = root;
           //初始化:存在左孩子
           bool isLeftChild = true;
           //循环执行条件:判断根节点的值不等于要删除的值-->找到要删除的节点位置
           while (current.data != key)
{
               //将当前节点赋值给临时父节点
parent = current;
               //如果要删除的值小于当前节点
               if (key < current.data)
{
                   //标记存在左节点,并设置当前节点为左节点
                   isLeftChild = true;
current = current.left;
}
               //如果要删除的值大于当前节点
               else
{
                   //标记不存在左节点,并设置当前节点为右节点
                   isLeftChild = false;
current = current.right;
}
               //如果当前节点为空,则返回false
               if (current == null)
                   return false;
}
           //找到要删除的节点后,分四种情况来处理,建立新的二叉树关系
           //情况一:所删除节点是叶子节点,没有左右节点,可以直接删除
           if ((current.left == null) && (current.right == null))
{
               //如果当前节点和跟节点相同,设置根节点为空;
               if (current == root)
{
                   root = null;
}
               //如果删除节点小于父节点,则父节点的左节点设置为空;
               else if (isLeftChild)
{
                   parent.left = null;
}
               //如果删除节点大于父节点,则父节点的右节点设置为空;
               else
                   parent.right = null;
}
           //情况二:所删除节点是只有左节点
           else if (current.left != null && current.right == null)
{
               //如果当前节点和根节点相同,则删除原根节点,根节点指向当前节点的左节点
               if (current == root)
root = current.left;
               //如果删除节点小于父节点,父节点的左节点指向当前节点的左节点
               else if (isLeftChild)
parent.left = current.left;
               //如果删除节点大于父节点,父节点的右节点指向当前节点的左节点
               else
parent.right = current.left;
}
           //情况三:所删除节点是只有右节点
           else if (current.left == null && current.right != null)
{
               //如果当前节点和根节点相同,则删除原根节点,根节点指向当前节点的右节点
               if (current == root)
root = current.right;
               //如果删除节点小于父节点,父节点的左节点指向当前节点的右节点
               else if (isLeftChild)
parent.left = current.right;
               //如果删除节点大于父节点,父节点的右节点指向当前节点的右节点
               else
parent.right = current.right;
}
           //情况四,所删节点有左右两个孩子
           else
{
               Node successor = GetSuccessor(current);
               if (current == root)
root = successor;
               else if (isLeftChild)
{
parent.left = successor;
}
               else
{
parent.right = successor;
}
successor.left = current.left;
}
           return true;
}
       public Node GetSuccessor(Node delNode)
{
           Node sucessorParent = delNode;
           Node sucessor = delNode;
           Node current = delNode.right;
           while (current != null)
{
sucessorParent = sucessor;
sucessor = current;
current = current.left;
}
           if (sucessor != delNode.right)
{
sucessorParent.left = sucessor.right;
sucessor.right = delNode.right;
}
           return sucessor;
}
   }// end class BinarySearchTree
       //测试二叉查找树  
       //现在我们已经完成了二叉查找树所需的各个功能,下面我们来对代码进行测试:
   class Program
{
       static void Main(string[] args)
{
           BinarySearchTree b = new BinarySearchTree();
           /*插入节点*/
b.Insert(5);
b.Insert(7);
b.Insert(1);
b.Insert(12);
b.Insert(32);
b.Insert(15);
b.Insert(22);
b.Insert(2);
b.Insert(6);
b.Insert(24);
b.Insert(17);
b.Insert(14);
           /*插入结束 */
           /*对二叉查找树分别进行中序,先序,后序遍历*/
           Console.Write("\nInOrder:");
b.InOrder(b.root);
           Console.Write("\nPreOrder:");
b.PreOrder(b.root);
           Console.Write("\nPostOrder:");
b.PostOrder(b.root);
           Console.WriteLine(" ");
           /*遍历结束*/
           /*查最大值和最小值*/
b.FindMax();
b.FindMin();
           /*查找结束*/
           /*搜索节点*/
           Node x = b.Find(15);
           Console.WriteLine("\nThis is the search node:" + x.data);
           /*搜索结束*/
           /*测试删除*/
b.Delete(24);
           Console.Write("\nThis is the preoder after remove node 24:");
b.InOrder(b.root);
b.Delete(5);
           Console.Write("\nThis is the preoder after remove node 5:");
b.InOrder(b.root);
           Console.ReadKey();
           /*删除结束*/
}
}
}


参考:
http://blog.csdn.net/maths_bai/article/details/8046586

http://wenku.baidu.com/link?url=PjOCNG3SjWlFAsrWvIH9hx_Hp_dwyJjqlnoXO4hKZQRMGQk1yGEb4IyFruEQLk4rDSLvmcxTq5-QzeRbJYiXAQmevk041GFAuFEQf-rUh9u

http://www.cnblogs.com/CareySon/archive/2012/04/19/implebinarytreewithcsharp.html 

http://blog.csdn.net/vvhesj/article/details/8627089

   


 http://blog.csdn.net/cauchy8389/article/details/7538346 
http://www.cnblogs.com/CareySon/archive/2012/04/19/implebinarytreewithcsharp.html