基本数据结构-树

基本数据结构

简介

基本数据结构有:array, list, queue, stack, map, tree, heap, graph, sorting

array, list, queue, stack请参考c++ 标准库.

标准库中一般有对应的类:

  • array: std::vector, std::array, std::deque
  • list: std::list
  • queue: std::queue
  • stack: std::stack
  • heap: std::priority_queue
  • map: std::map(single value with same key), std::multimap(multiple values with same key)
  • sort: std::sort, std::qsort

对于这些数据结构的具体定义请参考专业书籍。

注意:我们后面仅用数学上的大于来代表实际的大于(比如实际上大于有可能是比较某个字符串,也有可能是非大于,即小于等于)。

暂时省略掉array, list, queue, stack的实现,下面主要实现一些基本的数据: (由于c++模板中没有virutal method,所以全部采用int来作为数据类型。如果是其他类型,可以用一个map来映射int->具体数据类型)

Tree

Tree: 树。

树的遍历:先序、中序、后序遍历。其中先序遍历得到的表达式,我们称为前缀表达式(波兰表达式,Polish Notation),中序遍历得到的表达式称为中缀表达式,后序遍历得到的表达式称为后缀表达式(逆波兰表达式,Reverse Polish Notation)。

这里主要实现二叉树和二叉搜索树

二叉树长这个样子:

bitree

二叉树

// .h

//
// Created on 3/22/18.
//

#ifndef AGORITHM_BITREE_H
#define AGORITHM_BITREE_H

#include <string>
#include <functional>
#include <stack>
#include <iostream>
#include "Node.h"

struct BiTree {
public:
    struct Node;

    using VisitFunc = std::function<void(Node *)>;
    using DeleteFunc = std::function<void(Node *)>;

    BiTree() : BiTree(nullptr, nullptr) {}

    explicit BiTree(int value) : BiTree(NewNode(value), nullptr) {}

    explicit BiTree(const DeleteFunc &func) : BiTree(nullptr, func) {}

    BiTree(Node *root, const DeleteFunc &func) {
        this->root = root;
        if (!func) {
            this->deleteFunc = [](Node *p) { delete p; };
        } else {
            this->deleteFunc = func;
        }
    }

    virtual ~BiTree();

    struct Node {
        Node() = default;

        explicit Node(int value, Node *left = nullptr, Node *right = nullptr) {
            this->value = value;
            this->left = left;
            this->right = right;
        }

        Node *left = nullptr;
        Node *right = nullptr;
        int value = 0;
    };


    virtual bool Empty() { return root == nullptr; }

    static void InOrderTraverse(const BiTree &tree, const VisitFunc &fn);

    static void PreOrderTraverse(const BiTree &tree, const VisitFunc &fn);

    static void PostOrderTraverse(const BiTree &tree, const VisitFunc &fn);

    static Node *NewNode(int value);

    static Node *NewNode();

protected:
    virtual void deleteTree(Node *node);

public:
    Node *root = nullptr;
    DeleteFunc deleteFunc = nullptr;;
};

#endif //AGORITHM_BITREE_H

// .cpp

//
// Created on 3/27/18.
//

#include "BiTree.h"

void BiTree::InOrderTraverse(const BiTree &tree, const BiTree::VisitFunc &fn) {
    auto p = tree.root;
    std::stack<decltype(p)> stk;

    while (p) { // push left
        stk.push(p);
        p = p->left;
    }
    while (!stk.empty()) {
        p = stk.top();
        stk.pop();
        fn(p);
        if (p->right) {
            p = p->right;
            while (p) { // push right first, then push leftmost of right
                stk.push(p);
                p = p->left;
            }
        }
    }
}

void BiTree::PreOrderTraverse(const BiTree &tree, const BiTree::VisitFunc &fn) {
    auto p = tree.root;
    std::stack<decltype(p)> stk;
    stk.push(p);

    while (!stk.empty()) {
        p = stk.top();
        stk.pop();
        if (p->right) {
            stk.push(p->right);
        }
        if (p->left) {
            stk.push(p->left);
        }
        fn(p);     // place at end of block, because it's used in tree destruction
    }
}

void BiTree::PostOrderTraverse(const BiTree &tree, const BiTree::VisitFunc &fn) {
    auto p = tree.root;
    std::stack<decltype(p)> stk;
    decltype(p) pre = nullptr;

    stk.push(p);
    while (!stk.empty()) {
        p = stk.top();
        if ((!p->left && !p->right) || (pre && (p->left == pre || p->right == pre))) {
            stk.pop();
            fn(p);
            pre = p;
        } else {
            // right and left node are always on top of root node. if previous visited node is left
            // or right child of this node, it means that its left and child nodes are visited
            if (p->right) {
                stk.push(p->right);
            }
            if (p->left) {
                stk.push(p->left);
            }
        }
    }
}

BiTree::Node *BiTree::NewNode(int value) {
    return new Node(value);
}

BiTree::Node *BiTree::NewNode() {
    return new Node();
}

BiTree::~BiTree() {
    deleteTree(root);
    root = nullptr;
}

void BiTree::deleteTree(BiTree::Node *node) {
    PreOrderTraverse(*this, this->deleteFunc);
}


二叉搜索树: 对于任一非叶子节点,它的值小于它的左子节点的值,大于它右子节点的值。

// .h
#ifndef AGORITHM_BSNODE_H
#define AGORITHM_BSNODE_H


#include "BiTree.h"

class BSTree : public BiTree {
public:
    virtual bool Insert(int value);

