本文章參考了《數據結構與算法分析——C語言描述》這本書。書中寫的還是挺透徹的
首先介紹一下二叉樹。什麼是二叉樹呢?
二叉樹(binary tree)是一棵樹,其每個節點最多能有兩個兒子。由於整個特性,實現二叉樹的方式是創建兩個指針分別指向左兒子和右兒子。
二叉查找樹是一種特殊的二叉樹,其特點是:對於樹的每個節點x,其左子樹中所有關鍵字的值都小於x, 其右子樹中所有關鍵字的值都大於x。其左右子樹也都是二叉查找樹,並且在二叉查找樹中沒有重複的鍵值。
首先是聲明二叉查找樹:
#ifndef _TREE_H_
typedef int ElementType;
typedef struct treeNode *Position;
typedef struct treeNode *SearchTree;
struct treeNode
{
ElementType Element;
SearchTree lch;
SearchTree rch;
};
SearchTree MakeEmpty(SearchTree T);
Position Find(SearchTree T, ElementType x);
Position FindMin(SearchTree T);
Position FindMax(SearchTree T);
SearchTree Insert(SearchTree T, ElementType x);
SearchTree Delete(SearchTree T, ElementType x);
void PrintTree(SearchTree T);
void Destroy(SearchTree T);
#endif // _TREE_H_
對於二叉查找樹主要有以下幾個操作:
1.Find查找操作
這個操作返回指向樹T中具有關鍵字X的節點的指針,如果不存在就返回NULL
樹的結構令這種操作變得十分簡單,只需要令X與當前節點比較就可以了,如果X小於當前節點,就用遞歸的方式去當前節點的左子樹尋找,如果x大於當前節點就去右子樹查找。如果相等就返回當前節點的指針,如果找不到就返回NULL;
Position Find(SearchTree T, ElementType x)
{
if(T == NULL)
return NULL;
if(T->Element > x)
return Find(T->lch, x);
else if(T->Element < x)
return Find(T->rch, x);
else
return T;
}
2.尋找最小值FindMin操作與尋找最大值FindMax操作
根據二叉查找樹的特點可以發現其最小值總是在最左邊的子樹,其最大值在最右邊的子樹。
// 遞歸實現
Position FindMin(SearchTree T)
{
if(T == NULL)
return NULL; // 考慮是空樹的情況
if(T->lch == NULL)
return T;
else
return FindMin(T->lch);
}
// 非遞歸實現
Position FindMax(SearchTree T)
{
if(T != NULL)
while(T->rch != NULL)
T = T->rch;
return T;
}
3.插入Inset操作
插入操作也比較簡單,像find一樣在樹中尋找插入的位置,將X插入該路徑最後的位置,如果X一存在,則說明也不用做
SearchTree Insert(SearchTree T, ElementType x)
{
if(T == NULL)
{
T = (struct treeNode *)malloc(sizeof(struct treeNode));
T->Element = x;
T->lch = T->rch = NULL;
}
else if(x < T->Element)
T->lch = Insert(T->lch, x);
else if(x > T->Element)
T->rch = Insert(T->rch, x);
else if(x == T->Element)
cout << "數據已存在!\n";
return T;
}
4.刪除Delete操作
刪除是比較複雜的操作,有多種情況要考慮的
1>如果要刪除的節點是葉子節點,則直接刪除就可以了
2>如果刪除的節點又一個兒子,就讓其父節點繞過這個字節就可以了
3>比較麻煩的是要刪除的節點又兩個子節點。我所採用的策略是將其右節點最小的數據來代替該節點的數據,然後將那個被代替的數據給刪除
SearchTree Delete(SearchTree T, ElementType x)
{
Position TemCell;
if(T == NULL)
cout << "沒有該數據,刪除失敗!\n";
else if(x < T->Element)
T->lch = Delete(T->lch, x);
else if(x > T->Element)
T->rch = Delete(T->rch, x);
else if(T->lch && T->rch) // 有連個子節點的情況
{
TemCell = FindMin(T->rch);
T->Element = TemCell->Element;
T->rch = Delete(T->rch, T->Element);
}
else // 有一個子節點或者沒有子節點的情況下
{
TemCell = T;
if(T->lch == NULL)
T = T->rch;
else if(T->rch == NULL)
T = T->lch;
free(TemCell);
cout << "數據刪除成功!\n";
}
return T;
}
圖示:完整代碼:
頭文件代碼:
// tree.h
// binary search tree
// 二叉搜索樹,也稱二叉查找樹,有序二叉樹
// 使二叉樹成爲二叉查找樹的性質是:
// 對於樹的每個節點x,其左子樹中所有關鍵字的值都小於x;
// 其右子樹中所有關鍵字的值都大於x。
// 由於樹的遞歸定義,所以通常是遞歸的操作,
// 因爲二叉查找樹的平均深度爲O(log N),所以一般不必擔心棧空間被用盡
// 二叉查找樹的聲明
#ifndef _TREE_H_
typedef int ElementType;
typedef struct treeNode *Position;
typedef struct treeNode *SearchTree;
struct treeNode
{
ElementType Element;
SearchTree lch;
SearchTree rch;
};
SearchTree MakeEmpty(SearchTree T);
Position Find(SearchTree T, ElementType x);
Position FindMin(SearchTree T);
Position FindMax(SearchTree T);
SearchTree Insert(SearchTree T, ElementType x);
SearchTree Delete(SearchTree T, ElementType x);
void PrintTree(SearchTree T);
void Destroy(SearchTree T);
#endif // _TREE_H_
實現部分:
// ree.cpp
// 二叉查找樹實現
#include <iostream>
#include <cstdio>
#include "tree.h"
using namespace std;
// 建立一棵空樹
// 利用遞歸將所有子樹的地址都釋放,返回一個空指針
SearchTree MakeEmpty(SearchTree T)
{
if(T != NULL)
{
MakeEmpty(T->lch);
MakeEmpty(T->rch);
delete T;
}
return T;
}
// 二叉查找樹的Find操作
// 比較x的值與當前節點的值,如果x小,就往左子樹查找,如果x大就往右子樹查找
// 如果沒有找到就返回空,找到就返回x的地址
Position Find(SearchTree T, ElementType x)
{
if(T == NULL)
return NULL;
if(T->Element > x)
return Find(T->lch, x);
else if(T->Element < x)
return Find(T->rch, x);
else
return T;
}
// 二叉查找樹的FindMin操作
// 根據二叉查找樹的特性,最左邊的子樹就是其最小值
// 遞歸實現
Position FindMin(SearchTree T)
{
if(T == NULL)
return NULL; // 考慮是空樹的情況
if(T->lch == NULL)
return T;
else
return FindMin(T->lch);
}
// 二叉查找樹的FindMax操作
// 根據二叉查找樹的特性,最右邊的子樹就是其最大值
// 非遞歸實現
Position FindMax(SearchTree T)
{
if(T != NULL)
while(T->rch != NULL)
T = T->rch;
return T;
}
// 二叉查找樹的Inset操作
// 像Find一樣遍歷查找。如果找到x,則給出提示,否則將x插入到遍歷的最後一點上
SearchTree Insert(SearchTree T, ElementType x)
{
if(T == NULL)
{
T = (struct treeNode *)malloc(sizeof(struct treeNode));
T->Element = x;
T->lch = T->rch = NULL;
}
else if(x < T->Element)
T->lch = Insert(T->lch, x);
else if(x > T->Element)
T->rch = Insert(T->rch, x);
else if(x == T->Element)
cout << "數據已存在!\n";
return T;
}
// 二叉查找樹的Delete的實現
// 分三種情況進行考慮:1.如果節點是一片葉子,則直接刪除就可以了
// 2.如果節點只有一個兒子,就繞過這個節點把這個節點刪除就可以
// 3.如果幾點有兩個節點就比較複雜了,一般的策略是找到其右子樹最小的數據
// 代替該節點的數據並遞歸的刪除那個被代替的節點
SearchTree Delete(SearchTree T, ElementType x)
{
Position TemCell;
if(T == NULL)
cout << "沒有該數據,刪除失敗!\n";
else if(x < T->Element)
T->lch = Delete(T->lch, x);
else if(x > T->Element)
T->rch = Delete(T->rch, x);
else if(T->lch && T->rch) // 有連個子節點的情況
{
TemCell = FindMin(T->rch);
T->Element = TemCell->Element;
T->rch = Delete(T->rch, T->Element);
}
else // 有一個子節點或者沒有子節點的情況下
{
TemCell = T;
if(T->lch == NULL)
T = T->rch;
else if(T->rch == NULL)
T = T->lch;
free(TemCell);
cout << "數據刪除成功!\n";
}
return T;
}
// 中序遍歷
void PrintTree(SearchTree T)
{
if(T != NULL)
{
PrintTree(T->lch);
cout << T->Element << " ";
PrintTree(T->rch);
}
}
// 銷燬二叉查找樹
void Destroy(SearchTree T)
{
if(T->lch != NULL)
Destroy(T->lch);
if(T->rch != NULL)
Destroy(T->rch);
free(T);
}
測試程序:
// usetree.cpp 於tree.cpp一起編譯
// 二叉查找樹測試程序
#include <iostream>
#include "tree.h"
using namespace std;
int main()
{
SearchTree T = NULL;
Position p;
int cmd;
MakeEmpty(T);
do
{
int x;
cout << "菜單:\t1.插入數據\n"
<< "\t2.查找數據\n"
<< "\t3.查找最小值\n"
<< "\t4.查找最大值\n"
<< "\t5.刪除數據\n"
<< "\t6.遍歷二叉查找樹\n"
<< "\t0.銷燬二叉查找樹並退出程序\n"
<< "輸入命令:";
cin >> cmd;
switch(cmd)
{
case 1:
cout << "請輸入插入的數據:";
cin >> x;
T = Insert(T, x);
break;
case 2:
cout << "請輸入插入的數據:";
cin >> x;
p =Find(T, x);
if(p != NULL)
cout << "數據存在!\n";
else
cout << "數據未找到!\n";
break;
case 3:
p = FindMin(T);
if(p != NULL)
cout << "最小值爲:" << p->Element;
else
cout << "當前爲空樹!";
cout << endl;
break;
case 4:
p = FindMax(T);
if(p != NULL)
cout << "最大值:" << p->Element;
else
cout << "當前爲空樹!";
cout << endl;
break;
case 5:
cout << "請輸入要刪除的數據:";
cin >> x;
T = Delete(T, x);
break;
case 6:
if(T != NULL)
PrintTree(T);
else
cout << "當前爲空樹!";
cout << endl;
break;
}
}while(cmd != 0);
Destroy(T);
return 0;
}