寻找未排序的n个不同元素中最大值和最小值所需的最少比较次数是多少?
上述算法的最佳时间复杂度可能是什么?
从最少比较次数的角度,我指的是针对最坏情况最有效的算法。
寻找未排序的n个不同元素中最大值和最小值所需的最少比较次数是多少?
上述算法的最佳时间复杂度可能是什么?
从最少比较次数的角度,我指的是针对最坏情况最有效的算法。
最优算法需要进行3/2*n次比较。
它的工作原理如下:
5 2 6 7 3 1 10 35 4 6
每一步(n/2步),您将比较第i个和第n-i个元素,并移动到“bigger”和“lower”表格。
n/2步后,您知道最小值在“lower”表格中,最大值在“bigger”表格中。在这两个表格中查找最小值和最大值是(n/2) * 2,因此总共进行了(3/2) * n次比较。
这是 N * (3/2).
下限(从记忆中重建;不确定引用应该是什么)
当n为偶数时,此处有一个对手会强制进行(3/2) n - 2次比较,而当n为奇数时,则需要(3/2) n - 3/2次比较。Marcin所描述的算法,在经过仔细分析后,可以达到这些下限。
每个元素都处于四种状态之一:{min, max}
(从未被比较过,因此可以是最小值或最大值),{min}
(从未大于另一个元素,因此可以是最小值但不是最大值),{max}
(从未小于另一个元素,因此可以是最大值但不是最小值),{}
(大于另一个元素,小于另一个元素,既不是最小值也不是最大值),其中“可以是…”意味着存在与算法执行的比较兼容的总序,以便…成立。
设T为元素e的状态基数之和。在开始时,T = 2n。在结束时,T = 2,否则最小值或最大值将不唯一确定。以下对手确保T每次比较时至多减少2,并且除非两个元素首次进行比较,否则至多减少1。由此得到了上述下限。
对手必须保持T不会过快减少,同时至少保留一个一致的总序。对手如何确定比较结果?如果两个元素都不处于状态{min, max}
中,则很容易解决。状态不同,我们根据{min}
< {}
< {max}
进行解决,并且T保持不变;或者它们相同,我们给出任意一致的答案,并且T减少1。我们通过反证法证明了一致性得以维护。假设最近的比较创建了一个循环。现在循环中的所有元素都必须处于状态{}
中,这仅在两者先前都处于状态{}
时才可能发生。这与我们为处于相同状态的元素一致地回答的策略相矛盾。
否则,至少要比较的其中一个元素处于状态{min, max}
中。如果另一个元素处于状态{min}
中,则{min}
< {min, max}
。如果另一个元素处于状态{max}
中,则{min, max}
< {max}
。否则,任意解决即可。显然,当且仅当比较是在两个{min, max}
元素之间进行时,T减少2。这种比较不会创建循环,因为处于状态{min, max}
的元素在比较图中的度数为1。
这可以通过以下方式实现
3*n/2-2 list element comparisons if n is even
3*(n-1)/2 list element comparisons if n is odd.
这里是代码
minVal = maxVal = a[0];
int i;
for(i = 1; i < n-1; i += 2) {
if(a[i] < a[i+1]) {
if(a[i] < minVal)
minVal = a[i];
if(a[i+1] > maxVal)
maxVal = a[i+1];
}
else {
if(a[i+1] < minVal)
minVal = a[i+1];
if(a[i] > maxVal)
maxVal = a[i];
}
}
// here i == n-1 or i == n
if(i < n) {
if(a[i] < minVal)
minVal = a[i];
else if(a[i] > maxVal)
maxVal = a[i];
}
n
是2,可能需要2次比较?听起来不太理想。 - Steve Jessopif(i < n)
,你能解释一下吗? - usern = 6
。然后循环的第一次迭代处理a [1]
和a [2]
。第二次迭代处理a [3]
和a [4]
。然后我们以i = 5
退出循环。然后条件为真,if块处理a [5]
。 - Johan Råde如果您想比较数字值,实际上可以完全不需要进行任何比较!诀窍是扩展两个值之间差异的符号位,并将其用作二进制掩码。
也许这更像是一个聪明的技巧,而不是您正在寻找的“计算机科学”的答案,但是,根据编译器和 CPU 的不同,以下方法可能比使用 if 语句的其他方法更快:
void minmax(int values[], size_t count) {
int min = values[0];
int max = min;
for(int i = 1; i < count; ++i) {
int v = values[i];
int maxMask = (v - max) >> 31; // assuming 32-bit int
max = (max & maxMask) | (v & ~maxMask);
int minMask = (min - v) >> 31;
min = (min & minMask) | (v & ~minMask);
}
printf("max=%d min=%d\n", max, min);
}
示例调用:
int main() {
int values[] = {
20, -5, 13, -100, 55
};
minmax(values, 5); // prints max=55 min=-10
}
总计:0次比较,除了循环使用的一次之外,如果展开循环,则可以将其删除 :-)
这个算法的好处是它在机器代码层面上不使用条件跳转,因此没有管道停顿的风险。该算法也可以轻松地扩展到同时比较多个值(例如使用64位寄存器同时比较八个字节)。
实际上它在n-1和2(n-1)之间,因为您必须将每个元素与当前的最大值和最小值进行比较,但如果第一个比较返回true,则不必执行第二个比较
从其他答案中借鉴代码,我的解决方案如下:
var largest = list[0];
var smallest = list[0];
for(var i=1;i<list.length;i++)
{
if(list[i] > largest) {
largest = list[i];
} else if(list[i] < smallest) {
smallest = list[i];
}
}
使用maxmin算法[https://en.wikipedia.org/wiki/Minimax][minmax]
在n个不同元素中查找最大值和最小值所需的比较次数为(3/2)n - 2。
nums是一组数字,n(nums)是nums中元素的数量:
minMax (nums) {
if (n(nums) == 2) {
nums = {x, y};
return (max (x, y), min (x, y));
}
else {
divide nums equally into two sets, set1, set2;
minMax (set1);
minMax (set2);
}
}
(n-1) * 2
。var largest = list[0];
var smallest = list[0];
foreach(var i in list.Skip(1))
{
if(i > largest)
largest = i;
else if(i < smallest)
smallest = i;
}
if
为真,你真的需要运行第二个吗? - IVladi<list.length
条件使用了比你声称的多n
次比较。 - blubb