1.问题描述。
给定一个任意长度的字符串,如何快速的查找出其中的重复字符,并把它们剔除?例如,假设给定字符串“abcdahtfhil”,可以找出重复字符为a,h,剔除这2个重复字符后的结果为“abcdhtfil”.
2.问题分析
该问题有以下几个特点:
(1)需要处理的字符串长度是未知的,可能很长,长到可能普通人无法在几天内手动解决该问题。因此必须要借助于计算机来对可能是一个很大量的数据进行处理。
(2)为了解决这个问题,我们的第一步是找到那些重复的字符,然后从总的字符数中剔除它们。
(3)由于初始数据可能是海量的,我们如果能对原始数据进行压缩,可能对解决问题比较有帮助。这就相当于,我们先从总体上把握数据的概要,然后深入分析,各个击破。因为,显然,即使借助于计算机编程,我们也要考虑CPU效率的问题,因此算法的选择比较重要。
3.解决思路
对于一个熟悉哈希算法的程序员来说,虽然可能不能马上就能把这道题的解答细节对答如流,但思路立马就应该蹦入脑海。为什么这么说呢?哈希算法的思想与这道题有何相通之处?我们从先来复习下哈希算法:
Hash,一般翻译做“散列”,也有直接音译为“哈希”的,就是把任意长度的输入(又叫做预映射, pre-image),通过散列算法,变换成固定长度的输出,该输出就是散列值。这种转换是一种压缩映射,也就是,散列值的空间通常远小于输入的空间,不同的输入可能会散列成相同的输出(我们要解决问题,就是要找出那些“相同的”,并剔除它们,比较相似),而不可能从散列值来唯一的确定输入值。简单的说就是一种将任意长度的消息压缩到某一固定长度的消息摘要的函数。
哈希函数的基本概念
* 若结构中存在和关键字K相等的记录,则必定在f(K)的存储位置上。由此,不需比较便可直接取得所查记录。称这个对应关系f为散列函数(Hash function),按这个思想建立的表为散列表。 ()
* 对不同的关键字可能得到同一散列地址,即key1≠key2,而f(key1)=f(key2),这种现象称冲突。具有相同函数值的关键字对该散列函数来说称做同义词。综上所述,根据散列函数H(key)和处理冲突的方法将一组关键字映象到一个有限的连续的地址集(区间)上,并以关键字在地址集中的“象” 作为记录在表中的存储位置,这种表便称为散列表,这一映象过程称为散列造表或散列,所得的存储位置称散列地址。
* 若对于关键字集合中的任一个关键字,经散列函数映象到地址集合中任何一个地址的概率是相等的,则称此类散列函数为均匀散列函数(Uniform Hash function),这就是使关键字经过散列函数得到一个“随机的地址”,从而减少冲突。
读者读到这里,如果还没有想到用哈希函数来如何解决本文开头提到的问题,就应该要反思了。不过也没关系,任何技术的提高,都是通过学习、练习、获得经验总结和最后熟练使用的过程。这一部分读者只要加强学习、练习和总结,一定能磨练出深功夫。下面看看我是如何一步步解决这个问题的。
(1)既然我们看出,本题待解决的问题,与哈希思想有某种概念上的联系,那么我们首先就往哈希算法这边探索和思考下,看看能否找到我们解决问题的金钥匙;
(2)首先,我试图对可能无限长的字符串数据,进行一个压缩,即获取一个摘要式的、可公式化的印象。很显然,要想做到这一步,唯一的办法是找到一个哈希函数,因为只有它可以对数据进行压缩存储。前面提到,“若结构中存在和关键字K相等的记录,则必定在f(K)的存储位置上”。有什么办法能往这句话靠一靠呢?我们知道,每一个字符的存储,在计算机中,都以数字的形式存储的,即ASCII码值。那么,对于每一个相同的字符,都必定对应同一个ASCII值。我这里就选取哈希函数 f(K) = K的ASCII值。例如f(A) = 65, f(a)=97等。对于字符串来讲,f(k)的最大值为128,因为ASCII使用7位二进制数来表示所有的大写和小写字母,数字0到9、标点符号,以及在美式英语中使用的特殊控制字符。很好,我们现在把“任意长度的”数据印象压缩到了最大128.
(3)建立一个足够大的数组,数组的元素用来存储所有可能出现的字符的出现次数。这个数组,称之为“哈希表”。
下面是我写的一个示例C代码,vs2008调试通过。
// Find RepeatString.cpp : Defines the entry point for the console application.
//
#include
#include "stdafx.h"
#include
#define MAX 128
#define MAX_OUT_LEN 1024
int main(void)
{
int i;
char HASH[MAX] = {0};
char* szStr = "abcdaaccbbdds";
//int StrSize = strlen(szStr) +1;
char FilterStr[MAX ] = {0};
char Output[MAX_OUT_LEN] = {0};
int NewIndex;
int FilterIndex = 0;
/*查找重复出现的字符*/
for( i=0,NewIndex = 0;i< strlen(szStr); i++ )
{
/*对单次出现的字符进行保存,并重新排序*/
if ( HASH[ szStr[i] ] == 0 )
{
HASH[szStr[i] ] = 1;
Output[NewIndex]= szStr[i];
NewIndex ++;
}
else
{
HASH[ szStr[i] ] ++;
/*打印重复出现的字符*/
printf("Char %c is already existed....\n", szStr[i] );
if ( HASH[ szStr[i] ] == 2 )
{
FilterStr[ FilterIndex ] = szStr[i];
FilterIndex ++;
}
else
{
continue;
}
getchar();
}
}
/*打印出我们需要过滤的字符组成的字符串*/
printf("### %s Is Repeated in the original string.\n ", FilterStr);
getchar();
/*打印出我们需要过滤后字符重新排序后组成的字符串*/
printf(" ###OutPut string %s .\n ", Output);
getchar();
return 0;
}
运行程序,结果如下图所示: