哈希表hashTable_哈希表在字符串处理中的应用

目标:学会应用哈希表以统计的思维对字符串进行处理!学会快速分析出算法的时间和空间复杂度!

 

目录:

1.哈希表基础知识复习

2.get第一次只出现一次的字符

3.从第一个字符串中删除所有在第二个字符串中出现过的字符

4.删除字符串中所有重复出现的字符(结果串不含重复字符)

5.判断两个英文单词是否为变位词

6.get字符流中第一个中出现一次的字符

 

 

  1. 数据结构:哈希表:冲突处理有哪几种方法?其中链地址法有什么缺点吗?怎么改进?

链地址法缺点:1.时间复杂度较其他的高(因为可能要遍历链表)2.有的关键字得到的哈希地址频次交高,则该节点对于的链表就非常长?怎么改进?(再哈希法!)

首先你要知道哈希表是干嘛的啊?!

哈希表是一种查找表!哈希函数、地址冲突、平均查找长度、装填因子等概念!

一旦哈希表建立好了,一来一个关键字,我用哈希函数一算就可与算出其在表中的地址,就可与直接去该地址处看这个关键字对应的元素存在吗?如果存在就找到了,如果不存在则查找失败!

哈希函数表的定义:

根据设定的哈希函数H(key)和一种冲突处理方法将一组关键字映像到一个有限连续的地址集(区间)上,并以关键字在地址集中的“像”作为记录在表中的存储位置,这种表便是哈希表,这一映像过程称为哈希造表或散列,所得存储位置成为哈希地址或散列地址

    1. 哈希函数的构造方法
  1. 直接定址法
  2. 数字分析法
  3. 平方取中法
  4. 折叠法
  5. 除留余数法
  6. 随机数法

选择哈希函数要考虑的因素:

  1. 哈希函数的复杂度(计算哈希函数所需要时间)
  2. 关键字的长度
  3. 哈希表的大小
  4. 关键字的分布情况
  5. 记录的查找频率

2.哈希表是一个压缩映像,所有会出现冲突。

假设哈希表的地址集为0~(n-1),冲突是指由关键字得到的哈希地址为j(0<=k<=n-1)的位置上已经存在有记录,则“处理冲突“就是为该关键字的记录找到另外一个”空“的哈希地址。在处理冲突的过程中可能得到一个地址序列Hi,i=1,2,...,k,(Hi属于[0,n-1]),依次类推,直到Hk不发生冲突为止,则Hk为记录在表中的地址。

处理冲突的方法:

1)开发定址法

Hi=(H(key)+di)MODm i=1,2,...,k(k<=m-1)

H()为哈希函数,k为哈希表长度,di为增量序列:

di=1,2,3,...,m-1,成为线性探测再散列法

di=12,-12,22,...,+-k2(k<=m/2)称为二次探测再散列法

di=伪随机数序列,则称为随机探测再散列法。

线性探测再散列法容易引起“二次聚集“现象。

  2)再哈希法

                     Hi=RHi(key) i=1,2,...,k

              RHi均是不同的哈希函数。

 3)链地址法

       将所有关键字为同义词的记录(具有相同函数值的关键字对该哈希函数来说称为同义词synonym)存储在同一线性链表中。假设某个哈希函数产生的哈希地址在区间[0,m-1]上,则设立一个指针型向量

Chain ChainHash[m];

其每个分量的初始状态都为空指针。凡哈希地址为i的记录都插入到头指针为ChainHash[i]的链表中,在链表中插入的位置要保证同义词在同一线性列表中按关键字有序(以便进行二分查找)

 4)建立公共溢出区

        HashTable[0..m-1]为基本表

        OverTable[0...v]为溢出表

参考

[1] 严蔚敏, 吴伟民. 数据结构(C语言版)[J]. 计算机教育, 2012, No.168(12):62-62.

