📜  笛卡尔树

📅  最后修改于: 2021-04-17 13:35:29             🧑  作者: Mango

笛卡尔树是根据一组数据创建的树数据结构,这些数据遵循以下结构不变式:

  1. 树遵循最小(或最大)堆属性–每个节点小于(或大于)其子节点。
  2. 节点的有序遍历产生的值与它们在初始序列中出现的顺序相同。

假设我们有一个输入数组-{5,10,40,30,28}。则最大堆的笛卡尔树将是。

笛卡尔树0

上面输入数组的最小堆笛卡尔树将是-
笛卡尔树1

笔记:

  1. 笛卡尔树不是高度平衡的树。
  2. 具有不同数字序列的笛卡尔树始终是唯一的。

具有不同数字序列的笛卡尔树始终是唯一的。
我们将使用归纳法证明这一点。作为基本情况,空树始终是唯一的。对于归纳情况,假定对于所有包含n'

如何构造笛卡尔树?
这里讨论了用于构造笛卡尔树的AO(n 2 )解(注意,上面的程序在这里构造了“特殊二叉树”(除了笛卡尔树之外什么都没有))

AO(nlogn)算法:
平均可以用O(NlogN)时间的数据序列构建笛卡尔树。从空树开始

从左到右扫描给定序列,添加新节点,如下所示:

  1. 将节点定位为最右边节点的右边子节点。
  2. 从节点的父节点向上扫描到树的根节点,直到找到其值大于当前值的节点为止。
  3. 如果找到了这样的节点,则将其右子节点设置为新节点,并将新节点的左子节点设置为先前的右子节点。
  4. 如果找不到此类节点,则将新子节点设置为根节点,并将新节点的左子节点设置为前一棵树。
CPP
// A O(n) C++ program to construct cartesian tree
// from a given array
#include
  
/* A binary tree node has data, pointer to left
   child and a pointer to right child */
struct Node
{
    int data;
    Node *left, *right;
};
  
/* This funtcion is here just to test buildTree() */
void printInorder (Node* node)
{
    if (node == NULL)
        return;
    printInorder (node->left);
    cout << node->data << " ";
    printInorder (node->right);
}
  
// Recursively construct subtree under given root using
// leftChil[] and rightchild
Node * buildCartesianTreeUtil (int root, int arr[],
          int parent[], int leftchild[], int rightchild[])
{
    if (root == -1)
        return NULL;
  
    // Create a new node with root's data
    Node *temp = new Node;
    temp->data = arr[root] ;
  
    // Recursively construct left and right subtrees
    temp->left = buildCartesianTreeUtil( leftchild[root],
                    arr, parent, leftchild, rightchild );
    temp->right = buildCartesianTreeUtil( rightchild[root],
                    arr, parent, leftchild, rightchild );
  
    return temp ;
}
  
// A function to create the Cartesian Tree in O(N) time
Node * buildCartesianTree (int arr[], int n)
{
    // Arrays to hold the index of parent, left-child,
    // right-child of each number in the input array
    int parent[n],leftchild[n],rightchild[n];
  
    // Initialize all array values as -1
    memset(parent, -1, sizeof(parent));
    memset(leftchild, -1, sizeof(leftchild));
    memset(rightchild, -1, sizeof(rightchild));
  
    // 'root' and 'last' stores the index of the root and the
    // last processed of the Cartesian Tree.
    // Initially we take root of the Cartesian Tree as the
    // first element of the input array. This can change
    // according to the algorithm
    int root = 0, last;
  
    // Starting from the second element of the input array
    // to the last on scan across the elements, adding them
    // one at a time.
    for (int i=1; i<=n-1; i++)
    {
        last = i-1;
        rightchild[i] = -1;
  
        // Scan upward from the node's parent up to
        // the root of the tree until a node is found
        // whose value is greater than the current one
        // This is the same as Step 2 mentioned in the
        // algorithm
        while (arr[last] <= arr[i] && last != root)
            last = parent[last];
  
        // arr[i] is the largest element yet; make it
        // new root
        if (arr[last] <= arr[i])
        {
            parent[root] = i;
            leftchild[i] = root;
            root = i;
        }
  
        // Just insert it
        else if (rightchild[last] == -1)
        {
            rightchild[last] = i;
            parent[i] = last;
            leftchild[i] = -1;
        }
  
        // Reconfigure links
        else
        {
            parent[rightchild[last]] = i;
            leftchild[i] = rightchild[last];
            rightchild[last] = i;
            parent[i] = last;
        }
  
    }
  
    // Since the root of the Cartesian Tree has no
    // parent, so we assign it -1
    parent[root] = -1;
  
    return (buildCartesianTreeUtil (root, arr, parent,
                                    leftchild, rightchild));
}
  
