数据结构与算法分析-C++描述 第4章 树

二叉查找树的操作的时间复杂度为O(logN)

1.预备知识

树:一些结点的集合,这个集合要么是空集,要么由根结点r和0个或多个非空子树组成,它们的根结点都被来自r的一条有向边连接。
每一棵子树的根叫做跟r的儿子,r是它们的父亲。
没有儿子的结点叫做叶结点(不再向下延伸了)。
路径:从父亲到儿子的序列。
层数:根结点的层数为0,其他结点的层数是父结点的层数+1.
对于任意节点,根节点到它的路径长叫做它的深度,它到它的所有叶节点的路径长的最大值叫做它的高度。一棵树的高度等于它根的高度,一棵树的深度等于它所有叶节点深度的最大值,这两个数是相等的。
如果存在n_1n_2的一条路径,则n_1n_2的祖先,如果n_1!=n_2,则叫真祖先。
树的实现:用一个指针连接第一个儿子,再用一个指针连接自己的兄弟。

struct tree {
    int data;
    tree *firstchild;
    tree *nextsibling;
};

二叉树:每个结点最多有两个儿子。

struct tree {
    int data;
    tree *pright;
    tree *pleft;
};

2.树的遍历

以二叉树为例:
前序遍历:先访问父结点,再访问左子结点,再访问右子结点。
中序遍历:先访问左子结点,再访问父结点,再访问右子结点。
后序遍历:先访问左子结点,再访问右子结点,再访问父结点。
代码:(栈实现不是很容易理解)

#include<iostream>
#include<string>
#include<stack>
using namespace std;
struct Tree {
    int data;
    struct Tree *pleft;
    struct Tree *pright;
}tree[11];

void midtravelR(Tree *proot) {//中序遍历的递归实现
    if (proot == nullptr) return;
    else {
        if (proot->pleft != nullptr) midtravelR(proot->pleft);
        cout << proot->data << endl;
        if (proot->pright != nullptr) midtravelR(proot->pright);
    }
}
void pretravel(Tree *proot) { //前序遍历
    if(proot == nullptr) return;
    else {
        cout << proot->data << endl;
        if (proot->pleft != nullptr) pretravel(proot->pleft);
        if (proot->pright != nullptr) pretravel(proot->pright);
    }
}
void posttravel(Tree *proot) { //后序遍历
    if (proot == nullptr) return;
    else {
        if (proot->pleft != nullptr) posttravel(proot->pleft);
        if (proot->pright != nullptr) posttravel(proot->pright);
        cout << proot->data << endl;
    }
}

void midtravelS(Tree *proot) {//中序遍历的栈实现
    if (proot == nullptr) return;
    else {
        Tree *p = proot;
        stack<Tree *> treestack;
        while (!treestack.empty() || p != nullptr) { //直到栈空而且p==nullptr
            while (p != nullptr) {//遍历左结点,并将其全部进栈
                treestack.push(p);
                p = p->pleft;
            }
            if (!treestack.empty()) {
                p = treestack.top();
                cout << p->data << endl;
                treestack.pop();//输出栈顶元素
                p = p->pright;//切换到右结点,如果没有右结点,下一循环的栈顶元素为父结点
            }
        }
    }
}

int main() {
    Tree *proot;
    for (int i = 0; i <= 10; i++) cin >> tree[i].data;
    proot = &tree[0];
    tree[0].pleft = &tree[1];
    tree[0].pright = &tree[2];
    tree[1].pleft = &tree[3];
    tree[1].pright = &tree[4];
    tree[2].pleft = &tree[5];
    tree[2].pright = &tree[6];
    tree[3].pleft = &tree[7];
    tree[3].pright = &tree[8];
    tree[4].pleft = &tree[9];
    tree[4].pright = &tree[10];
    midtravelR(proot);
    cout << endl;
    midtravelS(proot);
    cout << endl;
    pretravel(proot);
    cout << endl;
    posttravel(proot);
    return 0;
}

3.二叉树

(1)表达式树:叶子结点是数,其他结点是运算符的树。
对表达式树进行前序、中序、后序遍历并输出对应的正好是前缀、中缀、后缀表达式。
把后缀表达式转化为表达式树:建一个树指针栈,将表达式读入,遇到数字就建立一个单节点树并让它进栈;遇到运算符就从栈里弹出两棵树,并将它们以读到的运算符为根形成一棵新树,把这棵新树的指针压进栈里。
代码:

#include<iostream>
#include<stack>
#include<string>
#include<cctype>
using namespace std;
string input;
struct Tree {
    int data;
    Tree *pleft;
    Tree *pright;
};
stack<Tree *> treestack;
Tree roots[100];
int num;

void midtravel(Tree* proot) {
    if (proot == nullptr) return;
    if (proot->pleft != nullptr) {
        cout << '(';
        midtravel(proot->pleft);
        cout << ')';
    }
    cout << char(proot->data);
    if (proot->pright != nullptr) {
        cout << '(';
        midtravel(proot->pright);
        cout << ')';
    }
}