[2]STL的map、hash_map的使用https://blog.csdn.net/q_l_s/article/details/52416583

例如:每本书都有一个ISBN编号!但我们的小图书馆只有10000本书,就可以用哈希函数对其做一压缩映像

 

2.哈希表应该的一个例子:《剑指offer》第5章优化时间和空间效率 

面试题50:求一字符串中只出现一次的字符

/*
《剑指offer》求一个字符串中第一个只出现一次的字符:
如输入"abaccdeff"则应该输出:'b'
*/
 

//法2:哈希表映射法:时间复杂度O(n),空间复杂度O(1)
//字符出现了多少次?:统计的思维(很优雅)
/*
我们定义哈希表的键是字符的ASCII值,值是该字符出现的次数。
只需扫描字符串两次:
第一次是统计每个字符出现的次数。
第二次是将第一个只出现一次的字符输出。
处理特殊情况:没有只出现一次的字符,每个字符都出现了两次或两次以上。
*/

char getFirstNotRepeatingChar(string str)
{
	if (str.size() == 0)
		return '\0';
	//ASCII码总共有256个
	const int hashTableSiez = 256;
	unsigned int* hashTable = new unsigned int[hashTableSiez]();//数组元素全初始化为0

	for (int i = 0; i < str.size(); i++)
	{
		hashTable[str[i]]++;
	}
	
	for (int i = 0; i < str.size(); i++)
	{
		if (hashTable[str[i]] == 1)
			return str[i];
	}
	//处理特殊情况:没有只出现一次的字符,每个字符都出现了两次或两次以上。
	return '\0';
}

另外附上暴力搜索法:苦逼的上下而求索啊!(思路简单,但难写更难看!)

/*
最直观的暴力扫描法:对于每一个字符,分别在其前、其后的子串中查找该字符,如都没有出现,则为只出现一次的字符,找到这样的第一个字串!
*/

char getFirstNotRepeatingCharBruteForce(string str)
{
	if (str.size() == 0)
		return '\0';
	string::size_type pos_after;
	string::size_type pos_before;
	string substr = str;
	string substr_after;
	string substr_before;
	for (int i = 0; i < str.size(); i++)
	{
		/*
		size_type find(_Elem _Ch, size_type _Off = 0) const
		{	// look for _Ch at or after _Off
		return (find((const _Elem *)&_Ch, _Off, 1));
		}
		注意是at or after _Off!!
		*/
		if (i == 0)
		{
			substr = str.substr(i + 1);
			pos_after = substr.find(str[i]);
			if (string::npos == pos_after)
			{//没找到就输出该字符
				return str[i];
			}
			else
			{
				continue;
			}
		}
		else if (i > 0 && i < str.size() - 1)
		{
			/*
			_Myt substr(size_type _Off = 0, size_type _Count = npos) const
			{	// return [_Off, _Off + _Count) as new string
				return (_Myt(*this, _Off, _Count, get_allocator()));
			}
			*/
			//在str[i]之前找
			substr_before = str.substr(0, i);
			pos_before = substr_before.find(str[i]);
			//在str[i]之后找
			substr_after = str.substr(i + 1, string::npos);
			pos_after = substr_after.find(str[i]);

			if (pos_before == string::npos&&pos_after == string::npos)
			{
				return str[i];
			}
			else
			{
				continue;
			}

		}
		else//对于最后一个字符:
		{
			substr_before = str.substr(0, i);
			pos_before = substr_before.find(str[i]);
			if (pos_before == string::npos)
			{
				return str[i];
			}
			else
			{
				return '\0';
			}
		}
	}
}

下面是几个测试例子:

#include "stdafx.h"
#include<iostream>
#include<string>
#include<vector>
#include<algorithm>

using namespace std;

