编程珠玑——磁盘排序算法1

终于开始阅读《编程珠玑 》这本书了,期待了好久,不知道是翻译的比较晦涩,还是书籍本身就比较难,看起来那叫一个吃力啊,体会到看中文如此费尽,瞬间觉得对不起语文老师了。
    第一章讲述的是一个磁盘排序算法,问题的具体描述如下所示。
     输入: 一个最多包含n个正整数的文件,每个数都小于n,其中n = 10 ^ 7。这n个数字没有重复出现,没有其他数据与该整数相关连。
     输出: 按升序排列的输入整数的列表。
     约束: 最多有(大约 )1MB内存空间可用,有充足的磁盘存储空间可用。运行的时间最多几分钟,运行时间为10秒就不需要再继续优化了。

    文中给出了两种解决方法,一种是多路归并排序,另一种是位图法。
方法一:多路归并排序
    题目中的限制为所有正整数都不重复。这表示:
    假设内存空间正好为1MB,那么一次可以读入内容的整数个数为250 000个,那么我们每次读入25000个数字,然后用内存排序方法进行排序,将排序后的结果保存到相应的临时文件中。
     多路归并排序的思路:
    1、第一次遍历文件,读取文件内的第1到第250 000个数字,进行排序,将排序后的正整数存储在临时的磁盘文件filename1.txt中;
    2、第二次遍历文件,读取文件内的第250 001到底500 000个数字,进行排序,将排序后的正整数存储在临时的磁盘文件filename2.txt中;
    ......
    将文件中的整数分别进行排序后,然后使用多路归并排序进行整合,首先读取40个文件中的第一个数字,查找这40个数字中最小的数字,将其输出到结果文件中result.txt中,然后读取该最小的元素对应的文件中的下一个数字,替代其位置,依次进行,最后得到排序号的文件。

    代码如下所示。

//源自:http://www.cnblogs.com/daoluanxiaozi/archive/2012/03/29/2423087.html
#include <iostream>
#include <algorithm>
#include <string>
#include <fstream>
#include <time.h>
using namespace std;

#define MAX 10000        //总数据量,可修改
#define MAX_ONCE 2500 //内存排序MAX_ONCE个数据
#define FILENAME_LEN 20

//range:范围
//num :个数
void Random(int range, int num)
{
    int *a = new int[range];
    int i, j;

    fstream dist_file;

    //初始化随机数种子
    srand((unsigned)time(NULL));
    for(i = 0; i < range; i++)
    {
        a[i] = i + 1;
    }

    //打表预处理
    for(j = 0; j < range; j++)
    {
        int ii = (rand() * RAND_MAX + rand()) % range;
        int jj = (rand() * RAND_MAX + rand()) % range;
        swap(a[ii], a[jj]);
    }//for

    dist_file.open("data.txt", ios::out);

    //写入文件
    for(i = 0; i < num; i++)
    {
        dist_file << a[i] << " ";
    }
    
    //回收
    delete []a;
    dist_file.close();
}

bool comp(int &a, int &b)
{
    return a < b;
}

//index: 文件的下标
char *create_filename(int index)
{
    char *a = new char[FILENAME_LEN];
    sprintf(a, "data %d.txt", index);
    return a;
}

//num:每次读入内存的数据量
void mem_sort(int num)
{
    fstream fs("data.txt",ios::in);
    int temp[MAX_ONCE];        //内存数据暂存
    int file_index = 0;        //文件下标

    int count;                //实际读入内存数据量
    bool eof_flag = false;    //文件末尾标识

    while(!fs.eof())
    {
        count = 0;
        for(int i = 0; i < MAX_ONCE; i++)
        {
            fs >> temp[count];

            //读入一个数据后判断是否到了末尾
            if(fs.peek() == EOF)
            {
                eof_flag = true;
                break;
            }//if

            count++;
        }//for

        if(eof_flag)    //如果到达文件末尾
        {
            break;
        }

        //内存排序
        sort(temp, temp + count, comp);
        
        //写入文件
        char *filename = create_filename(++file_index);
        fstream fs_temp(filename, ios::out);
        for(int i = 0; i < count; i++)
        {
            fs_temp << temp[i] << " ";
        }
        fs_temp.close();
        delete []filename;
    }//while
    
    fs.close();
}

void merge_sort(int filecount)
{
    fstream *fs = new fstream[filecount];
    fstream ret("ret.txt", ios::out);

    int index = 1;
    int temp[MAX_ONCE];
    int eofcount = 0;
    bool *eof_flag = new bool[filecount];
    memset(eof_flag, false, filecount * sizeof(bool));

    for(int i = 0; i < filecount; i++)
    {
        fs[i].open(create_filename(index++), ios::in);
    }

    for(int i = 0; i < filecount; i++)
    {
        fs[i] >> temp[i];
    }

    while(eofcount < filecount)
    {
        int j = 0;

        //找到第一个未结束处理的文件
        while(eof_flag[j])
        {
            j++;
        }

        int min = temp[j];
        int fileindex = 0;
        for(int i = j + 1; i < filecount; i++)
        {
            if(temp[i] < min && !eof_flag[i])
            {
                min = temp[i];
                fileindex = i;
            }
        }//for

        ret << min << " ";
        fs[fileindex] >> temp[fileindex];

        //末尾判断
        if(fs[fileindex].peek() == EOF)
        {
            eof_flag[fileindex] = true;
            eofcount++;
        }
    }//while

    delete []fs;
    delete []eof_flag;
    ret.close();
}
    
