浅谈哈希表

浅谈哈希表

定义

散列表,通过散列函数把键值映射到散列表中的一个位置,根据关键字可以直接访问内存存储位置的数据结构。

实现

散列函数

  1. 直接寻址法:一次线性函数映射,若冲突则向下一位移动

  2. 平方取中法:键值取平方,再取中间的几位数作哈希地址

  3. 除数取余法 :关键字被小于等于表长度的数除取得余数作散列地址。一般取作素数,可使散列表均匀。

冲突解决

开放定址法(线性探测再散列,二次探测再散列),再散列法,拉链法。

装填因子

填入表中的元素个数/散列表的长度

以下实现由除数取余法+拉链法的散列表

节点定义

struct Node {  //节点结构体
	Node(int i) :key(i), next(nullptr) {}
	int key;
	Node* next;  //维护映射到统一的地址的key
};

哈希表类

class HashTable {  //哈希表
public:
	HashTable(int);
	~HashTable() {
		clear();
		delete[] tableList;
	}
	void insert(int);
	void remove(int);
	Node* find(int);
	void clear();
	unsigned int hash(int);
	void display();
private:
	int tableSize;
	Node** tableList;
};

构造

哈希函数的二维Node数组,直观来说,第一维是纵向的,第二维是横向的。哈希表的建立,就是在一维上指定素数大小的Node数组,再初始化二维的数组为nullptr

HashTable::HashTable(int n) {
	tableSize = setSize(n);  //指定哈希表长度为素数
	tableList = new Node * [tableSize];  //哈希表维护一个Node链表
	for (int i = 0; i < tableSize; ++i)  //初始化Node链表
		tableList[i] = nullptr;
}

插入

首先是对键值哈希,然后找到一维位置,判断哈希表中是否已有该记录,若无,则采用前插法,将新节点置顶。(若后插法需要while循环到nullptr的节点处)

void HashTable::insert(int k) {
	unsigned int index = hash(k);
	Node* pos = find(k);  //寻找插值位置

	if (pos == nullptr) {
		Node* node = new Node(k);
		node->next = tableList[index];  //插在前面
		tableList[index] = node;  //移动头指针
	}
}

另外,允许重复键值的改动是

	while (pos != nullptr)  //将	if (pos == nullptr) 条件换成这个循环体
		pos = pos->next;

删除

  1. 若删除的是置顶元素,则将对应一维Node指针指向下一个元素,delete置顶元素
  2. 若删除元素在中间或末尾,临时对象保存删除节点,将指针越过删除节点,释放临时节点。
  3. 若哈希表中无此元素,无操作。
void HashTable::remove(int k) {
	unsigned int index = hash(k);
	Node* node = tableList[index];

	if (node != nullptr) {
		if (node->key == k) {  //case1:头节点元素
			tableList[index] = node->next;
			delete node;
		}
		else {
			while (node != nullptr && node->next != nullptr) {  //case2:中间或结尾元素
				if (node->next->key == k) {  //case3:数据不在哈希表中,无操作
					Node* temp = node->next;
					node->next = temp->next;
					delete temp;
				}
				node = node->next;
			}  //end while
		}  //end else
	}  //end if 
}

源码

#include<iostream>
#include <random>
#include<iomanip>
using namespace std;

struct Node {  //节点结构体
	Node(int i) :key(i), next(nullptr) {}
	int key;
	Node* next;  //维护映射到统一的地址的key
};

class HashTable {  //哈希表
public:
	HashTable(int);
	~HashTable() {
		clear();
		delete[] tableList;
	}
	void insert(int);
	void remove(int);
	Node* find(int);
	void clear();
	unsigned int hash(int);
	void display();
private:
	int tableSize;
	Node** tableList;
};

bool isPrime(int number)
{
	if (number == 1)  //1不是素数
		return false;
	if (number == 2)  //2是素数
		return true;
	for (int i = 2; i * i <= number; i++)
		if (number % i == 0)
			return false;  //找到因子就返回false
	return true;
}

unsigned int setSize(int n) {  //以大于等于N的素数为哈希表的大小
	while (true) {
		if (isPrime(n++))  //选择素数可以减少冲突
			return --n;
	}
}

HashTable::HashTable(int n) {
	tableSize = setSize(n);  //指定哈希表长度为素数
	tableList = new Node * [tableSize];  //哈希表维护一个Node链表
	for (int i = 0; i < tableSize; ++i)  //初始化Node链表
		tableList[i] = nullptr;
}

unsigned int HashTable::hash(int key) {  //除数取余法
	return key % tableSize;
}

//unsigned int HashTable::hash(const string& key){  //string hash
//	unsigned int hashVal = 0;
//	for (char ch : key)
//		hashVal = 37 * hashVal + ch;  //key[0]+key[1]
//
//	return hashVal % tableSize;
//
//}

Node* HashTable::find(int k) {  //查找时间=计算散列函数值所需要的常数时间+遍历链表所用的时间
	unsigned int index = hash(k);
	Node* curList = tableList[index];
	while (curList != nullptr && curList->key != k)
		curList = curList->next;
	return curList;
}

void HashTable::insert(int k) {
	unsigned int index = hash(k);
	Node* pos = find(k);  //寻找插值位置

	//while (pos != nullptr)
	//	pos = pos->next;
	if (pos == nullptr) {
		Node* node = new Node(k);
		node->next = tableList[index];  //插在前面
		tableList[index] = node;  //移动头指针
	}
}

void HashTable::remove(int k) {
	unsigned int index = hash(k);
	Node* node = tableList[index];

	if (node != nullptr) {
		if (node->key == k) {  //case1:头节点元素
			tableList[index] = node->next;
			delete node;
		}
		else {
			while (node != nullptr && node->next != nullptr) {  //case2:中间或结尾元素
				if (node->next->key == k) {  //case3:数据不在哈希表中,无操作
					Node* temp = node->next;
					node->next = temp->next;
					delete temp;
				}
				node = node->next;
			}  //end while
		}  //end else
	}  //end if 
}

void HashTable::clear() {
	Node* node = nullptr;
	for (int i = 0; i < tableSize; ++i) {
		node = tableList[i];
		while (node != nullptr) {
			Node* temp = node;
			node = node->next;
			delete temp;
		}  //end while
	}  //end for
	tableSize = 0;
}

void HashTable::display() {

	if (tableSize == 0) {
		cout << "Hash table is empty!" << endl;
		return;
	}

	cout << "Hash table:" << endl;
	for (int i = 0; i < tableSize; i++)
	{
		cout << i << " -> ";
		if (tableList[i] != nullptr) {
			Node* node = tableList[i];
			while (node != nullptr)
			{
				cout << node->key << " -> ";
				node = node->next;
			}
			cout << endl;
		}  //end if
		else
			cout << endl;
	}  //end for
}
int main()
{
	HashTable* hashTable = new HashTable(7);

	random_device rd;
	mt19937 mt(rd());
	for (int i = 0; i < 13; i++)
		hashTable->insert(mt() % 20);

	hashTable->display();

	if (hashTable->find(4) != nullptr)
		cout << "element  4 exits!" << endl;

	hashTable->clear();
	hashTable->display();
}

测试结果

posted @ 2020-08-04 16:58  kite97  阅读(142)  评论(0编辑  收藏  举报