我有一个未排序的数组。我在查询中给出一个范围,然后必须返回该范围的最大值。例如:
array[]={23,17,9,45,78,2,4,6,90,1};
query(both inclusive): 2 6
answer: 78
我构造了哪种算法或数据结构来快速检索任何范围内的最大值。(有很多查询)
编辑: 这确实是实际问题的简单版本。我可以将数组的大小设置为最大100000,查询数量最多为100000。因此,我绝对需要进行一些预处理,以利于快速查询响应。
我有一个未排序的数组。我在查询中给出一个范围,然后必须返回该范围的最大值。例如:
array[]={23,17,9,45,78,2,4,6,90,1};
query(both inclusive): 2 6
answer: 78
我构造了哪种算法或数据结构来快速检索任何范围内的最大值。(有很多查询)
编辑: 这确实是实际问题的简单版本。我可以将数组的大小设置为最大100000,查询数量最多为100000。因此,我绝对需要进行一些预处理,以利于快速查询响应。
Answers:
我认为您可以构建某种二叉树,其中每个节点代表其子节点的最大值:
78
45 78
23 45 78 6
23 17 9 45 78 2 4 6
然后,您只需要找到一种方法即可确定您最少需要检查哪些节点才能找到所查询范围内的最大值。在此示例中,要获得索引范围[2, 6]
(含)内的最大值,您将拥有max(45, 78, 4)
而不是max(9, 45, 78, 2, 4)
。随着树的生长,增益将更大。
78
(跳过2
)的所有6
子项,因为对于所有信息,它都知道索引在该子树中。
补充ngoaho91的答案。
解决此问题的最佳方法是使用细分树数据结构。这使您可以在O(log(n))中回答此类查询,这意味着算法的总复杂度为O(Q logn),其中Q是查询数。如果您使用朴素算法,则总复杂度将为O(Q n),这显然要慢一些。
但是,使用段树有一个缺点。它会占用大量内存,但是很多时候您对内存的关心不如对速度的关心。
我将简要描述此DS使用的算法:
段树只是二进制搜索树的一种特例,其中每个节点都保存了分配给它的范围的值。根节点的范围为[0,n]。左边的孩子被分配了范围[0,(0 + n)/ 2],右边的孩子被分配了范围[(0 + n)/ 2 + 1,n]。这样树将被建立。
创建树:
/*
A[] -> array of original values
tree[] -> Segment Tree Data Structure.
node -> the node we are actually in: remember left child is 2*node, right child is 2*node+1
a, b -> The limits of the actual array. This is used because we are dealing
with a recursive function.
*/
int tree[SIZE];
void build_tree(vector<int> A, int node, int a, int b) {
if (a == b) { // We get to a simple element
tree[node] = A[a]; // This node stores the only value
}
else {
int leftChild, rightChild, middle;
leftChild = 2*node;
rightChild = 2*node+1; // Or leftChild+1
middle = (a+b) / 2;
build_tree(A, leftChild, a, middle); // Recursively build the tree in the left child
build_tree(A, rightChild, middle+1, b); // Recursively build the tree in the right child
tree[node] = max(tree[leftChild], tree[rightChild]); // The Value of the actual node,
//is the max of both of the children.
}
}
查询树
int query(int node, int a, int b, int p, int q) {
if (b < p || a > q) // The actual range is outside this range
return -INF; // Return a negative big number. Can you figure out why?
else if (p >= a && b >= q) // Query inside the range
return tree[node];
int l, r, m;
l = 2*node;
r = l+1;
m = (a+b) / 2;
return max(query(l, a, m, p, q), query(r, m+1, b, p, q)); // Return the max of querying both children.
}
如果您需要进一步的解释,请告诉我。
顺便说一句,Segment Tree还支持更新O(log n)中的单个元素或一系列元素
O(log(n))
将每个元素添加到树中。因此,总的复杂性O(nlog(n))
最佳算法将在O(n)时间中,如下所示,开始,结束是范围边界的索引
int findMax(int[] a, start, end) {
max = Integer.MIN; // initialize to minimum Integer
for(int i=start; i <= end; i++)
if ( a[i] > max )
max = a[i];
return max;
}
max
为a[i]
并从for
处开始循环i+1
。)
start
,停在end
)。我同意,这是一次性查找的最佳选择。@ThijsvanDien的答案只有在查找将要发生多次的情况下才会更好,因为初始设置需要更长的时间。
基于二叉树/分段树的解决方案确实指向了正确的方向。但是,可能有人反对说他们需要很多额外的内存。这些问题有两种解决方案:
第一点是因为树是高度结构化的,因此您可以使用类似堆的结构隐式定义树,而不用用节点,左右指针,间隔等表示树。这样就节省了很多内存不会影响性能-您确实需要执行更多的指针运算。
第二点是,您可以使用M元树而不是二叉树来进行评估,而这需要花费更多的精力进行评估。例如,如果您使用三叉树,则一次将最多计算3个元素,一次将计算9个元素,然后计算27个,依此类推。那么所需的额外存储量为N /(M-1)-您可以用几何级数公式证明。例如,如果选择M = 11,则将需要二叉树方法存储空间的1/10。
您可以验证Python中的这些天真的和优化的实现是否给出了相同的结果:
class RangeQuerier(object):
#The naive way
def __init__(self):
pass
def set_array(self,arr):
#Set, and preprocess
self.arr = arr
def query(self,l,r):
try:
return max(self.arr[l:r])
except ValueError:
return None
与
class RangeQuerierMultiLevel(object):
def __init__(self):
self.arrs = []
self.sub_factor = 3
self.len_ = 0
def set_array(self,arr):
#Set, and preprocess
tgt = arr
self.len_ = len(tgt)
self.arrs.append(arr)
while len(tgt) > 1:
tgt = self.maxify_one_array(tgt)
self.arrs.append(tgt)
def maxify_one_array(self,arr):
sub_arr = []
themax = float('-inf')
for i,el in enumerate(arr):
themax = max(el,themax)
if i % self.sub_factor == self.sub_factor - 1:
sub_arr.append(themax)
themax = float('-inf')
return sub_arr
def query(self,l,r,level=None):
if level is None:
level = len(self.arrs)-1
if r <= l:
return None
int_size = self.sub_factor ** level
lhs,mid,rhs = (float('-inf'),float('-inf'),float('-inf'))
#Check if there's an imperfect match on the left hand side
if l % int_size != 0:
lnew = int(ceil(l/float(int_size)))*int_size
lhs = self.query(l,min(lnew,r),level-1)
l = lnew
#Check if there's an imperfect match on the right hand side
if r % int_size != 0:
rnew = int(floor(r/float(int_size)))*int_size
rhs = self.query(max(rnew,l),r,level-1)
r = rnew
if r > l:
#Handle the middle elements
mid = max(self.arrs[level][l/int_size:r/int_size])
return max(max(lhs,mid),rhs)
尝试“段树”数据结构
有2个步骤
build_tree()O(n)
query(int min,int max)O(nlogn)
http://en.wikipedia.org/wiki/Segment_tree
编辑:
你们只是不读我发送的维基!
该算法是:
-遍历数组1次以构建树。O(n)
-接下来的100000000次您想知道数组任何部分的最大值,只需调用查询函数即可。每个查询的O(登录)
-C ++在此处实现geeksforgeeks.org/segment-tree-set-1-range-minimum-query/
旧算法是:
每个查询都只需遍历选定区域并查找。
因此,如果您要使用此算法处理一次,那么,它比以前的方法要慢。但是,如果您要处理大量查询(十亿),则可以非常高效地生成这样的文本文件,对于测试
行1:从0-1000000中的50000个随机数,用“(space)”(它是数组)
行分割2:从1到50000的2个随机数,除以'(space)'(这是查询)
...
200000行:喜欢第2行,它也是随机查询
这是示例问题,很抱歉,但是这在越南语中
http://vn.spoj.com/problems/NKLINEUP/
如果以旧方式解决,则您永远不会通过。
O(n)
搜索数组快得多,如tarun_telang的答案所述。首先的直觉是,O(log n + k)
它比快O(n)
,但O(log n + k)
仅是子数组的检索-等同于O(1)
给定起点和终点的数组访问。您仍然需要遍历它才能找到最大值。