    virtual bool Insert(int value, Node *&location);

    virtual bool Search(int value, Node *&location);

    virtual bool Remove(int value);

private:
    bool Search(Node *node, Node *&location, Node *parent, int value);

    void RemoveNode(Node *&node);
};

#endif //AGORITHM_BSNODE_H

//.cpp

#include "BSTree.h"

bool BSTree::Insert(int value) {
    Node *location = nullptr;
    return Insert(value, location);
}

bool BSTree::Insert(int value, Node *&location) {
    if (this->Empty()) {
        this->root = NewNode(value);
        return true;
    }

    Node *pos = nullptr;
    bool found = Search(value, pos);
    if (found) {
        return false;
    }

    auto newNode = NewNode(value);
    if (pos->value > value) {
        pos->left = newNode;
    } else {
        pos->right = newNode;
    }

    location = newNode;
    return true;
}


bool BSTree::Search(int value, Node *&location) {
    if (Empty()) {
        return false;
    }

    return Search(this->root, location, nullptr, value);
}

bool BSTree::Search(Node *node, Node *&location, Node *parent, int value) {
    if (!node) {
        location = parent;
        return false;
    }

    if (node->value < value) {
        return Search(node->right, location, node, value);
    } else if (node->value > value) {
        return Search(node->left, location, node, value);
    }

    location = node;
    return true;
}

bool BSTree::Remove(int value) {
    if (this->Empty()) {
        return false;
    }

    std::stack<std::reference_wrapper<Node *>> stk;
    stk.push(this->root);

    while (!stk.empty()) {
        auto &p = stk.top().get();
        stk.pop();

        if (p) {
            if (p->value == value) {
                RemoveNode(p);
                return true;
            } else if (p->value < value) {
                stk.push(p->right);
            } else {
                stk.push(p->left);
            }
        }
    }
    return false;
}


void BSTree::RemoveNode(Node *&node) {
    Node *old = node;
    if (!node->left) {  // left child null.
        node = node->right;
    } else if (!node->right) {  // right null
        node = node->left;
    } else {    // both left and right not null.
        auto cur = node->left;
        auto pre = node;
        while (cur->right) {  // rightmost element of left child of node
            pre = cur;
            cur = cur->right;
        }

        if (pre != old) {   // rightmost exists
            pre->right = cur->left;
            cur->left = node->left;
        } else {    // rightmost node doesn't exist
//            cur->right = node->right;
        }
        cur->right = node->right;
        node = cur;
    }
    deleteFunc(old);
}

对于一颗普通的二叉搜索树,插入/删除/搜索的时间复杂度为O(n)。

对于一颗AVL树(平衡二叉树),插入/删除/搜索的时间复杂度为O(logn)。(这里没有实现AVL树)

红黑树, B-树,B+

红黑树

AVL树每一次插入删除通常都会引起旋转。而红黑树追求局部平衡,所以整体性能比AVL树更高。

红黑树是每个节点都带有颜色属性的二叉查找树,颜色为红色或黑色。在二叉查找树强制一般要求以外,对于任何有效的红黑树我们增加了如下的额外要求:

  1. 节点是红色或黑色。
  2. 根是黑色。
  3. 所有叶子都是黑色(叶子是NIL节点)。
  4. 每个红色节点必须有两个黑色的子节点。(从每个叶子到根的所有路径上不能有两个连续的红色节点。)
    从任一节点到其每个叶子的所有简单路径都包含相同数目的黑色节点。

一颗红黑树长这个样子:

rbtree

B-树

B-树是一种平衡的多路查找树。它在文件系统中很有用。

一颗m阶B-树或为空树,或满足如下特性:

  1. 树中每个节点至多有m棵子树
  2. 若根节点不是叶子节点,则至少有两颗子树
  3. 除根节点之外的所有非终端节点至少有k棵子树,k的值为不小于m/2的最小整数
  4. 所有的非叶子节点包含以下信息
    (n, A0, K1, A1, K2, A2, ... An)
    其中:n是该节点叶子节点的个数,Ai指向第i个子树(i从0开始),Ki(i从1开始)是第i个关键字。对于Ki,它大于Ai-1指向的子树的所有节点的关键字,它小于Ai指向的子树的所有节点的关键字。
  5. 所有的叶子节点出现在同一层次上,并且不带信息。(可视作空指针)

一棵4阶B树如下图:

btree

时间复杂度为logn

B+

B+树是应文件系统所需而出的一种B-树的变形树。一棵m阶B-树和B+树的差异在与:

  1. 有n棵子树的节点中含有n个关键字 (B-树是n-1个关键字)
  2. 所有的叶子节点包含了全部关键字信息,且叶子节点本身依关键字从小到大顺序链接。
  3. 所有的非叶子节点可以看成是全部关键字的一个子集,且节点中仅含有其子树的最大关键字。

一棵3-阶B+树如下图所示:

bplustree

时间复杂度为logn

至于为什么会在文件系统中用B+树,我们可以自己算一下:假设有n树个节点,如果用AVL树,来找,至多会进行logn次查找,都是从磁盘中读取的。对于B+树,也至多会进行logn次查找,但对在某个节点中,查找一个关键字时,是在内存中进行的。由于访问内存的速度比磁盘快,所以B+树的查找速度快于AVL树。

具体实现请参考专业书籍

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,047评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,807评论 3 386
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,501评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,839评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,951评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,117评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,188评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,929评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,372评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,679评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,837评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,536评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,168评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,886评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,129评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,665评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,739评论 2 351