算法、数据结构一直是自已薄弱的环节,一直想学习但觉得自己水平太臭,怕打击信心.平时工作中也只是会用库中的函数或类来解决问题。
逛书店无意间找到编程珠玑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.