数据结构与算法--二叉排序树(C++实现)

前言

这里的二叉树是指二叉排序树。二叉树也是常用的数据结构。一棵二叉树由根节点(可以为空)和左右子树(也是二叉树)构成。通常情况下,二叉树的效率比线性链表高,因为二叉树上的查询、插入等操作即相当于二分法操作,时间复杂度为 O ( l o g 2 N ) O(log_{2} {N}) 。但二叉树也很容易失衡,极端情况下将退化为线性链表。本文用C++实现二叉树的算法,是C++、数据结构及算法学习的一个小练习。供大家参考。

二叉树上的操作

二叉树用递归定义,其上的操作基本也是基于递归定义的。编程比较方便。
1、遍历。二叉树上的遍历分为前序、中序和后序遍历。本文只讨论中序遍历。
2、搜索。二叉树的天生优势就在于搜索。相当于二分法查询。效率比较高。
3、插入。
4、删除。节点的删除是二叉树操作中比较难一点的。难点在于删除节点后新节点的选取以及要保持二叉树的顺序。

数据结构和算法描述

节点

节点(Node)分为两部分,数据和链接部分。数据部分存储数据,可以根据实际情况分为若干字段。本文数据部分分两个字段:key和data。key字段用于排序和查找节点。本文允许key重复,因此查找和删除节点要遍历所有相同key值的节点。data则存储节点的其它信息。链接部分是两个指针,分别指向左子树和右子树。C++代码如下:

struct Node {
    int key;
    string data;
    Node *lpt=nullptr, *rpt=nullptr;
    Node(int ky, const string &da, Node *lp=nullptr, Node *rp=nullptr) : key(ky), data(da), lpt(lp), rpt(rp) {}
};

算法描述

遍历

仅作中序遍历。前序和后续遍历算法从略。
(1)如果当前节点是空,返回;否则(2)。
(2)遍历左子树。
(3)访问/输出当前节点。
(4)遍历右子树。

搜索

(1)如果是空树,返回;否则(2)。
(2)如果给定参数小于当前节点的 k e y key 值,在左子树搜索;否则(3)。
(3)如果给定参数等于当前节点的 k e y key 值,命中,返回当前节点指针;否则(4)。
(4)在右子树搜索。

插入

(1)如果是空树,在当前插入新节点,否则(2)。
(2)如果新节点键值小于当前节点键值,在左子树插入新节点,否则(3)。
(3)在右子树插入新节点。

删除

(1)如果是空树,返回;否则(2)。
(2)如果参数小于当前节点键值,则在左子树中删除等于给定键值的节点;否则(3)。
(3)如果参数等于当前节点键值,命中。删除当前节点,分以下两种情况:
(3.1)如果当前节点左子树不是空,找到其左子树的最右边的节点(即循环搜索左子树的右节点,直到其右指针为空。),令此最右节点的右指针指向待删除节点右子树。选取待删除节点的左子树的根作为新的根(即以左子树的根取代待删除节点),删除待删除节点。递归删除当前节点右子树。
(3.2)如果当前节点左子树是空,选取待删除节点右子树的根作为新的根,删除待删除节点。递归删除当前节点右子树。
(4)如果不满足(3)的条件,即参数大于当前节点键值,则在右子树中删除等于给定键值的节点。

查找最大节点

(1)如果是空树,返回空指针;否则(2)。
(2)如果右子树非空,返回右子树的最大节点;否则(3)。
(3)返回当前节点指针。

查找最小节点

(1)如果是空树,返回空指针;否则(2)。
(2)如果左子树非空,返回左子树的最小节点;否则(3)。
(3)返回当前节点指针。

类的定义

二叉树定义成一个类,其上定义了构造函数、析构函数、插入、搜索、删除、判断空函数(isEmpty())等等操作,以及指向左右子树的指针。类的方法中,插入、搜索、遍历、删除等方法都有两个定义,一个为公有方法,供实例调用;另一个是私有方法(前面加下划线"_",以作区分),用于递归实现相应的操作。这样做的原因是公用方法应该尽量隐蔽节点信息。用户(实例)只通过键值(key)来访问数据,而不涉及数据存储的细节。当然,私有方法也可以和公有方法同名,而不必前面加下划线来区分。因为函数重载可以区分不同的函数调用。但文中为了避免混淆,还是将它们定义成不同的名字。具体C++代码如下:

