数据结构:(5)二叉树

数据结构:字符串、栈、队列、数组、链表、二叉树
c++中STL常用的数据结构:
string、stack、queue、deque、vector、list、map、iterators

数据结构:(5)二叉树

目录

1、二叉树知识

树存在先序遍历、中序遍历、后序遍历三种遍历方式。

1.1二叉树的概念

树是一些节点的集合,节点之间用边链接,节点之间不能有环路。上层的节点称为父节点,下层节点称为子节点。最上层的节点称为根节点。

二叉树是特殊的树。对于每个节点而言,与之直接相连的子节点不能超过两个(可以为0)。左边的子节点称为左子树,右边的子节点称为右子树。如下图就是一颗二叉树:
这里写图片描述
与树相关的一些概念:

没有任何子节点的节点称为树叶,或叶子节点。

深度:对于任意节点N,其深度指的是从根节点到N的唯一路径的长。根的深度为0。深度最深的叶子节点的深度为树的深度。可以理解为:树根是一个入口,离树根越远,就越深。如上图:A、B、C的深度为1,D、E的深度为2。

高: 对于任意节点N,从N到一片树叶的最远路径的长为N的高度。(只可以从从上到下不能经过从下到上的节点。)树叶的高为0。树的高为根的高。如上图,根的高度为2。A的高度为1,其他节点高度为0。

1.2二叉树的应用和时间复杂度

二叉树是一种常见的数据结构,常常用于查找,也运用于unix等常见操作系统的文件系统中。c++STL(标准模板库)中的set和map也使用二叉树中的红黑树实现。

  • 二叉树的查找思想基于:在二叉树中,对于任意节点N,左子树中的所有项的值不大于节点N中存储的值,右子树中的所有项的值不小于节点N中存储的值。如下图:

这里写图片描述
这样,在查找时,只需要不断比较需要查找的x与N的大小,若小于N中的值,只需要搜索左子树,若大于N中的值,只需要搜索右子树。这样每次就能缩小搜索的范围。经过证明,普通二叉树的平均时间复杂度是O(LogN)。

看到这里,我们发现其实二叉树的搜索思想和二分查找一致,每次不断的减少搜索范围。但是二者之间还是有区别的。

对于二分查找而言,每次的时间复杂度不会超过O(LogN)。但是对于二叉树,搜索时间的复杂度取决于树的形状。在最坏情况下可能达到O(N)。如下图,如果要找到10,则要查找5次。

这里写图片描述
那我们为什么还要使用二叉树而不直接使用二分查找来代替?

这是因为,二分查找一般基于数组,如果需要插入或删除数据,则会带来很大的开销。因为每次插入或者删除数据需要将改变节点之后的数据往后挪或者往前挪。但是对于二叉树而言,只需要改变一下指向下一个节点的指针就可以很方便的实现插入或者删除。而且一些特殊的二叉树如红黑树可以保证查找的最坏复杂度不超过O(LogN)。

所以,如果是对于静态数据,不需要改变的数据而言,采用数组存储,使用二分查找比较好。而对于动态数据,需要频繁插入或者删除数据的,采取二叉树存储是较好的。

1.3二叉树的插入

思路:插入数据x,从根节点开始,不断比较节点与x的大小。若x小于节点,下一次比较x与节点的左子树,反之,比较x与节点的右子树。直到遇到一个空的节点,插入数据。(我们不考虑插入重复数据) 。如下图:
这里写图片描述
过程:比较4与7,4<7,再比较4与7的左子树6,4<6,比较4与6的左子树3,4>3,比较4与3的右子树,为空,插入4。

代码:

template< typename T>
void BinaryTree<T>::insert(const T &theElement, BinaryNode * &t ) {
    if ( nullptr == t ){
        t = new BinaryNode (theElement);
    } else if ( theElement < t->element ) {
          insert( theElement, t->leftNode );
    } else if ( theElement > t->element ) {
          insert ( theElement, t->rightNode );
    } else {//重复的数据不添加到树中
    }
};

