BAT 经典算法笔试题 —— 磁盘多路归并排序

多路归并算法从理论到应用(易懂) 一、多路归并算法的由来 假定现在有一包含大量整数的文本文件存放于磁盘中,其文件大小为10GB,而本机内存只有4GB。此时若我们要对该文件中的所有整数进行升序排序,肯定不能直接将文件中的所有数据一次性读入内存中,再使用快速、归并等排序算法对这么大规模的整数进行排序。 好像陷入了难题? 我们不妨换一个思路,为何不将10GB大文件拆分为10个1GB的小文件呢? 逐个对10个文件进行排序后,再将其写入磁盘中,此时就得到了10份已排序后的临时文件。 每一份文件都是... 阅读详情

在 LevelDB 数据库中高层数据下沉到低层时需要经历一次 Major Compaction,将高层文件的有序键值对和低层文件的多个有序键值对进行归并排序。磁盘多路归并排序算法的输入是来自多个磁盘文件的有序键值对,在内存中将这些文件的键值对进行排序,然后输出到一到多个新的磁盘文件中。

640?wx_fmt=png
图片

多路归并排序在大数据领域也是常用的算法,常用于海量数据排序。当数据量特别大时,这些数据无法被单个机器内存容纳,它需要被切分位多个集合分别由不同的机器进行内存排序(map 过程),然后再进行多路归并算法将来自多个不同机器的数据进行排序(reduce 过程),这是流式多路归并排序,为什么说是流式排序呢,因为数据源来源于网络套接字。

640?wx_fmt=png
图片

多路归并排序的优势在于内存消耗极低,它的内存占用和输入文件的数量成正比,和数据总量无关,数据总量只会线性正比影响排序的时间。

下面我们来亲自实现一下磁盘多路归并算法,为什么是磁盘,因为它的输入来自磁盘文件。

算法思路

我们需要在内存里维护一个有序数组。每个输入文件当前最小的元素作为一个元素放在数组里。数组按照元素的大小保持排序状态。

640?wx_fmt=png
图片

接下来我们开始进入循环,循环的逻辑总是从最小的元素下手,在其所在的文件取出下一个元素,和当前数组中的元素进行比较。根据比较结果进行不同的处理,这里我们使用二分查找算法进行快速比较。注意每个输入文件里面的元素都是有序的。

1. 如果取出来的元素和当前数组中的最小元素相等,那么就可以直接将这个元素输出。再继续下一轮循环。不可能取出比当前数组最小元素还要小的元素,因为输入文件本身也是有序的。

640?wx_fmt=png
图片

2. 否则就需要将元素插入到当前的数组中的指定位置,继续保持数组有序。然后将数组中当前最小的元素输出并移除。再进行下一轮循环。

640?wx_fmt=png
图片

3. 如果遇到文件结尾,那就无法继续调用 next() 方法了,这时可以直接将数组中的最小元素输出并移除,数组也跟着变小了。再进行下一轮循环。当数组空了,说明所有的文件都处理完了,算法就可以结束了。

640?wx_fmt=png
图片

值得注意的是,数组中永远不会存在同一个文件的两个元素,如此才保证了数组的长度不会超过输入文件的数量,同时它也不会把没有结尾的文件挤出数组导致漏排序的问题。

二分查找

需要特别注意的是Java 内置了二分查找算法在使用上比较精巧。

public class Collections {
  ...
  public static <T> int binarySearch(List<T> list, T key) {
    ...
    if (found) {
      return index;
    } else {
      return -(insertIndex+1);
    }
  }
  ...
}

比如 binarySearch 返回了 index=-1,那么 insertIndex 就是 -(index+1),也就是 0,插入点在数组开头。如果返回了 index=-size-1,那么 insertIndex 就是 size,是数组末尾。其它负数会插入数组中间。

640?wx_fmt=png
图片

输入文件类

对于每一个输入文件都会创建一个 MergeSource 对象,它提供了 hasNext() 和 next() 方法用于判断和获取下一个元素。注意输入文件是有序的,下一个元素就是当前输入文件最小的元素。

class MergeSource implements Closeable {
    private BufferedReader reader;
    private String cachedLine;
    private String filename;

    public MergeSource(String filename) {
        this.filename = filename;
        try {
            FileReader fr = new FileReader(filename);
            this.reader = new BufferedReader(fr);
        } catch (FileNotFoundException e) {
        }
    }

    public boolean hasNext() {
        String line;
        try {
            line = this.reader.readLine();
            if (line == null || line.isEmpty()) {
                return false;
            }
            this.cachedLine = line.trim();
            return true;
        } catch (IOException e) {
        }
        return false;
    }

