散列——分离链接法详解 (C++实现)

散列(Hash):

    其实看到名字大概就明白是个什么东西了,至少哈希函数有听说过吧。实际上就是将关键字尽可能随机的映射到一张表上的一种函数。这种数据结构并不提供其他作用,数据也不存在排列的顺序,毕竟是相对随机的。

    但是,很显然,他终归只是一张表,是有极限的,而关键字可能有非常非常多,那么就容易出现一种意外,也就是哈希碰撞(两个不同的关键字却被映射到了同一个位置),这是有可能的,单可能性大小取决于你如何实现这个函数。

    比方说,如果我的哈希函数是:hash=Key%11,假设Key并没有限制,那就有很多个关键字会有同样的hash值了。(假设现在Key是整形的数字)

    所以为了解决这种问题,一种方法便是分离链接法。

    举一个比较形象的例子吧。

    假设散列表是一个平面,他有X轴和Y轴,两个轴的坐标都必须是整数(整数只是为了好理解一些罢了)。

    比方说(1,1)。现在有两个关键字都被映射到了这个点上,那如何解决?为它增加一个Z轴。

    那么关键字便能够这样储存:(1,1,key1)和(1,1,key2)

    就像是在这个点下面挂上了两个关键字一样。它没有解决碰撞的问题,但是容许了碰撞的出现。因为即便hash值相同,关键字也同样能够被顺利储存下来。

    在C++中,这种结构是实现方法便是链表。所谓的Z轴就相当于在每一个节点下面挂上一条链表。

注:个人是跟着书上学的,所以代码大致和黑皮书相同。

    必要的声明:(因为各种声明有些绕,所以加了些许方便理解的注释和一些没必要的名词。)

struct Listnode;//表节点
typedef struct Listnode* Position;//指向表节点的“位置指针”
struct HashTbl;//哈希表
typedef struct HashTbl* HashTable;//指向哈希表的“表指针”
typedef Position List;//“位置指针”也将作为“列表指针”

struct Listnode {
	int info;//关键字
	Position Next;//指向下一个表节点的“位置指针”
};
struct HashTbl {
	int TableSize;//表尺寸
	List* TheLists;//指向“位置指针”的“列表指针”
}

建立表:

HashTable InitializeTable(int TableSize)
{
	HashTable H;
	H = new HashTbl;

	//H->TableSize = NextPrime(TableSize);
	H->TableSize = TableSize;//这要求Tablesize是大于表大小的素数
	H->TheLists = new List[TableSize];

	for (int i = 0; i < TableSize; i++)
	{
		H->TheLists[i] = new Listnode;
		H->TheLists[i]->Next = NULL;
	}
	return H;
}

流程说明:

    该函数将会返回一个“表指针”,这个指针指向我们刚刚建立的哈希表。

    首先,新建一个表指针,并为其开辟一个哈希表空间。现在这个表指针H已经指向了刚开辟的空间。

    将我们输入的“表尺寸”作为这张新表的尺寸,并根据这个尺寸,在表中开辟一个数组,这个数组的元素是“列表指针”。现在,新表中的列表指针TheLists成为了刚开辟好的数组的第一个元素——一个新的列表指针。注意,这个数组的大小会和设定好的“表尺寸一样大”。

    接下来为每一个“表指针”开辟一个新节点,并将节点的Next指针指向NULL。

    现在,TheLists中的每一个列表指针指向新节点。并且,节点中的关键字都还没有初始化。

Find函数:该表将会返回找到的关键字的“位置指针”

Position Find(HashTable H,int Key)
{
	Position P;
	List L;
	L = H->TheLists[Hash(Key, H->TableSize)];
	P = L->Next;
	while (P != NULL && P->info != Key)//strcmp  strcpy
		P = P->Next;
	return P;
}

流程说明:

    新建一个“位置指针”P和“列表指针”L

    让“列表指针”临时成为关键字本该出现的那一列。(我总觉得这种说辞有些不太简洁。)

    Hash(Key,H->TableSize)其实就是将关键字进行映射,假设结果被映射到了5,那么“列表指针”将临时成为数组中的第六个。

    “位置指针”临时成为L->Next

    之所以是临时,目的是不改变哈希表本来的结构,所以引入临时变量来操作数据。

    接下来开始遍历这一列上的每一个节点,直到找到了相同的关键字或者已经枚举尽了。

    注:因为关键字不一定都是整数,像是字符串之类的关键字,则必须用strcmp和strcpy这样的函数来比较。

插入函数:

void Insert(int Key,HashTable H)
{
	Position tmpPos, Newcell;
	List L;

	tmpPos = Find(H, Key);
	if (tmpPos == NULL)
	{
		Newcell = new Listnode;
		L = H->TheLists[Hash(Key, H->TableSize)];
		Newcell->Next = L->Next;
		Newcell->info = Key;
		L->Next = Newcell;
	}
}

流程说明:

    输入关键字和哈希表地址。

    首先,新建一个“临时位置指针”tmpPos和Newcell,以及一个“列表指针”L。

    查找这张表中是否已经存在这个关键字了,如果存在就直接跳过,否则才进行添加。

    假设本不存在这个关键字。

    为Newcell新建一个节点。

    让L指针临时成为指向相应坐标的位置指针。

    那么现在L将指向某个节点,比方说TheLists[5]。

    令Newcell节点中的Next指针成为L指针指向的节点的Next指针。

    关键字赋予。

    将L指向的节点中Next指针指向这个新节点。

    (这个新节点将被挂在最靠近“轴”的那一侧。)

最后是删除函数:

void Deletenode(int Key, HashTable H)
{
	Position tmpPos,tmpP2;
	List L;
	tmpPos = Find(H,Key);
	L = H->TheLists[Hash(Key, H->TableSize)];
	if (tmpPos != NULL&&L->info!=Key)
	{
		tmpP2 = tmpPos->Next;
		while(L->Next!=NULL)
		{
			if (L->Next->info == Key)
			{
				L->Next = tmpP2;
				delete tmpPos;
			}
			else
				L = L->Next;
		}
	}
	else if (L->info = Key)
	{
		int K;
		L->info = K;
	}
}

书上并没有给出删除数据的函数,所以这个函数是我自己现写的,不太确定是否完全正确。

但思路很简单,就是找到关键字那一排,然后把节点删掉,然后再把链表重新拼起来。如果关键字存在头节点,那就只替换掉关键字就行。

(所以最好是不要往头节点放东西,将表制成头节点不包含数据的样式最佳)

猜你喜欢

转载自blog.csdn.net/Tokameine/article/details/113781520
今日推荐