/* Driver program to test above functions */
int main()
{
    /* Assume that inorder traversal of following tree
       is given
         40
       /   \
      10     30
     /         \
    5          28 */
  
    int arr[] = {5, 10, 40, 30, 28};
    int n = sizeof(arr)/sizeof(arr[0]);
  
    Node *root = buildCartesianTree(arr, n);
  
    /* Let us test the built tree by printing Inorder
       traversal */
    printf("Inorder traversal of the constructed tree : \n");
    printInorder(root);
  
    return(0);
}


Java
// A O(n) Java program to concartesian tree
// from a given array
  
/* A binary tree node has data, pointer to left
child and a pointer to right child */
class GFG
{
static class Node
{
    int data;
    Node left, right;
};
  
/* This funtcion is here just to test buildTree() */
static void printInorder (Node node)
{
    if (node == null)
        return;
    printInorder (node.left);
    System.out.print(node.data + " ");
    printInorder (node.right);
}
  
// Recursively consubtree under given root using
// leftChil[] and rightchild
static Node buildCartesianTreeUtil (int root, int arr[],
        int parent[], int leftchild[], int rightchild[])
{
    if (root == -1)
        return null;
  
    // Create a new node with root's data
    Node temp = new Node();
    temp.data = arr[root] ;
  
    // Recursively conleft and right subtrees
    temp.left = buildCartesianTreeUtil( leftchild[root],
                    arr, parent, leftchild, rightchild );
    temp.right = buildCartesianTreeUtil( rightchild[root],
                    arr, parent, leftchild, rightchild );
  
    return temp ;
}
  
// A function to create the Cartesian Tree in O(N) time
static Node buildCartesianTree (int arr[], int n)
{
    // Arrays to hold the index of parent, left-child,
    // right-child of each number in the input array
    int []parent = new int[n];
    int []leftchild = new int[n];
    int []rightchild = new int[n];
  
    // Initialize all array values as -1
    memset(parent, -1);
    memset(leftchild, -1);
    memset(rightchild, -1);
  
    // 'root' and 'last' stores the index of the root and the
    // last processed of the Cartesian Tree.
    // Initially we take root of the Cartesian Tree as the
    // first element of the input array. This can change
    // according to the algorithm
    int root = 0, last;
  
    // Starting from the second element of the input array
    // to the last on scan across the elements, adding them
    // one at a time.
    for (int i = 1; i <= n - 1; i++)
    {
        last = i - 1;
        rightchild[i] = -1;
  
        // Scan upward from the node's parent up to
        // the root of the tree until a node is found
        // whose value is greater than the current one
        // This is the same as Step 2 mentioned in the
        // algorithm
        while (arr[last] <= arr[i] && last != root)
            last = parent[last];
  
        // arr[i] is the largest element yet; make it
        // new root
        if (arr[last] <= arr[i])
        {
            parent[root] = i;
            leftchild[i] = root;
            root = i;
        }
  
        // Just insert it
        else if (rightchild[last] == -1)
        {
            rightchild[last] = i;
            parent[i] = last;
            leftchild[i] = -1;
        }
  
        // Reconfigure links
        else
        {
            parent[rightchild[last]] = i;
            leftchild[i] = rightchild[last];
            rightchild[last] = i;
            parent[i] = last;
        }
  
    }
  
    // Since the root of the Cartesian Tree has no
    // parent, so we assign it -1
    parent[root] = -1;
  
    return (buildCartesianTreeUtil (root, arr, parent,
                                    leftchild, rightchild));
}
  
static void memset(int[] arr, int value) 
{
    for (int i = 0; i < arr.length; i++)
    {
        arr[i] = value;
    }
      
}
  
/* Driver code */
public static void main(String[] args)
{
    /* Assume that inorder traversal of following tree
    is given
        40
    / \
    10     30
    /         \
    5         28 */
  
    int arr[] = {5, 10, 40, 30, 28};
    int n = arr.length;
  
    Node root = buildCartesianTree(arr, n);
  
    /* Let us test the built tree by printing Inorder
    traversal */
    System.out.printf("Inorder traversal of the" +
                        " constructed tree : \n");
    printInorder(root);
}
}
  
// This code is contributed by PrinciRaj1992


C#
// A O(n) C# program to concartesian tree
// from a given array
  
/* A binary tree node has data, pointer to left
child and a pointer to right child */
using System;
  