int main() {
    getline(cin, input);
    int len = input.size();
    for (int i = 0; i < len; i++) {
        if (input[i] == ' ') continue;
        if (isalnum(input[i])) {
            roots[num].data = input[i];
            roots[num].pleft = roots[num].pright = nullptr;
            treestack.push(&roots[num]);
            num++;
        }
        else {
            roots[num].data = input[i];
            roots[num].pright = treestack.top();
            treestack.pop();
            roots[num].pleft = treestack.top();
            treestack.pop();
            treestack.push(&roots[num]);
            num++;
        }
    }
    midtravel(treestack.top());
    return 0;
}

注意在一开始要开一个大数组来装根结点,不能在函数体内声明,不然会把原来的根结点的地址覆盖了。用这种方法可以实现将后缀表达式转化为前缀表达式和中缀表达式,但是会输出多余的括号。
(2)二叉查找树
对于树中的每个结点,它的左子树的所有结点的值小于它的值,右子树的所有结点的值大于它的值,则称此树为二叉查找树,易知中序遍历一棵二叉查找树即为升序输出它的结点值。
这样说默认了树的结点是两两互异且可比较的。
具体实现:插入,查找,中序遍历,返回最大/最小结点

#include <iostream>
using namespace std;
struct Tree {
    int data;
    Tree *pleft;
    Tree *pright;
};
Tree* search(Tree *proot,int x){
    if(!proot||proot->data==x) return proot;
    if(proot->data<x) return search(proot->pright,x);
    return search(proot->pleft,x);
}
void insert(Tree *&proot, int x) { //在树中插入值为x的结点:先查找它,若没有,插入到最后访问的结点处
    if (proot == nullptr) {
        proot = new Tree;
        proot->pleft = proot->pright = nullptr;
        proot->data = x;
        return;
    }
    if (x < proot->data) insert(proot->pleft, x);
    else insert(proot->pright, x);
}
Tree* CreatTree(){
    Tree* proot=nullptr;
    int data;
    while(cin>>data) insert(proot,data);
    return proot;
}
bool find(Tree* proot, int x) {  //查找树中是否有值为x的结点
    if (proot == nullptr) return false;
    if (proot->data == x) return true;
    else if (proot->data > x) return find(proot->pleft, x);
    else return find(proot->pright, x);
}

void midtravel(Tree *proot) {
    if (proot == nullptr) return;
    midtravel(proot->pleft);
    cout << proot->data << ' ';
    midtravel(proot->pright);
}

Tree* mindata(Tree *proot) {//返回最小结点(最左边的子结点)
    if(!proot) return nullptr;
    while(proot->pleft) proot=proot->pleft;
    return proot;
}

Tree* maxdata(Tree *proot) {//返回最大结点(最右边的子结点)
    if(!proot) return nullptr;
    while(proot->pright) proot=proot->pright;
    return proot;
}

int main() {
    freopen("in.txt","r",stdin);
    freopen("out.txt","w",stdout);
    Tree *proot = CreatTree();
    midtravel(proot);
    cout << endl;
    cout << (*maxdata(proot)).data << endl;
    cout << (*mindata(proot)).data;
    return 0;
}

删除操作比较麻烦:
若删除的是叶子结点,直接删就行;如果删除的结点只有左子树或者右子树,直接让该结点的左儿子或者右儿子取代它的位置即可;若删除的结点既有左子树又有右子树,用其右子树的最小结点的值取代它,再递归删除右子树的最小结点(它一定没有左子树)。

void remove(Tree * &proot,int x) { //删除值为x的结点,这里指针一定要加上引用 
    if (proot == nullptr) return;
    if (x < proot->data) remove(proot->pleft, x);
    else if (x > proot->data) remove(proot->pright, x);
    else if (proot->pleft != nullptr && proot->pright != nullptr) {
        proot->data = mindata(proot->pright)->data;
        remove(proot->pright, proot->data);
    }
    else {//如果只有左子树或者只有右子树,就用子结点取代父节点
        Tree *oldnode = proot;
        if (proot->pleft != nullptr) proot = proot->pleft;
        else proot = proot->pright; //如果在函数头不加引用,函数调用结束后proot根本没变
        delete oldnode;
    }
}

二叉查找树的平均运行时间为O(logN),最坏运行时间为O(N)

4.AVL树、伸展树、B树(先欠着)

5.set和map

(1)pair
定义在#include<utility>中的类模板,用于将两种数据类型捆绑,使它们可以互相索引,或者当函数需要返回两个值时,也可以用pair。
pair<T1,T2> p;
pair是个结构体,有两个成员first和second,分别代表了T1的具体变量和T2的具体变量。
pair重载了小于运算符,遵循字典序:如 p1.first < p2.first 或者 !(p2.first < p1.first) && (p1.second < p2.second) 则返回true。(仅当p1的每个分量都>=p2时,称p1>=p2,否则p1<p2)
(2)set和map
要使用set,必须重载小于运算符,而且小于函数后面必须加上const。
insert(s.end(),x)的速度快于insert(x)。
erase(x),erase(it),erase(begin(),end()) 分别返回删掉的元素个数(0或1),删掉的迭代器的下一个迭代器,end()。
map和set的查找方法find()的返回值是对象的迭代器,如果找不到,则返回end()。
map和set操作的时间复杂度:O(logN)
先进二叉树再中序遍历的排序算法的时间复杂度:O(NlogN)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容