1.4二叉树的查找

思路:与插入类似,不断比较插入值与节点的值。带代码如下:

template< typename T>
bool BinaryTree<T>::isFind(const T &theElement, BinaryNode * t ) const {
    if ( nullptr == t ){
        return false;
    } else if ( theElement < t->element ) {
        return isFind( theElement, t->leftNode );
    } else if ( theElement > t->element ) {
         return isFind ( theElement, t->rightNode );
    } else { //匹配
        return true;
    }
};

1.5二叉树的遍历

二叉树的遍历有三种方式:

  前序遍历(DLR):首先访问根结点。然后如果有子树,则对于左孩子也采用DLR的遍历规则。没有就忽略。然后如果有右子树,则对右子树也采用DRL的遍历规则。没有就忽略。

  中序遍历(LDR):首先访问根节点的左子树(对左子树也采用LDR),没有则忽略。再访问根节点。最后则对右子树也采用LDR的遍历规则,没有就忽略。

  后序遍历:首先访问根节点的左子树(对左子树也采用LRD),没有则忽略。再对右子树也采用LRD的遍历规则,没有就忽略。最后则对右子树也采用LRD的遍历规则,没有就忽略。

三种遍历方式其实是根据根节点的访问顺序命名的。根最先方位为前序,次之访问为中序遍历。最后访问为后序遍历。

先序遍历图1的二叉树,结点的访问顺序为: e→b→a→d→c→f→g

中序遍历图1的二叉树,结点的访问顺序为:a→b→c→d→e→f→g

后序遍历图1的二叉树,结点的访问顺序为: a→c→d→b→g→f→e
这里写图片描述

  • 这里采用递归方式实现:

前序遍历:

template< typename T>
void BinaryTree<T>::preOrder( BinaryNode *bNode ) const {
    if( nullptr != bNode ) {
        std::cout << bNode->element << " " ;
        preOrder(bNode->leftNode);
        preOrder(bNode->rightNode);
    }

};

中序遍历:

template< typename T>
void BinaryTree<T>::inOrder( BinaryNode *bNode ) const {
    if( nullptr != bNode ) {
        inOrder(bNode->leftNode);
        std::cout << bNode->element << " " ;
        inOrder(bNode->rightNode);
    }
};

后序遍历:

template< typename T>
void BinaryTree<T>::postOrder( BinaryNode *bNode ) const {
    postOrder(bNode->leftNode);
    postOrder(bNode->rightNode);
    std::cout << bNode->element << " " ;
};

1.6二叉树的删除

二叉树的删除需要分三种情况考虑。

第一种,删除节点是树叶,则直接删除;

第二种是被删除的节点只有一个子节点,此时只需要将删除节点的上一个节点的指向该节点的指针指向该节点唯一的子节点;

第三种是被删除的节点有两个子节点,这种情况是最麻烦的。
我们采用的思想是将该节点的该节点右子树中最小的一个节点的值覆盖该节点中的值,然后再删除该节点的右子树中的最小的那个子节点。因为,该节点的右子树中的最小的那个子节点的值刚好大于被删除节点的左子树中所有的值,又小于被删除节点的右子树中所有的值。最小的那个子节点不可能有左子树,不然它就不是最小的节点,删除该节点就转换为删除一个只有一个子节点的节点,即第二种情况。

(1)第二种情况(删除节点7)
这里写图片描述
这里写图片描述

(2) 第三种情况(删除节点5)
其实是将5的那个节点赋值为6,然后删除节点6.
这里写图片描述
这里写图片描述

代码:

