【问题标题】:What's an efficient algorithm to make one list equal to another one?使一个列表等于另一个列表的有效算法是什么?
【发布时间】:2009-12-07 03:46:57
【问题描述】:

假设我有一个列表 A,它需要看起来与列表 B 完全相同。B 拥有但 A 没有的所有对象都需要添加到 A。A 拥有但 B 没有的所有对象都需要添加从 A 中删除。

我需要这个的原因是因为我有一个播放器的 ArrayList,我要保存到一个文件中。每次更新 Player 的属性时,我都会通过调用查看 Player 的 ArrayList 并保存它的方法来保存对文件的更改。这是可行的,因为 ArrayList 具有对 Players 的引用。

但是,每当我在列表中搜索播放器时,我首先会通过读取其存储位置的文件来更新列表。这会将所有引用替换为全新的对象。在我这样做之后,如果我对以前获取的用户进行更改并尝试保存它。 Player 的新实例被保存,而不是我在其中进行更改的那个。

会想出一个好的算法来使一个列表等于另一个解决方案吗?或者有没有更好的方法来更新整个列表,同时保留在那里使用的引用?

更新:更新的解决方案,在 O(nlogm) 时间内运行。遍历目标中的每个元素,在源中搜索它。如果找到,请从源中删除。如果不是,则从目的地中删除。然后将源中的剩余元素添加到目标。列表当然需要排序,但我从文件中获取的列表已经排序,因为我在添加时排序。

import java.util.Collections;
import java.util.Comparator;
import java.util.List;

public class CopyList {
    public static void copyList(List dest, List src) {
        copy(dest, src);

        // the remaining elements in src list will be those that were originally
        // in src but not in dest and so they need to be added
        dest.addAll(src);
    }

    public static void copyList(List dest, List src, Verify v) {
        copy(dest, src);

        // the remaining elements in src list will be those that were originally
        // in src but not in dest and so they need to be added
        addAll(dest, src, v);
    }

    public static void copyList(List dest, List src, Comparator c) {
        copy(dest, src, c);

        // the remaining elements in src list will be those that were originally
        // in src but not in dest and so they need to be added
        dest.addAll(src);
    }

    public static void copyList(List dest, List src, Comparator c, Verify v) {
        copy(dest, src, c);

        // the remaining elements in src list will be those that were originally
        // in src but not in dest and so they need to be added
        addAll(dest, src, v);
    }

    private static void copy(List dest, List src) {
        // go through dest list to search if every element is in the new list
        // travel backwards through dest because we will be removing elements from it
        for(int i = dest.size()-1; i >= 0 ; i--) {
            int src_i = Collections.binarySearch(src, dest.get(i));
            if(src_i >= 0)
                // if element is found in src list, remove it from src list
                src.remove(src_i);
            else
                // if element is NOT found in src list, remove it from dest list
                dest.remove(i);
        }
    }

    private static void copy(List dest, List src, Comparator c) {
        // go through dest list to search if every element is in the new list
        // travel backwards through dest because elements might be removed
        for(int i = dest.size()-1; i >= 0 ; i--) {
            int src_i = Collections.binarySearch(src, dest.get(i), c);
            if(src_i >= 0)
                // if element is found in src list, remove it from src list
                src.remove(src_i);
            else
                // if element is NOT found in src list, remove it from dest list
                dest.remove(i);
        }
    }

    private static void addAll(List dest, List src, Verify v) {
        // verify each element in src list before adding it to dest list
        for(Object o: src)
            if(v.verify(o))
                dest.add(o);
    }
}

【问题讨论】:

    标签: java algorithm search reference arraylist


    【解决方案1】:

    最简单的解决方案不是创建列表 B 的副本吗?这只需要创建底层数组的副本,这比复杂的算法需要更少的时间和精力(更不用说更容易维护了)。

    【讨论】:

    • 这不是 OP 要求的 - 他们想要两个匹配的不同列表。
    • java.util.Collections.copy(List super T> dest, List extends T> src)
    • 我不能简单地做 users = new_users 因为引用被替换了。
    • java.sun.com/j2se/1.4.2/docs/api/java/util/… 这不是我想做的。它替换目标列表中的所有元素,无论它们是否已经在源列表中。如果目标列表更长,一些在目标中但不在源中的元素仍然存在。
    【解决方案2】:

    我会退后一步考虑一下您的程序的设计。听起来有一个管理所有玩家的类会更合理——保存它们、更新它们、分发对它们的引用。然后不要在其他任何地方保存对 Player 的引用,保存对 PlayerManager 的引用(或任何你想调用的名称),然后查询它以获取 Players。

    这将为您提供一个规范的列表,您无需担心过时的引用。

    【讨论】:

    • 这就是我正在做的,但是每次调用获取方法时都必须更新这个列表。也许我不明白你的意思。
    • 如果你没有在任何地方持有引用,为什么不像其他人建议的那样直接说 PlayerManager.listOfPlayers = loadListOfPlayers()?
    • 没关系我读错了我不只在存储类中持有引用。许多其他类都可以访问玩家。如果我按照您的建议进行操作,那么加载播放器的效率会更高。但是由于我将使用更多的内存,因为我将制作多个玩家副本,并且每次保存时我都必须搜索存储中的玩家列表。目前,由于所有引用都已经存在,因此保存需要线性时间。
    【解决方案3】:

    但是,每当我在列表中搜索播放器时,我首先会通过读取其存储位置的文件来更新列表。

    你为什么要这样做?搜索内存中的副本会更有意义。

    哦,还有一个非常简单的方法可以让a 包含与b 相同的内容:

    a = b;
    

    【讨论】:

    • b's 引用分配给a 意味着您只需对同一实例有两个引用,这不是OP 要求的。
    • 我这样做是因为可以打开程序的各种实例,并且可以在另一个实例中更新列表。
    【解决方案4】:

    我同意这里的另一个提交,这对于您的应用程序来说似乎是不必要的,但是如果您想要一种算法比 n^2 次更好地完成您所描述的事情,那么出于兴趣,有一个(并且它在 n* 中运行)记录 n 次)

    sort list users (n log n)
    sort list new_users (n log n)
    iterate through users and new_users in parallel, adding or removing items from users as necessary ( n )
    

    【讨论】:

      【解决方案5】:

      您是否考虑将HashSet 用于您的列表?这样你就可以做addAll() 并拥有这些列表的联合。如果要保留原始顺序,请使用 LinkedHashSet。会快很多。您只需在 Player 类中覆盖 hashCode() 即可返回其名称的哈希值。

      【讨论】:

        【解决方案6】:

        如果您使用集合而不是列表,您的程序会更快,即 O(1) 时间复杂度假设没有哈希冲突。

        for(User u : new_users) {
            if (!users.contains(u)) {
                users.add(u);
            }
        }
        for(User u : users) {
            if (!new_users.contains(u)) {
                users.remove(u);
            }
        }
        

        【讨论】:

        • 我怀疑在循环中删除会导致 ConcurrentModificationException。使用 List 时应该使用 ListIterator。
        • 我对套装不太熟悉。我知道它们是什么,但从未在代码中使用它们。这不是 O(n) 时间吗?
        • 这不满足将那些在users中但不在new_users中的元素从用户中删除的条件。这需要搜索,即使在集合中也可能需要 O(n) 时间,对吗?
        猜你喜欢
        • 2011-11-28
        • 1970-01-01
        • 2011-04-14
        • 2023-03-06
        • 2014-08-25
        • 2018-08-14
        • 1970-01-01
        • 1970-01-01
        • 2011-09-04
        相关资源
        最近更新 更多