1. 程式人生 > 實用技巧 >樹演算法(1)

樹演算法(1)

容易忘的樹基本操作

以中序與任意其他方法遍歷建二叉樹

// 中序與後續為例
struct node {
    int data;
    node *l, *r;
};
// 中序的hash陣列
int hashIn[MAX];
vector<int> in(MAX), post(MAX);
node *creatTree(int postR, int inR, int len) {
    if (len <= 0) return nullptr;
    node *root = new node;
    root->data = post[postR];
    int k = hashIn[post[postR]];
    int rLen = inR - k, lLen = len - 1 - rLen;
    root->l = creatTree(postR - 1 - rLen, k - 1, lLen);
    root->r = creatTree(postR - 1, inR, rLen);
    return root;
}

BST

前驅與後繼

inline node *precursor(node *root) {
    node *ans = nullptr;
    ans = root->l;
    while (ans->r) {
        ans = ans->r;
    }
    return ans;
}
inline node *successor(node *root) {
    node *ans = nullptr;
    ans = root->r;
    while (ans->l) {
        ans = ans->l;
    }
    return ans;
}

刪除

刪除有兩種方法:

  1. 找到需要刪除的節點z後,根據下列兩種情況進行刪除
    1. z無孩子,直接刪除。
    2. 若有前驅或後繼,選擇其中一個的資料覆蓋需刪除的節點,再對前驅或後繼所在的子樹遞迴刪除
  2. 是第前一種方法的優化方案(代價是需要節點額外存父指標),情況2時不需要進行遞迴,由於前驅無右子節點,後繼無左子節點,所以選擇其中一個的資料覆蓋需刪除的節點後,用前驅(後繼)的左子節點(右子節點)替換掉前驅(後繼)。
// 直接使用方法2,並在`z`左右子節點都存在時,交替使用前驅和後繼
inline void transPlant(node *u, node *v) {
    if (u->p->l == u)
        u->p->l = v;
    else
        u->p->r = v;
    if (v) v->p = u->p;
}
node *findNext(node *root) {
    node *ans = nullptr;
    if (root->l && root->r) {
        static int cnt = 0;
        if (cnt & 1)
            ans = precursor(root);
        else
            ans = successor(root);
        ++cnt;
    } else if (root->l)
        ans = precursor(root);
    else if (root->r)
        ans = successor(root);
    return ans;
}

void deleteNode(node *&root, int data) {
    if (!root) return;
    if (data == root->data) {
        if (!root->l && !root->r) {
            delete root;
            root = nullptr;
        } else {
            node *next = findNext(root);
            root->data = next->data;
            if (next->l)
                transPlant(next, next->l);
            else
                transPlant(next, next->r);
        }
    }
    if (data < root->data)
        deleteNode(root->l, data);
    else
        deleteNode(root->r, data);
}

AVL 樹

基本

struct node {
    int data = 0, height = 1;
    node *l = nullptr, *r = nullptr;
};

inline int getH(node *const root) {
    if (root == nullptr) return 0;
    return root->height;
}

inline int getBF(node *const root) { return getH(root->l) - getH(root->r); }

inline void updataH(node *const root) {
    root->height = max(getH(root->l), getH(root->r)) + 1;
}

左右旋

void leftR(node *&root) {
    if (root && root->r) {
        node *tmp = root->r;
        root->r = tmp->l;
        updataH(root);
        tmp->l = root;
        updataH(tmp);
        root = tmp;
    }
}

void rightR(node *&root) {
    if (root && root->l) {
        node *tmp = root->l;
        root->l = tmp->r;
        updataH(root);
        tmp->r = root;
        updataH(tmp);
        root = tmp;
    }
}

插入

插入四種情況:

樹型 判定條件 調整方法
LL BF(root) == 2 && BF(root->child[0]) == 1 右旋root
LR BF(root) == 2 && BF(root->child[0]) == -1 左旋root->child[0],右旋root
RR BF(root) == -2 && BF(root->child[1]) == -1 左旋root
RL BF(root) == -2 && BF(root->child[1]) == 1 右旋root->child[1],左旋root

刪除和插入比較多時,用紅黑樹

void insertNode(node *&root, int data) {
    if (root == nullptr) {
        root = new node;
        root->data = data;
        return;
    }
    if (data < root->data) {
        insertNode(root->l, data);
        updataH(root);
        if (getBF(root) == 2) {
            if (getBF(root->l) == -1) leftR(root->l);
            rightR(root);
        }
    } else {
        insertNode(root->r, data);
        updataH(root);
        if (getBF(root) == -2) {
            if (getBF(root->r) == 1) rightR(root->r);
            leftR(root);
        }
    }
}