大规模文本文件的外部归并排序(python实现)

    下述代码实现的是一种外部排序算法,它主要利用了归并排序的思想,并且使用了堆(优先队列)来辅助合并过程。这里的核心思想是将一个大文件分割成多个小文件(称为“块”或“chunk”),然后对这些小文件进行排序,最后将它们合并成一个有序的大文件。

这个过程可以分为三个主要步骤:

1. 分割(Splitting):将大文件分割成多个小文件,每个小文件包含一定数量的记录(例如,每行一个记录)。

2. 局部排序(Local Sorting):对每个小文件进行排序。由于小文件的大小适中,可以将其全部加载到内存中进行排序,通常使用快速排序、归并排序或其他高效的内部排序算法。

3. 归并(Merging):将所有已排序的小文件合并成一个有序的大文件。这是通过使用一个最小堆来实现的,堆中的每个元素都是下一个要处理的记录(来自某个小文件的第一行),以及该记录所在文件的索引。每次从堆中取出最小的记录并写入输出文件,然后从相应的小文件中读取下一条记录,将其加入堆中。这个过程重复进行,直到所有小文件都被完全读取。

① 首先,生成一个由随机字母组成的.txt的大型文本文件。
import random
import string

def generate_random_word(length=5):
    letters = string.ascii_lowercase  # 包含所有小写字母
    return ''.join(random.choice(letters) for _ in range(length))

with open('random_words.txt', 'w') as file:
    for _ in range(1000000):
        random_word = generate_random_word(5)  # 每个单词长度为5
        if random.random() < 0.05:  # 有5%的概率添加一个空行
            file.write('\n')
        file.write(random_word + '\n')

 

文本文件内容如图所示(这里设定的单词长度为5,空行插入率为5%):

 

② 导入需要的包
import heapq
import os
import argparse

 

 ③  步骤一:从原始文件中读取数据块并进行排序
def sort_and_save_chunk(chunk, chunk_num):
    # 对数据块进行排序
    chunk.sort()
    # 将排序后的数据块写入一个中间文件,命名为 chunk_{chunk_num}.txt
    with open(f'chunk_{chunk_num}.txt', 'w') as chunk_file:
        for word in chunk:
            chunk_file.write(f'{word}\n')

      步骤二:分割原始文件成多个小文件,并去除空行
def split_file(input_file, chunk_size):
    chunk = []  # 用于存储数据块
    chunk_num = 1  # 数据块编号
    with open(input_file, 'r') as f:
        for line in f:
            line = line.strip()
            if line:  # 检查行是否非空
                chunk.append(line)  # 从原始文件中读取单词并存储在数据块中
                if len(chunk) == chunk_size:  # 数据块大小达到 chunk_size 时,执行排序和保存
                    sort_and_save_chunk(chunk, chunk_num)
                    chunk = []  # 重置数据块
                    chunk_num += 1
        if chunk:  # 处理剩余的数据块
            sort_and_save_chunk(chunk, chunk_num)

 

       步骤三:归并排序
def merge_sorted_files(output_file, chunk_files):
    with open(output_file, 'w') as output:
        file_handles = []  # 用来存储所有中间文件的文件句柄
        data = []  # 存储堆中的数据,每个元素是一个包含单词和文件索引的元组

        # 遍历所有中间文件
        for i, chunk_file in enumerate(chunk_files):
            handle = open(chunk_file, 'r')  # 打开文件
            file_handles.append(handle)   # 将文件句柄添加到列表中
            line = handle.readline()  # 读取第一个单词
            if line:
                data.append((line.strip(), i))  # 如果读取到单词,将其添加到数据列表中

        heapq.heapify(data)  # 将数据列表转换为最小堆

        while data:
            smallest_word, file_index = heapq.heappop(data)  # 从堆中取出最小单词及其索引
            output.write(f'{smallest_word}\n')  # 将最小单词写入输出文件

            next_word = file_handles[file_index].readline()  # 读取下一个单词
            if next_word:
                heapq.heappush(data, (next_word.strip(), file_index))  # 将新单词添加到堆中

        for handle in file_handles:
            handle.close()  # 关闭所有中间文件的文件句柄
 ④ 定义主函数,并构建命令行界面
def main():
    parser = argparse.ArgumentParser(description="Sort large files containing words using external sorting and remove empty lines.")
    parser.add_argument("input_file", type=str, help="Input file containing words to be sorted.")
    parser.add_argument("output_file", type=str, help="Output file for the sorted words.")
    parser.add_argument("--chunk_size", type=int, default=10000, help="Size of each chunk to sort and save.")
    args = parser.parse_args()

    # 分割文件
    split_file(args.input_file, args.chunk_size)

    # 获取所有中间文件
    chunk_files = [f for f in os.listdir() if f.startswith('chunk_') and f.endswith('.txt')]

    # 归并排序
    merge_sorted_files(args.output_file, chunk_files)

if __name__ == "__main__":
    main()

 

 ⑤ 在pycharm的终端(Terminal)运行脚本文件。(我这里将上述代码脚本命名为:external_sort_words.py )

python external_sort_words.py random_words.txt sorted_words.txt --chunk_size 10000

这里 random_words.txt 是要排序的输入文件sorted_words.txt 是排序后的输出文件--chunk_size每个数据块的大小(可选,默认为10000)。

 

 

排序效果展示如下:

时间复杂度的分析

- 分割步骤:时间复杂度为 O(n),其中 n 是大文件中的记录数。每条记录只需要读取一次。

- 局部排序步骤:对于 k 个小文件,每个文件大约有 n/k 条记录。如果使用快速排序或归并排序,每个文件的排序时间复杂度为 O((n/k) log(n/k))。因此,k 个文件的总时间复杂度为 O(n log(n/k))。

- 归并步骤:在最坏情况下,每次从堆中取出一个元素后,都需要将新元素从磁盘读取到内存中,这需要 O(k log k) 时间(k 是小文件的数量)。由于需要处理 n 条记录,所以总的时间复杂度为 O(n log k)。

综合考虑,整个外部排序的时间复杂度为 O(n log n),这与内部排序算法的时间复杂度相同,但外部排序适用于处理大型数据集,这些数据集太大而无法全部加载到内存中

空间复杂度的分析

- 分割步骤:空间复杂度为 O(n/k),因为我们需要存储每个小文件的内容。

- 局部排序步骤:空间复杂度取决于所使用的排序算法,但通常为 O(n/k),因为这是排序每个小文件所需的额外空间。

- 归并步骤:空间复杂度为 O(k),因为我们需要存储堆中的所有 k 个元素,以及每个小文件的当前读取位置。

总的来说,外部排序的空间复杂度主要取决于小文件的数量 k 和记录的大小。在最坏的情况下,空间复杂度为 O(n),即所有小文件的内容都需要存储在磁盘上。然而,由于这些文件是逐步处理的,所以实际上只需要保持一小部分在内存中。

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值