算法、数据结构一直是自已薄弱的环节,一直想学习但觉得自己水平太臭,怕打击信心.平时工作中也只是会用库中的函数或类来解决问题。

      逛书店无意间找到编程珠玑II,看了看目录感觉像小故事一样的来讲解算法,一下子就有了兴趣。俺们一比较喜欢的轻松一类的书籍比如: 《大话设计模式》、 《走出软件作坊》、《head first》系列( 好是好就是有点贵呀!) 。

      带回家后看了第一章就被作者神奇的排序方法所折服,真是太神奇了。
 
      简述一下问题:电话号码排序
      电话号码来自于一个外部文件中,电话号码是个数在上百万个且不重复,要求是将这个文件中的电话号码排序,输出到另一个文件,排序算法中内存使用不超过1M,时间在几分钟内,最好在10秒即可。
      我思前想后,想不出来办法,我以往的做法是:将电话号码放入一个数组、或链表、容器中,然后使用冒泡法排序或快速排序。现在的电脑内存一般够大,直接放进来就是。但是现在有要求了,内存不能多用,时间还得快。
     
      我实在想不出来,看作者用的是什么高招: 位图或称为位向量表,这是什么意思呢?下面举个下下列子。
      1字节为8位,每一位只能用0,1来表示。
      如果排序:n[] ={1,3,5,7}这几个数字,我们使用一个字节的位图来表示为:01010101  其中:
     
      第1位的0表示数字0,在数组n中没有0所以这一位为0,
      第2位的1表示数字1,在数组n中有1所以这一位为1.
      第3位的0表示数字2,在数组n中没有2所以这一位为0
      第4位的1表示数字3,在数组n中有3所以这一位为1.
      以下依此类推。
      我们将01010101 为1的位的序号依次输出,及为排序后的效果.其中升序、降序只需要循环方向就行了。
     
      按以上的数据结构来表示就可以解决内存问题了,排序只是按位向量表输出即可,根本没什么运算。时间主要消耗在了对文件的读、写上面了。

 

             

      以下是delphi主要代码:

 上面delphi代码在我的电脑上执行:5百万个34秒、1百万个是6.8秒

unit SortInteger;

interface
uses
  classes, SysUtils, Windows;


type
  TSortType = (stASC, stDesc);

  TSortInteger = class(TObject)
  private
    FInputFile:string;
    FOutputFile: string;
    FSortType:TSortType;
    FBits:TBits;
    procedure Read;
    procedure Write;
  public
    constructor Create(InputFile, OutputFile :string);
    destructor Destroy;override;
    function Sort:DWORD;
    property SortType:TSortType read FSortType write FSortType;
  end;



implementation

{ TSortInteger }

constructor TSortInteger.Create(InputFile, OutputFile: string);
begin
  FInputFile := InputFile;
  FOutputFile := OutputFile;
  FSortType := stASC;
  FBits := TBits.Create;
  FBits.Size := 1024 * 1024 * 8;
end;

destructor TSortInteger.Destroy;
begin
  FBits.Free;
  inherited;
end;

procedure TSortInteger.Read;
var
  FileStream:TFileStream;
  Num:Integer;
begin
  FileStream := TFileStream.Create(FInputFile, fmOpenRead);
  while FileStream.Size <> FileStream.Position do
  begin
    FileStream.Read(Num, SizeOf(Integer));
    FBits[Num] := True;
  end;
  FileStream.Free;
end;

procedure TSortInteger.Write;
var
  i:Integer;
  FileStream:TFileStream;
begin
  FileStream := TFileStream.Create(FOutputFile, fmCreate);
  if FSortType = stASC then
  begin
    for i := 0 to FBits.Size - 1 do
    begin
      if FBits.Bits[i] then
        FileStream.Write(i, sizeof(Integer));
    end;
  end
  else begin
    for i := FBits.Size - 1 downto 0 do
    begin
      if FBits.Bits[i] then
        FileStream.Write(i, sizeof(Integer));
    end;
  end;
  FileStream.Free;
end;

function TSortInteger.Sort:DWORD;
var
  BeginTime:DWORD;
begin
  BeginTime := GetTickCount;
  Read;
  Write;
  Result := GetTickCount - BeginTime;
end;

end.

   

       C++实现方式:

#include <stdio.h>
#include <windows.h>
#include <bitset>
#include <iostream>
#include <string>