class GFG
{
      
class Node
{
    public int data;
    public Node left, right;
};
  
/* This funtcion is here just to test buildTree() */
static void printInorder (Node node)
{
    if (node == null)
        return;
    printInorder (node.left);
    Console.Write(node.data + " ");
    printInorder (node.right);
}
  
// Recursively consubtree under given root using
// leftChil[] and rightchild
static Node buildCartesianTreeUtil (int root, int []arr,
        int []parent, int []leftchild, int []rightchild)
{
    if (root == -1)
        return null;
  
    // Create a new node with root's data
    Node temp = new Node();
    temp.data = arr[root] ;
  
    // Recursively conleft and right subtrees
    temp.left = buildCartesianTreeUtil( leftchild[root],
                    arr, parent, leftchild, rightchild );
    temp.right = buildCartesianTreeUtil( rightchild[root],
                    arr, parent, leftchild, rightchild );
  
    return temp ;
}
  
// A function to create the Cartesian Tree in O(N) time
static Node buildCartesianTree (int []arr, int n)
{
    // Arrays to hold the index of parent, left-child,
    // right-child of each number in the input array
    int []parent = new int[n];
    int []leftchild = new int[n];
    int []rightchild = new int[n];
  
    // Initialize all array values as -1
    memset(parent, -1);
    memset(leftchild, -1);
    memset(rightchild, -1);
  
    // 'root' and 'last' stores the index of the root and the
    // last processed of the Cartesian Tree.
    // Initially we take root of the Cartesian Tree as the
    // first element of the input array. This can change
    // according to the algorithm
    int root = 0, last;
  
    // Starting from the second element of the input array
    // to the last on scan across the elements, adding them
    // one at a time.
    for (int i = 1; i <= n - 1; i++)
    {
        last = i - 1;
        rightchild[i] = -1;
  
        // Scan upward from the node's parent up to
        // the root of the tree until a node is found
        // whose value is greater than the current one
        // This is the same as Step 2 mentioned in the
        // algorithm
        while (arr[last] <= arr[i] && last != root)
            last = parent[last];
  
        // arr[i] is the largest element yet; make it
        // new root
        if (arr[last] <= arr[i])
        {
            parent[root] = i;
            leftchild[i] = root;
            root = i;
        }
  
        // Just insert it
        else if (rightchild[last] == -1)
        {
            rightchild[last] = i;
            parent[i] = last;
            leftchild[i] = -1;
        }
  
        // Reconfigure links
        else
        {
            parent[rightchild[last]] = i;
            leftchild[i] = rightchild[last];
            rightchild[last] = i;
            parent[i] = last;
        }
  
    }
  
    // Since the root of the Cartesian Tree has no
    // parent, so we assign it -1
    parent[root] = -1;
  
    return (buildCartesianTreeUtil (root, arr, parent,
                                    leftchild, rightchild));
}
  
static void memset(int[] arr, int value) 
{
    for (int i = 0; i < arr.Length; i++)
    {
        arr[i] = value;
    }
      
}
  
/* Driver code */
public static void Main(String[] args)
{
    /* Assume that inorder traversal of following tree
    is given
        40
    / \
    10     30
    /         \
    5         28 */
  
    int []arr = {5, 10, 40, 30, 28};
    int n = arr.Length;
  
    Node root = buildCartesianTree(arr, n);
  
    /* Let us test the built tree by printing Inorder
    traversal */
    Console.Write("Inorder traversal of the" +
                        " constructed tree : \n");
    printInorder(root);
}
}
  
// This code is contributed by 29AjayKumar


输出:

Inorder traversal of the constructed tree :
5 10 40 30 28

时间复杂度:
乍一看,由于buildCartesianTree()中有两个循环,因此代码似乎要花费O(n 2 )时间。但实际上,排序的遍历平均需要O(NlogN)时间和O(n ^ 2)。

辅助空间:
我们为每个节点以及三个额外的数组声明一个结构-leftchild [],rightchild [],parent [],以保存输入数组中每个值的左子项,右子项,父项的索引。因此,总体O(4 * n)= O(n)额外空间。

笛卡尔树的应用

  • 笛卡尔树排序
  • 序列上的范围最小查询等同于序列的笛卡尔树上的最低公共祖先查询。因此,可以使用序列的笛卡尔树将RMQ简化为LCA。
  • Treap是一种平衡的二叉搜索树结构,是(键,优先级)对的笛卡尔树;它根据优先级值进行堆排序,并且顺序遍历按排序顺序给出键。
  • 字符串的后缀树可以由后缀数组和最长的公共前缀数组构成。第一步是计算最长公共前缀数组的笛卡尔树。

参考:
http://wcipeg.com/wiki/Cartesian_tree