posts - 225, comments - 62, trackbacks - 0, articles - 0
   :: 首页 :: 新随笔 :: 联系 :: 聚合  :: 管理
还记得一次ZJU的月赛,有一题用到了Trie树,而且对时间要求非常高,我将动态内存分配改成了静态才过,这是修改过后的Trie的代码

//-------静态空间的Trie-----------
struct TrieTree{
    TrieTree
* next[26];
    
int flag;
};

TrieTree _alloc[
200000],*_p;
inline 
void init_alloc(){_p=_alloc;}
inline TrieTree
* myalloc(){memset(_p,0,sizeof(TrieTree));return _p++;}

void insert(TrieTree* p,const char word[]){
    
for(int i=0;word[i];i++){
        
if(p->next[word[i]-'a']==NULL){
            p
->next[word[i]-'a']=myalloc();
        }
        p
=p->next[word[i]-'a'];
    }
    p
->flag=1;
}

int search(TrieTree* p,const char word[]){
    
for(int i=0;word[i];i++){
        
if(p->next[word[i]-'a']==NULL){
            
return 0;
        }
        p
=p->next[word[i]-'a'];
    }
    
return p->flag;
}

当时的题目只需要插入和查询,不存在删除,而每次case结束只要将_p指针复位就等价于释放了当前的全部节点
其实有更简便的,同样高效的解决方法,
解决方案1,重载operator new:
#include <iostream>
#include 
<algorithm>
using namespace std;

struct TrieTree{
    
static TrieTree _alloc[200000];
    
static TrieTree *_p;

    TrieTree
* next[26];
    
int flag;
    
void* operator new(size_t size){
        
void *res=_p++;
        memset(res,0,sizeof(TrieTree));
        
if(_p>=_alloc+200000)_p-=200000;
        
return res;
    }
};

TrieTree TrieTree::_alloc[
200000];
TrieTree
* TrieTree::_p=TrieTree::_alloc;

void insert(TrieTree* p,const char word[]){
    
for(int i=0;word[i];i++){
        
if(p->next[word[i]-'a']==NULL){
            p
->next[word[i]-'a']=new TrieTree();
        }
        p
=p->next[word[i]-'a'];
    }
    p
->flag=1;
}

int search(TrieTree* p,const char word[]){
    
for(int i=0;word[i];i++){
        
if(p->next[word[i]-'a']==NULL){
            
return 0;
        }
        p
=p->next[word[i]-'a'];
    }
    
return p->flag;
}

int main()
{
    TrieTree
* t=new TrieTree();
    insert(t,
"hello");
    insert(t,
"hi");
    cout
<<search(t,"hello")<<endl;
    cout
<<search(t,"hey")<<endl;
}

这样做的好处是,只要增加静态空间的代码和重载operator new的代码,其余的代码完全无需变化,在insert中仍然使用new来分配即可。这种方法应该说是最优雅的。

其实还有一个解决方案,方案2,placement new
#include <iostream>
#include 
<algorithm>
using namespace std;

struct TrieTree{
    TrieTree
* next[26];
    
int flag;
};

TrieTree _alloc[
200000],*_p;
inline 
void init_alloc(){memset(_alloc,0,sizeof(_alloc));_p=_alloc;}

void insert(TrieTree* p,const char word[]){
    
for(int i=0;word[i];i++){
        
if(p->next[word[i]-'a']==NULL){
            p
->next[word[i]-'a']=new(_p++) TrieTree();//注意此处new的使用
        }
        p
=p->next[word[i]-'a'];
    }
    p
->flag=1;
}

int search(TrieTree* p,const char word[]){
    
for(int i=0;word[i];i++){
        
if(p->next[word[i]-'a']==NULL){
            
return 0;
        }
        p
=p->next[word[i]-'a'];
    }
    
return p->flag;
}

int main()
{
    init_alloc();
    TrieTree
* t=new(_p++) TrieTree();
    insert(t,
"hello");
    insert(t,
"hi");
    cout
<<search(t,"hello")<<endl;
    cout
<<search(t,"hey")<<endl;
}

其实C++提供从已经分配的内存空间上构造对象的方法,就是placement new,只是我当初不知道。它的用法就是在new运算符后面加上用于构造对象的空间的首地址。
placement new主要适用于:在对时间要求非常高的应用程序中,因为这些程序分配的时间是确定的;长时间运行而不被打断的程序;以及执行一个垃圾收集器(garbage collector)。
BTW, placement new是不能被重载的
只有注册用户登录后才能发表评论。