先决条件:笛卡尔树
笛卡尔排序是一种自适应排序,因为如果对数据进行部分排序,它可以更快地对数据进行排序。实际上,很少有排序算法可以利用这一事实。
例如,考虑数组{5,10,40,30,28}。输入数据也被部分排序,因为“ 40”和“ 28”之间只有一次交换会导致完全排序。请在下面查看笛卡尔树排序将如何利用这一事实。
以下是用于排序的步骤。
步骤1:根据给定的输入序列构建(最小堆)笛卡尔树。
第2步:从构建的笛卡尔树的根开始,将节点推入优先级队列。
然后,我们将节点弹出优先级队列的顶部,并以预排序的方式将弹出节点的子级推入优先级队列。
- 弹出优先级队列顶部的节点,并将其添加到列表中。
- 首先推送弹出节点的左子节点(如果存在)。
- 下一步将弹出节点的右子节点(如果存在)推入。
如何建立(最小堆)笛卡尔树?
构造min-heap与构造(max-heap)笛卡尔树(在上一篇文章中讨论过)相似,不同之处在于现在我们从节点的父节点向上扫描到树的根,直到找到其值为比当前的树小(并且在最大堆的笛卡尔树的情况下不大),然后相应地重新配置链接以构建最小堆的笛卡尔树。
为什么不只使用优先级队列?
可能会感到奇怪的是,如果我们简单地将输入数组的编号一个接一个地插入优先级队列中(即,不构建笛卡尔树),那么使用优先级队列总会得到排序后的数据。
但是所花费的时间相差很多。
假设我们采用输入数组– {5,10,40,30,28}
如果我们简单地一个接一个地插入输入数组数字(不使用笛卡尔树),那么每次插入数字时我们可能不得不浪费很多操作来调整队列顺序(就像典型的堆在输入数组数字时执行这些操作一样)。插入新数字,因为优先级队列不过是堆)。
而在这里,我们可以看到使用笛卡尔树仅进行了5次操作(请参见上面的两个图,其中我们不断推入并弹出笛卡尔树的节点),这是线性的,因为在输入数组中也有5个数字。因此,我们看到笛卡尔树排序的最佳情况是O(n),在这种情况下,堆排序将需要更多的操作,因为它没有利用输入数据被部分排序的事实。
为什么要进行遍历?
答案是,由于笛卡尔树基本上是堆数据结构,因此遵循堆的所有属性。因此,根节点始终小于其两个子节点。因此,我们使用一种前推方式来进行弹出和推入,因为这样,根节点总是比优先级队列中的子节点更早地被推送,并且由于根节点总是比其子节点都少,因此我们不会必须在优先级队列中做额外的操作。
请参考下图,以更好地理解-
// 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