template< typename T>
void BinaryTree<T>::remove(const T &theElement, BinaryNode * &t ) {
    if( nullptr == t ) {
        return;
    } else {
        if ( theElement < t->element) {
            remove(t->leftNode);
        } else if ( theElement > t->element ) {
            remove (t->rightNode);
        } else if  (nullptr != t->leftNode && nullptr != t->rightNode ) {  //需要删除的节点两个儿子

             t->element = findMin(t->rightNode)->element;
            remove(t->element, t->rightNode);
        } else {
            BinaryNode * oldNode = t;
            t = ( nullptr!= t->leftNode) ? t->leftNode : t->rightNode;
            delete oldNode;
        }
    }
};
template< typename T>
typename BinaryTree<T>::BinaryNode * BinaryTree<T>::findMin(BinaryNode *bNode) const {
    if ( nullptr!= bNode) {
        while( nullptr != bNode->leftNode) {
            bNode = bNode->leftNode;
        }
    }

    return bNode;
}

参考博客:http://blog.csdn.net/u014182411/article/details/69831492
整个二叉树的工程文件在github上可以查阅:
https://github.com/yuanzoudetuzi/binaryTree

2、二叉树基本操作

二叉树实现
1.创建二叉树
2.递归输出二叉树
2.1递归先序输出
2.2递归中序输出
2.3递归后序输出
3.非递归输出
3.1非递归先序输出
3.2非递归中序输出
3.3非递归后序输出
4.层次遍历二叉树
5.求树高
6.求树叶子节点
7.按值查找对应节点,输出左孩子结点值和右孩子结点值
8.计算所有节点数

参考博客:http://blog.csdn.net/j_anson/article/details/49888005
编译有错误!

/* 
二叉树实现 
    1.创建二叉树 
    2.递归输出二叉树 
        2.1递归先序输出 
        2.2递归中序输出 
        2.3递归后序输出 
    3.非递归输出 
        3.1非递归先序输出 
        3.2非递归中序输出 
        3.3非递归后序输出 
    4.层次遍历二叉树 
    5.求树高 
    6.求树叶子节点 
    7.按值查找对应节点,输出左孩子结点值和右孩子结点值 
    8.计算所有节点数 
*/  
#include<iostream>  
#include<string>  
#include<stack>  
#include<deque>  
#include<fstream>  
using namespace std;  

//const int MAX_N = 100;  
//数据节点  
class Node  
{  
public:  
    char data;//数据  
    class Node *lchild;//左节点  
    class Node *rchild;//右节点  
};  

//二叉树  
class Tree  
{  
public:  
    Tree(){}  
    ~Tree(){}  

    //构建二叉树  
    void Create(string name)  
    {  
        ifstream readfile;  
        string str;  
        readfile.open(name);  
        if (readfile.is_open())  
        {  
            getline(readfile, str);//读取一行  
        }  
        readfile.close();  
        CreateNode(str);//构建二叉树  
    }  


    //先序遍历非递归算法  
    void Disp()  
    {  
        if (t == NULL)  
        {  
            return;  
        }  
        stack<Node *> m_stack;//定义栈  
        m_stack.push(t);  
        while (!m_stack.empty())  
        {  
            Node *p = m_stack.top();//赋值一份当前双亲节点  
            cout << p->data << ends;  
            m_stack.pop();  
            if (p->rchild)//先存储右子树,确保先输出左子树  
            {  
                m_stack.push(p->rchild);  
            }  
            if (p->lchild)//后存储左子树  
            {  
                m_stack.push(p->lchild);  
            }  
        }  

    }  

    //非递归中序遍历二叉树  
    void DispMid()  
    {  
        if (t == NULL)  
        {  
            return;  
        }  
        Node *p = t;  
        stack<Node *>m_stack;  
        while (p != NULL || !m_stack.empty())  
        {  
            while (p != NULL)//一路直走至左下角  
            {  
                m_stack.push(p);  
                p = p->lchild;  
            }  
            if (!m_stack.empty())  
            {  
                p = m_stack.top();//备份当前栈顶地址  
                m_stack.pop();  
                cout << p->data << ends;  
                p = p->rchild;  
            }  
        }  
    }  