struct Node {
    int key;
    string data;
    Node *lpt=nullptr, *rpt=nullptr;
    Node(int ky, const string &da, Node *lp=nullptr, Node *rp=nullptr) : key(ky), data(da), lpt(lp), rpt(rp) {}
};
class BinaryTree
{    
    public:
        BinaryTree();
        virtual ~BinaryTree();
        BinaryTree(const BinaryTree& other);
        BinaryTree& operator=(const BinaryTree& other);
        void insertNode(int ky, const string &da);
        void deleteNode(); // delete all
        void deleteNode(int ky); //delete where key==ky
        Node* searchNode(int ky);
        Node* searchMaxNode();
        Node* searchMinNode();
        void listTree();
        void listTree(int ky);        
        bool isEmpty();
    protected:        
    private:
        Node *dataPt=nullptr;
        void _deleteNode(Node* &ptr, const int ky);
        void _insertNode(Node* &ptr, int ky, const string &da);
        Node* _searchNode(Node *ptr, int ky);
        Node* _searchMaxNode(Node* ptr);
        Node* _searchMinNode(Node* ptr);
        void _listTree(Node *ptr);
        void _listTree(Node *ptr, int ky);
};

程序说明和清单

除了以上类和节点结构的定义外,作为实验定义了数据读取函数readData(filename),从数据文件中读入实验数据并生成二叉树。主程序则对二叉树的操作做了插入、遍历、删除等相应试验。

全部程序清单如下:

#include<iostream>
#include <fstream>
using namespace std;
struct Node {
    int key;
    string data;
    Node *lpt=nullptr, *rpt=nullptr;
    Node(int ky, const string &da, Node *lp=nullptr, Node *rp=nullptr) : key(ky), data(da), lpt(lp), rpt(rp) {}
};
class BinaryTree
{
    public:
        BinaryTree();
        virtual ~BinaryTree();
        BinaryTree(const BinaryTree& other);
        BinaryTree& operator=(const BinaryTree& other);
        void insertNode(int ky, const string &da);
        void deleteNode(); // delete all
        void deleteNode(int ky); //delete where key==ky
        Node* searchNode(int ky);
        Node* searchMaxNode();
        Node* searchMinNode();
        void listTree();
        void listTree(int ky);
        bool isEmpty();
    protected:
    private:
        Node *dataPt=nullptr;
        void _deleteNode(Node* &ptr, const int ky);
        void _insertNode(Node* &ptr, int ky, const string &da);
        Node* _searchNode(Node *ptr, int ky);
        Node* _searchMaxNode(Node* ptr);
        Node* _searchMinNode(Node* ptr);
        void _listTree(Node *ptr);
        void _listTree(Node *ptr, int ky);
};

/* public */
BinaryTree::BinaryTree()
{
    //ctor
    dataPt=nullptr;
}
BinaryTree::~BinaryTree()
{
    //dtor
    deleteNode();
}
BinaryTree::BinaryTree(const BinaryTree& other)
{
    //copy ctor
    dataPt=other.dataPt;
}
BinaryTree& BinaryTree::operator=(const BinaryTree& rhs)
{
    if (this == &rhs) return *this; // handle self assignment
    //assignment operator
    dataPt=rhs.dataPt;
    return *this;
}
void BinaryTree::insertNode(int ky, const string &da)
{
    _insertNode(dataPt, ky, da);
    return;
}
void BinaryTree::deleteNode() // delete all
{
    while(!isEmpty()) {
        deleteNode(dataPt->key);
    }
    return;
}
void BinaryTree::deleteNode(int ky) // delete the nodes whose key=ky
{
    _deleteNode(dataPt, ky);
    return;
}
bool BinaryTree::isEmpty()
{
    return (nullptr==dataPt);
}
Node* BinaryTree::searchNode(int ky)
{
    return _searchNode(dataPt, ky);
}
Node* BinaryTree::searchMaxNode()
{
    return _searchMaxNode(dataPt);
}
Node* BinaryTree::searchMinNode()
{
    return _searchMinNode(dataPt);
}
void BinaryTree::listTree() //List the whole tree in-order.
{
    _listTree(dataPt);
    return;
}
void BinaryTree::listTree(int ky) // List the special key in-order
{
    _listTree(dataPt,ky);
    return;
}
/* private */
void BinaryTree::_deleteNode(Node* &ptr, const int ky)
{
    Node *p=nullptr;
    if (nullptr==ptr) return; //empty tree

    if ( ky<ptr->key ) _deleteNode(ptr->lpt,ky); //delete in left side
    else {
        if ( ky==ptr->key ) { //delete current node
            if (nullptr!=ptr->lpt) {
                p=ptr->lpt;
                while ( nullptr!=p->rpt ) p=p->rpt; // move to find a new root
                p->rpt=ptr->rpt;
                p=ptr;
                ptr=ptr->lpt; // new root
                delete p;
                _deleteNode(ptr->rpt,ky); //continue to delete the rest same key
            }
            else {
                p=ptr;
                ptr=ptr->rpt;
                delete p;
                _deleteNode(ptr,ky); //continue to delete the rest same key
            }
        }
        else _deleteNode(ptr->rpt,ky); //delete in right side
    }
    return;
}
void BinaryTree::_insertNode(Node* &ptr, int ky, const string &da)
{
    if (nullptr==ptr) { //insert data
        ptr=new Node(ky,da);
    }
    else if (ky<ptr->key) { //insert left
            _insertNode(ptr->lpt, ky, da);
    }
    else { //insert right
        _insertNode(ptr->rpt, ky, da);
    }
    return;
}