    public int next() {
        if (this.cachedLine == null) {
            if (!hasNext()) {
                throw new IllegalStateException("no content");
            }
        }
        int num = Integer.parseInt(this.cachedLine);
        this.cachedLine = null;
        return num;
    }

    @Override
    public void close() throws IOException {
        this.reader.close();
    }
}

内存有序数组元素类

在排序前先把这个数组准备好,将每个输入文件的最小元素放入数组,并排序。

class Bin implements Comparable<Bin> {
    int num;
    MergeSource source;

    Bin(MergeSource source, int num) {
        this.source = source;
        this.num = num;
    }

    @Override
    public int compareTo(Bin o) {
        return this.num - o.num;
    }

}

List<Bin> prepare() {
      List<Bin> bins = new ArrayList<>();
    for (MergeSource source : sources) {
        Bin newBin = new Bin(source, source.next());
        bins.add(newBin);
    }
    Collections.sort(bins);
    return bins;
}

输出文件类

关闭输出文件时注意要先 flush(),避免丢失 PrintWriter 中缓冲的内容。

class MergeOut implements Closeable {
    private PrintWriter writer;

    public MergeOut(String filename) {
        try {
            FileOutputStream out = new FileOutputStream(filename);
            this.writer = new PrintWriter(out);
        } catch (FileNotFoundException e) {
        }
    }

    public void write(Bin bin) {
        writer.println(bin.num);
    }

    @Override
    public void close() throws IOException {
        writer.flush();
        writer.close();
    }
}

准备输入文件的内容

下面我们来生成一系列输入文件,每个输入文件中包含一堆随机整数。一共生成 n 个文件,每个文件的整数数量在 minEntries 到 minEntries 之间。返回所有输入文件的文件名列表。

List<String> generateFiles(int n, int minEntries, int maxEntries) {
    List<String> files = new ArrayList<>();
    for (int i = 0; i < n; i++) {
        String filename = "input-" + i + ".txt";
        PrintWriter writer;
        try {
            writer = new PrintWriter(new FileOutputStream(filename));
            ThreadLocalRandom rand = ThreadLocalRandom.current();
            int entries = rand.nextInt(minEntries, maxEntries);
            List<Integer> nums = new ArrayList<>();
            for (int k = 0; k < entries; k++) {
                int num = rand.nextInt(10000000);
                nums.add(num);
            }
            Collections.sort(nums);
            for (int num : nums) {
                writer.println(num);
            }
            writer.flush();
            writer.close();
        } catch (FileNotFoundException e) {
        }
        files.add(filename);
    }
    return files;
}

排序算法

万事俱备,只欠东风。将上面的类都准备好之后,排序算法很简单,代码量非常少。对照上面算法思路来理解下面的算法就很容易了。

public void sort() {
    List<Bin> bins = prepare();
    while (true) {
        // 取数组中最小的元素
        MergeSource current = bins.get(0).source;
        if (current.hasNext()) {
            // 从输入文件中取出下一个元素
            Bin newBin = new Bin(current, current.next());
            // 二分查找,也就是和数组中已有元素进行比较
            int index = Collections.binarySearch(bins, newBin);
            if (index == 0) {
                // 算法思路情况1
                this.out.write(newBin);
            } else {
                // 算法思路情况2
                if (index < 0) {
                    index = -(index+1);
                }
                bins.add(index, newBin);
                Bin minBin = bins.remove(0);
                this.out.write(minBin);
            }
        } else {
            // 算法思路情况3:遇到文件尾
            Bin minBin = bins.remove(0);
            this.out.write(minBin);
            if (bins.isEmpty()) {
                break;
            }
        }
    }
}

全部代码

读者可以直接将下面的代码拷贝粘贴到 IDE 中运行。

package leetcode;

import java.io.BufferedReader;
import java.io.Closeable;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.ThreadLocalRandom;

public class DiskMergeSort implements Closeable {

    public static List<String> generateFiles(int n, int minEntries, int maxEntries) {
        List<String> files = new ArrayList<>();
        for (int i = 0; i < n; i++) {
            String filename = "input-" + i + ".txt";
            PrintWriter writer;
            try {
                writer = new PrintWriter(new FileOutputStream(filename));
                int entries = ThreadLocalRandom.current().nextInt(minEntries, maxEntries);
                List<Integer> nums = new ArrayList<>();
                for (int k = 0; k < entries; k++) {
                    int num = ThreadLocalRandom.current().nextInt(10000000);
                    nums.add(num);
                }
                Collections.sort(nums);
                for (int num : nums) {
                    writer.println(num);
                }
                writer.close();
            } catch (FileNotFoundException e) {
            }
            files.add(filename);
        }
        return files;
    }