    //非递归后序遍历二叉树  
    void DispBehid()  
    {  
        if (t == NULL)  
        {  
            return;  
        }  
        Node *pre = NULL, *p = t;  
        stack<Node *>m_stack;  
        while (p != NULL || !m_stack.empty())  
        {  
            while (p != NULL)//一路直走至左下角  
            {  
                m_stack.push(p);  
                p = p->lchild;  
            }  
            p = m_stack.top();  
            //右子树为空或者已访问,输出当前节点  
            if (p->rchild == NULL || p->rchild == pre)  
            {  
                cout << p->data << ends;  
                pre = p;//将当前结点地址赋值pre作为下一次判断标志,防止重复访问  
                m_stack.pop();  
                p = NULL;//p赋值空以便访问右子树  
            }  
            else  
            {  
                p = p->rchild;//访问子树的右子树  
            }  
        }  
    }  

    //层次遍历  
    void level_display()  
    {  
        if (t == NULL)  
        {  
            return;  
        }  
        deque<Node *>m_qu;//定义队列  
        m_qu.push_back(t);//树根入队列  
        while (!m_qu.empty())  
        {  
            Node *p = m_qu.front();//拷贝当前对头  
            cout <<p->data << ends;//输出  
            m_qu.pop_front();  
            if (p->lchild)//左孩子入队列  
            {  
                m_qu.push_back(p->lchild);  
            }  
            if (p->rchild)//右孩子入队列  
            {  
                m_qu.push_back(p->rchild);  
            }  
        }  
    }  

    //递归先序遍历输出二叉树  
    void display()  
    {  
        cout << "递归先序:";  
        output(t);  
        cout << endl;  
    }  

    //递归中序遍历输出二叉树  
    void displayMid()  
    {  
        cout << "递归中序:";  
        outputMid(t);  
        cout << endl;  
    }  

    //递归后序遍历输出二叉树  
    void displayBhind()  
    {  
        cout << "递归后序";  
        outputBhind(t);  
        cout << endl;  
    }  
    //二叉树高度  
    void Height()  
    {  
        int height = get_height(t);  
        cout << "Height: " << height << endl;  
    }  

    //输出叶子节点值  
    void display_leaf()  
    {  
        cout << "Leaves: ";  
        output_leaf(t);  
        cout << endl;  
    }  

    //查找二叉树中值data域为elem的节点  
    void find_node(char elem)  
    {  
        Node *res = NULL;  
        res = find_node(t, elem, res);  
        if (res != NULL)  
        {  
            cout << "nice." << endl;  
            if (res->lchild)  
            {  
                cout << "left child:";  
                cout << leftchild(res)->data << endl;  
            }  
            if (res->rchild)  
            {  
                cout << "right child:";  
                cout << rightchild(res)->data << endl;  
            }  
        }  
        else  
        {  
            cout << "NO." << endl;  
        }  
    }  

    //计算节点数  
    void nodes_count()  
    {  
        int sum;  
        if (t == NULL)//若为空,则0个节点  
        {  
            sum = 0;  
        }  
        else  
        {  
            sum = node_count(t);  
            cout << "Total Nodes:" << sum + 1 << endl;  
        }  
    }  
private:  
    Node *t;  

    //构建二叉树  
    void CreateNode(string str)  
    {  
        stack<Node *> m_stack;  
        Node *p;  
        int k;  
        while (str.length() != 0)  
        {  
            //若当前为'(',将双亲节点推入栈,下一位存储的p值作为左节点处理  
            if (str[0] == '(')  
            {  
                m_stack.push(p); k = 1;  
            }  
            //为右括号则栈顶退出一位  
            else if (str[0] == ')')  
            {  
                m_stack.pop();  
            }  
            //为',',则下一个字符作右节点处理  
            else if (str[0] == ',')  
            {  
                k = 2;  
            }  
            //存储值用作双亲结点  
            else  
            {  
                p = (Node *)malloc(sizeof(Node));  
                p->data = str[0];  
                p->lchild = p->rchild = NULL;  
                //树根为空时,将第一个节点作为树根并赋值给私有成员变量  
                if (t == NULL)  
                {  
                    t = p;  
                }  
                //树根不为空  
                else  
                {  
                    if (k == 1)//作为左节点处理,将栈中双亲节点的左指针指向当前节点  
                    {  
                        m_stack.top()->lchild = p;  
                    }  
                    else//作为右节点处理  
                    {  
                        m_stack.top()->rchild = p;  
                    }  
                }  
            }  
            //重构串,除去首字符,并将串长度减小1  
            str.assign(str.substr(1, str.length() - 1));  
        }  
    }  