Node* BinaryTree::_searchNode(Node *ptr, int ky)
{
    if (nullptr!=ptr) {
        if (ky==ptr->key) return ptr;
        else if (ky < ptr->key) return _searchNode(ptr->lpt,ky);
            else return _searchNode(ptr->rpt,ky);
    }
    else return nullptr;
}
Node* BinaryTree::_searchMaxNode(Node *ptr)
{
    if (nullptr==ptr) return nullptr;
    if ( nullptr!=ptr->rpt ) return _searchMaxNode(ptr->rpt);
    return ptr;
}
Node* BinaryTree::_searchMinNode(Node *ptr)
{
    if (nullptr==ptr) return nullptr;
    if ( nullptr!=ptr->lpt ) return _searchMinNode(ptr->lpt);
    return ptr;
}
void BinaryTree::_listTree(Node *ptr)
{
    if (nullptr!=ptr) {
        if (nullptr!=ptr->lpt) _listTree(ptr->lpt);
        cout<<"("<<ptr->key<<", "<<ptr->data<<") ";
        if (nullptr!=ptr->rpt) _listTree(ptr->rpt);
    }
    return;
}
void BinaryTree::_listTree(Node *ptr, int ky)
{
    Node *p;
    p=_searchNode(ptr, ky);
    if (nullptr!=p) {
        if (nullptr!=p->lpt) _listTree(p->lpt,ky);
        if (ky==p->key) cout<<"("<<p->key<<", "<<p->data<<") ";
        if (nullptr!=p->rpt) _listTree(p->rpt,ky);
    }
    return;
}
BinaryTree bt;
int readData(const string &fn)
{
    int readItems=0, k;
    string str;
    ifstream dataFile(fn);
    if (!dataFile)
    {
        cout << "404, file not found.";
        exit (1);
    }
    while (dataFile >> k >> str)
    {
        bt.insertNode(k, str); // insert and sorted by key
        readItems++;
    }
    dataFile.close();
    return readItems;
}
int main()
{
    string fn="dataFile.txt";
    cout<<"\nRead data ... "<<readData(fn)<<" item(s) read."<<endl;
    cout<<"\nList data ... "<<endl;
    bt.listTree();
    cout<<endl<<"The max nodes: ";
    bt.listTree(bt.searchMaxNode()->key);
    cout<<endl<<"The min nodes: ";
    bt.listTree(bt.searchMinNode()->key);
    for (int k=0; k!=30; k++) {
        cout<<"\nSearch "<<k<<" ... ";
        bt.listTree(k);
    }
    cout<<"\nDelete nodes ...\n";
    /* delete nodes one by one */
    for (int k=20; k!=6; k--) {
        cout<<"\nSearch "<<k<<" ... ";
        bt.listTree(k);
        cout<<"\nDelete "<<k<<" ... "<<endl;
        bt.deleteNode(k);
        bt.listTree();
    }
    return 0;
}

小结

总的来说程序不算复杂。二叉树是递归定义,各种操作基本上定义好了就不难编出代码。这也是递归引人入胜的地方。相对来说节点的删除复杂一点。要琢磨一下。

供试验用的数据文件,datafile.txt。内容如下:

24 Xyz
2 Bcd
3 Cde
1 Abc
4 Def
10 Exa
22 Vwx
7 Ghi
8 Hij
9 Ijk
1 A
10 Jkl
5 Efg
24 Xz
11 Klm
12 Lmn
26 Z
13 Mno
25 Sam
1 Ab
14 Nop
6 Fgh
15 Opq
16 Pqr
1 bc
18 Rst
19 Stu
20 Tuv
24 Xy
21 Txt
17 Qrs
21 Uvw
23 Wxy
25 Yz
原创文章 8 获赞 9 访问量 4万+

猜你喜欢

转载自blog.csdn.net/yang_deyuan/article/details/105459647