结构体定义时写数据_数据结构|标准库的映射字典、自定义线性、哈希映射

我们知道,对于数组,我们知道了首地址,通过其下标及运算,便可以随机访问其元素。其访问的时间复杂度为O(1),这是数组的优势。

但如果把数字下标变为其它类型(如字符串),是否可以建立映射关系呢?

通过定义哈希函数便可以将字符串映射为一个整数,用做数组的下标索引。

1 标准库的映射字典

C++ STL的映射字典使用红黑树做为底层的数据结构,红黑树的访问的时间复杂度为:O(logn)。哈希的时间复杂度虽然是O(1),但其是无序的,而红黑树却是有序的。

#include #include  // 红黑树映射,映射也叫字典,不是哈希映射#include using namespace std;int main(){map m;m["bill"] = 98;m["Tom"] = 67;m["Mary"] = 100;// ……cout<< m["Tom"] <

2 自定义线性映射

线性映射自然不能随机访问,需要线性查找,其时间复杂度为O(n),此实例只是为了对比后述的哈希映射。

#include #include #include using namespace std;templateclass LinearMap{public:LinearMap(int size=101) : arr(size){currentSize = 0;}void Put(const Key& k, const Value& v){arr[currentSize] = DataEntry(k,v);++currentSize;}Value Get(const Key& k) // 线性查找和访问{for(size_t i=0; i arr;int currentSize;};int main(){LinearMap lm;lm.Put("Bill",99);lm.Put("Tom",88);lm.Put("Mary",77);cout<

3 自定义哈希映射(没有处理碰撞冲突)

使用Hash的查询算法分为两步:

① 用Hash函数将被查找的键转化为数组的一个索引。
理想情况下,不同的键都能转化为不同的索引值。当然,这只是理想情况,所以我们需要面对两个或者多个键都会散列到相同的索引值的情况。
② 处理碰撞冲突的过程。

以下小实例只是演示了如何将键转化为一个数组的下标索引,并没有考虑处理碰撞冲突的情况:

#include #include #include using namespace std;templateclass HashMap // 将字符串通过哈希函数映射为数据下标{public:HashMap(int size=101) : arr(size){currentSize = 0;}void Put(const Key& k, const Value& v){int pos = myhash(k);arr[pos] = DataEntry(k,v);++currentSize;}Value Get(const Key& k){int pos = myhash(k);if(arr[pos].key == k)return arr[pos].value;elsereturn Value();}unsigned hash(const Key& k) const{unsigned int hashVal = 0;//const char* keyp = reinterpret_cast(&k);for(size_t i=0; i arr;int currentSize;};int main(){HashMap hm;cout<

哈希函数实现方法:

(1) 余数法:先估计整个哈希表中的表项目数目大小。然后用这个估计值作为除数去除每个原始值,得到商和余数。用余数作为哈希值。因为这种方法产生冲突的可能性相当大,因此任何搜索算法都应该能够判断冲突是否发生并提出取代算法。

(2) 折叠法:这种方法是针对原始值为数字时使用,将原始值分为若干部分,然后将各部分叠加,得到的最后四个数字(或者取其他位数的数字都可以)来作为哈希值。

(3) 基数转换法:当原始值是数字时,可以将原始值的数制基数转为一个不同的数字。例如,可以将十进制的原始值转为十六进制的哈希值。为了使哈希值的长度相同,可以省略高位数字。

(4) 数据重排法:这种方法只是简单的将原始值中的数据打乱排序。比如可以将第三位到第六位的数字逆序排列,然后利用重排后的数字作为哈希值。

哈希函数并不通用,比如在数据库中用能够获得很好效果的哈希函数,用在密码学或错误校验方面就未必可行。在密码学领域有几个著名的哈希函数。这些函数包括MD2、MD4以及MD5,利用散列法将数字签名转换成的哈希值称为信息摘要(message-digest),另外还有安全散列算法(SHA),这是一种标准算法,能够生成更大的(60bit)的信息摘要,有点儿类似于MD4算法。

常见的两种解决碰撞的方法:

① 拉链法(separate chaining)

一个Hash函数能够将键转换为数组索引,Hash算法的第二步是碰撞处理,也就是处理两个或多个键的Hash值相同的情况。一种直接的办法是将大小为 M 的数组中的每个元素指向一条链表,链表中的每个结点都存储了Hash值为该元素的索引的键值对。这种方法被称为“拉链法”,因此发生冲突的元素都被存储在一个链表中。

