让我们想象一下我们想要在n个元素的数组中搜索一个元素,我们可以创建任意数量的线程。让我们为数组的每个元素分配一个单独的线程,并认为比较操作比创建线程要昂贵得多。说这种搜索算法是O(1)吗?
是否有效代码:
import java.util.concurrent.atomic.AtomicBoolean;
public class scratch {
static int[] a = new int[100];
static final int n = 3;
static boolean expensiveCompare(int pos) {
try {
Thread.sleep(1000);
} catch (InterruptedException e){}
return a[pos] == n;
}
static AtomicBoolean answer = new AtomicBoolean(false);
public static void main(String... args) {
long start = System.currentTimeMillis();
a[2] = 3;
Thread[] threads = new Thread[100];
for(int i=0; i < 100; ++i) {
final int k = i;
threads[i] = new Thread(new Runnable() {
@Override
public void run() {
if(expensiveCompare(k)) {
answer.compareAndExchange(false, true);
}
}
});
threads[i].start();
}
for(int i = 0; i < 100; ++i) {
try {
threads[i].join();
}catch (InterruptedException e) {}
}
System.out.println("Elapsed: " + (System.currentTimeMillis() - start));
System.out.println("Answer:" + answer);
}
}
打印:&#34;经过:1000&#34;
public class scratch {
static final int n = 3;
static int[] a = new int[100];
static boolean answer = false;
static boolean expensiveCompare(int pos) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
}
return a[pos] == n;
}
public static void main(String... args) {
long start = System.currentTimeMillis();
a[2] = 3;
for (int i = 0; i < 100; ++i) {
answer |= expensiveCompare(i);
}
System.out.println("Elapsed: " + (System.currentTimeMillis() - start));
System.out.println("Answer:" + answer);
}
}
打印:&#34;经历了100000&#34;。
答案 0 :(得分:4)
您的代码是O(1),不是因为线程,而是因为数组大小是常量(100)。如果我们用变量替换它,那么:
for(int i=0; i < n; ++i) {
已经是O(n)。你在循环中所做的事情并不重要(只要你不改变i
或n
),单独的循环标题就已经执行了O(n)说明书数量。
更一般地说,你甚至不能在O(1)时间内启动n个线程,因此启动n
个线程不会给你一个O(1)的运行时间(当然假设所有线程都是并行运行,即假装你拥有无限数量的核心。
认为比较操作比创建线程要贵得多
每项操作的成本并不重要。这些只是常量而且大O不关心常数。
答案 1 :(得分:3)
让我们假设元素N的数量是无界的,并且所需的线程可以并行运行。
我们甚至假设所有线程都已启动并运行,以避免O(N)成本。然后可以在时间O(1)中进行比较,每个线程一个。但这并非全部:您还想知道是否找到了密钥,并且可选择通过哪个线程。
这个结果集合不能在O(1)中执行,因为没有处理器指令处理无限数量的参数。由于该数字是有界的,即使忽略对共享内存的争用,您可以做的最好的是以树状方式合并N个结果(N =&gt; N / k =&gt; N /k²=&gt; N /k³ ... =&gt; 1),并在O(Log N)阶段后获得单个结果(要处理的位数以几何方式减少)。
所以最好的复杂性是O(Log N)。 [具有讽刺意味的是,如果对数据进行排序,则无法击败具有十亿个数据的单个线程。]
这是一般规则:无论计算能力如何,都可以在时间O(1)内解决所有输入数据对解决方案有所贡献的问题。
这深入到了物理学:你不能建立一个与2输入一样快的N输入逻辑门。
答案 2 :(得分:2)
请注意,即使您拥有数百万个线程,也必须将每个线程分配给特定的CPU进行执行。
在现实生活中,您拥有有限数量的CPU或计算节点。如果您有 k CPU,则类似于 O(n / k),因为您一次进行k次比较。
现在,如果k远小于n(这是PC的典型值),那么O(n / k)~O(n)。就像你的CPU有4个核心(它没有增长),k = 4,如果n = 100000,那么O(n / k)仍然是O(n)。
如果你的数组非常小,并且k接近n(某些常数C的n / k
答案 3 :(得分:0)
这个问题涉及big-O符号的一个更微妙的方面:成本模型。
当我们考虑有多少&#34;步骤&#34;某个算法将完成一项任务,我们需要在一步中决定我们可以做什么。大多数情况下,人们会假设一个成本模型,其中任何基本指令,如比较,基本算术运算或函数调用都需要1步。这是一个有用的简化,即使在实际计算机中,这些操作可能需要花费大量不同的时间来执行,这取决于模型未考虑的因素(例如,我们在寄存器中添加两个数字,在缓存中,在主数据库中内存,在磁盘上?)因为这样做让我们思考程序如何扩展到更大的输入,而不必担心太多的细节。
但是,在某些应用程序中,不同的成本模型是合适的:例如,在编写网络软件时,您可能希望将网络传输计为一步,将单个节点上发生的任何计算都视为空闲,无论算法如何。类似地,根据他们需要从主存储器读取多少次来分析设计为与内存局部性良好协作的算法可能是有用的,忽略了他们对所读取内容所做的任何工作的成本。这些方法都不是&#34;对&#34;一个 - 他们都在测量一个抽象的,虚构的数字 - 但它们可能对不同的东西有用。
所以直接回答你的问题:这个算法是O(1)还是O(n)取决于成本模型。如果您认为启动线程是空闲的,并且任意数量的线程上的一个操作都算作一个步骤,那么是的,它是O(1)。如果你决定开始一个线程花费一个工作单元,或者单个线程上的操作算作一个工作单元,那么它就是O(n)。哪种成本模型适合您的用途取决于您要回答的问题。
(稍微不那么腼腆,我会说第一个成本模型对我来说似乎没什么用,因为我不知道我想要承担的任何情况我可以拥有尽可能多的CPU,或者工作的成本可以忽略不计,但也许我正在分析一个用大型MapReduce集群处理数据的算法或者有意义的事情。)