int _tmain(int argc, _TCHAR* argv[])
{
	string str = "abaccdeff";
	string str2 = "aabbccdde";
	string str3 = "";
	string str4 = "aabbcc";

	char firstNotRepeatChat = getFirstNotRepeatingCharBruteForce(str4);
	cout << "First Not Repeating Char in " << str4 << " is " << firstNotRepeatChat << endl;

	system("pause");
	return 0;
}

3.从第一个字符串中删除所有在第二个字符串中出现过的字符

/*《剑指offer》50题附加1:P246定义一个函数,
输入两个字符串,从第一个字符串中删除在第二个字符串中
出现过的所有字符。
建立第二个字符串的哈希表,扫描第一个字符串:
能在O(1)的时间内判断出该字符是否在第二个字符串中出现过!
*/

/*《剑指offer》50题附加1:P246定义一个函数,
输入两个字符串,从第一个字符串中删除在第二个字符串中
出现过的所有字符。
建立第二个字符串的哈希表,扫描第一个字符串:
能在O(1)的时间内判断出该字符是否在第二个字符串中出现过!
*/
string deleteAwhichInB(string A, string B)
{
	if (A.size() == 0)
		return A;
	if (B.size() == 0)
		return A;
	//ASCII码总共有256个
	const int hashTableSiez = 256;
	unsigned int* hashTable = new unsigned int[hashTableSiez]();//数组元素全初始化为0

	string tempString = "";

	//用哈希表来统计B中出现过的字符
	for (int i = 0; i < B.size(); i++)
	{
		hashTable[B[i]]++;
	}

	for (int i = 0; i < A.size(); i++)
	{
		if (hashTable[A[i]] == 0)
		{
			//用原位删除的话改变了循环边界,有bug!
			//A=A.erase(i,1);//删除A中下标为i的字符,[i,i+1),你一删,A.size()就变了!
			//还是另设一个空字符串,将没有重复出现过的字符append上去!
			/*
			STL对字符串的操作要么用size_type类型参数的函数,要么用迭代器类型的参数!
				_Myt& erase(size_type _Off, size_type _Count)
				{// erase elements [_Off, _Off + _Count)
				}

				iterator erase(const_iterator _Where)
				{	// erase element at _Where
				}
			*/
			tempString = tempString.append(1, A[i]);
		}
		else
		{
			continue;
		}
	}

	delete[] hashTable;
	return tempString;
}

 

4.删除字符串中所有重复出现的字符(结果串不含重复字符)
/*
《剑指offer》50题附加2:P246
定义一个函数:删除字符串中重复出现的字符。(即结果串无重复字符)
如输入"google",则输出"gole"

思路:扫描每一个字符,如果它在前面出现过,就将其删除!
*/


string deleteRepeatedChar(string str)
{
	if (str.size() == 0)
		return str;
	//ASCII码总共有256个
	const int hashTableSiez = 256;
	unsigned int* hashTable = new unsigned int[hashTableSiez]();//数组元素全初始化为0

	string strTemp = "";

	//用哈希表来统计B中出现过的字符
	for (int i = 0; i < str.size(); i++)
	{
		if (0 == hashTable[str[i]])
		{//将不重复的字符追加在strTemp后面!然后设置哈希表对应的值为1
			strTemp.append(1,str[i]);
			/*
			_Myt& append(size_type _Count, _Elem _Ch)
			{	// append _Count * _Ch
			}
			*/
			hashTable[str[i]] = 1;
		}
		else if (1 == hashTable[str[i]])
		{//用erase的话原地删除A中下标为i的字符,[i,i+1),你这里一删除了字符,
			/*str.size()大小就变!循环就有问题啊!哥!*/
			continue;
		}

	}
	delete[] hashTable;
	return strTemp;
}

 

5.判断两个英文单词是否为变位词
/*
《剑指offer》50题附加3:P246
写一个函数判断两个单词是否为变位词(Anagram)?
变位词(Anagram):
silent与listen
evil 与 live

思路:
用数组实现一个哈希表:
扫描第一个字符串,统计串中字符出现的次数。
再扫描第二个字符串,将出现的字符对应的哈希表的位置-1;
最后扫描哈希表,如果哈希表的所有元素都为0,怎这两个单词为变位词。

*/