int _tmain(int argc, _TCHAR* argv[])
{
    Random(MAX, MAX);
    clock_t start = clock();
    mem_sort(MAX);
    merge_sort(4);
    clock_t end = clock();
    
    double cost = (end - start) * 1.0 / CLK_TCK;
    cout << "耗时" << cost << "s" << endl;
    return 0;
}
多路归并的方法需要多次进行磁盘I/O读写,会增加额外的时间开销,总体来说,该方法的时间复杂度比较高。

方法二:位图法
    位图法适合排序、查询、去除重复和判断数组是否存在重复。判断集合中存在重复是常见的编程任务之一,当集合中的数据量比较大时,通常希望少进行几次扫描。
    位图法比较适合这种情况,它的做法是按照集合中最大元素max创建一个长度为max + 1的新数组,然后再次扫原数组,遇到几就给新数组的第几位置上置1,如遇到5就个新数组的第六个元素置1,这样下次再遇到5想置位时发现新数组的第六个元素已经是1了,这说明这次的数据肯定和以前的数据存在重复。这种给新数组初始化时置零其后置一的做法类似于位图的处理方法,故称为位图法。它的运算次数最坏的情况为2N。如果已知数组的最大值即能事先给新数组定长的话效率还能提高一倍。
    对于磁盘文件排序这个问题,使用位图法的思路如下所示。
    1、10000 000个正整数所占用的内存空间大约为1MB;
    2、创建有10000 000个位的字符串,并将其每一位初始化为0;
    3、读取包含正整数的文件,对于每一个正整数,将其对应的位置设置为1;
    4、按位顺序读取字符串,当读取到bit[i]为1时,输出i;
    代码如下所示。

#include <iostream>
#include <algorithm>
#include <time.h>
#include <bitset>
using namespace std;

#define DATA_NUM 10000            //生成的随机数的个数
#define SOURCE "data.txt"        //保存随机数的文件名称
#define RESULT     "result.txt"    //保存排序结果的文件名称

//功能:生成随机数文件
//参数:num 生成随机数的个数
void make_data(int num)
{
    int *temp = new int[DATA_NUM];
    if(temp == NULL)
    {
        cout << "new error in make_data()" << endl;
        return;
    }

    for(int i = 0; i < DATA_NUM; i++)
    {
        temp[i] = i + 1;
    }

    for(int i = 0; i < DATA_NUM; i++)
    {
        int ii = (rand() * RAND_MAX + rand()) % DATA_NUM;
        int jj = (rand() * RAND_MAX + rand()) % DATA_NUM;
        swap(temp[ii], temp[jj]);
    }

    //写入文件
    FILE *fp;
    fp = fopen(SOURCE, "w");
    if(fp == NULL)
    {
        cout << "fopen() error in make_data()." << endl;
    }
    for(int i = 0; i < DATA_NUM; i++)
    {
        fprintf(fp, "%d ", temp[i]);
    }
    fclose(fp);
    cout << "随机数文件生成成功." << endl;
}

void BitMapSort()
{
    //这里对这10000个正整数做如下处理:
    //首先对1-5000之内的数进行处理
    //然后对5001-10000之内的数进行处理
    //这样对文件扫描了两遍,增加了时间开销
    //如果数据不是很大的话,那么可以直接处理的,这里为了防止数据过大,进行多次处理

    bitset<DATA_NUM/2 + 2> bitmap;
    bitmap.reset();

    FILE *fpsrc;
    fpsrc = fopen(SOURCE, "r");
    if(fpsrc == NULL)
    {
        cout << "fopen() error in BitMapSort()" << endl;
        return;
    }

    int data;
    while(fscanf(fpsrc, "%d ", &data) != EOF)
    {
        if(data <= DATA_NUM / 2)
        {
            bitmap.set(data, 1);
        }
    }

    //将排序好的数写入到结果文件中
    FILE *fpdst;
    fpdst = fopen(RESULT, "w");
    if(fpdst == NULL)
    {
        cout << "fopen() error in BitMapSort()." << endl;
        return;
    }

    for(int i = 0; i <= DATA_NUM / 2 + 1; i++)
    {
        if(bitmap[i] == 1)
        {
            fprintf(fpdst, "%d ", i);
        }
    }
    
    //处理剩下的数据
    int res = fseek(fpsrc, 0, SEEK_SET);
    if(res)
    {
        cout << "fseek() error in BitMapSet()." << endl;
        return;
    }
    bitmap.reset();
    while(fscanf(fpsrc, "%d ", &data) != EOF)
    {
        if(data <= DATA_NUM && data > DATA_NUM / 2)
        {
            data = data - DATA_NUM / 2;    //注意:开始时出错了
            bitmap.set(data, 1);
        }
    }

    for(int i = 0; i <= DATA_NUM / 2 + 1; i++)
    {
        if(bitmap[i] == 1)
        {
            fprintf(fpdst, "%d ", i + DATA_NUM / 2);
        }
    }
    cout << "排序成功." << endl;
    fclose(fpdst);
    fclose(fpdst);
}


int _tmain(int argc, _TCHAR* argv[])
{
    make_data(DATA_NUM);

    clock_t start = clock();
    BitMapSort();
    clock_t end = clock();

    cout << "排序所用时间为:" << (end - start) * 1.0 / CLK_TCK << "s" << endl;
    return 0;
}



  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值