    //递归先序遍历输出二叉树  
    void output(Node *t)  
    {  
        if (t != NULL)//当树根不为空时  
        {  
            cout << t->data;//输出  
            if (t->lchild != NULL || t->rchild != NULL)//左/右结点不为空时递归到下一层  
            {  
                cout << "(";  
                output(t->lchild);  
                if (t->rchild != NULL)//当左节点遍历结束后,左节点递归返回一层,递归右节点  
                {  
                    cout << ",";  
                }  
                output(t->rchild);  
                cout << ")";  
            }  
        }  
    }  

    //递归中序遍历二叉树  
    void outputMid(Node *t)  
    {  
        if (t == NULL)//空则返回  
        {  
            return;  
        }  
        else  
        {  
            cout << "(";  
            outputMid(t->lchild);//递归左孩子节点  
            if (t->rchild != NULL)  
            {  
                cout << ",";  
            }  
            cout << t->data;//输出  
            outputMid(t->rchild);//递归右孩子结点  
            cout << ")";  
        }  
    }  

    //递归后序遍历输出二叉树  
    void outputBhind(Node *t)  
    {  
        if (!t)//空则返回  
        {  
            return;  
        }  
        else  
        {  
            cout << "(";  
            outputBhind(t->lchild);//递归左孩子节点  
            if (t->rchild != NULL)  
            {  
                cout << ",";  
            }  
            outputBhind(t->rchild);//递归右孩子结点  
            cout << t->data;//输出  
            cout << ")";  
        }  
    }  
    //求树高  
    int get_height(Node *t)  
    {  
        int leftheight, rightheight;  
        if (t == NULL)//递归至不存在子节点时返回0  
        {  
            return 0;  
        }  
        else  
        {  
            leftheight = get_height(t->lchild);//递归求左子树高度  
            rightheight = get_height(t->rchild);//递归其右子树高度  
            return leftheight > rightheight ? leftheight+1 : rightheight+1;//递归返回时返回最大值  
        }  
    }  

    //查找左节点  
    Node *leftchild(Node *p)  
    {  
        return p->lchild;  
    }  

    //查找右节点  
    Node *rightchild(Node *p)  
    {  
        return p->rchild;  
    }  

    //输出叶子节点  
    void output_leaf(Node *t)  
    {  
        if (t != NULL)//树根不为空时  
        {  
            //当前节点没有子节点时输出节点数据  
            if (t->lchild == NULL&&t->rchild == NULL)  
            {  
                cout << t->data << ends;  
            }  
            output_leaf(t->lchild);//递归左子树  
            output_leaf(t->rchild);//递归右子树  
        }  
    }  

    //查找二叉树中值data域为elem的节点  

    Node * find_node(Node *t, char elem, Node *res = NULL)  
    {  
        //Node *res = NULL;  
        if (t == NULL)//若当前节点为空,则返回结束  
        {  
            return NULL;  
        }  
        else  
        {  
            if (t->data == elem)//若找到值,返回地址  
            {  
                //res = t;  
                return t;  
            }  
            else  
            {  
                if (res == NULL)//若保存结果的指针不为空,则递归查找左节点  
                {  
                    res = find_node(t->lchild, elem, res);  
                }  
                if (res == NULL)//若保存结果的指针不为空,且左节点为搜索到,则递归查找右节点  
                {  
                    res = find_node(t->rchild, elem, res);  
                }  
            }  
            return res;  
        }  
    }  