bool isAnagram(string str1, string str2)
{
	if (str1.size() == 0||str2.size()==0)
		return false;
	const int hashTableSiez = 256;
	unsigned int* hashTable = new unsigned int[hashTableSiez]();//数组元素全初始化为0

	for (int i = 0; i < str1.size(); i++)
	{
		hashTable[str1[i]]++;
	}
	for (int i = 0; i < str2.size(); i++)
	{
		hashTable[str2[i]]--;
	}
	for (int i = 0; i < 256; i++)
	{
		if (hashTable[i] != 0)
		{
			return false;
		}
		else
		{
			continue;
		}
	}
	return true;

}

 

6.get字符流中第一个中出现一次的字符


/*
offer50_2:求字符流中第一个只出现一次的字符。
当从字符流中只读出"go"两个字符时,第一个只出现一次的字符是g
当从字符流中读入前6个字符"google"时,第一个只出现一次的字符是l

定义一个哈希表的数据容器来保存字符在字符流中的位置:
当第一个字符第一次从字符流中读入时,把它在字符流中的位置保存到数据容器里。
当以后再次读入该字符时,更新数据容器里的值(设为特殊值:-2),表面它在前面出现过。
*/


class CharStaistics{
private:
	/*
	occurrence[i]=-1;//这个字符目前没有出现
	occurrence[i]=-2;//这个字符出现了多次
	occurrence[i]=index;//这个字符只出现了一次,且存了其出现时的流下标
	*/
	int occurrence[256];
	int index;
	std::string str;

public:
	CharStaistics() :index(0)
	{
		str = "";
		for (int i = 0; i < 256; i++)
		{
			occurrence[i] = -1;
		}
	}
	void Append(char ch)
	{
		if (occurrence[ch] == -1)
		{
			occurrence[ch] = index;
		}
		else if (occurrence[ch] >= 0)
		{
			occurrence[ch] = -2;
		}
		str.append(1,ch);//把ch字符接到流str尾部
		index++;
	}
	char getFirstAppearingOnceCahr()
	{
		char ch = '\0';
		//找到哈希表中>=0的最下值对应的键的ASCII即该字符
		int minIndex = std::numeric_limits<int>::max();//返回编译器允许的最大int值。
		for (int i = 0; i < 256; i++)
		{
			if(occurrence[i] >= 0 && occurrence[i] < minIndex)
			{
				ch = (char)i;
				minIndex = occurrence[i];
			}
		}
		return ch;
	}
};

以上3~6题的测试例子:

#include<iostream>
#include<string>

using namespace std;


int _tmain(int argc, _TCHAR* argv[])
{
	string str1 = "We are students";
	string str2 = "aeiou";
	string ans = deleteAwhichInB(str1, str2);
	cout << "delete "<<str1<<"\'s char which in "<<str2 <<":"<< ans << endl;

	string repeatedChar = "google";
	string notRepeatedChar = deleteRepeatedChar(repeatedChar);
	cout << "notRepeatedChar=" << notRepeatedChar << endl;//notRepeatedChar=gole

	string anagram1 = "silent";
	string anagram2 = "listen";
	bool isanagram = isAnagram(anagram1, anagram2);
	cout << "isanagram=" << isanagram << endl;

	string stringStream = "google";
	CharStaistics charSta;
	for (int i = 0; i < stringStream.size(); i++)
	{
		charSta.Append(stringStream[i]);
	}
	char FirstAppearingOnceChar = charSta.getFirstAppearingOnceCahr();
	cout << "FirstAppearingOnceChar=" << FirstAppearingOnceChar << endl;

	system("pause");
	return 0;
}

 

 

哈希表  哈希表应用 剑指offer

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值