给定 K 个排序列表,每个列表中最多包含 N 个元素,返回所有项目的排序迭代器

2024-02-09

Example: List 1: [1, 4, 5, 8, 9]
     List 2: [3, 4, 4, 6]
     List 3: [0, 2, 8]
    Would yield the following result:

    Iterator -> [0, 1, 2, 3, 4, 4, 4, 5, 6, 8, 8, 9]

我不愿意创建一个“合并”方法来接受 k 个列表并将列表的内容本着空间复杂性的精神合并到另一个列表。这是一个可以使用“min Heap”实现的k-way合并问题吗?任何指示都会非常有帮助。

public class CustomListIterator<E> implements Iterator<E>{

private boolean canAddIterators = true;
private boolean balanceTreeIteratorFlag = false;
private E f_element;
private E s_element;
private Iterator<E> left;
private Iterator<E> right;
private final Comparator<E> comparator;

public CustomListIterator(Comparator<E> comparator){
    this.comparator = comparator;
}

public CustomListIterator(Iterator<E> left, Iterator<E> right, Comparator<E> comparator){
    this.left = left;
    this.right = right;
    this.comparator = comparator;
}

public void addIterator(Iterator<E> iterator){
    if (!canAddIterators)
        throw new ConcurrentModificationException();

    if (right == null){
        right = iterator;
        return;
    }else if (left == null){
        left = iterator;
        return;
    }

    if (!balanceTreeIteratorFlag){
        right = balanceTreeOfIterators(iterator, right);
    }else{
        left = balanceTreeOfIterators(iterator, left);
    }

    balanceTreeIteratorFlag = !balanceTreeIteratorFlag;
}

private Iterator<E> balanceTreeOfIterators(Iterator<E> iterator_1, Iterator<E> iterator_2){
    if (iterator_2 instanceof CustomListIterator){
        ((CustomListIterator<E>)iterator_2).addIterator(iterator_1);
    } else{
        iterator_2 = new CustomListIterator<E>(iterator_1, iterator_2, comparator);
    }
    return iterator_2;
}

public boolean hasNext() {
    if (canAddIterators){
        if (left != null && left.hasNext()){
            f_element = left.next();
        }
        if (right != null && right.hasNext()){
            s_element = right.next();
        }
    }
    canAddIterators = false;
    return f_element != null || s_element != null;
}

public E next() {
    E next;
    if (canAddIterators){
        if (left.hasNext()){
            f_element = left.next();
        }
        if (right.hasNext()){
            s_element = right.next();
        }
    }

    canAddIterators = false;

    if (s_element == null && f_element == null){
        throw new NoSuchElementException();
    }

    if (f_element == null){
        next = s_element;
        s_element = right.hasNext() ? right.next() : null;
        return next;
    }

    if (s_element == null){
        next = f_element;
        f_element = left.hasNext() ? left.next() : null;
        return next;
    }

    return findNext();
}

public void remove() {

}

private E findNext(){
    E next;
    if (comparator.compare(f_element, s_element) < 0){
        next = f_element;
        f_element = left.hasNext() ? left.next() : null;
        return next;
    }
    next = s_element;
    s_element = right.hasNext() ? right.next() : null;
    return next;
}

}

我不认为这是最好的方法(使用树)。关于如何仅通过重写 next() hasNext() 和 remove() 来实现这一点,有什么建议吗?


合并多个排序列表基本上有三种不同的方法:

  1. 连续双向合并
  2. 分而治之
  3. 基于优先级队列

在下面的讨论中,n指所有列表中组合的项目总数。k指列表的数量。

情况 1 是最容易设想的,但也是效率最低的。假设您有四个列表:A、B、C 和 D。使用此方法,您可以合并 A 和 B 以创建 AB。然后合并 AB 和 C 以创建 ABC。最后,将 ABC 与 D 合并以创建 ABCD。该算法的复杂度接近O(n*k)。您迭代 A 和 B 3 次,C 2 次,D 1 次。