using namespace std;
enum SortTyp {stASC, stDesc};

class SortInteger
{
private:
 string _inPutFile;
 string _outPutFile;
 SortTyp _SortType;

 bitset<1024 * 1024 * 8> *_bits;

 void read()
 {
     FILE* File= fopen(_inPutFile.c_str(), "r");
  int Num;
  for (int i = 0; i < _bits->size(); ++i)
  {
   fread(&Num, sizeof(int), 1, File);
   _bits->set(Num);
  }
  fclose(File);
 }

 void write()
 {
  FILE* File= fopen(_outPutFile.c_str(), "w");
  if (_SortType = stASC)
  {
   for(int i = 0; i < _bits->size(); ++i)
   {
    if(_bits->test(i))
     fwrite(&i, sizeof(int), 1, File);
   }
  }
  else
  {
   for(int i = _bits->size() - 1; i >= 0; --i)
   {
    if(_bits->test(i))
     fwrite(&i, sizeof(int), 1, File);
   }
  }
 }
public:
 SortInteger(PCHAR inPutFile, PCHAR outPutFile):
   _inPutFile(inPutFile),
   _outPutFile(outPutFile)
 {
  _bits = new bitset<1024 * 1024 * 8>;
  _bits->reset();
 }

 ~SortInteger()
 {
  delete _bits; 
 }
 void SetSortType(SortTyp sortType)
 {
  _SortType = sortType;
 }

 long Sort()
 {
  long beginTime = GetTickCount();
  read();
  write();
  return GetTickCount() - beginTime;
 }

};

int main(int argc, char* argv[])
{
 if (argc < 4)
  return 0;
 
 SortInteger sortInteger(argv[1], argv[2]);
 if (strcmp(argv[3], "DESC") == 0)
   sortInteger.SetSortType(stDesc);
 else
   sortInteger.SetSortType(stASC);

 cout << "请等待:..."<<endl;
 long useTime = sortInteger.Sort(); 
 cout << "用时:" << useTime << "毫秒。" << endl;
 getchar();
}

      上面C++代码在我的电脑上执行还是debug版的:1百万个是4.2秒, C++用的不太会,编译成release版后的一闪而过连我控制台的输出都看不到,可能是优化的太过了吧!!

      上面对文件的读写都是以二进制的方式没有保存为ASCII码,这是为了测试算法速度,减少读出字符串后将字符串转换为整数,又在写入文件时将整数转换为字符串。

     

      下面代码是用delphi生成一个数据文件的工具,包含了对上面TSortInteger类的使用。

unit FrmMain;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, Spin, SortInteger;

type
  TDelphiDemo = class(TForm)
    GroupBox1: TGroupBox;
    Label1: TLabel;
    Edit1: TEdit;
    Button1: TButton;
    SpinEdit1: TSpinEdit;
    Label2: TLabel;
    GroupBox2: TGroupBox;
    Label3: TLabel;
    Edit3: TEdit;
    RadioButton1: TRadioButton;
    RadioButton2: TRadioButton;
    Button2: TButton;
    Label4: TLabel;
    Edit2: TEdit;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  DelphiDemo: TDelphiDemo;

implementation

{$R *.dfm}

procedure TDelphiDemo.Button1Click(Sender: TObject);
var
  i:Integer;
  FileStream:TFileStream;
  BeginTime:DWORD;
  Msg:string;
begin
  BeginTime := GetTickCount;
  FileStream := TFileStream.Create(Edit1.Text, fmCreate);
  for i := 0 to SpinEdit1.Value - 1 do
    FileStream.Write(i, Sizeof(Integer));
  FileStream.Free;
  Msg := Format('生成完成, 用时: %d 毫秒', [GetTickCount - BeginTime]);
  ShowMessage(Msg);

end;

procedure TDelphiDemo.Button2Click(Sender: TObject);
var
  SortInteger:TSortInteger;
  UseTime:DWORD;
  Msg:string;
begin

  SortInteger := TSortInteger.Create(Edit2.Text, Edit3.Text);
  SortInteger.SortType := stDesc;
  UseTime := SortInteger.Sort;
  SortInteger.Free;
  Msg := Format('生成输出文件完成, 用时: %d 毫秒', [UseTime]);
  ShowMessage(Msg);
end;

end.