    private List<MergeSource> sources;
    private MergeOut out;

    public DiskMergeSort(List<String> files, String outFilename) {
        this.sources = new ArrayList<>();
        for (String filename : files) {
            this.sources.add(new MergeSource(filename));
        }
        this.out = new MergeOut(outFilename);
    }

    static class MergeOut implements Closeable {
        private PrintWriter writer;

        public MergeOut(String filename) {
            try {
                this.writer = new PrintWriter(new FileOutputStream(filename));
            } catch (FileNotFoundException e) {
            }
        }

        public void write(Bin bin) {
            writer.println(bin.num);
        }

        @Override
        public void close() throws IOException {
            writer.flush();
            writer.close();
        }
    }

    static class MergeSource implements Closeable {
        private BufferedReader reader;
        private String cachedLine;

        public MergeSource(String filename) {
            try {
                FileReader fr = new FileReader(filename);
                this.reader = new BufferedReader(fr);
            } catch (FileNotFoundException e) {
            }
        }

        public boolean hasNext() {
            String line;
            try {
                line = this.reader.readLine();
                if (line == null || line.isEmpty()) {
                    return false;
                }
                this.cachedLine = line.trim();
                return true;
            } catch (IOException e) {
            }
            return false;
        }

        public int next() {
            if (this.cachedLine == null) {
                if (!hasNext()) {
                    throw new IllegalStateException("no content");
                }
            }
            int num = Integer.parseInt(this.cachedLine);
            this.cachedLine = null;
            return num;
        }

        @Override
        public void close() throws IOException {
            this.reader.close();
        }
    }

    static class Bin implements Comparable<Bin> {
        int num;
        MergeSource source;

        Bin(MergeSource source, int num) {
            this.source = source;
            this.num = num;
        }

        @Override
        public int compareTo(Bin o) {
            return this.num - o.num;
        }
    }

    public List<Bin> prepare() {
        List<Bin> bins = new ArrayList<>();
        for (MergeSource source : sources) {
            Bin newBin = new Bin(source, source.next());
            bins.add(newBin);
        }
        Collections.sort(bins);
        return bins;
    }

    public void sort() {
        List<Bin> bins = prepare();
        while (true) {
            MergeSource current = bins.get(0).source;
            if (current.hasNext()) {
                Bin newBin = new Bin(current, current.next());
                int index = Collections.binarySearch(bins, newBin);
                if (index == 0 || index == -1) {
                    this.out.write(newBin);
                    if (index == -1) {
                        throw new IllegalStateException("impossible");
                    }
                } else {
                    if (index < 0) {
                        index = -index - 1;
                    }
                    bins.add(index, newBin);
                    Bin minBin = bins.remove(0);
                    this.out.write(minBin);
                }
            } else {
                Bin minBin = bins.remove(0);
                this.out.write(minBin);
                if (bins.isEmpty()) {
                    break;
                }
            }
        }
    }

    @Override
    public void close() throws IOException {
        for (MergeSource source : sources) {
            source.close();
        }
        this.out.close();
    }

    public static void main(String[] args) throws IOException {
        List<String> inputs = DiskMergeSort.generateFiles(100, 10000, 20000);
        // 运行多次看算法耗时
        for (int i = 0; i < 20; i++) {
            DiskMergeSort sorter = new DiskMergeSort(inputs, "output.txt");
            long start = System.currentTimeMillis();
            sorter.sort();
            long duration = System.currentTimeMillis() - start;
            System.out.printf("%dms\n", duration);
            sorter.close();
        }
    }
}

640?wx_fmt=png

从零开始:TexLive(Latex)安装与配置全指南 本文提供了一份详尽的TexLive(Latex)安装与配置全指南,旨在帮助用户从零开始快速搭建LaTeX工作环境。内容涵盖从镜像下载、安装步骤详解、环境验证到高效编辑器(如VS Code)配置,并特别强调了中文排版支持与宏包管理,确保用户能顺利处理学术论文等专业文档的排版需求。 阅读详情

相关推荐

告别TwinCAT:手把手教你用IgH EtherCAT Master在LinuxCNC上搭建实时运动控制平台

本文详细介绍了如何利用IgH EtherCAT Master和LinuxCNC搭建开源实时运动控制平台,替代商业软件TwinCAT。通过分步指南,包括实时Linux环境配置、EtherCAT主站安装、LinuxCNC集成及多轴同步控制实现,帮助开发者构建高性能、低成本的工业自动化解决方案。

weixin_42641741的博客 402

二路归并排序和多路归并排序