分而治之的解决方案是将 A 和 B 合并以创建 AB。然后合并C和D以创建CD。然后合并AB和CD以创建ABCD。在最好的情况下,当列表具有相似数量的项目时,此方法的时间复杂度为 O(n * log(k))。但如果列表的长度变化很大,该算法的运行时间可能会接近 O(n*k)。

有关这两种算法的更多信息,请参阅我的博客文章,仔细观察成对合并 http://blog.mischel.com/2014/11/20/a-closer-look-at-pairwise-merging/。有关分而治之方法的更多详细信息,请参阅合并多个列表的不同方式 http://blog.mischel.com/2014/11/17/a-different-way-to-merge-multiple-lists/.

基于优先级队列的合并工作原理如下:

Create a priority queue to hold the iterator for each list
while the priority queue is not empty
    Remove the iterator that references the smallest current number
    Output the referenced value
    If not at end of iterator
        Add the iterator back to the queue

该算法被证明是 O(n * log(k))在最坏的情况下。您可以看到每个列表中的每个项目都恰好被添加到优先级队列一次,并从优先级队列中删除一次。但队列只包含k随时物品。所以内存需求非常小。

Java 中迭代器的实现使得优先级队列的实现稍微不方便,但是可以通过一些帮助器类轻松修复。最重要的是,我们需要一个迭代器,让我们可以查看下一个项目而不消耗它。我称其为PeekableIterator,看起来像这样:

// PeekableIterator is an iterator that lets us peek at the next item
// without consuming it.
public class PeekableIterator<E> implements Iterator<E> {
    private final Iterator<E> iterator;
    private E current;
    private boolean hasCurrent;

    public PeekableIterator(Iterator<E> iterator) {
        this.iterator = iterator;
        if (iterator.hasNext()) {
            current = iterator.next();
            hasCurrent = true;
        }
        else {
            hasCurrent = false;
        }
    }

    public E getCurrent() {
        // TODO: Check for current item
        return current;
    }

    public boolean hasNext() {
        return hasCurrent;
    }

    public E next() {
        // TODO: Error check to see if there is a current
        E rslt = current;
        if (iterator.hasNext()) {
            current = iterator.next();
        }
        else {
            hasCurrent = false;
        }
        return rslt;
    }

    public void remove() {
        iterator.remove();
    }

然后,由于优先级队列将保存迭代器而不是单个项目,因此我们需要一个比较器来比较两个项目的当前项目PeekableIterator接口。这很容易创建:

// IteratorComparator lets us compare the next items for two PeekableIterator instances.
public class IteratorComparator<E> implements Comparator<PeekableIterator<E>> {
    private final Comparator<E> comparator;

    public IteratorComparator(Comparator<E> comparator) {
        this.comparator = comparator;
    }

    public int compare(PeekableIterator<E> t1, PeekableIterator<E> t2) {
        int rslt = comparator.compare(t1.getCurrent(), t2.getCurrent());
        return rslt;
    }
}

这两个类是您为获取和比较各个迭代器的下一项而编写的代码的更正式实现。

最后,MergeIterator初始化一个PriorityQueue<PeekableIterator>这样你就可以调用hasNext and next迭代合并列表的方法:

// MergeIterator merges items from multiple sorted iterators
// to produce a single sorted sequence.
public class MergeIterator<E> implements Iterator<E> {
    private final IteratorComparator<E> comparator;
    private final PriorityQueue<PeekableIterator<E>> pqueue;

    // call with an array or list of sequences to merge
    public MergeIterator(List<Iterator<E>> iterators, Comparator<E> comparator) {
        this.comparator = new IteratorComparator<E>(comparator);

        // initial capacity set to 11 because that's the default,
        // and there's no constructor that lets me supply a comparator without the capacity.
        pqueue = new PriorityQueue<PeekableIterator<E>>(11, this.comparator);

        // add iterators to the priority queue
        for (Iterator<E> iterator : iterators) {
            // but only if the iterator actually has items
            if (iterator.hasNext())
            {
                pqueue.offer(new PeekableIterator(iterator));
            }
        }
    }

