网上能够查到的数据结构相关资料,多是用C语言代码实现。
C语言是面向过程的,C++是面向对象的,尽管它们有诸多的相同之处,尽管把C语言实现BST的代码拷贝在C++中差不多就能pass了。但既然学习了C++,就尽可能使用面向对象的思想,将函数封装在类中,而不是在main函数外面写了一个又一个的函数。那样的话,即使是用了一些C++的语法,整个程序也可以说是标准的C语言式。
BST,Binary Search Tree,是一种很典型并且广泛使用的数据结构。它的特点是左边节点的数据值比该节点小,右边节点的数据值比该节点大。利用这一点,在AVL Tree(这是BST的升级版,以后再写)中可以实现O(log n)的查找效率。
在这边,BSTree类提供了插入节点、删除节点、搜索节点、判断空树,以及四中遍历方式等功能(修改?搜索出来就可以改了)
其中建树、添加节点、遍历等,需要注意的点都不多,唯独删除节点。
- 因为删除的节点只能是目标节点一个,而对其下的子树(若有),所有的节点必须保留。
- 而且删除目标节点之后,还需要保证该二叉树还是BST。
至于删除目标节点,以及其下所有子孙节点的函数,实现起来比较简单。有需要的慕友可自行增加这一函数。
代码设计:
- 节点类。一些函数在节点类中比较容易实现,把这个任务交给它。还有一些辅助函数,删除节点函数所需要的,也在节点类中设计。
- 树类。提供建一颗空树、判空、根据数据增加节点、查找、删除的功能,以及前序、中序、后序、层序,四种遍历。
- 测试
BSTree类的头文件BSTree.h以及实现文件BSTree.cpp如下:
#pragma once
#include "Node.h"
class BSTree {
public:
BSTree(); //建一颗空树。
bool isEmpty(); //判断树是否为空。
void insertNode(ElemType &elem); //传入元素值,插入一个节点
bool deleteNode(ElemType &elem); //删除给定元素值的节点。
Node* searchNode(ElemType &elem); //查找给定元素值的节点,返回其地址。
void preOrderTaversal(); //前序、中序、后序以及层序遍历。
void inOrderTaversal();
void postOrderTaversal();
void levelOrderTaversal();
private:
Node *rootPtr;
};
#include "BSTree.h"
#include <iostream>
BSTree::BSTree() {
this->rootPtr = NULL;
}
bool BSTree::isEmpty() {
return NULL == rootPtr;
}
void BSTree::insertNode(ElemType &elem) {
if (isEmpty())
rootPtr = new Node(elem);
//若树非空,插入一个新节点的操作在Node类中更容易实现,所以把这个任务交给Node类。
else
rootPtr->addNode(elem);
}
bool BSTree::deleteNode(ElemType &elem) {
Node *objNode = searchNode(elem);
if (NULL == objNode) return false;
//树根节点的情况比较特殊,单独考虑。
if (objNode == rootPtr && !(rootPtr->leftChild != NULL && rootPtr->rightChild != NULL)) {
if (rootPtr->leftChild == NULL && rootPtr->rightChild == NULL)
rootPtr = NULL;
else if (rootPtr->leftChild == NULL) { //rootPtr->rightChild != NULL
rootPtr = rootPtr->rightChild;
rootPtr->parent = NULL;
}
else if (rootPtr->rightChild == NULL) {
rootPtr = rootPtr->leftChild;
rootPtr->parent = NULL;
}
delete objNode;
//树根有两个子节点的情况可做一般性处理。
}
else
objNode->eraseNode();
return true;
}
Node* BSTree::searchNode(ElemType &elem) {
if (isEmpty()) return NULL;
//若树非空,搜索指定元素值的节点在Node类中也更容易实现,同样将这个任务交给Node类。
return rootPtr->findNode(elem);
}
void BSTree::preOrderTaversal() {
if (!isEmpty()) rootPtr->traversalByPreOrder();
}
void BSTree::inOrderTaversal() {
if (!isEmpty()) rootPtr->traversalByInOrder();
}
void BSTree::postOrderTaversal() {
if (!isEmpty()) rootPtr->traversalByPostOrder();
}
void BSTree::levelOrderTaversal() {
if (!isEmpty()) rootPtr->traversalByLevelOrder();
}
Node类的头文件和实现文件如下:
#pragma once
typedef int ElemType;
class Node {
public:
Node(ElemType data = 0);
void addNode(ElemType &elem);
Node* findNode(ElemType &elem); //返回给定元素值的节点的地址,若找不到,返回NULL。
Node* findMin(); //返回以此节点为根节点的树的元素值最小的节点。
Node* findMax();
/*
删除当前节点,若有子节点,保留,并保证依然是BST结构。
在BSTree类中,特别处理了树根节点的情况。
*/
void eraseNode();
void traversalByPreOrder(); //前序、中序、后序以及层序遍历以此节点为根节点的树。
void traversalByInOrder();
void traversalByPostOrder();
void traversalByLevelOrder();
public:
ElemType data;
Node *parent;
Node *leftChild;
Node *rightChild;
};
#include "Node.h"
#include <iostream>
#include <queue>
using namespace std;
Node::Node(ElemType data /* = 0 */) {
this->data = data;
parent = leftChild = rightChild = NULL;
}
void Node::addNode(ElemType &elem) {
if (elem < data) {
if (leftChild == NULL) {
leftChild = new Node(elem);
leftChild->parent = this;
}
else
leftChild->addNode(elem);
}
else if (elem > data) {
if (rightChild == NULL) {
rightChild = new Node(elem);
rightChild->parent = this;
}
else
rightChild->addNode(elem);
}
//do nothing when 'elem == data'.
}
Node* Node::findNode(ElemType &elem) {
if (elem == data) return this;
else if (elem < data && leftChild != NULL)
return leftChild->findNode(elem);
else if (elem > data && rightChild != NULL)
return rightChild->findNode(elem);
return NULL;
}
Node* Node::findMin() {
Node *tmp = this;
while (tmp->leftChild != NULL)
tmp = tmp->leftChild;
return tmp;
}
Node* Node::findMax() {
Node *tmp = this;
while (tmp->rightChild != NULL)
tmp = tmp->rightChild;
return tmp;
}
/*
1. 两个孩子节点都存在的情况:在右子树找最小值,或在左子树找最大值,
填充根节点,再删掉找出来的节点(这个节点顶多只有一个子节点,变成简单的情况)。
2. 有一个孩子节点,或者没有孩子节点的情况。
*/
void Node::eraseNode() {
if (leftChild != NULL && rightChild != NULL) {
Node *tmp = leftChild->findMax();
data = tmp->data;
tmp->eraseNode();
}
else {
if (leftChild == NULL) { //左孩子节点是NULL,有右孩子节点,或者没有子节点。
if (this == parent->leftChild)
parent->leftChild = rightChild;
else
parent->rightChild = rightChild;
if (rightChild != NULL) rightChild->parent = parent;
}
else { //有左孩子,右孩子为NULL
if (this == parent->leftChild)
parent->leftChild = leftChild;
else
parent->rightChild = leftChild;
leftChild->parent = parent;
}
}
}
void Node::traversalByPreOrder() {
cout << data << " ";
if (leftChild != NULL)
leftChild->traversalByPreOrder();
if (rightChild != NULL)
rightChild->traversalByPreOrder();
}
void Node::traversalByInOrder() {
if (leftChild != NULL)
leftChild->traversalByInOrder();
cout << data << " ";
if (rightChild != NULL)
rightChild->traversalByInOrder();
}
void Node::traversalByPostOrder() {
if (leftChild != NULL)
leftChild->traversalByPostOrder();
if (rightChild != NULL)
rightChild->traversalByPostOrder();
cout << data << " ";
}
void Node::traversalByLevelOrder() {
queue<Node *> que;
que.push(this);
while (!que.empty()) {
Node *tmp = que.front();
que.pop();
cout << tmp->data << " ";
if (tmp->leftChild != NULL)
que.push(tmp->leftChild);
if (tmp->rightChild != NULL)
que.push(tmp->rightChild);
}
}
测试文件如下:
#include "BSTree.h"
#include <iostream>
using namespace std;
int main(void) {
BSTree *bst = new BSTree;
cout << boolalpha << "空树?" << bst->isEmpty() << endl;
int intArr[] = { 6, 5, 8, 4, 7, 9, 2, 1, 3 };
for (int i = 0; i < sizeof(intArr) / sizeof(int); i++) {
bst->insertNode(intArr[i]);
}
/*
建起来的这棵树长这模样:
6
/ \
5 8
/ / \
4 7 9
/
2
/ \
1 3
*/
cout << boolalpha << "空树?" << bst->isEmpty() << endl;
//测试 deleteNode() 函数
int elem = 10;
bst->deleteNode(elem);
elem = 3;
bst->deleteNode(elem);
elem = 4;
bst->deleteNode(elem);
elem = 8;
bst->deleteNode(elem);
elem = 6;
bst->deleteNode(elem);
bst->preOrderTaversal();
cout << "先序遍历" << endl;
bst->inOrderTaversal();
cout << "中序遍历" << endl;
bst->postOrderTaversal();
cout << "后序遍历" << endl;
bst->levelOrderTaversal();
cout << "层序遍历" << endl;
delete bst;
system("pause");
return 0;
}
慕友也可将之修改成一个类模板,但要切记,许多编译器不支持类模板的头文件和实现文件分离,要把它们都放在头文件中。
热门评论
排版都不知道怎么搞,纠结o(╯□╰)o。
要我用一块又一块的代码框框起来吗?