2e637f5e441ae6ba47261f86aec64ac0.png

这种方法的基本思想就是选择足够大的M,使得所有链表都尽可能短以保证高效的查找。查找分两步:首先根据Hash值找到对应的链表,然后沿着链表顺序查找对应的键。 当你能够预知所需要的符号表的大小时,该方法能够得到不错的性能。一种更可靠的方案是动态调整链表数组的大小,这样无论在符号表中有多少键值对都能保证链表较短。

② 线性探测法(linear probing)

实现哈希表的另一种方式就是用大小为 M 的数组保存 N 个键值对,其中 M > N。我们需要依靠数组中的空位解决碰撞冲突。基于这种策略的所有方法被统称为“开放地址”哈希表。

线性探测法(“开放地址”哈希表的一种实现方式):
开放地址哈希表中最简单的方法叫做“线性探测”法:当碰撞发生时(当一个键的Hash值已经被另一个不同的键占用),我们直接检测哈希表中的下一个位置(将索引值加 1)。这样的线性探测可能会产生三种结果:
a)命中,该位置的键和被查找的键相同;
b)未命中,键为空(该位置没有键)
c)继续查找,该位置的键和被查找的键不同。 我们用Hash函数找到键在数组中的索引,检查其中的键和被查找的键是否相同。如果不同则继续查找(将索引增大,到达数组结尾时折回数组的开头),直到找到该键或者遇到一个空元素。
我们习惯将检查一个数组位置是否含有被查找的键的操作称作探测。在这里它可以等价于我们一直使用的比较,不过有些探测实际上是在测试键是否为空。
“开放地址”哈希表的核心思想是与其将内存用于链表,不如将它们作为哈希表的空元素。这些空元素可以作为查找结束的标志。

4 自定义哈希映射(有处理碰撞冲突)

// 1.用数组形式建立哈希表// 2.使用链式方法解决冲突// 3.平方取中法通过字符串Hash函数求hash值(还有很多种其他的hash函数)#include #include #include #include #include  using namespace std; typedef unsigned long(*GetKeyValue)(const string& ); //该类用来处理冲突的节点templatestruct HashNode{TypeA key;TypeB value;HashNode *next;HashNode(TypeA key,TypeB value){HashNode::key = key;HashNode::value = value;next = NULL;}HashNode& operator=(const HashNode& node){key = node.key;value = node.key;next = node.next;return *this;}};  //该类是HashMap用来存放hash表templateclass HashMap{HashNode **table;unsigned long capacity;FuncType GetKeyValue;const TypeB TYPEB_NULL;public:HashMap(FuncType func,const TypeB& _null);~HashMap();TypeB Put(const HashNode& hashNode); //插入一个HashNode 返回该节点的value值TypeB GetValue(const TypeA& key); // 查找某个hashNode 其key为“key”的元素TypeB Delete(const TypeA& key); // 删除某个hashNode 其key为“key”的元素};  template //在调用的时候指定函数HashMap::HashMap(FuncType func,const TypeB& _null) : TYPEB_NULL(_null){capacity = 10000000;GetKeyValue = func;table = new HashNode*[capacity];for(unsigned i = 0;i < capacity;i++)table[i] = NULL;}  templateHashMap::~HashMap(){for(unsigned i = 0;i < capacity;i++){HashNode *currentNode = table[i];while(currentNode){HashNode *temp = currentNode;currentNode = currentNode->next;delete temp;}}delete table;}  //新增节点操作,用鏈式法解決衝突templateTypeB HashMap::Put(const HashNode& hashNode){HashNode *newNode = NULL;unsigned long index = GetKeyValue(hashNode.key);if(table[index] == NULL){table[index] = new HashNode(hashNode.key,hashNode.value);newNode = table[index];}else{newNode = table[index];while(newNode->next){newNode = newNode->next;}newNode->next = new HashNode(hashNode.key,hashNode.value);newNode = newNode->next;}return newNode->value;}  //由键值获得valuetemplateTypeB HashMap::GetValue(const TypeA& key){unsigned long index = GetKeyValue(key);if(table[index] == NULL)return TYPEB_NULL;else{HashNode *currentNode = table[index];while(currentNode){if(currentNode->key == key)return currentNode->value;currentNode = currentNode->next;}}return TYPEB_NULL;}  //由键值查找后,删除该节点,返回该删除的节点的valuetemplateTypeB HashMap::Delete(const TypeA& key){TypeB deletedNodeValue = TYPEB_NULL;unsigned long index = GetKeyValue(key);if(table[index] == NULL)return deletedNodeValue;else{HashNode *currentNode = table[index];if(currentNode->key == key){table[index] = currentNode->next;deletedNodeValue = currentNode->value;delete currentNode;return deletedNodeValue;}while(currentNode){if(currentNode->next->key == key){HashNode *temp = currentNode->next;currentNode->next = currentNode->next->next;deletedNodeValue = temp->value;delete temp;return deletedNodeValue;;}currentNode = currentNode->next;}}return deletedNodeValue;} //平方取中法//实现,取字符串中间3个字母,不足3个用0补足unsigned long GetKeyValue_1(const string& key){unsigned long keyValue = 0;int strSize = key.size();string tempStr(key);for(int i = strSize;i < 3;i++)tempStr = "0" + tempStr;//如果大于3就取中间3位if(strSize >= 3){tempStr[0] = key[strSize / 2 - 1];tempStr[1] = key[strSize / 2];tempStr[2] = key[strSize / 2 + 1];}tempStr = tempStr.substr(0,3);unsigned long num = 10000 * (unsigned long)(48);num += 100 * (unsigned long)(tempStr[1]);num += (unsigned long)(tempStr[2]);num *= num;char *numStr = new char[15];numStr = itoa(num,numStr,10) ;int strLen = strlen(numStr);tempStr = "000000";for(i = -2;i < 4;i++)tempStr[2 + i] = numStr[strLen / 2 + i];keyValue = atoi(tempStr.c_str()); delete []numStr;return keyValue;} int main(){ clock_t start = clock(); //传入一个求哈希散列值的方法GetKeyValue_1 HashMap hashMap(GetKeyValue_1,"NULL"); for(int i = 0;i < 100000;i++){ char *ckey = new char[20]; char *cvalue = new char[20]; ckey = itoa(i,ckey,10); cvalue = itoa(i,cvalue,10); string key(ckey); string value(cvalue); if(i == 67){ key = "67"; value = "hello hash No.67"; } HashNode node1(key,value); //插入该节点 hashMap.Put(node1);// cout << "index: " <