    //计算节点数  
    int node_count(Node *t)  
    {  
        int lcount = 0, rcount = 0;  
        if (t == NULL)//空则返回  
        {  
            return 0;  
        }  
        else  
        {  
            if (t->lchild != NULL)//遍历左孩子节点  
            {  
                lcount = node_count(t->lchild);  
                lcount += 1;  
            }  
            if (t->rchild != NULL)//遍历右孩子节点  
            {  
                rcount = node_count(t->rchild);  
                rcount += 1;  
            }  
            return lcount + rcount;//返回当前左右孩子节点数  
        }  
    }  
};  


int main()  
{  
    Tree m_tree;  
    m_tree.Create("data");  
    m_tree.display();//递归先序输出  
    m_tree.displayMid();//递归中序输出  
    m_tree.displayBhind();//递归后序输出  
    m_tree.Height();//树高  
    m_tree.display_leaf();//叶子节点  
    cout << "非递归先序:";  
    //cout << "Fir:";  
    m_tree.Disp();//非递归先序遍历  
    cout << endl;  
    cout << "非递归中序:";  
    //cout << "Mid:";  
    m_tree.DispMid();//非递归中序遍历  
    cout << endl;  
    cout << "非递归后序:";  
    //cout << "Bac:";  
    m_tree.DispBehid();//非递归后序遍历  
    cout << endl;  
    cout << "层次遍历:";  
    m_tree.level_display();//层次遍历  
    cout << endl;  
    cout << "Input element:";  
    char elem;  
    cin >> elem;  
    m_tree.find_node(elem);//按节点值查找  
    m_tree.nodes_count();//计算节点数  
    return 0;  
}  

测试结果
这里写图片描述

3、创建二叉树、三种遍历

#include<iostream>  
#include<fstream>   
#include<string.h>   
using namespace std;  

/*二叉树的结构体*/  
typedef struct BTree  
{  
    int val;  
    struct BTree *left,*right;    
}BTree;  

/*二叉树的类,包含着操作二叉树的各种方法*/   
class Tree  
{  
public:  

    BTree *create_node(int level,string pos);  
    void PreOrder(BTree *t);   //先序遍历   
    void InOrder(BTree *t);    //中序遍历   
    void PostOrder(BTree *t);  //后序遍历   

    BTree *root;      
};  

/*用先序遍历的方法递归构造一课二叉树*/   
BTree* Tree::create_node(int level,string pos)  
{  
    int data;  
    BTree *node = new BTree;  

    cout<<"please enter data:level "<<level<<" "<<pos<<endl;  
    cin>>data;  

    //若输入的数据为0,则把该结点的子结点置为空   
    if(data == 0)  
    {  
        return NULL;  
    }  

    node->val= data;  

    /*create_node()的    参数用于在给二叉树赋值时表明 
    现在赋值的是哪个结点*/   
    node->left = create_node(level+1,"left");  
    node->right= create_node(level+1,"right");             
    return node;  
}  

void Tree::PreOrder(BTree *t)  
{  
    if(t)  
    {  
        cout<<t->val<<endl;;  
        PreOrder(t->left);  
        PreOrder(t->right);                
    }  
}   

void Tree::InOrder(BTree *t)  
{  
    if(t)  
    {  
        InOrder(t->left);  
        cout<<t->val<<endl;;  
        InOrder(t->right);  
    }  
}  

void Tree::PostOrder(BTree *t)  
{  
    if(t)  
    {  
        PostOrder(t->left);  
        PostOrder(t->right);  
        cout<<t->val<<endl;    
    }  
}  

int main()  
{  
    Tree tree;  
    tree.root = tree.create_node(1,"root");  
    cout<<"Pre"<<endl;  
    tree.PreOrder(tree.root);  

    cout<<"In"<<endl;  
    tree.InOrder(tree.root);  

    cout<<"Post"<<endl;  
    tree.PreOrder(tree.root);     

    return 0;  
}  

输入

       7
  4         10
3   5     8  12

这里写图片描述

  • 5
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值