首页 > 其他分享 >树 Tree

树 Tree

时间:2022-10-28 11:47:10浏览次数:59  
标签:node Right curr comp Tree null Left

树的结构

后继节点 和 该后继节点的父节点,(一个节点的后继节点是指,这个节点在中序遍历序列中的下一个节点,相应的,前驱节点是指这个节点在中序遍历序列中的上一个节点

树的结构多种多样,然而现实中最常用的是二叉树,也就是每个节点度至多为二的数
在这里我介绍两种主要的二叉树

二叉堆

堆(heap)是优先队列(priority queue)的一种实现,其主要接口为插入元素和删除最大(最小)元素
堆则是优先队列的树状实现,可在 \(T(n)=\log{n}\) 时间复杂度(最坏情况)下完成插入和删除,分最小堆和最大堆,除了二叉堆外也有三叉堆等,这里展示了二叉堆的实现

请主要关注 insert(), delete_head(), shift_up(), shift_down() 方法,不妨以最大堆为例

template <typename T>
class PriorityQueue
{
public:
    PriorityQueue(std::function<bool(T, T)> comp = std::less<T>())
        : _element(), _comp(comp) {}
    PriorityQueue(std::initializer_list<T> initializer,
                  std::function<bool(T, T)> comp = std::less<T>())
        : _element(initializer), _comp(comp)
    {
        std::sort(_element.begin(), _element.end(), std::not2(_comp));
    }
    template <typename Iterator>
    PriorityQueue(Iterator begin, Iterator end,
                  std::function<bool(T, T)> comp = std::less<T>())
        : _element(), _comp(comp)
    {
        while (begin != end)
            insert(*begin++);
    }

    size_t size() const { return _element.size(); }
    size_t capacity() const { return _element.capacity(); }

    auto cbegin() const { return _element.cbegin(); }
    auto cend() const { return _element.cend(); }
    // 只读,不允许修改,以保证堆的状态不被破坏

    void insert(const T &value)
    {
        _element.push_back(value);
        shift_up(size() - 1);
    }
    T delete_head()
    {
        if (_element.size() == 0)
            throw std::runtime_error("empty priority queue");
        swap(0, size() - 1); // 现在最值被放在最后了
        T res = _element.back();
        _element.pop_back();
        shift_down(size());
        return res;
    }

private:
    std::vector<T> _element;
    std::function<bool(T, T)> _comp;

    // 两个工具函数
    void swap(int i, int j)
    {
        T t(_element[i]);
        _element[i] = _element[j];
        _element[j] = t;
    }
    bool compare(int i, int j) { return _comp(_element[i], _element[j]); }

    void shift_down(int n) // 自顶向下
    {
        int i = 0;
        int down;
        while ((down = (i << 1) + 1) < n) // [down]是[i]的左子节点,而[down+1]是i的右子节点
        {
            if (down + 1 < n && compare(down, down + 1)) // 若[i]小于[down]或[down+1],交换其中更大者
                ++down;
            if (compare(down, i))
                return;
            swap(down, i);
            i = down;
        }
    }
    void shift_up(int i) // 自底向上
    {
        int up; // up是i的父节点
        while (i > 0 && compare(up = (i - 1) >> 1, i)) // 若[up]小于[i],交换二者
        {
            swap(i, up);
            i = up;
        }
    }
    // shift_up 和 shift_down 并称堆的有序化操作,方法都是化局部无序为有序
};

函数定义放在类中主要是为了方便读者阅读

二叉堆的应用
  • 寻找最大的\(M\)个数

    先举出使用不同方法的复杂度(\(N\)为输入量)

    算法 时间复杂度 空间复杂度
    排序 \(N\log{M}\) \(N\)
    数组和链表 \(NM\) \(M\)
    \(N\log{M}\) \(M\)

    可以看到堆对时间和空间的利用都很好

  • 堆排序
    堆排序和快速排序、归并排序并称三大 \(N\log{N}\) 排序算法
    其思路为:从左至右将数组变为堆,将堆的最值取出从右至左放入

void heap_sort(int a[], int len)
{
    using std::swap;
    int index = 0;
    while (++index < len)
    {
        int up;
        int i = index;
        while (i > 0 && a[up = (i - 1) >> 1] < a[i])
        {
            swap(a[up], a[i]);
            i = up;
        }
    }
    while (--index)
    {
        swap(a[0], a[index]);
        int i = 0;
        int down;
        while ((down = (i << 1) + 1) < index)
        {
            if (down + 1 < index && a[down] < a[down + 1])
                ++down;
            if (a[down] < a[i])
                break;
            swap(a[down], a[i]);
            i = down;
        }
    }
}

二叉搜索树

二叉搜索树(Binary Search Tree)保证每一次查找都是二分搜索(\(T(n)=\log{n}\)),这是因为它的键值(值)是有序的,满足每个节点的键值都大于其左子树的任意节点的键值,小于其右子树的任意节点的键值

使用符号表,包含 Insert(), Remove(), IndexOf(), Select() 插入、删除、支持两种索引(下标和键值)的二叉搜索树

public class BinarySearchTree<TKey, TValue>
    where TKey : IComparable<TKey>, IEquatable<TKey>
{
    public class Node
    {
        public TKey Key { get; internal set; }
        public TValue Val { get; set; }
        public Node Left { get; internal set; }
        public Node Right { get; internal set; }
        public int SubNodeCount { get; internal set; }
        public Node(TKey key, TValue val, Node left, Node right, int subNodeCount)
        {
            Key = key;
            Val = val;
            Left = left;
            Right = right;
            SubNodeCount = subNodeCount;
        }
    }
    private Node _Root;
    public int Count { get => SubNodeCount(_Root); }
    private int SubNodeCount(Node node)
    {
        return node is null ? 0 : node.SubNodeCount;
    }
    public TValue this[TKey key]
    {
        get
        {
            Node node = _Root;
            while (node != null)
            {
                int comp = node.Key.CompareTo(key);
                if (comp < 0)
                    node = node.Left;
                else if (comp > 0)
                    node = node.Right;
                else
                    return node.Val;
            }
            return default(TValue);
        }
        set
        {
            if (_Root is null)
                return;
            Node prev;
            Node curr = _Root;
            int comp;
            do
            {
                comp = curr.Key.CompareTo(key);
                if (comp == 0)
                {
                    curr.Val = value;
                    return;
                }
                prev = curr;
                if (comp < 0)
                    curr = curr.Left;
                else
                    curr = curr.Right;
            }
            while (curr is not null);
            if (comp < 0)
                prev.Right = new Node(key, value, null, null, 1);
            else
                prev.Left = new Node(key, value, null, null, 1);
            ++prev.SubNodeCount;
        }
    }
    public void Insert(TKey key, TValue val)
    {
        if (_Root is null)
        {
            _Root = new Node(key, val, null, null, 1);
            return;
        }
        Node prev;
        Node curr = _Root;
        int comp;
        do
        {
            comp = curr.Key.CompareTo(key);
            if (comp == 0)
                return;
            prev = curr;
            if (comp > 0)
                curr = curr.Left;
            else
                curr = curr.Right;
        }
        while (curr is not null);
        if (comp < 0)
            prev.Right = new Node(key, val, null, null, 1);
        else
            prev.Left = new Node(key, val, null, null, 1);
        ++prev.SubNodeCount;
    }
    public bool Remove(TKey key)
    {
        if (_Root is null)
            return false;
        Node prev = _Root;
        Node curr = _Root;
        int comp;
        Stack<Node> path = new Stack<Node>();

        do
        {
            path.Push(curr);
            comp = curr.Key.CompareTo(key);
            if (comp == 0)
                break;
            prev = curr;
            if (comp > 0)
                curr = curr.Left;
            else
                curr = curr.Right;
        }
        while (curr is not null);

        if (curr is null)
            return false;

        if (curr.Left is null)
        {
            if (curr == _Root)
                _Root = _Root.Right;
            else if (curr == prev.Left)
                prev.Left = curr.Right;
            else
                prev.Right = curr.Right;
            --prev.SubNodeCount;
        }
        else if (curr.Right is null)
        {
            if (curr == _Root)
                _Root = _Root.Left;
            else if (curr == prev.Left)
                prev.Left = curr.Left;
            else
                prev.Right = curr.Right;
            --prev.SubNodeCount;
        }
        else
        {
            Node replacePrev;
            Node replaceCurr = curr.Right;
            while (replaceCurr.Left is not null)
            {
                path.Push(replaceCurr);
                replacePrev = replaceCurr;
                replaceCurr = replaceCurr.Left;
            }

            replacePrev = replaceCurr.Right;
            replaceCurr.Left = curr.Left;
            replaceCurr.Right = curr.Right;

            if (curr == _Root)
                _Root = replaceCurr;
            else if (curr == prev.Left)
                prev.Left = replaceCurr;
            else
                prev.Right = replaceCurr;

            curr = replaceCurr;
        }

        foreach (Node node in path)
            --node.SubNodeCount;
        curr.SubNodeCount = SubNodeCount(curr.Left) + SubNodeCount(curr.Right) + 1;
        return true;
    }
    public Node Select(int index)
    {
        if (_Root is null || index < 0 || _Root.SubNodeCount <= index)
            return null;
        Node node = _Root;
        int t;
        while ((t = SubNodeCount(node.Left)) != index)
        {
            if (t > index)
            {
                node = node.Left;
            }
            else
            {
                node = node.Right;
                index -= t + 1;
            }
        }
        return node;
    }
    public int IndexOf(TKey key)
    {
        Node node = _Root;
        int t = 0;
        int comp;
        while (node is not null && (comp = node.Key.CompareTo(key)) != 0)
        {
            if (comp < 0)
            {
                node = node.Left;
            }
            else
            {
                t += SubNodeCount(node.Left) + 1;
                node = node.Right;
            }
        }
        if (node is null)
            return -1;
        return t + SubNodeCount(node.Left);
    }
}
树的四种遍历方法(前序遍历、中序遍历、后序遍历和层序遍历)

下面列出这四种方法的迭代版本(使用栈或队列)

// 返回值也可以是IEnumerable<TValue>
public IEnumerator<TValue> PreorderTraversal()
{
    if (_Root is null)
        yield break;
    Stack<Node> nodeStack = new Stack<Node>();
    Node node = _Root;
    while (node is not null || !nodeStack.IsEmpty())
    {
        while (node is not null)
        {
            yield return node.Val;
            nodeStack.Push(node);
            node = node.Left;
        }
        node = nodeStack.Pop().Right;
    }
}
public IEnumerator<TValue> InorderTraversal()
{
    if (_Root is null)
        yield break;
    Stack<Node> nodeStack = new Stack<Node>();
    Node node = _Root;
    while (node is not null || !nodeStack.IsEmpty())
    {
        while (node is not null)
        {
            nodeStack.Push(node);
            node = node.Left;
        }
        node = nodeStack.Pop();
        yield return node.Val;
        node = node.Right;
    }
}
public IEnumerator<TValue> PostorderTraversal()
{
    if (_Root is null)
        yield break;
    Stack<Node> indexStack = new Stack<Node>();
    Node node = _Root;
    Node prev = null;
    while (node is not null || !indexStack.IsEmpty())
    {
        while (node is not null)
        {
            indexStack.Push(node);
            node = node.Left;
        }
        node = indexStack.Top;
        if (node.Right is null || node.Right == prev)
        {
            yield return node.Val;
            prev = node;
            node = null;
            indexStack.Pop();
        }
        else
            node = node.Right;
    }
}
public IEnumerator<TValue> LayerorderTraversal()
{
    if (_Root is null)
        yield break;
    Queue<Node> nodeQueue = new Queue<Node>();
    Node node;
    nodeQueue.Enqueue(_Root);
    while (nodeQueue.Count != 0)
    {
        node = nodeQueue.Dequeue();
        yield return node.Val;
        if (node.Left is not null)
            nodeQueue.Enqueue(node.Left);
        if (node.Right is not null)
            nodeQueue.Enqueue(node.Right);
    }
}

延伸阅读

常见的树除了二叉树还有四叉树、八叉树等。

四叉树常见的应用有图像处理、二维场景中的碰撞检测等。

标签:node,Right,curr,comp,Tree,null,Left
From: https://www.cnblogs.com/violeshnv/p/16833428.html

相关文章

  • The 2021 ICPC Asia Nanjing Regional Contest E.Paimon Segment Tree 区间合并线段树
    The2021ICPCAsiaNanjingRegionalContestE.PaimonSegmentTree区间合并线段树/维护矩阵乘法题目大意给定长度为的序列,要求支持区间加操作,同时对操作记录历史版本,查......
  • 3.CF343D Water Tree 树剖+线段树区间覆盖
    3.CF343DWaterTree树剖+线段树区间覆盖线段树维护树上覆盖问题,树剖序列化维护序列覆盖。洛谷传送门:​​CF343DWaterTree-洛谷|计算机科学教育新生态(luogu.com.c......
  • 9.CF490F Treeland Tour 线段树合并
    9.CF490FTreelandTour线段树合并给出一棵带点权树,求树上最长上升子序列的长度对每个点开两棵线段树,记录叶节点到当前节点的LIS和LDS,然后合并时取最大值即可洛谷传送门:​......
  • Nauuo and Binary Tree 题解
    linkSolution超级有意思的题目,可惜还是做不出来。/kk我们首先看出我们可以求出每一个点的深度。然后考虑深度从小到大考虑对于每一个点找出它的父亲。你发现如果求出两......
  • [repo] error.GitError: cannot initialize work tree && contains uncommitted chang
    备忘一下error.GitError:cannotinitializeworktree.repo/repo/repo--tracesync-cdfjuwan@juwan-n85-dls:/media/juwan/70970A1D041A95C2/rk3399_linux_relea......
  • mysqlb+tree结构
    怎样在MySQL表中存储树形结构数据很高兴为您解答。一般比较普遍的就是四种方法:(具体见SQLAnti-patterns这本书)AdjacencyList:每一条记录存parent_idPathEnumerations:每一......
  • Git worktree(工作树)
    worktree工作树简介重新给分支/提交一个新的工作区域,且原工作区无法在switch到那个分支,只有释放了才行新增一个工作树之后,原仓库目录的.git文件夹中产生一个worktree......
  • [Oracle] LeetCode 314 Binary Tree Vertical Order Traversal
    Giventherootofabinarytree,returntheverticalordertraversalofitsnodes'values.(i.e.,fromtoptobottom,columnbycolumn).Iftwonodesareinth......
  • 2022 年上海市大学生程序设计竞赛 - 十月赛 B. Questions on Binary Tree
    给定一棵n个节点的完全二叉树以及m次询问,每次询问需要计算给定节点在先序遍历的顺序。思路就是从给定的节点不断向上跳,如果当前节点是左儿子则给答案+1,是右儿子则给答案+1......
  • CF1092F Tree with Maximum Cost
    题目链接:​​传送门​​是​​这个题​​​的一个变形就是最小值改成最大值懒了直接改了改当时的代码当时的题解里也有解析#include<iostream>#include<cstdio>#inclu......