    public boolean hasNext() {
        return pqueue.size() > 0;
    }

    public E next() {
        PeekableIterator<E> iterator = pqueue.poll();
        E rslt = iterator.next();
        if (iterator.hasNext()) {
            pqueue.offer(iterator);
        }
        return rslt;
    }

    public void remove() {
        // TODO: Throw UnsupportedOperationException
    }
}

我创建了一个小测试程序来演示它是如何工作的:

private void DoIt() {
    String[] a1 = new String[] {"apple", "cherry", "grape", "peach", "strawberry"};
    String[] a2 = new String[] {"banana", "fig", "orange"};
    String[] a3 = new String[] {"cherry", "kumquat", "pear", "pineapple"};

    // create an ArrayList of iterators that we can pass to the
    // MergeIterator constructor.
    ArrayList<Iterator<String>> iterators = new ArrayList<Iterator<String>> (
            Arrays.asList(
                    Arrays.asList(a1).iterator(),
                    Arrays.asList(a2).iterator(),
                    Arrays.asList(a3).iterator())
    );

    // String.CASE_INSENSITIVE_ORDER is a Java 8 way to get
    // a String comparator. If there's a better way to do this,
    // I don't know what it is.
    MergeIterator<String> merger = new MergeIterator(iterators, String.CASE_INSENSITIVE_ORDER);
    while (merger.hasNext())
    {
        String s = merger.next();
        System.out.println(s);
    }
}

我对分治法和优先级队列合并的性能比较表明,分治法can be比使用优先级队列更快,具体取决于比较的成本。当比较成本较低时(例如,原始类型),成对合并速度更快,尽管它做了更多工作。随着键比较变得更加昂贵(例如比较字符串),优先级队列合并具有优势,因为它执行的比较更少。

更重要的是,成对合并需要的内存是优先级队列方法的两倍。我的实现使用了 FIFO 队列,但即使我构建了一棵树,成对合并也将需要更多内存。另外,正如您的代码所示,您仍然需要PeekableIterator and IteratorComparator类(或类似的东西)如果你想实现成对合并。

See 测试合并性能 http://blog.mischel.com/2014/12/21/testing-merge-performance/有关这两种方法的相对性能的更多详细信息。

由于我上面详述的原因,我得出的结论是优先级队列合并是最好的方法。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

给定 K 个排序列表,每个列表中最多包含 N 个元素,返回所有项目的排序迭代器 的相关文章

  • Android 2.2 SDK - Droid X 相机活动无法正常完成

    我注意到我在 Droid X 上调用的默认相机活动与我的 Droid 和 Nexus One 上的默认相机活动看起来不同 在 Droid 和 Nexus One 上选择 确定 后 活动将完成 Droid X 有一个 完成 按钮 它将带您返回
  • 添加动态数量的监听器(Spring JMS)

    我需要添加多个侦听器 如中所述application properties文件 就像下面这样 InTopics Sample QUT4 Sample T05 Sample T01 Sample JT7 注意 这个数字可以多一些 也可以少一些
  • 无法使用maven编译java项目

    我正在尝试在 java 16 0 1 上使用 maven 构建 IntelliJ 项目 但它无法编译我的项目 尽管 IntelliJ 能够成功完成 在此之前 我使用maven编译了一个java 15项目 但我决定将所有内容更新到16 0 1
  • 如何在 JSP 中导入类?

    我是一个完全的JSP初学者 我正在尝试使用java util List在 JSP 页面中 我需要做什么才能使用除以下类之外的类java lang 使用以下导入语句进行导入java util List 顺便说一句 要导入多个类 请使用以下格式
  • Java套接字:在连接被拒绝异常时重试的最佳方法?

    现在我正在这样做 while true try SocketAddress sockaddr new InetSocketAddress ivDestIP ivDestPort downloadSock new Socket downloa
  • 在 HTTP 标头中发送 UTF-8 值会导致 Mojibake

    我想使用 servlet 发送阿拉伯语数据HTTPServletResponse给客户 我正在尝试这个 response setCharacterEncoding UTF 8 response setHeader Info arabicWo
  • Firestore - RecycleView - 图像持有者