啥也先不说,先上图,上图最好理解 其实归并排序挺好理解的,也挺好实现的。其实也挺像我们的平常分工合作的。就像一样事情分成几份,由不同的人去做。再合并起来,采用了分治的思想。 对于一个数列,也同是如此。 我们只需要不断地对着中点切分就可以了。 就这样类似的下去,针对每一个每次分割的取键合并 总共可以分两个过程,一个cut,一个merge cut:对...

weixin_43272605的博客 7514

深入Linux内核:PTP硬件时间戳(HW Timestamp)是如何炼成的?

本文深入探讨了Linux内核中PTP硬件时间戳(HW Timestamp)的实现原理与实战应用。通过分析IEEE 1588协议在网络时间同步中的关键作用,详细解析了硬件时间戳在Linux内核中的架构设计、驱动开发及性能优化技巧,帮助开发者实现纳秒级精度的时间同步。

weixin_30727835的博客 652

二分归并排序算法

1. 问题 对于n个不同的数构成的数组A[1…n]进行排序,其中n = 2k。 2. 解析 二分归并排序算法原理(假设数组A中共有n个元素): 将数组A中n个元素看成n个独立的子序列,因此每个子序列的长度为1,然后两两合并,得到[n/2]个长度为2或1(注意如果n为奇数时,就会出现多出一个元素无法与其他元素合并)的有序子序列;再两两合并,一种重复下去,直到得到一个长度为n的有序数组为止。 二分归并排序算法实现步骤(分而治之): 1、将待排序数组从中间一分为二,对左右两边在进行递归分割操作,得到若干个相互独立

weixin_50941638的博客 8028

深入解析Cuk、Zeta和Sepic拓扑:非隔离型开关电源的设计奥秘

本文深入解析了Cuk、Zeta和Sepic三种非隔离型开关电源拓扑的设计奥秘。它们均能实现升降压转换,但特性各异:Cuk拓扑输出反向电压,输入输出纹波极低;Sepic拓扑输出同极性电压,应用广泛;Zeta拓扑输出纹波小,适合精密供电。文章详细对比了其电路结构、工作原理、核心公式及选型指南,帮助工程师根据输入电压范围、输出极性及纹波要求,灵活选择最合适的拓扑方案。

weixin_28308325的博客 481

双向晶闸管交流调压基础知识及Multisim电路仿真

本文介绍了双向晶闸管交流调压电路的工作原理及应用。该电路由双向晶闸管、RC移相电路和双向触发二极管组成,通过调节电位器改变触发角实现电压调节。当电位器阻值减小时,触发角减小,输出电压升高;阻值增大时,触发角增大,输出电压降低。Multisim仿真展示了不同电位器档位下的波形特征:0%档位时全导通,50%档位时半导通,100%档位时基本关断。该电路结构简单、调节平滑,广泛应用于调光、调速等场合,但会产生谐波干扰。

XU157303764的博客 844

【算法06】归并排序:从二路到多路

我们所熟知的和都是非常优秀的排序算法。但是快速排序和归并排序的一个就是:快速排序是一种而归并排序是一种。假设递归的终止条件就是剩下三个以下的元素就可以排序了。完成了排序之后再复制回去。

weixin_55252589的博客 2147

CANopen中SDO、PDO字典以及COB-ID理解

CANopen SDO PDO

sunarmy的专栏 2万+

二分归并排序_BAT 经典算法笔试题 —— 磁盘多路归并排序

在 LevelDB 数据库中高层数据下沉到低层时需要经历一次 Major Compaction,将高层文件的有序键值对和低层文件的多个有序键值对进行归并排序。磁盘多路归并排序算法的输入是来自多个磁盘文件的有序键值对,在内存中将这些文件的键值对进行排序,然后输出到一到多个新的磁盘文件中。多路归并排序在大数据领域也是常用的算法,常用于海量数据排序。当数据量特别大时,这些数据无法被单个机器内存容纳,它需...

weixin_39913648的博客 66

基于 YOLO + SAM 的自动图像标注实战

摘要: 本文提出一种基于YOLO检测和SAM分割的自动化数据标注流水线方案,通过YOLO定位目标并生成检测框,再以检测框为提示驱动SAM完成精细分割,最终输出YOLO格式的标注文件。该方案将人工标注升级为半自动流程,显著提升标注效率,尤其适用于多类别、大规模图像的实例级标注需求。代码模块化设计支持模型灵活替换,并集成异常处理与进度可视化功能。虽依赖YOLO检测质量且对小目标存在局限,但作为工程落地方案,可有效降低私有数据集构建成本,适用于工业检测、遥感等场景的冷启动标注。