-End-

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
#include #include typedef struct node { int data; struct node *next; }node; init_hash(node **A,int n) { int i; for(i=0;idata=0; A[i]->next=NULL; } } insert_hash(node **A,int value,int n) { int key; node *p,*q; key=value%n; if(A[key]->next!=NULL) { p=A[key]->next; while(p->next!=NULL) p=p->next; q=(node *)malloc(sizeof(node)); q->data=value; q->next=NULL; p->next=q; } else { q=(node *)malloc(sizeof(node)); q->data=value; q->next=NULL; A[key]->next=q; } } int search_hash(node **A,int value,int n) { int key; node *p; key=value%n; if(A[key]->next==NULL) return 0; else { p=A[key]->next; while(p!=NULL) { if(p->data==value) return 1; } return 0; } } delete_hash(node **A,int value,int n) { int key; node *p,*q; key=value%n; p=A[key]; q=A[key]->next; while(q->data!=value) { p=q; q=q->next; } p->next=q->next; free(q); } print_hash(node **A,int n) { int i; node *p; for(i=0;inext!=NULL) { p=A[i]->next; while(p!=NULL) { printf("%d ",p->data); p=p->next; } } } printf("\n"); } main() { int i,n,value,Case; node **A; printf("输入待排序元素个数:\n"); scanf("%d",&n); A=(node **)malloc(sizeof(node*)*n); //申请一个指针型数组A[n] init_hash(A,n);//初始化数组A printf("输入hash表的值(空格键分开):\n"); for(i=0;i<n;i++) //建hash表 { scanf("%d",&value); insert_hash(A,value,n); } printf("请选择hash表的操作\n1.查询\t2.插入\t3.删除\t4.输出\n"); scanf("%d",&Case); switch(Case) { case 1: { printf("请输入要查询的元素:"); scanf("%d",&value); printf(search_hash(A,value,n)?"Success!\n":"Failure!\n"); //查询 } case 2: { printf("请输入要插入的元素:"); scanf("%d",&value); insert_hash(A,value,n); //插入 } case 3: { printf("请输入要删除的元素:"); scanf("%d",&value); printf(search_hash(A,value,n)?"删除成功!\n":"表中不存在该元素!\n"); delete_hash(A,value,n); } case 4: //输出 { printf("表中元素为:\n"); print_hash(A,n); } } }

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值