C语言中压缩字符串的简单算法小结

所属分类: 软件编程 / C 语言 阅读数: 41
收藏 0 赞 0 分享

应用中,经常需要将字符串压缩成一个整数,即字符串散列。比如下面这些问题:
(1)搜索引擎会通过日志文件把用户每次检索使用的所有检索串都记录下来,每个查询串的长度为1-255字节。请找出最热门的10个检索串。
(2)有一个1G大小的一个文件,里面每一行是一个词,词的大小不超过16字节,内存限制大小是1M。返回频数最高的100个词。
(3)有10个文件,每个文件1G,每个文件的每一行存放的都是用户的query,每个文件的query都可能重复。要求你按照query的频度排序。
(4)给定a、b两个文件,各存放50亿个url,每个url各占64字节,内存限制是4G,让你找出a、b文件共同的url。
(5)一个文本文件,大约有一万行,每行一个词,要求统计出其中最频繁出现的前10个词。

这些问题都需要将字符串压缩成一个整数,或者说是散列到某个整数 M 。然后再进行取余操作,比如 M%16,就可以将该字符串放到编号为M%16的文件中,相同的字符串肯定是在同一个文件中。通过这种处理,就可以将一个大文件等价划分成若干小文件,而对于小文件,就可以用常规的方法处理,内排序、hash_map等等。最后将这些小文件的处理结果综合起来,就可以求得原问题的解。
下面介绍一些字符串压缩的算法。

方法1:最简单就是将所有字符加起来,代码如下:

unsigned long HashString(const char *pString, unsigned long tableSize)
{
 unsigned long hashValue = 0;
 while(*pString)
    hashValue += *pString++;
 return hashValue % tableSize;
}

分析:如果字符串的长度有限,而散列表比较大的话,浪费比较大。例如,如果字符串最长为16字节,那么用到的仅仅是散列表的前16*127=2032。假如散列表含2729项,那么2032以后的项都用不到。

方法2:将上次计算出来的hash值左移5位(乘以32),再和当前关键字相加,能得到较好的均匀分布的效果。

unsigned long HashString(const char *pString,unsigned long tableSize)
{
 unsigned long hashValue = 0;
 while (*pString)
 hashValue = (hashValue << 5) + *pString++;
 return hashValue % tableSize;
}

分析:这种方法需要遍历整个字符串,如果字符串比较大,效率比较低。

方法3:利用哈夫曼算法,假设只有0-9这十个字符组成的字符串,我们借助哈夫曼算法,直接来看实例: 

#define Size 10 
int freq[Size]; 
string code[Size]; 
string word; 
struct Node 
{ 
 int id; 
 int freq; 
 Node *left; 
 Node *right; 
 Node(int freq_in):id(-1), freq(freq_in) 
 { 
  left = right = NULL; 
 } 
}; 
struct NodeLess 
{ 
 bool operator()(const Node *a, const Node *b) const 
 { 
  return a->freq < b->freq; 
 } 
}; 
 
void init() 
{ 
 for(int i = 0; i < Size; ++i) 
  freq[i] = 0; 
 for(int i = 0; i < word.size(); ++i) 
  ++freq[word[i]]; 
} 
void dfs(Node *root, string res) 
{ 
 if(root->id >= 0) 
  code[root->id] = res; 
 else 
 { 
  if(NULL != root->left) 
   dfs(root->left, res+"0"); 
  if(NULL != root->right) 
   dfs(root->right, res+"1"); 
 } 
} 
 
void deleteNodes(Node *root) 
{ 
 if(NULL == root) 
  return ; 
 if(NULL == root->left && NULL == root->right) 
  delete root; 
 else 
 { 
  deleteNodes(root->left); 
  deleteNodes(root->right); 
  delete root; 
 } 
} 
void BuildTree() 
{ 
 priority_queue<Node*, vector<Node*>, NodeLess> nodes; 
 for(int i = 0; i < Size; ++i) 
 { 
//0 == freq[i] 的情况未处理 
    Node *newNode = new Node(freq[i]); 
  newNode->id = i; 
  nodes.push(newNode); 
 } 
 while(nodes.size() > 1) 
 { 
  Node *left = nodes.top(); 
  nodes.pop(); 
  Node *right = nodes.top(); 
  nodes.pop(); 
  Node *newNode = new Node(left->freq + right->freq); 
    newNode->left = left; 
    newNode->right = right; 
    nodes.push(newNode); 
 } 
 Node *root = nodes.top(); 
 dfs(root, string("")); 
 deleteNodes(root); 
} 

更多精彩内容其他人还在看

C++广播通信实例

这篇文章主要介绍了C++实现广播通信的方法,实例讲述了C++ socket广播通信的原理与实现方法,需要的朋友可以参考下
收藏 0 赞 0 分享

C++计算ICMP头的校验和实例

这篇文章主要介绍了C++计算ICMP头的校验和的方法,代码简单实用,对于校验ICMP报文来说有不错的实用价值,需要的朋友可以参考下
收藏 0 赞 0 分享

C++设置超时时间的简单实现方法

这篇文章主要介绍了C++设置超时时间的简单实现方法,涉及系统函数setsockopt对套接口的操作,具有一定的实用价值,需要的朋友可以参考下
收藏 0 赞 0 分享

C++实现ping程序实例

这篇文章主要介绍了C++实现ping程序实例,涉及C++对于ICMP数据包的发送与回显处理,具有一定的实用价值,需要的朋友可以参考下
收藏 0 赞 0 分享

C++之boost::array的用法

这篇文章主要介绍了C++之boost::array的用法,以实例的形式简单讲述了静态数组的容器boost::array的使用技巧,具有一定的参考借鉴价值,需要的朋友可以参考下
收藏 0 赞 0 分享

C++之Boost::array用法简介

这篇文章主要介绍了C++之Boost::array用法简介,较为详细的分析了Boost::array中的常见用法,并用实例的形式予以总结归纳,需要的朋友可以参考下
收藏 0 赞 0 分享

VC文件目录常见操作实例汇总

这篇文章主要介绍了VC文件目录常见操作实例汇总,总结了VC针对文件目录的各种常用操作,非常具有实用价值,需要的朋友可以参考下
收藏 0 赞 0 分享

VC打印word,excel文本文件的方法

这篇文章主要介绍了VC打印word,excel文本文件的方法,是VC操作文本文件中非常实用的技巧,需要的朋友可以参考下
收藏 0 赞 0 分享

VC++获得当前进程运行目录的方法

这篇文章主要介绍了VC++获得当前进程运行目录的方法,可通过系统函数实现该功能,是非常实用的技巧,需要的朋友可以参考下
收藏 0 赞 0 分享

VC中SendMessage和PostMessage的区别

这篇文章主要介绍了VC中SendMessage和PostMessage的区别,较为全面的分析了SendMessage和PostMessage运行原理及用法上的不同之处,非常具有实用价值,需要的朋友可以参考下
收藏 0 赞 0 分享
查看更多