具有下列性质的二叉树:
- 左子树上所有节点的值均小于它的根节点的值
- 右子树上所有节点的值均大于它的根节点的值
- 任意节点的左、右子树也分别为二叉查找树
- 没有相等的节点
二叉树的元素添加:
公有修饰符 Add是依附于私有修饰符 Add的。 假设添加 8、4、6、7 四个元素: (1)Add(8)➡ Add(null,8)➡ 由于 node 为空,所以容量加一,8 将会作为根节点进行返回。 (2)Add(4)➡ Add(8,4)➡ 由于 8 大于 4 ,8.left = Add(8.left==null,4)➡由于 8.left 为空,所以 4将作为 8 的左孩子。 (3)Add(6)➡ Add(8,6)➡ 由于 8 大于 6,8.left = Add(8.left==4,6)➡由于 4 小于 6 ,4.right = Add(4.right==null,6),最后 Add(null,6),6 将作为 4 的右孩子节点 (4)Add(7)➡ Add(8,7)➡ 由于8 大于 7,8.left = Add(8.left==4,7)➡ 由于4小于7,4.right= Add(4.right==6,7)➡由于 6 大于 7 ,6.right = Add(6.right==null,7),最后Add(null,7),7 将作为 6 的右孩子节点。
class BST1<E> where E : IComparable<E>
{
private class Node
{
public E e;
public Node left;
public Node right;
public Node(E e)
{
this.e = e;
left = null;
right = null;
}
}
private Node root;
private int N;
public BST1()
{
root = null;
N = 0;
}
public int Count { get { return N; } }
public bool IsEmpty { get { return N == 0; } }
/// <summary>
/// 非递归方式的二叉树添加元素
/// </summary>
/// <param name="e">添加的元素</param>
public void add(E e)
{
//如果根节点未空,就调用 Node 的构造函数,并作为根节点
//容量加一,退出这个方法
if (root == null)
{
root = new Node(e);
N++;
return;
}
Node pre = null;
Node cur = root;
//只要 cur 不为空就一直循环
//(1)如果添加的元素跟 cur.e 的节点一致就跳出这个循环
//(2)如果添加的元素小于 cur.e 的节点, cur 的左节点就是 cur,否则 cur 的右节点就是 cur
//找到了一个空的位子跳出循环
//(1)调用 Node 的构造函数,传进来的 e ,就是要添加的元素,并且在 cur 的位子上,因为 cur 为空
//(2)在判断添加的元素是否小于 pre(就是cur 的上一个节点,这里是确定 cur 是父节点的左孩子还是右孩子) 的节点,那 pre 的左孩子就是 cur,否则右孩子就是 cur
//最后容量加一
while (cur != null)
{
if (e.CompareTo(cur.e) == 0)
return;
pre = cur; //pre 记录 cur 上一个节点
if (e.CompareTo(cur.e) < 0)
cur = cur.left;
else
cur = cur.right;
}
cur = new Node(e);
if (e.CompareTo(pre.e) < 0)
pre.left = cur;
else
pre.right = cur;
N++;
}
/// <summary>
/// 递归方式的给二叉树添加元素
/// </summary>
public void Add(E e)
{
root = Add(root, e);
}
/// <summary>
/// 以node为根的树中添加元素e,添加后返回根节点node
/// </summary>
/// <param name="node">根节点</param>
/// <param name="e">添加的元素</param>
/// <returns>返回根节点</returns>
private Node Add(Node node, E e)
{
if (node == null)
{
N++;
return new Node(e);
}
if (e.CompareTo(node.e) < 0)
node.left = Add(node.left, e);
else if (e.CompareTo(node.e) > 0)
node.right = Add(node.right, e);
return node;
}
public bool Contains(E e)
{
return Contains(root, e);
}
/// <summary>
/// 以 node 为根的树是否包含元素 e
/// </summary>
private bool Contains(Node node, E e)
{
if (node == null)
return false;
if (e.CompareTo(node.e) == 0)
return Contains(node.left, e);
else if (e.CompareTo(node.e) < 0)
return Contains(node.left, e);
else //e.CompareTo(node.e) > 0
return Contains(node.right, e);
}
//前序遍历
public void PreOrder()
{
PreOrder(root);
}
/// <summary>
/// 前序遍历以 node 为根的二叉查找树
/// </summary>
private void PreOrder(Node node)
{
if (node == null)
return;
Console.WriteLine(node.e);
PreOrder(node.left);
PreOrder(node.right);
}
//中序遍历
public void InOrder()
{
InOrder(root);
}
//中序遍历以 node 为根的二叉查找树
private void InOrder(Node node)
{
if (node == null)
return;
InOrder(node.left);
Console.WriteLine(node.e);
InOrder(node.right);
}
//后序遍历
public void PostOrder()
{
PostOrder(root);
}
private void PostOrder(Node node)
{
if (node == null)
return;
PostOrder(node.left);
PostOrder(node.right);
Console.WriteLine(node.e);
}
//层序顺序
public void LevelOrder()
{
Queue<Node> p = new Queue<Node>();
p.Enqueue(root);
while(p.Count != 0)
{
Node cur = p.Dequeue();
Console.WriteLine(cur.e);
if (cur.left != null)
p.Enqueue(cur.left);
if (cur.right != null)
p.Enqueue(cur.right);
}
}
}
//在数组中添加元素
class Program
{
private static void Main(string[] args)
{
int[] a = { 8, 4, 12, 2, 6, 10, 14 };
BST1<int> bst = new BST1<int>();
for (int i = 0; i < a.Length; i++)
bst.Add(a[i]);
bst.PreOrder();
Console.WriteLine();
bst.InOrder();
Console.WriteLine();
bst.PostOrder();
Console.WriteLine();
bst.LevelOrder();
Console.ReadKey();
}
}
bst.PreOrder()的运行结果:(不管是Add或add添加元素的方法,他们的运行结果都是一致的)
bst.InOrder()的运行结果:
bst.postOrder()的运行结果:
bst.LevelOrder()的运行结果:
删除最大元素和最小元素
/////////////////////// // 8 // // / \ // // 4 12 // // / \ / \ // // 2 6 10 14 // ///////////////////////
首先查找最大、最小元素的节点,即最右边、最左边。 而查找到元素的方法也很简单,就一直返回调用 node.left ,直到 node.left 为空,说明左孩子已经没元素了,那当前节点元素就是最小的了。查找最大元素同理! 首先,Min函数即是锁定最小的值,代码逻辑就是,往最左边一直走,直到 node.left 为空,此时只需返回 node即可。 然后就是 RemoveMin 函数,它的删除操作就是,赋值取代。第一步的操作 node.left = RemoveMin(node.left);return node;直到 node.left 为空。最后的结果:返回 8 ; 具体逻辑:先传进来的 node = 8;那就是 4 = removemin(4);return 8 ; 2 = removemin(2);return 4; null = removemin(null); return 2; 此时,2.left 为空;容量减一,并且 return null; 又因为锁定了 node = 2;就是最小,随后进行容量减一。 那现在 最左边就是 4 ;但是 此时的 4.left 还不为 null 所以当 4.left = removemin(4.left),而 removemin(4.left)返回一个 null ,也就完成了复制取代 4.left = null 4 = removemin(4);return 8 ;知道了 removemin(4)返回了 4,也就验证了 4 = 4 ;return 8;
// 寻找二叉查找树的最小元素
public E Min()
{
if (IsEmpty)
throw new ArgumentException("空树");
return Min(root).e;
}
/// <summary>
/// 返回以node为根的二叉查找树的最小值所在的节点
/// </summary>
private Node Min(Node node)
{
if (node.left == null)
return node;
return Min(node.left);
}
public E Max()
{
if (IsEmpty)
throw new ArgumentException("空树");
return Max(root).e;
}
/// <summary>
/// 返回以node为根的二叉查找树的最大值所在的节点
/// </summary>
private Node Max(Node node)
{
if (node.right == null)
return node;
return Max(node.right);
}
public E RemoveMin()
{
E ret = Min();
root = RemoveMin(root);
return ret;
}
/// <summary>
/// 删除掉以Node为根的二叉查找树中的最小节点
/// 返回删除节点后新的二叉查找树的根
/// </summary>
private Node RemoveMin(Node node)
{
if (node.left == null)
{
N--;
return node.right;
}
node.left = RemoveMin(node.left);
return node;
}
public E RemoveMax()
{
E ret = Min();
root = RemoveMax(root);
return ret;
}
/// <summary>
/// 删除掉以Node为根的二叉查找树中的最大节点
/// 返回删除节点后新的二叉查找树的根
/// </summary>
private Node RemoveMax(Node node)
{
if (node.right == null)
{
N--;
return node.left;
}
node.right = RemoveMax(node.right);
return node;
}
//Main函数中,删除最大、小元素
class Program
{
private static void Main(string[] args)
{
int[] a = { 8, 4, 12, 2, 6, 10, 14 };
BST1<int> bst = new BST1<int>();
for (int i = 0; i < a.Length; i++)
bst.Add(a[i]);
bst.RemoveMin();
bst.InOrder();
Console.WriteLine();
bst.RemoveMax();
bst.InOrder();
Console.ReadKey();
}
}
运行结果:
任意删除元素
Remove 函数的运行逻辑: 调用 Remove(4); remove(8,4); if(4.compareTo(8) < 0){8.left = remove(4.4) return node} if(4.compareTo(4) == 0){if(4.left ==0)、if(4.right ==0);不符合条件,不运行。} 此时的Node为 4 node s = min(node.left) =>node s = min(6) => node s = 6; s.right = RemoveMin(node.right) =>6.right = removemin(6) => 6.right = null s.left = node.left => 6.left = 6.left => 6.left = null return s => return 6; 然后回到 8.left = remove(4.4) =>8.left = 6 return 8;
public void Remove(E e)
{
Remove(root, e);
}
/// <summary>
/// 删除掉以Node为根的二叉查找树中的最大节点
/// 返回删除节点后新的二叉查找树的根
/// </summary>
private Node Remove(Node node,E e)
{
//如果二叉树中没有要删除的元素节点,就直接返回 null
if (node == null)
return null;
//当二叉树的根节点只有左(右)孩子时
//如果要删除的元素(大)小于当前节点元素,那就意味着要删除的元素在左(右)边
//跟 RemoveMin(RemoveMax) 函数同样的代码逻辑
if (e.CompareTo(node.e) < 0)
{
node.left = Remove(node.left, e);
return node;
}
else if (e.CompareTo(node.e) > 0)
{
node.right = Remove(node.right, e);
return node;
}
else //e.CompareTo(node.e)== 0
{
//假设 e 为 4.
//那他的左右孩子分别为 2、6
//也就不符合判断条件
//跳出循环
if (node.right == null)
{
N--;
return node.left;
}
if (node.left == null)
{
N--;
return node.right;
}
//要删除的节点左右都有孩子
//找到比待删除节点大的最小节点,即待删除节点右子树的最小节点
//用这个节点顶替待删除节点的位置
//Node s = min(6)
//Node s = 6
//6.right = removemin(6) => null = removemin(6)
//6.right = null
//6.left = null
Node s = Min(node.right);
s.right = RemoveMin(node.right);
s.left = node.left;
return s;
}
}
}
class Program
{
private static void Main(string[] args)
{
int[] a = { 8, 4, 12, 2, 6, 10, 14 };
BST1<int> bst = new BST1<int>();
for (int i = 0; i < a.Length; i++)
bst.Add(a[i]);
///////////////////////
// 8 //
// / \ //
// 4 12 //
// / \ / \ //
// 2 6 10 14 //
///////////////////////
///
bst.Remove(4);
bst.InOrder();
Console.ReadKey();
}
}
运行结果:
树的最高度和性能分析
树的最高度决定了二叉树的时间复杂度,按简单的来看,分为两种极端的情况:《最好情况》《最坏情况》 而二叉树的时间复杂度在O(Log N)- O(N) 之间 当添加的数据随机性越强,他的时间复杂度越趋近O(Log N),而添加的数据是有顺序的话那时间复杂度就是O(N),其查找的时间复杂度有是O(N) 当查找数据时,会忽略掉一些不需要查找的元素,其时间复杂度也是O(log N) 而我们查找到数据的最高度,是为了更好的看到当前的二叉树的时间复杂度
《最好情况》:
///////////////////////// 8 // // / \ // // 4 12 // // / \ / \ // // 2 6 10 14 // ///////////////////////
《最坏情况》: //2-4-6-8-10-12-14 他们之间的时间复杂度:
public int MaxHeight()
{
return MaxHeight(root);
}
private int MaxHeight(Node node)
{
if (node == null)
return 0;
//int l = MaxHeight(node.left);
//int r = MaxHeight(node.right);
return Math.Max(MaxHeight(node.left), MaxHeight(node.right)) + 1;
}
class Program
{
private static void Main(string[] args)
{
int[] a = { 8, 4, 12, 2, 6, 10, 14 };
BST1<int> bst = new BST1<int>();
for (int i = 0; i < a.Length; i++)
bst.Add(a[i]);
///////////////////////
// 8 //
// / \ //
// 4 12 //
// / \ / \ //
// 2 6 10 14 //
///////////////////////
Console.WriteLine(bst.MaxHeight());
Console.ReadKey();
}
}