在实现层次汇合聚类算法时,使用std::map的一些心得体会。主要有map的operator []的使用。map的iterator的使用。还有就是关于性能方面的一点见解。

  为了完成《web搜索》课的作业,我奋斗了两天把层次汇合聚类HAC算法和基于亲和性消息的聚类算法给实现了。为了实现这两个算法,第一件事就是把文档向量给计算出来。具体而言就是文本集中的索引词构成了向量空间的一个维度。这样有m个索引词就构成了m维的特征向量。在构建特征向量的过程中需要频繁使用std::map。因为我需要知道一个文档中这个索引词,出现的概率是多少。一下是我的一些经验和大家分享一下:

  1.operator [] 。这个[]的作用很大,不仅可以把key所对应value的引用取出来,还有插入的功能。展示一个基本的使用方法先:
  

using namespace std;
...
map<string,int> elem;
....
//insert operation
...
//get inserted value
string keyword;
int freq = elem[keyword];

  这样就可以把map中key对应的value取出来!如果我输入的keyword,这个map里面没有怎么办?这时就使用了[]的插入功能。如果用户填入了一个map没有的keyword。operator []可以插入一个新的pair。并调用mapped data的构造函数。有代码为证!

1 struct NumIDF
 2 {
 3     int num;
 4     bool showup;
 5     NumIDF()
 6     {
 7         num = 0;
 8         showup = false;
 9         cout << "set to 0 and false" << endl;
10     }
11 };
12 ...
13 map<string,NumIDF> m_IDF;
14 //insert elements
15 ...
16 //query elements
17 string newKeyword;//这个词m_IDF中没有
18 if(!m_IDF[newKeyword].showup)
19 {
20     cout << "construct a new one" << endl;
21 }

  如果把上面代码的输出是

set to 0 and false
construct new one

  也就是说,当在[]内输入了一个新的key之后,map可以自动添加一个新的pair,新pair的key就是输入的newkeyword。而mapped data就是经过初始化之后的实例。这个功能非常好。我以前都是先用find函数找一下,如果是新的,再手动添加。那样的话会非常繁琐。

  2.map的iterator的使用
  说实在的,我用iterator用的比较少。所以犯了几个很低级的错误。写在这里也给自己提个醒。我出错的情况是这样的:我想实现一个类似下面代码要实现的功能。

vector<int> a;
for(int i = 0; i < a.size()-1; i++)
{
for(int j = i+1, j < a.size(); j++)
    {
//some operation about i and j
    }
}

   我想用iterator来实现上面的功能,于是就有了下面悲壮的一幕:

map<string,int>::iterator iterI;
....
//这是错误的啊!
int i = 5;
iterI = iterI + i;
//这是错误的啊!

  我想当然的以为,iterator+多少就会往后面跳多少。可以编译不过啊!出了一大堆错!!有木有!!!于是我用了下面的方法

1 map<string,int> m_Tree;
 2 map<string,int>::iterator iterI = m_Tree.begin();
 3 map<string,int>::iterator iterJ;
 4 int i = 0;
 5         for( ; i < m_Tree.size()-1; ++iterI,i++)
 6         {
 7             //iterJ = m_Tree.begin();
 8 //advance(iterJ, i+1);
 9             iterJ = iterI;
10             iterJ++;
11             for(; iterJ != m_Tree.end(); iterJ++)
12             {
13                 float s = S((iterI->dvmap),(iterJ->dvmap));
14                 if(s > mostSim)
15                 {//this is the pair
16                     mostSim = s;
17                     sp.s1 = iterI;
18                     sp.s2 = iterJ;
19                 }
20             }
21         }

我想得到iterI所指向的下一个元素,于是我采用了第9,10行的方法。其实第7,8行的代码也是可以的,只是不如9,10行的高效!如果你有更好的方法可以带到这个功能,请你告诉我哈!

  3.性能方面,不要让std复制内存,传指针吧!
  在计算文档相似性的时候要用到一个多维的文档向量。这个大向量是使用了std::vector来处理。在性能方面我注意到了两点。
    1)使用reserve申请足够多的内存。为push_back做准备
    2)使用push_back的时候要注意。如果在函数体内声明了一个vector<float>。这个vector的size超大。这是你想把它push_back给类的私有成员的时候势必要复制大量的内存。
  基于上面的两点我采用了下面的方法

1 vector<float> dv;
2 pair<map<string,vector<float> >::iterator,bool> pr;
3 pr = m_TF_IDF.insert(pair<string,vector<float> >(filename, dv));
4 vector<float>& rkdv = pr.first->second;
5 rkdv.reserve(m_IDF.size());

  其中m_TF_IDF是类的私有成员,我现insert了一个空的vector。然后把这个空vector的引用取出来,如第4行所示。然后就可以用大vector的引用来push_back新的数据,这样就免去了内存的复制。
  指针的使用,在避免内存复制上,指针也是一个快捷高效的实现方式。在我的程序中,不知一个地方使用了前面所说的超大vector<float>。为了让想用vector<float>的人都能用上他,我把vector<float>的指针传了出来。 我定义了如下所示的结构体:

struct dvPair
{
string names;
    map<string,vector<float>*> dvmap;
};

我传入的是vector<float>的指针,而不是vector<float>!

  也就是这么多了,别没什么了