考研复试之数据结构——树(二叉树、堆、并查集)

知识汇总

树相关的知识点比较多,个人感觉重点会是二叉树的遍历,搜索二叉树、堆,并查集可能会涉及,其他的还有平衡二叉树,这个应该不会让手撕。

二叉树

二叉树相关问题最常考的就是二叉树遍历的模板:

递归模板

// Traverse
void traverse(TreeNode* root) {
    // 先序遍历
    traverse(root->left);
    // 中序遍历
    traverse(root->right);
    // 后序遍历
}

迭代模板(先序遍历和中序遍历)

// 先序遍历和中序遍历
while (!stack.empty() || node) {
    while (node) {
        stack.push_back(node);
        node = node->left;
        // 先序遍历
    }
    node = stack[stack.size()-1];
    // 中序遍历
    stack.pop_back();
    node = node->right;
}

迭代模板(后序遍历)

如果使用模拟递归的迭代方法进行后序遍历的时候,需要考虑向右遍历时何时弹栈

while (!stack.empty() || node) {
    while (node) {
        stack.push_back(node);
        node = node->left;
    }
    node = stack[stack.size()-1];
    stack.pop_back();
    if (!node->right || node->right == pre) {
        returnArray.push_back(node->val);
        pre = node;
        node = nullptr;
    } else {
        stack.push_back(node);
        node = node->right;
    }
}

还有一种方法则是利用了遍历顺序的特征,因为后序遍历是“左右根”,逆序则为“根右左”,而根右左实现起来要简单很多,即将先序遍历访问左右子树的顺序反过来。代码在此则不再赘述。

关于堆的问题我认为只要把握好三个堆的方法和两种操作,手撕起来也不复杂。其中三个方法分别是建堆入堆出堆;两种操作是SiftUp和SiftDown;

void swap(int& a, int& b) {
    int c = a;
    a = b;
    b = c;
}

void SiftDown(vector<int>& heap, int start) {
    int n = heap.size();
    while (start*2+1 < n) {    // 为非叶子结点时
        if (start*2+2 < n) {    //有右儿子的时候
            if (heap[start*2+1] < heap[start] && heap[start*2+1] < heap[start*2+2]) {
                swap(heap[start], heap[start*2+1]);
                start = start * 2 + 1;
            }
            else if (heap[start*2+2] < heap[start] && heap[start*2+2] < heap[start*2+2]) {
                swap(heap[start, heap[start*2+2]);
                start = start * 2 + 2;
            } else break;
        } else {    
            if (heap[start*2+1] < heap[start]) {
                swap(heap[start*2+1], heap[start]);
                start = start * 2 + 1;
            } else break;
    }
 }

void SiftUp(vector<int>& heap, int start) {
    int n = heap.size();
    while (start > 0) {
        if (heap[start] < heap[(start - 1) / 2]) {
            swap(heap[start], heap[(start - 1) / 2]);
            start = (start - 1) / 2;
        }
    }
}

建堆

建堆的过程是从最后一个非叶子结点开始做SiftDown操作,直到根节点结束

n = heap.size();
int lastNode = (n / 2) - 1;
for (int i = lastNode; i >= 0; --i) SiftDown(heap, i);

入堆

入堆是将结点作为完全二叉树的最后一个结点插入之后,对其做SiftUp操作

heap.push_back(k);
SiftUp(heap, heap.size()-1);

出堆

出堆是将根节点弹出后,把堆的最后一个结点放在根节点的位置,然后对其做一次SiftDown操作

int topVal = heap[0];    // 保留堆顶的值
heap[0] = heap[heap.size() - 1];    // 将最后一个结点的值放到堆顶
heap.pop_back();    // 弹出最后一个结点
SiftDown(heap, 0);    //对新堆顶进行一个SiftDown操作

并查集

并查集的关键在于三点:并(Union)、查(Find)和路径压缩

并(Union)

查询两个结点的祖先,如果相同则说明两个结点已经在同一颗树中,如果不同则合并它们对应的两颗树

int a_ancestor = find(a);    // 找到a的祖先
int b_ancestor = find(b);    // 找到b的祖先
if (a_ancestor != b_ancestor) {    // 如果两者祖先不同则合并
    father[b_ancestor] = a_ancestor;
}

查(Find)

递归查找祖先结点

int find(int a) {    // 递归查找祖先结点
    if (father[a] != a) {
        return find(father[a]);
    } else return a;
}

路径压缩

路径压缩有几个方面,这里介绍最方便写的一个方面,就是尽量减小树的深度,实现方式是在find的递归过程中,将返回值赋给非根结点

int find(int a) {    // 递归查找祖先结点
    if (father[a] != a) {
        father[a] = find(father[a]);    // 这一步即为路径压缩,沿途的父亲都变成祖先
        return father[a];
    } else return a;
}

总结

个人感觉这几个点本身难度不大,主要在于编程实现的时候留意一些细节。以上的代码均未调试,如果有什么问题也请指正。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值