如何在 Big-O(N) 时间内将 3 个排序数组合并为 1 个排序数组?
How to merge 3 sorted arrays into 1 sorted array in Big-O(N) time?
正在尝试将 3 个数组合并为一个,以便最终数组按顺序排列。
给定
int[] a = {1,3};
int[] b = {2,4};
int[] c = {1,5};
合并数组使得最终数组 d = {1,1,2,3,4,5}
不能只是连接它们然后对 d 数组进行排序,因为那样会使时间复杂度大于 Big-O(N)。
这就是我到目前为止所得到的。遇到索引超出范围的问题:
public static void main(String[] args) {
// Sort these 3 arrays. The final array should be d = {1,1,2,3,4,5}
int[] a = {1,3};
int[] b = {2,4};
int[] c = {1,5};
int[] d = new int[a.length + b.length + c.length];
int i = 0;
int j = 0;
int k = 0;
int l = 0;
for (int iteration = 0; iteration <= d.length; iteration++){
if ((i != a.length || j != b.length) && a[i] < b[j]){
if (a[i] < c[k]){
// then a[i] is smallest
d[l] = a[i];
i++;
l++;
displayArrayContents(a,b,c,d,i,j,k,l);
}
else if (a[i] > c[k]){
// then c[k] is smallest
d[l] = c[k];
k++;
l++;
displayArrayContents(a,b,c,d,i,j,k,l);
}
else if (a[i] == c[k]){
d[l] = a[i];
i++;
l++;
d[l] = c[k];
k++;
l++;
displayArrayContents(a,b,c,d,i,j,k,l);
}
}
else if(b[j] < a[i]){
if (b[j] < c[k]){
// b[j] is smallest
d[l] = b[j];
l++;
j++;
displayArrayContents(a,b,c,d,i,j,k,l);
}
else if (b[j] > c[k]){
// c[k] is smallest
d[l] = c[k];
l++;
k++;
displayArrayContents(a,b,c,d,i,j,k,l);
}
else if (b[j] == c[k]){
d[l] = b[j];
j++;
l++;
d[l] = c[k];
k++;
l++;
displayArrayContents(a,b,c,d,i,j,k,l);
}
}
}
}
按照以下步骤操作:
- 从这里获取答案代码:How to merge two sorted arrays into a sorted array?
- 在
a
和 b
上调用该函数以获得结果数组 ab
- 在
ab
和 c
上调用该函数以获得结果 abc
- 您已经调用了一个
O(n)
函数两次,所以它仍然是 O(n)
。砰。
事实是,摆弄数组索引令人沮丧。如果您可以将这些数组作为队列或迭代器来代替,只需 take()
或 next()
每次迭代中的最小值并将其放入结果列表中,它将更清晰。
您需要清楚什么随 N 发生变化。如果您总是只有三个数组,并且它们的大小或最大大小随 N 发生变化,那么几乎所有重复 selects 最小数字的代码从三个数组中的任何一个可用,将其删除并将其附加到结果数组的末尾,将是 O(N)。您的 select 最小数字的代码可能笨拙且昂贵,但它只是一个常数因子,不会随着 N 的增加而改变。
如果要合并的数组数量随着 N 的增加而增加,那么您需要更加小心select 可用的最小数量,您最终会遇到排序问题,您可以'在通常的假设下,t 在线性时间内完成。
通常,外部排序会使用堆合并磁盘上保存的大量列表(例如 http://www.geeksforgeeks.org/external-sorting/)。这对于一次合并大量列表会更有效率,但只会让你获得一个常数因子,
假设这是 java,数组名是对数组的引用,可以像 C/C++ 中的指针一样交换。这可用于减少主合并循环中的条件数量,使代码更简单一些,但代价是交换。空数组检查在主合并循环之前完成。此方法可以轻松扩展以处理 4 路或更大的合并,否则需要大量条件。
static int[] Merge(int[] a, int[] b, int[] c)
{
int[] d = new int[a.length + b.length + c.length];
int[] e; // temp used for swap
int i = 0;
int j = 0;
int k = 0;
int l = 0;
int t;
// empty array checks
if(0 == b.length){ // if b empty
if(0 == c.length){ // if b and c empty
c = a; // c = a
a = b; // a = b = empty
} else { // if b empty, c not empty
e = a; // swap a and b
a = b;
b = e;
}
} else { // else b not empty
if(0 == c.length){ // if c empty
e = c;
c = b; // shift c = b, b = a
b = a;
a = e; // a = empty
}
}
// main merge loop
while(i < a.length){ // 3 way merge
if(a[i] > b[j]){ // if b smaller swap
e = a;
a = b;
b = e;
t = i;
i = j;
j = t;
}
if(a[i] > c[k]){ // if c smaller swap
e = a;
a = c;
c = e;
t = i;
i = k;
k = t;
}
d[l++] = a[i++];
}
while(j < b.length){ // 2 way merge
if(b[j] > c[k]){ // if c smaller swap
e = b;
b = c;
c = e;
t = j;
j = k;
k = t;
}
d[l++] = b[j++];
}
while(k < c.length) // copy rest of c
d[l++] = c[k++];
return d;
}
你的想法是正确的,代表了一个 O(n) 的解决方案。但是,你的代码确实存在一些问题,其中一些会导致越界异常:
- 您访问
c[k]
时未先确保 k < c.length
;
- 即使您在
length
上 做 测试,您也无法避免这种无效访问:(i != a.length || j != b.length) && a[i] < b[j]
仍然会导致a[i]
在 i === a.length
时被访问(特别是在 j != b.length
时);
- 外循环需要迭代的次数通常是错误的,因为有时(在相等的情况下)您将两个值存储在目标数组中,这使得数组填满的速度比循环预期的要快。其实相等的情况(比如
a[i] == c[k]
)其实并不需要单独对待。如果将它与 >
一起处理(因此:>=
)算法仍然正确:第二个(相等的)值将在下一次迭代中复制;
- 即使您解决了上一个问题,您的外循环仍然使一次迭代过多;
for
条件应该是 < d.length
而不是 <= d.length
没有问题,但是你的代码中有很多重复:
- 您可以将对
displayArrayContents(a,b,c,d,i,j,k,l);
的调用移到 if
构造之外,这样它就会始终执行,这正是您真正想要的;
- 由于您总是在
if
构造中分配给 d
,因此您可以使用三元运算符 ? ... :
; 来进行分配 "outside of the if
"
- 虽然像
i != a.length
这样的测试可以达到预期目的,但最好还是像这样进行测试:i < a.length
。
这里是考虑了上述因素的代码:
import java.util.Arrays; // for easy output of arrays with Arrays.toString().
class Main {
public static void main(String[] args) {
// Sort these 3 arrays. The final array should be d = {1,1,2,3,4,5}
int[] a = {1,3};
int[] b = {2,4};
int[] c = {1,5};
int[] d = new int[a.length + b.length + c.length];
int i = 0;
int j = 0;
int k = 0;
for (int l = 0; l < d.length; l++) {
d[l] = i < a.length && (j >= b.length || a[i] < b[j])
? (k >= c.length || a[i] < c[k]
? a[i++]
: c[k++])
: (j < b.length && (k >= c.length || b[j] < c[k])
? b[j++]
: c[k++]);
// Uncomment this if you still need it:
//displayArrayContents(a,b,c,d,i,j,k,l);
}
System.out.println(Arrays.toString(d));
}
}
最后一条语句的输出:
[1, 1, 2, 3, 4, 5]
在 repl.it 上查看 运行。
如果您觉得逻辑简单,请勾选此项。
逻辑:
从 3 个数组中找出最小的元素并将其添加到 mergeArray 中。
如果数组的所有元素都被覆盖(添加到 mergedArray 中),也可以处理,即从最小数字计算中忽略该数组。
import java.util.Arrays;
public class SortingAlgo {
public static void main(String[] args) {
int[] arr1 = {1,4,7,12,15,16,19,26,26, 29,35};
int[] arr2 = { 3,8,12,14,40, 44, 45};
int[] arr3 = {2,4,29, 30};
int[] merged = getMerged(arr1, arr2, arr3);
System.out.println(Arrays.toString(merged));
}
private static int[] getMerged(int[] arr1, int[] arr2, int[] arr3) {
int[] merged = new int[ arr1.length + arr2.length + arr3.length];
int i = 0; // Merged index
int i1 = 0, i2=0, i3=0; // for arr1, arr2, arr3
boolean i1Completed = false, i2Completed = false, i3Completed = false;
while(i1 < arr1.length || i2 < arr2.length || i3 < arr3.length) {
if(!i1Completed && (i2Completed || arr1[i1] <= arr2[i2]) && (i3Completed || arr1[i1] <= arr3[i3] )){
merged[i++] = arr1[i1++]; // arr1 element smallest
if(i1 == arr1.length)
i1Completed = true;
} else if(!i2Completed && (i1Completed || arr2[i2] <= arr1[i1] ) && ( i3Completed || arr2[i2] <= arr3[i3]) ){
merged[i++] = arr2[i2++];
if(i2 == arr2.length)
i2Completed = true;
} else if(!i3Completed && ( i2Completed || arr3[i3] <= arr2[i2] ) && ( i1Completed || arr3[i3] <= arr1[i1]) ){
merged[i++] = arr3[i3++];
if(i3 == arr3.length)
i3Completed = true;
}
}
return merged;
}
}
输出:[1, 2, 3, 4, 4, 7, 8, 12, 12, 14, 15, 16, 19, 26, 26, 29, 29, 30, 35, 40, 44, 45]
正在尝试将 3 个数组合并为一个,以便最终数组按顺序排列。
给定
int[] a = {1,3};
int[] b = {2,4};
int[] c = {1,5};
合并数组使得最终数组 d = {1,1,2,3,4,5}
不能只是连接它们然后对 d 数组进行排序,因为那样会使时间复杂度大于 Big-O(N)。
这就是我到目前为止所得到的。遇到索引超出范围的问题:
public static void main(String[] args) {
// Sort these 3 arrays. The final array should be d = {1,1,2,3,4,5}
int[] a = {1,3};
int[] b = {2,4};
int[] c = {1,5};
int[] d = new int[a.length + b.length + c.length];
int i = 0;
int j = 0;
int k = 0;
int l = 0;
for (int iteration = 0; iteration <= d.length; iteration++){
if ((i != a.length || j != b.length) && a[i] < b[j]){
if (a[i] < c[k]){
// then a[i] is smallest
d[l] = a[i];
i++;
l++;
displayArrayContents(a,b,c,d,i,j,k,l);
}
else if (a[i] > c[k]){
// then c[k] is smallest
d[l] = c[k];
k++;
l++;
displayArrayContents(a,b,c,d,i,j,k,l);
}
else if (a[i] == c[k]){
d[l] = a[i];
i++;
l++;
d[l] = c[k];
k++;
l++;
displayArrayContents(a,b,c,d,i,j,k,l);
}
}
else if(b[j] < a[i]){
if (b[j] < c[k]){
// b[j] is smallest
d[l] = b[j];
l++;
j++;
displayArrayContents(a,b,c,d,i,j,k,l);
}
else if (b[j] > c[k]){
// c[k] is smallest
d[l] = c[k];
l++;
k++;
displayArrayContents(a,b,c,d,i,j,k,l);
}
else if (b[j] == c[k]){
d[l] = b[j];
j++;
l++;
d[l] = c[k];
k++;
l++;
displayArrayContents(a,b,c,d,i,j,k,l);
}
}
}
}
按照以下步骤操作:
- 从这里获取答案代码:How to merge two sorted arrays into a sorted array?
- 在
a
和b
上调用该函数以获得结果数组ab
- 在
ab
和c
上调用该函数以获得结果abc
- 您已经调用了一个
O(n)
函数两次,所以它仍然是O(n)
。砰。
事实是,摆弄数组索引令人沮丧。如果您可以将这些数组作为队列或迭代器来代替,只需 take()
或 next()
每次迭代中的最小值并将其放入结果列表中,它将更清晰。
您需要清楚什么随 N 发生变化。如果您总是只有三个数组,并且它们的大小或最大大小随 N 发生变化,那么几乎所有重复 selects 最小数字的代码从三个数组中的任何一个可用,将其删除并将其附加到结果数组的末尾,将是 O(N)。您的 select 最小数字的代码可能笨拙且昂贵,但它只是一个常数因子,不会随着 N 的增加而改变。
如果要合并的数组数量随着 N 的增加而增加,那么您需要更加小心select 可用的最小数量,您最终会遇到排序问题,您可以'在通常的假设下,t 在线性时间内完成。
通常,外部排序会使用堆合并磁盘上保存的大量列表(例如 http://www.geeksforgeeks.org/external-sorting/)。这对于一次合并大量列表会更有效率,但只会让你获得一个常数因子,
假设这是 java,数组名是对数组的引用,可以像 C/C++ 中的指针一样交换。这可用于减少主合并循环中的条件数量,使代码更简单一些,但代价是交换。空数组检查在主合并循环之前完成。此方法可以轻松扩展以处理 4 路或更大的合并,否则需要大量条件。
static int[] Merge(int[] a, int[] b, int[] c)
{
int[] d = new int[a.length + b.length + c.length];
int[] e; // temp used for swap
int i = 0;
int j = 0;
int k = 0;
int l = 0;
int t;
// empty array checks
if(0 == b.length){ // if b empty
if(0 == c.length){ // if b and c empty
c = a; // c = a
a = b; // a = b = empty
} else { // if b empty, c not empty
e = a; // swap a and b
a = b;
b = e;
}
} else { // else b not empty
if(0 == c.length){ // if c empty
e = c;
c = b; // shift c = b, b = a
b = a;
a = e; // a = empty
}
}
// main merge loop
while(i < a.length){ // 3 way merge
if(a[i] > b[j]){ // if b smaller swap
e = a;
a = b;
b = e;
t = i;
i = j;
j = t;
}
if(a[i] > c[k]){ // if c smaller swap
e = a;
a = c;
c = e;
t = i;
i = k;
k = t;
}
d[l++] = a[i++];
}
while(j < b.length){ // 2 way merge
if(b[j] > c[k]){ // if c smaller swap
e = b;
b = c;
c = e;
t = j;
j = k;
k = t;
}
d[l++] = b[j++];
}
while(k < c.length) // copy rest of c
d[l++] = c[k++];
return d;
}
你的想法是正确的,代表了一个 O(n) 的解决方案。但是,你的代码确实存在一些问题,其中一些会导致越界异常:
- 您访问
c[k]
时未先确保k < c.length
; - 即使您在
length
上 做 测试,您也无法避免这种无效访问:(i != a.length || j != b.length) && a[i] < b[j]
仍然会导致a[i]
在i === a.length
时被访问(特别是在j != b.length
时); - 外循环需要迭代的次数通常是错误的,因为有时(在相等的情况下)您将两个值存储在目标数组中,这使得数组填满的速度比循环预期的要快。其实相等的情况(比如
a[i] == c[k]
)其实并不需要单独对待。如果将它与>
一起处理(因此:>=
)算法仍然正确:第二个(相等的)值将在下一次迭代中复制; - 即使您解决了上一个问题,您的外循环仍然使一次迭代过多;
for
条件应该是< d.length
而不是<= d.length
没有问题,但是你的代码中有很多重复:
- 您可以将对
displayArrayContents(a,b,c,d,i,j,k,l);
的调用移到if
构造之外,这样它就会始终执行,这正是您真正想要的; - 由于您总是在
if
构造中分配给d
,因此您可以使用三元运算符? ... :
; 来进行分配 "outside of the - 虽然像
i != a.length
这样的测试可以达到预期目的,但最好还是像这样进行测试:i < a.length
。
if
"
这里是考虑了上述因素的代码:
import java.util.Arrays; // for easy output of arrays with Arrays.toString().
class Main {
public static void main(String[] args) {
// Sort these 3 arrays. The final array should be d = {1,1,2,3,4,5}
int[] a = {1,3};
int[] b = {2,4};
int[] c = {1,5};
int[] d = new int[a.length + b.length + c.length];
int i = 0;
int j = 0;
int k = 0;
for (int l = 0; l < d.length; l++) {
d[l] = i < a.length && (j >= b.length || a[i] < b[j])
? (k >= c.length || a[i] < c[k]
? a[i++]
: c[k++])
: (j < b.length && (k >= c.length || b[j] < c[k])
? b[j++]
: c[k++]);
// Uncomment this if you still need it:
//displayArrayContents(a,b,c,d,i,j,k,l);
}
System.out.println(Arrays.toString(d));
}
}
最后一条语句的输出:
[1, 1, 2, 3, 4, 5]
在 repl.it 上查看 运行。
如果您觉得逻辑简单,请勾选此项。
逻辑:
从 3 个数组中找出最小的元素并将其添加到 mergeArray 中。
如果数组的所有元素都被覆盖(添加到 mergedArray 中),也可以处理,即从最小数字计算中忽略该数组。
import java.util.Arrays;
public class SortingAlgo {
public static void main(String[] args) {
int[] arr1 = {1,4,7,12,15,16,19,26,26, 29,35};
int[] arr2 = { 3,8,12,14,40, 44, 45};
int[] arr3 = {2,4,29, 30};
int[] merged = getMerged(arr1, arr2, arr3);
System.out.println(Arrays.toString(merged));
}
private static int[] getMerged(int[] arr1, int[] arr2, int[] arr3) {
int[] merged = new int[ arr1.length + arr2.length + arr3.length];
int i = 0; // Merged index
int i1 = 0, i2=0, i3=0; // for arr1, arr2, arr3
boolean i1Completed = false, i2Completed = false, i3Completed = false;
while(i1 < arr1.length || i2 < arr2.length || i3 < arr3.length) {
if(!i1Completed && (i2Completed || arr1[i1] <= arr2[i2]) && (i3Completed || arr1[i1] <= arr3[i3] )){
merged[i++] = arr1[i1++]; // arr1 element smallest
if(i1 == arr1.length)
i1Completed = true;
} else if(!i2Completed && (i1Completed || arr2[i2] <= arr1[i1] ) && ( i3Completed || arr2[i2] <= arr3[i3]) ){
merged[i++] = arr2[i2++];
if(i2 == arr2.length)
i2Completed = true;
} else if(!i3Completed && ( i2Completed || arr3[i3] <= arr2[i2] ) && ( i1Completed || arr3[i3] <= arr1[i1]) ){
merged[i++] = arr3[i3++];
if(i3 == arr3.length)
i3Completed = true;
}
}
return merged;
}
}
输出:[1, 2, 3, 4, 4, 7, 8, 12, 12, 14, 15, 16, 19, 26, 26, 29, 29, 30, 35, 40, 44, 45]