最初我有n个元素,它们在n个tile中。
我需要支持3种查询:
将两个图块合并为一个图块。
将一个瓷砖分成两个瓷砖。 (正式对于大小为k的图块,将其拆分为两个大小为k1和k2,k = k1 + k2的图块,第一个图块包含最小的k1元素,第二个图块包含其余的图块)
在一个图块中找到第k个最小元素。
仍假设有n个查询。我可以实现最糟糕的时间复杂度?
答案 0 :(得分:1)
那不是一个完整的答案,而是对可以做些什么的一些想法。
我的想法基于skip list。
让每个磁贴成为可索引的排序跳过列表。
拆分然后相当简单:找到k-th
元素并中断i > k1-th
和j <= k1-th
元素之间的每个链接(最多O(log n)
个链接)。
合并比较棘手。
首先,假设我们可以连接 O(log n)
中的两个跳过列表。
假设我们正在合并两个图块T1
和T2
。
t1
的{{1}}和来自T1
的{{1}}的第一个元素。让&#39; S
说t2
然后,在T2
中找到仍然低于t1 < t2
的最后t1'
。
我们必须在t2
之后立即插入T1
。但首先,我们正在t2
中t1'
后面查看元素t1*
。
现在在[{1}}中搜索仍然少于t1'
的最后T1
。
必须在t2'
和t1*
之间插入T2
的从T2
开始到t2
结尾的整个元素序列。
因此,我们正在t2'
和t1'
进行拆分,获取新列表t1*
,t1'
,{{1 },t2'
。
我们联接T1a
,T1b
和T2a
,获取新列表T2b
。
我们正在重复T1a
和T2a
的整个过程。
在一些伪python代码中:
T1b
最多T1*
次此类迭代,其中T1*
是T2b
和#skiplist interface:
# split(list, k) - splits list after the k-th element, returns two lists
# concat(list1, list2) - concatenates two lists, returns the new one
# index(list, k) - returns k-th element from the list
# upper_bound(list, val) - returns the index of the last element less that val
# empty(list) - check if list is empty
def Query(tile, k)
return index(tile, k)
def Split(tile, k)
return split(tile, k)
def Merge(tile1, tile2):
if empty(tile1):
return tile2
if empty(tile2):
return tile1
t1 = index(tile1, 0)
t2 = index(tile2, 0)
if t1 < t2:
#(1)
i1 = upper_bound(tile1, t2)
t1s = index(tile1, i1 + 1)
i2 = upper_bound(tile2, t1s)
t1_head, t1_tail = split(tile1, i1)
t2_head, t2_tail = split(tile2, i2)
head = concat(t1_head, t2_head)
tail = Merge(t1_tail, t2_tail)
return concat(head, tail)
else:
#swap tile1, tile2, do (1)
中的交错次数。每次迭代都需要O(p)
次操作才能完成。
正如@newbie所指出的,有一个例子,其中p
s的总和等于T1
。
这个python脚本为T2
生成了这样一个例子(输出中的加号代表合并):
O(log n)
p
:
n log n
给我们以下疑问:
k = log_2 n
这是一个二叉树,我们正在合并def f(l):
if len(l) == 2:
return "%s+%s" % (l[0], l[1])
if len(l) == 1:
return str(l[0])
l1 = [l[i] for i in xrange(0, len(l), 2)]
l2 = [l[i + 1] for i in xrange(0, len(l), 2)]
l_str = f(l1)
r_str = f(l2)
return "(%s)+(%s)" % (l_str, r_str)
def example(k):
print f(list(range(0, 2 ** k)))
个n = 16
个大小的瓷砖,高度为example(4)
。瓷砖的构造方式使它们的元素始终是交错的,因此对于大小为(
(
(0+8)+(4+12)
)
+
(
(2+10)+(6+14)
)
)
+
(
(
(1+9)+(5+13)
)
+
(
(3+11)+(7+15)
)
)
的瓷砖,我们正在进行2^(k-j)
分割 - 连接。
然而,对于这个特定情况,它仍然不会使2^j
的总体复杂性恶化,因为(非常非正式地说)每个小&#39;列出的成本低于j
,而且还有更多“小”的成本。列出的不是&#39; big&#39;。
我不确定是否有更糟糕的反例,但目前我认为q
查询的总体最差案例复杂性介于O(q)
和O(n log n)
之间。
答案 1 :(得分:0)
寻找:
1和2的线性复杂度。
取决于您的容器3,线性最差。
但目前尚不清楚你究竟在问什么。你有一些我们可以看的代码吗?
答案 2 :(得分:0)
当我问这个问题时,我不知道如何解决它,因为似乎可以回答我自己的问题,我会自己回答这个问题:/ < / em>的
首先,假设排序列表中的值是1~n之间的整数。如果没有,您可以对它们进行排序和映射。
让我们为每个排序列表构建一个分段树,根据值(1~n)构建分段树。在段树的每个节点中,存储该范围内的数量,让我们将其称为节点的值。
它似乎需要O(nlogn)空间来存储每个段树,但我们可以简单地删除值为0的节点,并且只有当它们的值变为&gt; 0时才真正分配这些节点。
因此,对于只包含一个元素的排序列表,我们只需构建此值的链,因此只需要O(logn)内存。
int s[SZ]/*value of a node*/,
ch[SZ][2]/*a node's two children*/;
//make a seg with only node p, return in the first argument
//call with sth. like build(root,1,n,value);
void build(int& x,int l,int r,int p)
{
x=/*a new node*/; s[x]=1;
if(l==r) return;
int m=(l+r)>>1;
if(p<=m) build(ch[x][0],l,m,p);
else build(ch[x][1],m+1,r,p);
}
当我们拆分段树(排序列表)时,只需递归地拆分两个子项:
//make a new node t2, split t1 to t1 and t2 so that s[t1]=k
void split(int t1,int& t2,int k)
{
t2=/*a new node*/;
int ls=s[ch[t1][0]]; //size of t1's left child
if(k>ls) split(ch[t1][1],ch[t2][1],k-ls); //split the right child of t1
else swap(ch[t1][1],ch[t2][1]); //all right child belong to t2
if(k<ls) split(ch[t1][0],ch[t2][0],k); //split the left child of t1
s[t2]=s[t1]-k; s[t1]=k;
}
当我们合并两个已排序的列表时,请强制合并它们:
//merge trees t1&t2, return merged segment tree
int merge(int t1,int t2)
{
if(t1&&t2);else return t1^t2; //nothing to merge
ch[t1][0]=merge(ch[t1][0],ch[t2][0]);
ch[t1][1]=merge(ch[t1][1],ch[t2][1]);
s[t1]+=s[t2]; /*erase t2, it's useless now*/ return t1;
}
看起来很简单,不是吗?但它的总复杂性实际上是O(nlogn)。
证明:
让我们研究分配的分段树节点的总数。
最初我们将分配O(nlogn)这样的节点(每个节点O(logn))。
对于每次分裂尝试,我们将最多分配O(logn),因此它总共也是O(nlogn)。原因显然是我们将递归地分割节点的左子节点或右子节点。
因此,分配的分段树节点的总数最多只有O(nlogn)。
考虑合并,除了没有合并之外,每次调用merge时,分配的分段树节点的总数将减少1(t2不是很有用)了)。显然没有什么可以合并的#39;只有在父亲真正合并时才会被调用,因此他们与复杂性无关。
分配的分段树节点总数为O(nlogn),对于每个有用的合并,它将减少1,因此所有合并的总复杂度为O(nlogn)。
总结一下,我们得到了结果。
查询k-th也非常简单,我们已经完成了:)
//query k-th of segment tree x[l,r]
int ask(int x,int l,int r,int k)
{
if(l==r) return l;
int ls=s[ch[x][0]]; //how many nodes in left child
int m=(l+r)>>1;
if(k>ls) return ask(ch[x][1],m+1,r,k-ls);
return ask(ch[x][0],l,m,k);
}