字符串压缩的一些算法 .
来源:互联网 发布:李松蔚 知乎 编辑:程序博客网 时间:2024/05/01 07:09
(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:暴雪公司使用的一个经典hash算法。有篇博客实现这个算法,非常好。可以参考这个。
近期由于需要,研究了魔兽文件打包管理器的相关算法,重点对其文件索引表的生成和查找进行了研究:采用哈希表进行,在冲突方面的处理方面,采用线性探测再散列。在添加和查找过程中进行了三次哈希,第一个哈希值用来查找,后两个哈希值用来校验,这样可以大大减少冲突的几率。
这里对其进行了简单的封装,扩展时,仅仅需要对结构体进行扩展即可。更为详细的说明,参考代码:【转载请保留版权,谢谢】
一、类声明头文件
- /////////////////////////////////////////////////////////////////////////////
- // Name: HashAlgo.h
- // Purpose: 使用魔兽Hash算法,实现索引表的填充和查找功能。
- // Author: 陈相礼
- // Modified by:
- // Created: 07/30/09
- // RCS-ID: $Id: treetest.h 43021 2009-07-30 16:36:51Z VZ $
- // Copyright: (C) Copyright 2009, TSong Corporation, All Rights Reserved.
- // Licence:
- /////////////////////////////////////////////////////////////////////////////
- #define MAXFILENAME 255 // 最大文件名长度
- #define MAXTABLELEN 1024 // 默认哈希索引表大小
- //////////////////////////////////////////////////////////////////////////
- // 测试宏定义,正式使用时关闭
- #define DEBUGTEST 1
- //////////////////////////////////////////////////////////////////////////
- // 哈希索引表定义
- typedef struct
- {
- long nHashA;
- long nHashB;
- bool bExists;
- char test_filename[MAXFILENAME];
- // ......
- } MPQHASHTABLE;
- //////////////////////////////////////////////////////////////////////////
- // 对哈希索引表的算法进行封装
- class CHashAlgo
- {
- public:
- #if DEBUGTEST
- long testid; // 测试之用
- #endif
- CHashAlgo( const long nTableLength = MAXTABLELEN )// 创建指定大小的哈希索引表,不带参数的构造函数创建默认大小的哈希索引表
- {
- prepareCryptTable();
- m_tablelength = nTableLength;
- m_HashIndexTable = new MPQHASHTABLE[nTableLength];
- for ( int i = 0; i < nTableLength; i++ )
- {
- m_HashIndexTable[i].nHashA = -1;
- m_HashIndexTable[i].nHashB = -1;
- m_HashIndexTable[i].bExists = false;
- m_HashIndexTable[i].test_filename[0] = '/0';
- }
- }
- void prepareCryptTable(); // 对哈希索引表预处理
- unsigned long HashString(char *lpszFileName, unsigned long dwHashType); // 求取哈希值
- long GetHashTablePos( char *lpszString ); // 得到在定长表中的位置
- bool SetHashTable( char *lpszString ); // 将字符串散列到哈希表中
- unsigned long GetTableLength(void);
- void SetTableLength( const unsigned long nLength );
- ~CHashAlgo()
- {
- if ( NULL != m_HashIndexTable )
- {
- delete []m_HashIndexTable;
- m_HashIndexTable = NULL;
- m_tablelength = 0;
- }
- }
- protected:
- private:
- unsigned long cryptTable[0x500];
- unsigned long m_tablelength; // 哈希索引表长度
- MPQHASHTABLE *m_HashIndexTable;
- };
二、类实现文件
- /////////////////////////////////////////////////////////////////////////////
- // Name: HashAlgo.cpp
- // Purpose: 使用魔兽Hash算法,实现索引表的填充和查找功能。
- // Author: 陈相礼
- // Modified by:
- // Created: 07/30/09
- // RCS-ID: $Id: treetest.h 43021 2009-07-30 16:36:51Z VZ $
- // Copyright: (C) Copyright 2009, TSong Corporation, All Rights Reserved.
- // Licence:
- /////////////////////////////////////////////////////////////////////////////
- #include "windows.h"
- #include "HashAlgo.h"
- //////////////////////////////////////////////////////////////////////////
- // 预处理
- void CHashAlgo::prepareCryptTable()
- {
- unsigned long seed = 0x00100001, index1 = 0, index2 = 0, i;
- for( index1 = 0; index1 < 0x100; index1++ )
- {
- for( index2 = index1, i = 0; i < 5; i++, index2 += 0x100 )
- {
- unsigned long temp1, temp2;
- seed = (seed * 125 + 3) % 0x2AAAAB;
- temp1 = (seed & 0xFFFF) << 0x10;
- seed = (seed * 125 + 3) % 0x2AAAAB;
- temp2 = (seed & 0xFFFF);
- cryptTable[index2] = ( temp1 | temp2 );
- }
- }
- }
- //////////////////////////////////////////////////////////////////////////
- // 求取哈希值
- unsigned long CHashAlgo::HashString(char *lpszFileName, unsigned long dwHashType)
- {
- unsigned char *key = (unsigned char *)lpszFileName;
- unsigned long seed1 = 0x7FED7FED, seed2 = 0xEEEEEEEE;
- int ch;
- while(*key != 0)
- {
- ch = toupper(*key++);
- seed1 = cryptTable[(dwHashType << 8) + ch] ^ (seed1 + seed2);
- seed2 = ch + seed1 + seed2 + (seed2 << 5) + 3;
- }
- return seed1;
- }
- //////////////////////////////////////////////////////////////////////////
- // 得到在定长表中的位置
- long CHashAlgo::GetHashTablePos(char *lpszString)
- {
- const unsigned long HASH_OFFSET = 0, HASH_A = 1, HASH_B = 2;
- unsigned long nHash = HashString(lpszString, HASH_OFFSET);
- unsigned long nHashA = HashString(lpszString, HASH_A);
- unsigned long nHashB = HashString(lpszString, HASH_B);
- unsigned long nHashStart = nHash % m_tablelength,
- nHashPos = nHashStart;
- while ( m_HashIndexTable[nHashPos].bExists)
- {
- if (m_HashIndexTable[nHashPos].nHashA == nHashA && m_HashIndexTable[nHashPos].nHashB == nHashB)
- return nHashPos;
- else
- nHashPos = (nHashPos + 1) % m_tablelength;
- if (nHashPos == nHashStart)
- break;
- }
- return -1; //没有找到
- }
- //////////////////////////////////////////////////////////////////////////
- // 通过传入字符串,将相应的表项散列到索引表相应位置中去
- bool CHashAlgo::SetHashTable( char *lpszString )
- {
- const unsigned long HASH_OFFSET = 0, HASH_A = 1, HASH_B = 2;
- unsigned long nHash = HashString(lpszString, HASH_OFFSET);
- unsigned long nHashA = HashString(lpszString, HASH_A);
- unsigned long nHashB = HashString(lpszString, HASH_B);
- unsigned long nHashStart = nHash % m_tablelength,
- nHashPos = nHashStart;
- while ( m_HashIndexTable[nHashPos].bExists)
- {
- nHashPos = (nHashPos + 1) % m_tablelength;
- if (nHashPos == nHashStart)
- {
- #if DEBUGTEST
- testid = -1;
- #endif
- return false;
- }
- }
- m_HashIndexTable[nHashPos].bExists = true;
- m_HashIndexTable[nHashPos].nHashA = nHashA;
- m_HashIndexTable[nHashPos].nHashB = nHashB;
- strcpy( m_HashIndexTable[nHashPos].test_filename, lpszString );
- #if DEBUGTEST
- testid = nHashPos;
- #endif
- return true;
- }
- //////////////////////////////////////////////////////////////////////////
- // 取得哈希索引表长
- unsigned long CHashAlgo::GetTableLength(void)
- {
- return m_tablelength;
- }
- //////////////////////////////////////////////////////////////////////////
- // 设置哈希索引表长
- void CHashAlgo::SetTableLength( const unsigned long nLength )
- {
- m_tablelength = nLength;
- return;
- }
三、测试主文件
- /////////////////////////////////////////////////////////////////////////////
- // Name: DebugMain.cpp
- // Purpose: 测试Hash算法封装的类,完成索引表的填充和查找功能的测试。
- // Author: 陈相礼
- // Modified by:
- // Created: 07/30/09
- // RCS-ID: $Id: treetest.h 43021 2009-07-30 16:36:51Z VZ $
- // Copyright: (C) Copyright 2009, TSong Corporation, All Rights Reserved.
- // Licence:
- /////////////////////////////////////////////////////////////////////////////
- //////////////////////////////////////////////////////////////////////////
- // 测试参数设定宏
- #define TESTNUM 32
- #include <iostream>
- #include <fstream>
- #include "HashAlgo.h"
- using namespace std;
- //////////////////////////////////////////////////////////////////////////
- // 测试主函数开始
- int main( int argc, char **argv )
- {
- CHashAlgo hash_test( TESTNUM );
- cout << "取得初始化散列索引表长为:" << hash_test.GetTableLength() << endl;
- bool is_success = hash_test.SetHashTable( "test" );
- if ( is_success )
- {
- cout << "散列结果一:成功!" << endl;
- }
- else
- {
- cout << "散列结果一:失败!" << endl;
- }
- is_success = hash_test.SetHashTable( "测试" );
- if ( is_success )
- {
- cout << "散列结果二:成功!" << endl;
- }
- else
- {
- cout << "散列结果二:失败!" << endl;
- }
- long pos = hash_test.GetHashTablePos( "test" );
- cout << "查找测试字符串:/"test/" 的散列位置:" << pos << endl;
- pos = hash_test.GetHashTablePos( "测试" );
- cout << "查找测试字符串:“测试” 的散列位置:" << pos << endl;
- //////////////////////////////////////////////////////////////////////////
- // 散列测试
- for ( int i = 0; i < TESTNUM; i++ )
- {
- char buff[32];
- sprintf(buff, "abcdefg%d.", i);
- is_success = hash_test.SetHashTable(buff);
- is_success ? cout << buff << "散列结果:成功!位置:" << hash_test.testid << endl : cout << buff << "散列结果:失败!" << endl;
- }
- system( "pause" );
- //////////////////////////////////////////////////////////////////////////
- // 查找测试
- for ( int i = 0; i < TESTNUM; i++ )
- {
- char buff[32];
- sprintf(buff, "abcdefg%d.", i);
- pos = hash_test.GetHashTablePos( buff );
- pos != -1 ? cout << "查找测试字符串:"<< buff <<" 的散列位置:" << pos << endl : cout << buff << "存在冲突!" << endl;
- }
- system( "pause" );
- return 0;
- }
最近我研究出来一种对字符串压缩的算法,虽然还有一定的缺陷。就是如果字符串中的字母过多的话,压缩效率会大大降低。这个算法主要是为压缩数字设计的。
现在把源码共享出来,希望大家多提意见。
using System;
namespace New919.Encrypt
{
/// <summary>
/// 改进之前的算法 , 对数字串采用四次截取压缩
/// </summary>
public class CompressStr
{
// 64进制字典
private static string[] strNumber = {"0","1","2","3","4","5","6","7","8","9","A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T",
"U","V","W","X","Y","Z","a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z",
"-","/","(",")"};
private static string strStringInString = "";
private static string strNumberInString = "";
#region 压缩方法
#endregion
#region 转换为64进制方法
public static string Compress(string p_string)
{
// try
// {
char[] charArrFirst = Separate(p_string).ToCharArray();
#region 第一次截取
string strConnectFirst = "";
string strConnectSecond = "";
string strConnectThird = "";
string strConnectFourth = "";
for(int i = charArrFirst.Length - 1; i > -1 ; i --)
{
int intTemp = (int)charArrFirst[i];
if(strConnectFirst.Length == 16)
{
// 大于16就跳出循环
break;
}
else
{
// if(intTemp >= 48 && intTemp <= 57)
// {
strConnectFirst = Convert.ToString(charArrFirst[i]) + strConnectFirst;
// }
// else
// {
// break;
// }
}
}
// 第一次取数字串之后 , 整个字符串遗留下来的部分
string strAfterFirst = p_string.Substring(0 , p_string.Length - strConnectFirst.Length);
// 第一次截取完毕 , 成功分离数字段和字母段
#endregion
#region 第二次截取 , 在第一次截取的基础上 , 在 strAfterFirst 字符串的末尾寻找数字字符
// 首先判断第一次截取的数字串的长度是不是为16位 , 如果是的 , 字母段的末尾还可能存在数字字符 , 进一步的截取
if(strConnectFirst.Length == 16)
{
// 第二次截取的数字段
char[] charArrSecond = strAfterFirst.ToCharArray();
for(int i = charArrSecond.Length - 1 ; i > -1 ; i --)
{
int intTemp = (int)charArrSecond[i];
if(strConnectSecond.Length == 16)
{
break;
}
else
{
// if(intTemp >= 48 && intTemp <= 57)
// {
strConnectSecond = Convert.ToString(charArrFirst[i]) + strConnectSecond;
// }
// else
// {
// break;
// }
}
}
// 第二次截取数字串之后 , 整个字符串遗留下来的部分
// 第二次截取成功完成
}
string strAfterSecond = strAfterFirst.Substring(0 , strAfterFirst.Length - strConnectSecond.Length);
#endregion
#region 第三次截取
if(strConnectSecond.Length == 16)
{
char[] charArrThird = strAfterSecond.ToCharArray();
for(int i = charArrThird.Length - 1 ; i > -1 ; i --)
{
int intTemp = (int)charArrThird[i];
if(strConnectThird.Length == 16)
{
break;
}
else
{
// if(intTemp >= 48 && intTemp <= 57)
// {
strConnectThird = Convert.ToString(charArrThird[i]) + strConnectThird;
// }
// else
// {
// break;
// }
}
}
}
string strAfterThird = strAfterSecond.Substring(0 , strAfterSecond.Length - strConnectThird.Length);
#endregion
#region 第四次截取
if(strConnectThird.Length == 16)
{
char[] charArrFourth = strAfterThird.ToCharArray();
for(int i = charArrFourth.Length - 1 ; i > -1 ; i --)
{
int intTemp = (int)charArrFourth[i];
if(strConnectFourth.Length == 16)
{
break;
}
else
{
// if(intTemp >= 48 && intTemp <= 57)
// {
strConnectFourth = Convert.ToString(charArrFourth[i]) + strConnectFourth;
// }
}
}
}
// string strAfterFourth = strAfterThird.Substring(0 , strAfterThird.Length - strConnectThird.Length);
#endregion
// 压缩之后的
string strAfterCompressed = To64(strConnectFourth) + To64(strConnectThird) + To64(strConnectSecond) + To64(strConnectFirst);
return strAfterCompressed;
// }
// catch(Exception ex)
// {
// MessageBox.Show(ex.Message.ToString());
// return "ads";
// }
}
/// <summary>
/// 转换为64进制方法
/// </summary>
/// <param name="strSource">源字符串(数字形式的)</param>
/// <returns>转换之后的字符串(里面的表示符由数据字典中定义的)</returns>
private static string To64(string strSource)
{
if(strSource.Trim().Length != 0)
{
// 数据字典
long longNumber = Convert.ToInt64(strSource);
long longTemp;
int intOrder = 0;
// 存储转数制的时候得到的余数
long[] longCompressLeft = new long[1000000];
do
{
longTemp = longNumber;
longNumber = longTemp / 64;
longCompressLeft[intOrder++] = longTemp % 64;
}while(longNumber != 0);
string strCompress = "";
for(int i = intOrder - 1 ; i > -1 ; i -- )
{
strCompress += strNumber[longCompressLeft[i]];
}
return strCompress;
}
else
{
return "";
}
}
#endregion
#region 把64进制转换为10进制的方法
/// <summary>
/// 把64进制转换为10进制的方法
/// </summary>
/// <param name="strSource">待转换的字符串(必须是数字形式的)</param>
/// <returns>转换完之后的十进制数</returns>
private static string To10(string strSource)
{
if(strSource.Length != 0)
{
long longAfter = 0;
string strBuf;
for(int i = 0 ; i < strSource.Length ; i ++)
{
strBuf = strSource.Substring(i , 1);
for(int j = 0 ; j < 64 ; j ++)
{
if(strNumber[j] == strBuf)
{
longAfter += j * (Convert.ToInt64(Math.Pow(64 , strSource.Length - i - 1)));
}
}
}
return Convert.ToString(longAfter);
}
else
{
// 如果传进来的字符串为空 , 说明原字符串没有到达某个指定的长度位数 , 返回空
return "";
}
}
#endregion
#region 解压缩的方法
/// <summary>
/// 解压缩的方法
/// </summary>
/// <param name="p_string">待解压缩的字符串</param>
/// <returns>解压缩之后的原数字串</returns>
public static string UnCompress(string p_string)
{
string strInciseFirst = "";
string strInciseSecond = "";
string strInciseThird = "";
string strInciseFourth = "";
// string strInciseLast = "";
string strSource = p_string;
// 把压缩后字符串的最后9位得到
strInciseFirst = Incise(strSource);
// 得到除去最后9位之后的压缩字符串
// 成功截取
string strTempFirst = p_string.Substring(0 , p_string.Length - strInciseFirst.Length);
// 把上面的这个字符串再次的从最后面截取9位
// 成功截取
strInciseSecond = Incise(strTempFirst);
string strTempSecond = strTempFirst.Substring(0 , strTempFirst.Length - strInciseSecond.Length);
strInciseThird = Incise(strTempSecond);
string strTempThird = strTempSecond.Substring(0 , strTempSecond.Length - strInciseThird.Length);
strInciseFourth = Incise(strTempThird);
// if(strInciseFourth.Length == 9)
// {
// strInciseLast = strSource.Substring(0 , strSource.Length - strInciseFirst.Length - strInciseSecond.Length - strInciseThird.Length - strInciseFourth.Length);
// }
string strCompressedNumber = To10(strInciseFourth) + To10(strInciseThird) + To10(strInciseSecond) + To10(strInciseFirst);
return ConnectString(strCompressedNumber);
}
#endregion
#region 分割字符串方法
private static string Incise(string p_string)
{
char[] charArr = p_string.ToCharArray();
string strInciseFirst = "";
if(p_string.Length >= 9)
{
for(int i = p_string.Length -1 ; i > p_string.Length - 10 ; i --)
{
strInciseFirst = charArr[i] + strInciseFirst;
}
return strInciseFirst;
}
else
{
return p_string;
}
}
#endregion
#region 分离数字和字母
/// <summary>
/// 把待压缩串中的数字和字母分离,以便分别处理
/// </summary>
/// <param name="p_string"></param>
/// <returns></returns>
private static string Separate(string p_string)
{
string strSource = p_string;
char[] charArr = p_string.ToCharArray();
for(int i = 0 ; i < charArr.Length ; i ++)
{
// 这个时候是从字符串的左边开始向右边遍历的 , 之后还原也应该按照同样的顺序加进去
// 不然就错位了 , 解压缩会失败
if((int)charArr[i] >= 48 && (int)charArr[i] <= 58)
{
// 数字
strNumberInString = strNumberInString + charArr[i];
}
else
{
// 字母
// 字母加上该字母所在的位置(转换成64进制)
strStringInString = strStringInString + charArr[i] + To64(i.ToString());
}
}
// 把字符串中的数字串部分返回
return strNumberInString;
}
#endregion
/// <summary>
/// 把字母段和数字段连接起来
/// </summary>
/// <param name="p_strNumber">数字段</param>
/// <returns>解压缩之后的字符串</returns>
private static string ConnectString(string p_strNumber)
{
// 字母串
string strStringBeforeCompress = "";
// 单个字母在原字符串中的位置(的集合)
string strLetterOrder = "";
// 先分析字母段
if(strStringInString.Length != 0)
{
char[] charArrString = strStringInString.ToCharArray();
for(int i = 0 ; i < strStringInString.Length ; i ++)
{
if(i % 2 == 0)
{
// 字母段
// 字符串的索引从0开始,偶数位都是字母
strStringBeforeCompress = strStringBeforeCompress + charArrString[i];
}
else
{
// 位置段
// 奇数位都是它之前偶数位字符的位置段
strLetterOrder = strLetterOrder + charArrString[i];
}
}
// 执行完上面的这个过程 , 已经成功的把字母段和每个字母的位置分析出来了
// 之后的要做的只是按照每个字母的位置逐个把每个字母添加到解压缩时候的数字段中去 , 就完成了解压缩的过程
// 在上面的过程中 , 还不能把位置段的每个位置转换为10进制 , 因为转换之后就不知道是应该去一位数字还是两位 , 必须在连接的循环过程中转换为10进制 , 那时候每次都只取一个字符(作为位置),但是前提是每个字母在原字符串中的位置必须小于64
}
// 如果长度等于0 , 说明压缩之前的字符串中没有字母,是纯数字串 , 不需要执行下面的过程
if(strStringBeforeCompress.Length != 0)
{
char[] charArrLetter = strStringBeforeCompress.ToCharArray();
char[] charArrLetterOrder = strLetterOrder.ToCharArray();
int intOrder = 0;
do
{
}while(intOrder > charArrLetterOrder.Length);
}
return p_strNumber;
}
}
}
- 字符串压缩的一些算法
- 字符串压缩的一些算法 .
- 字符串压缩的一些算法
- 字符串压缩的一些算法
- 字符串的压缩算法
- 字符串的一些算法
- 字符串的一些算法
- 【算法分析】特定类型字符串的压缩
- 经典算法:字符串的匹配压缩
- 关于字符串的一些算法
- 基于Zlib算法的流压缩、字符串压缩源码
- 基于Zlib算法的流压缩、字符串压缩源码
- 字符串压缩算法
- C字符串压缩算法
- 字符串压缩算法
- 字符串压缩算法
- 算法-字符串压缩
- 字符串压缩算法
- Windows 消息 机制
- centos下静态ip的配置
- C++成员函数的重载、覆盖与隐藏22
- 计算机视觉、图像处理学习资料汇总
- 增加tomcat jvm的虚拟内存
- 字符串压缩的一些算法 .
- 系统蓝屏代码全集
- tomcat配置管理用户名密码
- C++继承体系高级话题
- CheckboxTableViewer刷新table数据,复选框保持选中
- 每周推荐阅读网站低调上线
- mysql查询随机1条,5条
- windows 的事件驱动机制和消息循环
- Android(Java):android横竖屏切换