qq_64366203的博客 1150

Comsol 超声空化气泡仿真

当液体中受到高能量的超声波作用时,液体中的压力会发生剧烈变化,形成局部的低压区域。当液体中的压力降低到饱和蒸汽压以下时,就会发生空化现象,即液体中形成微小气泡。这种空化常用于一些工业应用,如清洗、乳化、混合等。这种空化常用于一些医学和生物学应用,如碎石术、肿瘤治疗、细胞破碎等。超声空化的应用非常广泛。在医学领域,超声空化广泛应用于诊断和治疗,如超声造影、超声刀、超声碎石术等。在工业领域,超声空化可用于清洗、乳化、混合等工艺。此外,超声空化还在食品加工、化学合成、环境保护等领域有着重要应用。

声学黑洞仿真工作室 2558

二分归并排序_BAT 经典算法笔试题磁盘多路归并排序算法分析

在 LevelDB 数据库中高层数据下沉到低层时需要经历一次 Major Compaction,将高层文件的有序键值对和低层文件的多个有序键值对进行归并排序。磁盘多路归并排序算法的输入是来自多个磁盘文件的有序键值对,在内存中将这些文件的键值对进行排序,然后输出到一到多个新的磁盘文件中。多路归并排序在大数据领域也是常用的算法,常用于海量数据排序。当数据量特别大时,这些数据无法被单个机器内存容纳,它需...

weixin_39821035的博客 84

WINCC7.3破解文件

WINCC7.3硬件够破解文件,有需要的下载!WINCC7.3硬件够破解文件,有需要的下载!

LevelDb(一)

从逻辑思维角度提升自己的表达技巧------------Sunface撩技术--------- 链接:https://blog.youkuaiyun.com/abv123456789/article/details/80989921 磁盘,数据库查找算法-----------https://blog.youkuaiyun.com/abv123456789/article/details/50992994 BAT经典算法...

oooooooooooooookjk的博客 285

ET2012新版免狗免安装绿色软件

ET2012新版免狗免安祖昂绿色软件。,新界面,新视觉,

最新的sdio 4.0 spec 协议标准 带书签 高清完整PDF版

Part-E1-SDIO-Specification-Ver4.00-Final-120220.pdf:最新的sdio 4.0 spec 协议标准 带书签 高清完整PDF版,包括SDIO 接口的外设,SDIO 接口官方协议等。

归并排序在硬盘当中的使用

归并排序在外存的使用

chihiro1122的博客 659

【算法】多路归并算法

多路归并算法

qq_66314292的博客 1581

二分归并排序

 二分归并排序 1.问题 二分归并排序:对n个不同的数构成的数组A[1…n]进行排序,其中n=2^k 2.解析 其算法核心思想就是把一个大问题在不改变前提的条件下拆分为若干个小问题,实现分而治之。该算法是采分治法的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二分归并。 图解: 3.设计 核心代码实现: int mergeList(int a[], int left, int mid, int right,

Makabakaba的博客 350

多路归并排序_多路平衡归并排序(胜者树、败者树)算法详解

通过上一节对于外部排序的介绍得知:对于外部排序算法来说,其直接影响算法效率的因素为读写外存的次数,即次数越多,算法效率越低。若想提高算法的效率,即减少算法运行过程中读写外存的次数,可以增加 k –路平衡归并中的 k 值。但是经过计算得知,如果毫无限度地增加 k 值,虽然会减少读写外存数据的次数,但会增加内部归并的时间,得不偿失。例如在上节中,对于 10 个临时文件,当采用 2-路平衡归并...

weixin_39609051的博客 2641

二分归并排序_归并排序过程详解

来源:https://blog.youkuaiyun.com/daigualu/article/details/78399168归并简介归并排序,英文名称是MERGE-SORT。它是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。算法的核心概念—二...

weixin_39847945的博客 1293

算法入门(快排,归并,二分详解!)

快速排序,归并排序,整数二分查找,浮点数二分

qq_62958114的博客 1049

归并排序算法分析

归并排序算法分析 1、算法思想 ​ 利用分治的思想,每一次把原数组分为两份,再进行对子数组进行排序,最后对排好序的数组进行合并,形成一个新的有序序列。 1.1执行过程 2、实现步骤 2.1 mergeSort()方法 public static void mergeSort(int[]arr){ if (arr == null || arr.length < 2){ return; } mergeSort(arr,0,arr

tyeerth的博客 364
上一篇: 阿里开源限流组件 Sentinel 集群流控全解析
下一篇: 深度解密HTTP通信细节
码洞
博客等级 码龄10年 41粉丝 137原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值