    我不知道如何编写图像的支架 我已经设置了 2 个文本 但我不知道图像的支架应该是什么样子 你能帮我告诉我图像的文字应该是什么样子才能正确显示吗 holder artistImage setImageResource model getArt
  • 内存一致性 - Java 中的happens-before关系[重复]

    这个问题在这里已经有答案了 在阅读有关内存一致性错误的 Java 文档时 我发现与创建 发生 之前 关系的两个操作相关的点 当语句调用时Thread start 每个具有 与该语句发生之前的关系也有一个 与 new 执行的每个语句之间发生的
  • 如何在android中设置多个闹钟,在这种情况下最后一个闹钟会覆盖以前的闹钟

    我正在开发一个Android应用程序 用户可以在其中设置提醒时间 但我在以下代码中遇到一个问题 即最后一个警报会覆盖之前的所有警报 MainActivity java public void setreminders DatabaseHan
  • 将表值参数与 SQL Server JDBC 结合使用

    任何人都可以提供一些有关如何将表值参数 TVP 与 SQL Server JDBC 一起使用的指导吗 我使用的是微软提供的6 0版本的SQL Server驱动程序 我已经查看了官方文档 https msdn microsoft com en
  • RSA OAEP、Golang 加密、Java 解密 -BadPaddingException:解密错误

    我正在尝试解密使用 RSA OAEP 在 Golang 中加密的字符串 但出现 BadPaddingException 解密错误 很难弄清楚我错过了什么 这是Golang加密方法 func encryptString rootPEM io
  • 获取给定类文件的目录路径

    我遇到的代码尝试从类本身的 class 文件所在的同一目录中读取一些配置文件 File configFiles new File this getClass getResource getPath listFiles new Filenam
  • Cloudfoundry:如何组合两个运行时

    cloundfoundry 有没有办法结合两个运行时环境 我正在将 NodeJS 应用程序部署到 IBM Bluemix 现在 我还希望能够执行独立的 jar 文件 但应用程序失败 APP 0 bin sh 1 java not found
  • Spring Security OAuth2简单配置

    我有一个简单的项目 需要以下简单的配置 我有一个 密码 grant type 这意味着我可以提交用户名 密码 用户在登录表单中输入 并在成功时获得 access token 有了该 access token 我就可以请求 API 并获取用户
  • 在 Spring Boot Actuator 健康检查 API 中启用日志记录

    我正在使用 Spring boot Actuator APIproject https imobilenumbertracker com 拥有一个健康检查端点 并通过以下方式启用它 management endpoints web base
  • 如何在 Eclipse Java 动态 Web 项目中使用 .properties 文件?

    我正在 Eclipse 中开发动态 Web 项目 我创建了一个 properties 文件来存储数据库详细信息 用户名 密码等 我通过右键单击项目和 New gt File 添加它 我使用了Java util包Properties类 但它不
  • 为什么java中的for-each循环中需要声明变量

    for 每个循环的通常形式是这样的 for Foo bar bars bar doThings 但如果我想保留 bar 直到循环结束 我可以not使用 foreach 循环 Foo bar null Syntax error on toke
  • Android - 9 补丁

    我正在尝试使用 9 块图片创建一个新的微调器背景 我尝试了很多方法来获得完美的图像 但都失败了 s Here is my 9 patch 当我用Draw 9 patch模拟时 内容看起来不错 但是带有箭头的部分没有显示 或者当它显示时 这部
  • 应用对数来导航树

    我曾经知道一种使用对数从树的一片叶子移动到树的下一个 有序 叶子的方法 我认为它涉及获取 当前 叶子的位置值 排名 并将其用作从根向下到新目标叶子的新遍历的种子 一直使用对数函数测试来确定是否沿着右或左节点向下到达叶子 我已经不记得如何运用
  • 在哪里存储 Java 的 .properties 文件?

    The Java教程 http download oracle com javase tutorial essential environment properties htmlon using Properties 讨论如何使用 Prop

随机推荐