📜  笛卡尔树排序

📅  最后修改于: 2021-05-06 23:42:34             🧑  作者: Mango

先决条件:笛卡尔树

笛卡尔排序是一种自适应排序,因为如果对数据进行部分排序,它可以更快地对数据进行排序。实际上,很少有排序算法可以利用这一事实。

例如,考虑数组{5,10,40,30,28}。输入数据也进行了部分排序,因为“ 40”和“ 28”之间只有一次交换会导致完全排序。请在下面查看笛卡尔树排序将如何利用这一事实。

以下是用于排序的步骤。

步骤1:根据给定的输入序列构建(最小堆)笛卡尔树。
ctree1

第2步:从构建的笛卡尔树的根开始,将节点推入优先级队列。
然后,我们将节点弹出优先级队列的顶部,并以预排序的方式将弹出节点的子级推入优先级队列。

  1. 弹出优先级队列顶部的节点,并将其添加到列表中。
  2. 首先推送弹出节点的左子节点(如果存在)。
  3. 下一步将弹出节点的右子节点(如果存在)推入。

ctree1

ctree2

如何建立(最小堆)笛卡尔树?
构造min-heap与构造(max-heap)笛卡尔树(在上一篇文章中讨论过)相似,不同之处在于现在我们从节点的父节点向上扫描到树的根,直到找到其值为比当前的树小(并且在最大堆的笛卡尔树的情况下不大),然后相应地重新配置链接以构建最小堆的笛卡尔树。

为什么不只使用优先级队列?
可能会感到奇怪的是,如果我们简单地将输入数组的编号一个接一个地插入优先级队列中(即,不构建笛卡尔树),那么使用优先级队列总会得到排序后的数据。

但是所花费的时间相差很多。

假设我们采用输入数组– {5,10,40,30,28}

如果我们简单地一个接一个地插入输入数组数字(不使用笛卡尔树),那么每次插入数字时我们可能不得不浪费很多操作来调整队列顺序(就像典型的堆在输入数组数字时执行这些操作一样)。插入新数字,因为优先级队列不过是堆)。

而在这里,我们可以看到使用笛卡尔树仅进行了5次操作(请参见上面的两个图,其中我们不断推入并弹出笛卡尔树的节点),这是线性的,因为在输入数组中也有5个数字。因此,我们看到笛卡尔树排序的最佳情况是O(n),在这种情况下,堆排序将需要更多的操作,因为它没有利用输入数据被部分排序的事实。

为什么要进行遍历?
答案是,由于笛卡尔树基本上是堆数据结构,因此遵循堆的所有属性。因此,根节点始终小于其两个子节点。因此,我们使用一种前推方式来进行弹出和推入,因为这样,根节点总是比优先级队列中的子节点更早地被推送,并且由于根节点总是比其子节点都少,因此我们不会必须在优先级队列中执行其他操作。

请参考下图,以更好地理解-
ctree3

// A C++ program to implement Cartesian Tree sort
// Note that in this program we will build a min-heap
// Cartesian Tree and not max-heap.
#include
using namespace std;
  
/* A binary tree node has data, pointer to left child
   and a pointer to right child */
struct Node
{
    int data;
    Node *left, *right;
};
  
// Creating a shortcut for int, Node* pair type
typedef pair iNPair;
  
// This function sorts by pushing and popping the
// Cartesian Tree nodes in a pre-order like fashion
void pQBasedTraversal(Node* root)
{
    // We will use a priority queue to sort the
    // partially-sorted data efficiently.
    // Unlike Heap, Cartesian tree makes use of
    // the fact that the data is partially sorted
    priority_queue , greater> pQueue;
    pQueue.push (make_pair (root->data,root));
  
    // Resembles a pre-order traverse as first data
    // is printed then the left and then right child.
    while (! pQueue.empty())
    {
        iNPair popped_pair = pQueue.top();
        printf("%d ",popped_pair.first);
  
        pQueue.pop();
  
        if (popped_pair.second->left != NULL)
            pQueue.push (make_pair(popped_pair.second->left->data,
                                   popped_pair.second->left));
  
        if (popped_pair.second->right != NULL)
             pQueue.push (make_pair(popped_pair.second->right->data,
                                    popped_pair.second->right));
    }
  
    return;
}
  
  
Node *buildCartesianTreeUtil(int root, int arr[],
           int parent[], int leftchild[], int rightchild[])
{
    if (root == -1)
        return NULL;
  
    Node *temp = new Node;
  
    temp->data = arr[root];
    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 smaller 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 smallest 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));
}
  
// Sorts an input array
int printSortedArr(int arr[], int n)
{
    // Build a cartesian tree
    Node *root = buildCartesianTree(arr, n);
  
    printf("The sorted array is-\n");
  
    // Do pr-order traversal and insert
    // in priority queue
    pQBasedTraversal(root);
}
  
/* Driver program to test above functions */
int main()
{
    /*  Given input array- {5,10,40,30,28},
        it's corresponding unique Cartesian Tree
        is-
  
        5
          \
          10
            \
             28
            /
          30
         /
        40
    */
  
    int arr[] = {5, 10, 40, 30, 28};
    int n = sizeof(arr)/sizeof(arr[0]);
  
    printSortedArr(arr, n);
  
    return(0);
}

输出 :

The sorted array is-
5 10 28 30 40

时间复杂度: O(n)最佳情况下的行为(当对输入数据进行部分排序时), O(n log n)最佳情况下的行为(当对输入数据进行不部分排序时)

辅助空间:我们使用优先级队列和笛卡尔树数据结构。现在,在任何时候,优先级队列的大小都不会超过输入数组的大小,因为我们不断推入并弹出节点。因此,我们使用O(n)辅助空间。

参考 :
https://zh.wikipedia.org/wiki/Adaptive_sort
http://11011110.livejournal.com/283412.htmlhttp://gradbot.blogspot.in/2010/06/cartesian-tree-sort.htmlhttp://www.keithschwarz.com/interesting/code/?dir=cartesian -tree-sorthttps://zh.wikipedia.org/wiki/Cartesian_tree#Application_in_sorting