zoukankan      html  css  js  c++  java
  • Hash Table(散列表)

      这篇主要是基础的数据结构学习,写的时候才明白了书上说到的一些问题,由于该篇仅仅只是对这种数据结构进行一个理解,所以很基础,关于h(x)函数也只是简单的运用了除法散列,然后为了应对冲突,我用的是链接法。

      下面说说散列表和我对散列表的理解:

      1.什么是散列表?

      散列表就是一个关键值映射的地址组成的表,这里的地址是数组的下标...至于映射,学过数学就应该要知道这个概念并不难理解,就是自己设计一个优秀的映射(函数),一个好的映射可以很优秀的避免空间的浪费和冲突的发生,由于我比较菜233,自己设计不出并且也暂时没时间研究一个这样的函数,所以,我用的就是简单的关键值取余,写成数学函数就是 h(key) = key mod N(其中key为关键值,N为散列表的大小),显然,可以看到这个函数设计得并不好,很多关键值都可能被映射到同一个位置上去,然后就发生了冲突,并且还有一些表的空间可能一直都没有值映射到,就造成了空间的浪费。但这也是一个方案,至少让我们有了踏出第一步的勇气去探索更好的方法,或者说还能让初学者更快的、更明了的理解什么是散列表和冲突,基本上这就是什么是散列表了。

      2.什么是链接法?

      上面多次提到了冲突(也叫碰撞,collision),按照我们的映射的设计,很大可能会出现不同的两个关键值映射到了同一个地址上去,这样的行为就被称为冲突或者碰撞,所以,为了保证数据都可以保存在表中,而且不会被覆盖、丢失等,解决这个问题的一个方法就是链接法(也称拉链法,Chaining),即,将散列到同一个槽(地址)的关键值用链表的方法保存,这样说可能还是想像不出是什么样的情形,那我更细节一点说就是,把所有具有相同地址的数据用链表来保存,并把该链表的头节点保存到散列表中的对应的地址中,这样就可以避免冲突,而且分好了同类,只要输入的关键值被映射到这个地址,那么就遍历这个地址中的链表,添加到尾部(我认为链表可以换成栈或者队列,然后就实现了一个其他的数据结构,比如散列栈、散列队列233...不过不知道用处?),从这里我们也就可以知道,查找、删除和添加的最坏时间复杂度为O(N)了,N是散列表的大小,这种情况就是所有的数据全被散列到同一个地址上了...然后就是一个链表...

      好了,说得也差不多了,我再说一下昨晚一直烦恼的几个小问题,最大的问题就是开始写的时候,我的链表是没有头结点的...然后,大家知道没有头结点进行删除是有很大问题的,会删除的是删除对象的后一个节点,其实添加一个头结点就问题都解决了,但...我又感觉添加一个头结点改着麻烦,由于我太菜也没其他的解决办法,最后受不了了,睡觉。。。今天早上还是默默的添加了头结点,问题解决。。。另一个问题就是查找.....就是:假如我插入了几个具有相同地址的数据,也就是这个地址有了链表,然后我又删除其中一个以后,再查找删除的这个数据就会导致程序崩溃...因为,查找用的也是相同的散列函数,这样的话就因为这个位置保存的是地址相同的链表,删除一个,这里的链表还是存在,但没有了这个数据,我再查找删除的这个数据时,首先被映射到了这个链表头,然后会在里面找...结果已经被删除了,是不可能找得到的,但偏偏又要返回一个值...这个问题其实要解决也不难,把返回值改一下就好了,但我不想那么做...那么可能就需要再写个判断存在的函数,也感觉麻烦,于是就一直拖着了...然后就想着算了...嘛,之后再说吧,我有心情了再解决吧,还有个问题就是我本来想写一个输出散列表中的所有数据,结果接受不了一个地址一个地址的检查是否为NIL,但也没有其他好办法,嫌麻烦233。。。于是改成了输出某个地址的所有数据。。。可能还有其他地方也有些问题...暂时没发现,主要是测试得并不多~

    -------------------update 16:06:37---------------

      上面说的问题以及一些之前没注意到的BUG也都在午休以后全部解决了,应该没有问题了。

      下面给出我的实现方法:

    /**
     *     Hash table(散列表)
     *
     *		链接法(chaining)
     */
    #include <stdio.h>
    #include <stdlib.h>
    #include <conio.h>
    
    #define bool int
    #define true  1
    #define false 0
    
    #define ListSize 10
    
    typedef struct LinkedList {
    	struct LinkedList* next;		//当发生冲突时申请额外节点来存放冲突值
    	int key;		//key-value
    	int count;		//list个数
    }HashChain;			//链接法
    
    struct HashList {
    	HashChain* Node;
    }hashlist[ListSize];	//建立hashlist-LiseSize
    
    /* 定义全部函数 */
    HashChain* initHashtable();
    bool ListEmpty();
    bool ListFull();
    int GetListCount();
    static int Hashmap();
    static bool HashCollision();
    bool InsertList();
    HashChain* FindListKey();
    bool DeleteList();
    
    /**
     *	初始化hashlist的每一个位置的节点与个数
     */
    HashChain* initHashtable(void)
    {
    	HashChain* table;
    	int i;
    
    	for(i = 0; i < ListSize; i++)
    	{
    		hashlist[i].Node = NULL;
    	}
    
    	table = (HashChain*)malloc(sizeof(HashChain));
    	table->count = 0;
    	table->next = NULL;
    
    	return table;
    }
    
    /**
     *	判断hash表是否为空
     */
    bool ListEmpty(HashChain* table)
    {
    	return table->count == 0;
    }
    
    /**
     *	判断hash表是否满
     */
    bool ListFull(HashChain* table)
    {
    	return table->count == ListSize;
    }
    
    /**
     *	当前散列表中的数据个数
     */
    int GetListCount(HashChain* table)
    {
    	return table->count;
    }
    
    /**
     *	哈希函数-映射,将value映射为hash table中的一个地址
     */
    static int Hashmap(int val)
    {
    	return val % ListSize;
    }
    
    /**
     *	判断映射地址是否发生了冲突
     */
    static bool HashCollision(int val)
    {
    	if(hashlist[Hashmap(val)].Node != NULL )	//检查val的映射地址的第一个节点是否存在,存在则说明冲突。时间复杂度O(1)
    		return false;
    
    	return true;
    }
    
    /**
     *		插入数据
     */
    bool InsertList(HashChain* table, int X)
    {
    	HashChain* list, * temp;
    
    	if(ListFull(table))		//如果list满了
    	{
    		fprintf(stderr, "HashList is full!
    ");
    		return false;		//提前退出
    	}
    	else if(HashCollision(X))		//如果没冲突
    	{
    		hashlist[Hashmap(X)].Node = (HashChain*)malloc(sizeof(HashChain));		//对该地址申请一个节点,把节点保存到list中
    		temp = (HashChain*)malloc(sizeof(HashChain));
    		temp->key = X;
    		temp->next = NULL;
    		hashlist[Hashmap(X)].Node->next = temp;
    	}
    	else if(!HashCollision(X))		//如果冲突
    	{
    		list = hashlist[Hashmap(X)].Node;	//为了防止改变冲突的原地址的节点,把地址赋给一个hash结构变量,让它来完成查找插入的位置的任务
    		while(list->next != NULL)	//链表的线性查找,使得新插入的值插入到该地址中的链表的尾部
    		{
    			list = list->next;
    		}
    		temp = (HashChain*)malloc(sizeof(HashChain));
    		temp->key = X;
    		list -> next = temp;
    		temp -> next = NULL;
    	}
    	++table->count;	//计数
    
    	return true;
    }
    
    /**
     *		查找数据
     */
    HashChain* FindListKey(HashChain* table, int X)
    {
    	HashChain* list;
    
    	if(ListEmpty(table))		//如果表为空,节省一点查找的时间
    	{
    		fprintf(stderr, "HashList is empty!
    ");
    		return NULL;
    	}
    	else if(HashCollision(X))		//如果没有冲突,说明表中不存在该数据
    	{
    		fprintf(stderr, "The data does not exist!
    ");
    		return NULL;
    	}
    	else		//如果冲突,则从该地址中的链表的第一个节点开始比较查找,查找的平均时间复杂度为O(1+n),n为链表的节点个数,最坏情况为O(ListSize),最好情况为O(1)
    	{
    		list = hashlist[Hashmap(X)].Node;
    		while(list->next != NULL)
    		{
    			list = list->next;
    		}
    		return list;
    	}
    }
    
    /**
     *		删除数据
     */
    bool DeleteList(HashChain* table, int X)
    {
    	HashChain* list, * temp;
    	int flag;
    
    	if(ListEmpty(table))		//如果表为空,提前退出节省一点查找的时间
    	{
    		fprintf(stderr, "HashList is empty!
    ");
    		return false;
    	}
    	else if(HashCollision(X))		//如果没有冲突,说明表中不存在该数据
    	{
    		fprintf(stderr, "The data does not exist!
    ");
    		return false;
    	}
    	else if(!HashCollision(X))		//如果冲突有两种情况,1.删除的数据的映射地址和该地址相同,但数据不存在,2.删除的数据存在
    	{
    		list = hashlist[Hashmap(X)].Node;
    		while(list->next != NULL)
    		{
    			temp = list;
    			list = list->next;		//放在判断的前面是因为头结点没有数据
    			if(list->key != X)
    				flag = 0;
    			else
    				flag = 1;
    		}
    		if(flag)		//存在则删除
    		{
    			temp = list->next;
    			list->next = list->next->next;
    			free(temp);
    			temp = NULL;		//防止野指针
    		}
    		else
    		{
    			fprintf(stderr, "The data does not exist!
    ");
    			return false;
    		}
    	}
    	--table->count;
    
    	return true;
    }
    
    int main(void)
    {
    	HashChain* table, * PrintKey;
    	int val;
    	char c;
    
    	table = initHashtable();
    	puts("1) 添加		2) 删除");
    	puts("3) 查找		4) 关键值个数");
    	puts("5) 显示指定地址中的所有关键值");
    	puts("6) 退出");
    
    	while((c = getch()) != '6')
    	{
    		switch(c)
    		{
    		case '1' :	printf("
    添加数据:");
    					scanf("%d", &val);
    					InsertList(table, val);
    				break;
    		case '2' :	printf("
    删除数据:");
    					scanf("%d", &val);
    					DeleteList(table, val);
    				break;
    		case '3' :	printf("
    查找数据:");
    					scanf("%d", &val);
    					if(FindListKey(table, val)->key == val)
    						printf("您查找的数据为:%d
    ", FindListKey(table, val)->key);
    					else
    						printf("The data does not exist!
    ");
    				break;
    		case '4' :	printf("
    当前关键值个数为: %d
    ", GetListCount(table));
    				break;
    		case '5' :	printf("输入地址:");
    					scanf("%d", &val);
    
    					printf("显示数据:");
    					if(hashlist[val].Node != NULL && val < ListSize)
    					{
    						PrintKey = hashlist[val].Node;
    						while(PrintKey->next != NULL)
    						{
    							PrintKey = PrintKey->next;
    							printf("%d ", PrintKey->key);
    						}
    						printf("
    ");
    					}
    					else
    						printf("The address does not exist!
    ");
    				break;
    		}
    	}
    	free(table);
    	table = NULL;
    
    	return 0;
    }

      等自己学到更多姿势了,把其他解决冲突的办法和更好的映射方法再补充上。。。

  • 相关阅读:
    Python-函数
    Python-运数符
    Python-条件判断
    Python-变量
    移动端页面布局的那些事儿
    关于ie7下display:inline-block;不支持的解决方案
    SuperSlidev2.1 轮播图片和无缝滚动
    解决xmapp中Apache端口号占用问题
    npm 常用命令详解
    python函数总结
  • 原文地址:https://www.cnblogs.com/darkchii/p/7640557.html
Copyright © 2011-2022 走看看