背景:记录成长
开博客的第一篇文章便是字典树实现模糊匹配,那个时候提到会把字典树导入二进制文件后进行搜索
优缺点比较:
对字典树的深搜实现模糊匹配:
缺点:每次启动程序需要加载一颗字典树,浪费时间,浪费空间。
优点:程序较为简单,实现起来相对容易
对二进制文件的广搜实现模糊匹配:
缺点:代码繁琐,难以实现,设计二进制文件的数据结构极为难难难。
优点:设计的数据结构较优等时省时间,空间,线下实现,程序直接可以用。
就目前来说设计了一款比较low的二进制文件的数据格式
这款数据结构的设计花了我一个周的时间,虽然很low,但是要基于自己的能力去设计,考虑的因素很多,设计完以后怎么在二进制文件上进行搜索,包括二分,广度优先搜索,考虑到目前自己的能力只能在结构相等的情况去进行指针的偏移,所以这个结构设计下来到最终的实现,亲测10000组地区名称的数据,平均每组数据花费空间52B,有点难以接受,如果是万亿级别的数据(还是有点糟糕),后续会寻求师傅的帮助进行优化。预计还有一个月的时间。
实现对我自己设计的结构的搜索也锻炼了,提升了自己很多方面的能力,下面就我这种实现贴出讲解及代码。
offsize 即 取代字典树里结点的指针的功能,通过偏移offsize找到儿子结点。
idoffsize 由于文件最前面存id可以省去非常多的结点不用存8bsize的id,同时使得搜索的结构对称。
一下代码没有任何依赖,单独的工程,可在vs上运行。
有关于原始字典树的代码第一篇文章已经有了,在这里就不贴出来了。
充当迭代器的struct,用于记录搜索中间过程。
//充当迭代器的作用
//typedef struct SBinaryNode* pSBinaryNode;
struct SBinaryNode
{
char pNamevalue;
int pNodesonoff;
int pNodeidoff;
int pFirstchile;
SBinaryNode(){
pNodesonoff = -1;
pNodeidoff = -1;
pNamevalue = 'a';
pFirstchile = -1;
}
};
在存二进制文件之前,要计算好的offsize,广搜一遍记录id顺序
第一个儿子占10个字节,多一个字节记录父节点的儿子数目,便于搜索
所以这里结点设计出一个pBrotherNum,第一个儿子记录父结点的所有
儿子数量,其余的全部为0
void
CTree::IdentFirstChild(pSTreeNode pNode)
{
if (pNode == NULL) {
return;
}
queue<pSTreeNode> NodeQueue;
NodeQueue.push(pNode);
while (!NodeQueue.empty()) {
pSTreeNode Node_temp = NodeQueue.front();
NodeQueue.pop();
if (0 < Node_temp->pChildList.size()) {
for (int i =0 ; i < Node_temp->pChildList.size(); ++i) {
if (i == 0) {
Node_temp->pChildList[i]->pBrotherNum = Node_temp->pChildList.size();
}
else {
Node_temp->pChildList[i]->pBrotherNum = 0;
}
NodeQueue.push(Node_temp->pChildList[i]);
}
}
}
}
计算偏移,也就是建立所有结点在二进制文件里的物理关系,
方便认亲(哈哈哈),方便搜索
void
CTree::IdentNumber(pSTreeNode pNode)
{
if (pNode == NULL) {
return;
}
long long m_Index = 0;
queue<pSTreeNode> NodeQueue;
NodeQueue.push(pNode);
while (!NodeQueue.empty()) {
pSTreeNode Node_temp = NodeQueue.front();
if (pRoot != Node_temp) {
if (Node_temp->pBrotherNum > 0) {
m_Index += FIRSTCHILDV;
}
else {
m_Index += NFIRSTCHILDV;
}
Node_temp->m_IdenxNum = m_Index;
}
NodeQueue.pop();
if (0 < Node_temp->pChildList.size()) {
for (int i =0 ; i < Node_temp->pChildList.size(); ++i) {
NodeQueue.push(Node_temp->pChildList[i]);
}
}
}
}
预处理之后开始写文件
写文件的逻辑是光搜字典树按层次存放结点,文件分为两部分,
一部分是结点的值极逻辑关系,另一部分是结点的value ID值
void
CTree::WriteNodeInfo(pSTreeNode pNode)
{
if (pNode == NULL) {
return;
}
int pNodeOff;
int pNodeIdOff = 0;
fstream iofile("NodeInfo.dat",ios::in|ios::out|ios::binary);
int pNodeIdCount = GetNodeIdOffsize();
iofile.seekg(LONGLONGV * pNodeIdCount + INTVALUE,ios::beg);//偏移之后写Node信息
queue<pSTreeNode> NodeQueue;
NodeQueue.push(pNode);
while (!NodeQueue.empty()) {
pSTreeNode Node_temp = NodeQueue.front();
if (pRoot != Node_temp) {
if (Node_temp->pBrotherNum > 0) {
iofile.write((char *)&Node_temp->pBrotherNum, CHARVALUE);
}
iofile.write((char *)&Node_temp->namevalue, CHARVALUE);
if (0 < Node_temp->pChildList.size()) {
pNodeOff = Node_temp->pChildList[0]->m_IdenxNum - FIRSTCHILDV;
}
else {
pNodeOff = 0;
}
iofile.write((char *)&pNodeOff, INTVALUE);
if (Node_temp->id > 0) {
pNodeIdOff++;
iofile.write((char *)&pNodeIdOff, INTVALUE);
}
else {
int tmp_Off = 0;
iofile.write((char *)&tmp_Off, INTVALUE);
}
}
NodeQueue.pop();
if (0 < Node_temp->pChildList.size()) {
for (int i =0 ; i < Node_temp->pChildList.size(); ++i) {
NodeQueue.push(Node_temp->pChildList[i]);
}
}
}
iofile.seekg(0, ios::beg);
iofile.write((char *)&pNodeIdCount, INTVALUE);
pSTreeNode Node_temp = GetTreepRoot();
//queue<pSTreeNode> NodeQueue;
NodeQueue.push(Node_temp);
while (!NodeQueue.empty()) {
pSTreeNode Node_temp = NodeQueue.front();
if (pRoot != Node_temp) {
if (Node_temp->id > 0) {
iofile.write((char *)&Node_temp->id, LONGLONGV);
}
}
NodeQueue.pop();
if (0 < Node_temp->pChildList.size()) {
for (int i =0 ; i < Node_temp->pChildList.size(); ++i) {
NodeQueue.push(Node_temp->pChildList[i]);
}
}
}
iofile.seekg(0, ios::beg);
iofile.close();
}
二分,最好传过来一段buff,省的对文件频繁的seek操作
SBinaryNode
CTree::SearchBinaryThVal(int idx, char* buff_tmp, char pNum)
{
int Listleft = 0;
int Listright = pNum;
int Listmid = 0;
char pValue_tem;
SBinaryNode Node_tmp;
while (Listleft < Listright) {
Listmid = (Listleft + Listright) / 2;
buff_tmp += Listmid * ONESTRUCT;
pValue_tem = *(char*) buff_tmp;
if (idx < pValue_tem) {
buff_tmp -= Listmid * ONESTRUCT;
Listright = Listmid;
}
else if (idx > pValue_tem) {
buff_tmp -= Listmid * ONESTRUCT;
Listleft = Listmid + 1;
}
else {
Node_tmp.pNamevalue = pValue_tem;
buff_tmp += CHARVALUE;
Node_tmp.pNodesonoff = *(int*)buff_tmp;
//Node_tmp.pNodesonoff = Node_tmp.pNodesonoff - (pNum - Listmid -1) * ONESTRUCT;//易出错,与现在的位置差
buff_tmp += INTVALUE;
Node_tmp.pNodeidoff = *(int*)buff_tmp;
return Node_tmp;
}
}
return Node_tmp;
}
模糊匹配,遍历每个字母,对最后一个字母广搜,求出所以符合
条件的地区名称
void
CTree::BinarySearch(char* name, vector<long long>&nodeids, int pSonOffsize)
{
int idx = 0;
if(NULL == name) {
return;
}
fstream iofile("NodeInfo.dat",ios::in|ios::out|ios::binary);
iofile.seekg(0, ios::beg);
/*int pNodeIdCount = 0;
iofile.read((char *)&pNodeIdCount, INTVALUE);
iofile.seekg(LONGLONGV * pNodeIdCount, ios::cur);*/
nodeids.clear();
//vector<int>Id;
long long id;
queue<SBinaryNode> NodeQueue;
int nameLen = strlen(name);
for (int i = 0; i < nameLen; i++) {
//Id.clear();
nodeids.clear();
idx = get_index(name[i]);
if(IDXERR == idx) {
return;
}
iofile.seekg(INTVALUE + GetNodeIdOffsize() * LONGLONGV + pSonOffsize);
char pNum;
iofile.read((char *)&pNum, CHARVALUE);
char *buff_tmp = new char[pNum * ONESTRUCT];
iofile.read(buff_tmp, pNum * ONESTRUCT);
SBinaryNode Node_tmp = SearchBinaryThVal(idx, buff_tmp, pNum);
if (NULL != buff_tmp) {
delete buff_tmp;
buff_tmp = NULL;
}
if (Node_tmp.pNodeidoff < 0) {
return ;
}
if (Node_tmp.pNodeidoff > 0) {
iofile.seekg(INTVALUE + (Node_tmp.pNodeidoff - 1) * LONGLONGV);
iofile.read((char*)&id, LONGLONGV);
nodeids.push_back(id);
}
int position_tmp = 0;
int id_tmp;
int sonoff_tmp;
if (i == nameLen - 1) {
NodeQueue.push(Node_tmp);
while (!NodeQueue.empty()) {
SBinaryNode Node_temp = NodeQueue.front();
NodeQueue.pop();
iofile.seekg(INTVALUE + GetNodeIdOffsize() * LONGLONGV + Node_temp.pNodesonoff);
char pNum;
iofile.read((char *)&pNum, CHARVALUE);
char *Buff = new char[pNum * ONESTRUCT];
iofile.read(Buff, pNum * ONESTRUCT);
if (0 < pNum) {
position_tmp = 0;
for (int j = 0; j < pNum; j++) {
Buff += 1;
sonoff_tmp = *(int*)Buff;
if (sonoff_tmp > 0) {
SBinaryNode node;
node.pNodesonoff = sonoff_tmp;
NodeQueue.push(node);
}
Buff += 4;
id_tmp = *(int*)Buff;
if (id_tmp > 0) {
iofile.seekg(INTVALUE + (id_tmp - 1) * LONGLONGV);
iofile.read((char*)&id, LONGLONGV);
nodeids.push_back(id);
}
Buff += 4;
}
}
if (NULL != Buff) {
Buff -= pNum * ONESTRUCT;
delete Buff;
Buff = NULL;
}
}
}
pSonOffsize = Node_tmp.pNodesonoff;
}
}
预计一个月后发布,最终的算法及设计,优化程度会大大的提升,可能会有分块的思想(我所不会的),记录下自己的设计,自己的成就~
以上,欢迎留言交流~ 字典树的设计及实现在第一篇文章~