最近複習下資料結構,用C#實現了下二叉搜尋樹,後面再繼續實現平衡樹和紅/黑樹狀結構,以下是二叉搜尋樹(又稱二叉尋找樹)的定義和性質:
二叉尋找樹(Binary Search Tree),或者是一棵空樹,或者是具有下列性質的二叉樹:
若它的左子樹不空,則左子樹上所有結點的值均小於它的根結點的值;
若它的右子樹不空,則右子樹上所有結點的值均大於它的根結點的值;
它的左、右子樹也分別為二叉排序樹。
二叉排序樹的尋找過程和次優二叉樹類似,通常採取二叉鏈表作為二叉排序樹的儲存結構。中序遍曆二叉排序樹可得到一個關鍵字的有序序列,一個無序序列可以通過構造一棵二叉排序樹變成一個有序序列,構造樹的過程即為對無序序列進行排序的過程。每次插入的新的結點都是二叉排序樹上新的葉子結點,在進行插入操作時,不必移動其它結點,只需改動某個結點的指標,由空變為非空即可。搜尋,插入,刪除的複雜度等於樹高,O(log(n)).
二叉排序樹的尋找演算法:
在二叉排序樹b中尋找x的過程為:
若b是空樹,則搜尋失敗,否則:
若x等於b的根結點的資料域之值,則尋找成功;否則:
若x小於b的根結點的資料域之值,則搜尋左子樹;否則:尋找右子樹。
向一個二叉排序樹b中插入一個結點s的演算法:
過程為:
若b是空樹,則將s所指結點作為根結點插入,否則:
若s->data等於b的根結點的資料域之值,則返回,否則:
若s->data小於b的根結點的資料域之值,則把s所指結點插入到左子樹中,否則:
把s所指結點插入到右子樹中。
在二叉排序樹刪除結點的演算法:
在二叉排序樹刪去一個結點,分三種情況討論:
若*x結點為葉子結點,即xL(左子樹)和xR(右子樹)均為空白樹。由於刪去葉子結點不破壞整棵樹的結構,則只需修改其雙親結點的指標即可。
若*x結點只有左子樹xL或右子樹xR,此時只要令xL或xR直接成為其雙親結點*parent的左子樹或者右子樹即可,作此修改也不破壞二叉排序樹的特性。
若*x結點的左子樹和右子樹均不空。在刪去*x之後,為保持其它元素之間的相對位置不變,可按中序遍曆保持有序進行調整,可以有兩種做法:其一是令*x的左子樹為*parent的左子樹,*xsucc為*f左子樹的最右下的結點,而*x的右子樹為*xsucc的右子樹;其二是令*x的直接前驅(或直接後繼)替代*x,然後再從二叉排序樹中刪去它的直接前驅(或直接後繼)。
中序後繼結點替換要刪除的節點:從x的右兒子開始,一直靠左往下走,最後到達的節點就是所需的後繼結點,程式中用xsucc指向這個後繼結點,現在只需刪除xsucc指向的節點,可以根據情況1或者是情況2中的方法來刪除它。
代碼
public class BinaryNode<T>
{
private T data;
private BinaryNode<T> leftChild;
private BinaryNode<T> rightChild;
public T Data
{
get
{
return data;
}
set
{
data = value;
}
}
public BinaryNode<T> LeftChild
{
get
{
return leftChild;
}
set
{
leftChild = value;
}
}
public BinaryNode<T> RightChild
{
get
{
return rightChild;
}
set
{
rightChild = value;
}
}
}
public class BinaryTree<T>
{
private BinaryNode<T> root;
public BinaryNode<T> Root
{
get
{
return root;
}
set
{
root = value;
}
}
/// <summary>
/// 先序遍曆
/// </summary>
/// <param name="root"></param>
public void FirstVisit(BinaryNode<T> root)
{
if (root==null)
{
return;
}
//訪問當前節點資料
Console.WriteLine("此節點資料為:{0}", root.Data);
//遍曆左子樹
FirstVisit(root.LeftChild);
//遍曆右子樹
FirstVisit(root.RightChild);
}
/// <summary>
/// 中序遍曆
/// </summary>
/// <param name="root"></param>
public void MidVisit(BinaryNode<T> root)
{
if (root == null)
{
return;
}
//遍曆左子樹
MidVisit(root.LeftChild);
//訪問當前節點資料
Console.WriteLine("此節點資料為:{0}", root.Data);
//遍曆右子樹
MidVisit(root.RightChild);
}
/// <summary>
/// 後序遍曆
/// </summary>
/// <param name="root"></param>
public void AfterVisit(BinaryNode<T> root)
{
if (root == null)
{
return;
}
//遍曆右子樹
AfterVisit(root.RightChild);
//遍曆左子樹
AfterVisit(root.LeftChild);
//訪問當前節點資料
Console.WriteLine("此節點資料為:{0}", root.Data);
}
/// <summary>
/// 逐層遍曆
/// </summary>
/// <param name="root"></param>
public void LevelVisit(BinaryNode<T> root)
{
if (root == null)
{
return;
}
//用一個隊列來儲存節點
Queue<BinaryNode<T>> queue = new Queue<BinaryNode<T>>();
//根節點入隊
queue.Enqueue(root);
while (queue.Count>0)
{
BinaryNode<T> node = queue.Dequeue();
//訪問當前節點資料
Console.WriteLine("此節點資料為:{0}", root.Data);
if (node.LeftChild!=null)
{
queue.Enqueue(node);
}
if (node.RightChild != null)
{
queue.Enqueue(node);
}
}
}
}
public class BinarySearchTree
{
public bool SearCh(BinaryTree<int> bt, int key)
{
BinaryNode<int> p;
if (bt.Root == null)
{
//Console.WriteLine("the binaryTree is empty!");
return false;
}
p = bt.Root;
while (p != null)
{
if (p.Data == key)
{
//Console.WriteLine("search succeed!");
return true;
}
else if (key < p.Data)
{
p = p.LeftChild;
}
else
{
p = p.RightChild;
}
}
return false;
}
/************************************************************************/
/* 向一個二叉排序樹b中插入一個結點s的演算法,過程為:
1.若b是空樹,則將s所指結點作為根結點插入,否則:
2.若s->data等於b的根結點的資料域之值,則返回,否則:
3.若s->data小於b的根結點的資料域之值,則把s所指結點插入到左子樹中,否則:
4.把s所指結點插入到右子樹中。
* 注意:對二叉尋找樹插入一個節點時總是插入到分葉節點,即此時新增的節點一定是分葉節點
/************************************************************************/
public bool Insert(BinaryTree<int> bt, int key)
{
BinaryNode<int> p;
if (bt.Root == null)
{
bt.Root = new BinaryNode<int>();
bt.Root.Data = key;
return true;
}
p = bt.Root;
BinaryNode<int> parent = new BinaryNode<int>(); //用於保留插入時找到的父節點
while (p != null)
{
if (p.Data == key)
{
return false;
}
else if (key < p.Data)
{
parent = p;
p = p.LeftChild;
}
else
{
parent = p;
p = p.RightChild;
}
}
p = new BinaryNode<int>();
p.Data = key;
if (key < parent.Data)
{
parent.LeftChild = p;
}
else
{
parent.RightChild = p;
}
return true;
}
/************************************************************************/
/* 在二叉排序樹刪去一個結點,分三種情況討論:
1.若*p結點為葉子結點,即PL(左子樹)和PR(右子樹)均為空白樹。
* 由於刪去葉子結點不破壞整棵樹的結構,則只需修改其雙親結點的指標即可。
2.若*p結點只有左子樹PL或右子樹PR,
* 此時只要令PL或PR直接成為其雙親結點*f的左子樹即可,
* 作此修改也不破壞二叉排序樹的特性。
3.若*p結點的左子樹和右子樹均不空。在刪去*p之後,
* 為保持其它元素之間的相對位置不變,可按中序遍曆保持有序進行調整,
* 可以有兩種做法:其一是令*p的左子樹為*f的左子樹,
* *s為*f左子樹的最右下的結點,而*p的右子樹為*s的右子樹;
* 其二是令*p的直接前驅(或直接後繼)替代*p,
* 然後再從二叉排序樹中刪去它的直接前驅(或直接後繼)。 */
/************************************************************************/
//採取右子樹填充法
public bool Delete(BinaryTree<int> bt, int key)
{
BinaryNode<int> p = bt.Root;
BinaryNode<int> parent = new BinaryNode<int>(); //用於保留刪除時找到的父節點
while (p != null)
{
if (p.Data == key)
{
break;
}
else if (key < p.Data)
{
parent = p;
p = p.LeftChild;
}
else
{
parent = p;
p = p.RightChild;
}
}
//未找到關鍵碼
if (p == null)
{
return false;
}
//既沒有左子節點又沒有右子節點
if ((p.LeftChild == null) && (p.RightChild == null))
{
if (p.Data < parent.Data)
{
parent.LeftChild = null;
}
else
{
parent.RightChild = null;
}
}
//沒有右子節點
else if ((p.LeftChild != null) && (p.RightChild == null))
{
if (p.Data < parent.Data)//判斷刪除節點為父節點的左孩子還是右孩子
{
parent.LeftChild = p.LeftChild;
}
else
{
parent.RightChild = p.LeftChild;
}
}
//沒有左子節點
else if ((p.LeftChild == null) && (p.RightChild != null))
{
if (p.Data < parent.Data)
{
parent.LeftChild = p.RightChild;
}
else
{
parent.RightChild = p.RightChild;
}
}
//左右均有
else
{
BinaryNode<int> fillNode = p.RightChild;
BinaryNode<int> fillParentNode = p;
//迴圈找到p的右子樹中最小的節點,即最靠左的節點
while (fillNode.LeftChild != null)
{
fillParentNode = fillNode;
fillNode = fillNode.LeftChild;
}
//判斷填充節點為它的父節點的左孩子還是右孩子
if (fillNode.Data < fillParentNode.Data)//左孩子
{
if (fillNode.RightChild != null)//判斷填充節點是否有右孩子
{
fillParentNode.LeftChild = fillNode.RightChild;
}
else
{
fillParentNode.LeftChild = null;
}
}
else//右孩子
{
if (fillNode.RightChild != null)
{
fillParentNode.RightChild = fillNode.RightChild;
}
else
{
fillParentNode.RightChild = null;
}
}
fillNode.LeftChild = p.LeftChild;
fillNode.RightChild = p.RightChild;
if (p.Data != bt.Root.Data)
{
if (p.Data < parent.Data)//判斷刪除節點為父節點的左孩子還是右孩子
{
parent.LeftChild = fillNode;
}
else
{
parent.RightChild = fillNode;
}
}
else
{
bt.Root = fillNode;
}
}
return true;
}
}
class Program
{
static void Main(string[] args)
{
BinarySearchTree bst = new BinarySearchTree();
BinaryTree<int> bt=new BinaryTree<int>();
bst.Insert(bt, 10);
bst.Insert(bt, 6);
bst.Insert(bt, 16);
bst.Insert(bt, 25);
bst.Insert(bt, 8);
bst.Insert(bt, 22);
bst.Insert(bt, 12);
bst.Insert(bt, 5);
bst.Insert(bt, 7);
bst.Delete(bt, 10);
bool isExist = bst.SearCh(bt, 10);
string msg=isExist?"存在資料":"不存在資料";
Console.WriteLine(msg);
Console.ReadLine();
}
static void WriteOut(int[] seq)
{
foreach (int i in seq)
{
Console.Write("{0} ,", i);
}
Console.WriteLine();
}
}