[Coding Test] Coding Tips 7 : hash

삼성B형을 위한 코딩 팁 시리즈

Python 개발자의 PRO 취득을 위한 C++ 공부 여정

Hash

해시 알고리즘은 인덱스(Index, 색인)를 사용하는 검색 알고리즘으로 검색 성능이 O(1) 이기 때문에 데이터의 양과 관계없이 빠른 성능이 장점.

unordered_map(Hash)

템플릿 구성

template
< class Key,                                                               // unordered_map::key_type
    class T,                                                                  // unordered_map::mapped_type
    class Hash = hash<Key>,                                     // unordered_map::hasher
    class Pred = equal_to<Key>,                               // unordered_map::key_equal
    class Alloc = allocator<pair<const Key, T>>      // unordered_map::allocator_type
> class unordered_map;

Custom Hash

기존에 작성된 자료형이 아닌 개별 자료구조는 아래와 같은 방법으로 활용할 수 있다.

/***************************************************************************
	default : unordered_map<Data, value, hash<Data>, equal_to<Data>> hMap;
****************************************************************************/

struct Data {
	int x, y;
	bool operator==(const Data& r) const {
		return x == r.x && y == r.y;
	}
};

struct MyHash {
	// size_t : unsigned long long
	// * N : N 진법
	size_t operator()(const Data& key) const {
		return (size_t)key.x * N + key.y;
	}
};

unordered_map<Data, int, MyHash> hMap;

좌표해시

특정 좌표의 id 값, 특정 좌표의 이름(문자열) 등을 관리할 때 다음과 같은 방법으로 활용 가능하다.

좌표 값이 배열의 범위를 넘어가는 경우 편리하게 사용할 수 있다.

unordered_map<int, unordered_map<int, string>> hMap;
int r = 1000000;
int c = 1257569;
hMap[r][c] = string("hello");

중복 데이터 처리

일반적으로 Key + 여러 데이터 는 unordered_multimap 을 사용해야 한다.

하지만 unordered_map 에서 사용할 수 있는 [ ] 연산자를 사용할 수 없으며, data 탐색을 위해 range 검색을 수행해야 하는 번거로움이 있다.

따라서 저장하려는 값을 vector<> 에 감싸 넣는 방법을 활용한다.

// unordered_map<string, int> → unordered_map<string, vector<int>>

만약 중복된 Key 에 중복 없이, 정렬된 상태로 저장하고 싶다면 vector 대신 set 을 사용한다.

직접 구현

아래는 Hash 를 직접 구현해 사용하는 방법이다. unordered_map<string, int> 로 동일한 기능을 더 편리하게 사용할 수 있지만, 성능으로는 직접 구현해 사용하는 방법이 압도적인 성능차를 보이는 경우가 많다는 것을 알아두자.

#define TableSize 50000
#define MaxKeySize 11

struct Node {
	int data;
	char key[MaxKeySize];
	Node* child;
	Node* parent;
};

void mstrcpy(char* dest, const char* src) {
	int idx = 0;
	while ((dest[idx] = src[idx]) != '\0') ++idx;
}

int mstrcmp(char* str1, char* str2) {
	int idx = 0;
	while (str1[idx] != '\0' && str1[idx] == str2[idx]) ++idx;
	return str1[idx] == str2[idx] ? true : false;
}

struct Hash {
	int hashNum;
	Node node[100'005];
	Node* hashTable[TableSize];

	void init() {
		hashNum = 0;
		for (int i = 0; i < 100'005; i++) {
			node[i].child = nullptr;
			node[i].parent = nullptr;
		}
		for (int i = 0; i < TableSize; i++)
			hashTable[i] = nullptr;
	}

	unsigned long getHash(char* str) {
		unsigned long hash = 5381;
		int c;
		while (c = *str++)
			hash = (hash << 5) + hash + c;
		return hash % TableSize;
	}

	void add(char* key, int data) {
		Node* n = &node[hashNum++];
		unsigned long hKey = getHash(key);

		// Key, Value 삽입
		mstrcpy(n->key, key);
		n->data = data;

		// 충돌 조치
		Node* prev = hashTable[hKey];
		n->child = prev;
		if(prev != nullptr)
			prev->parent = n;
		hashTable[hKey] = n;
	}

	int get(char* key) {
		unsigned long hKey = getHash(key);
		for (Node* n = hashTable[hKey]; n != nullptr; n = n->child) {
			// Key 값 일치하면 반환
			if (mstrcmp(n->key, key)) return n->data;
		}
		return -1;
	}

	int remove(char* key) {
		unsigned long hKey = getHash(key);
		int data = -1;
		for (Node* n = hashTable[hKey]; n != nullptr; n = n->child) {
			// Key 값 일치하지 않으면 Pass
			if (!mstrcmp(n->key, key)) continue;

			// 삭제하는 데이터
			data = n->data;
			
			if (n->parent == nullptr) {
				// 삭제하는 데이터의 parent 노드가 존재하지 않는 경우
				hashTable[hKey] = n->child;
				if (n->child != nullptr) n->child->parent = nullptr;
				break;
			}
			else {
				// 삭제하는 데이터의 parent 노드가 존재하는 경우
				n->parent->child = n->child;
				if (n->child != nullptr) {
					n->child->parent = n->parent;
				}
				break;
			}
		}
		return data;
	}
};

Hash cHash;

Updated:

Leave a comment