// Hash.h
#pragma once
#include <string>
class CHash
{
public:
CHash()
{
}
~CHash()
{
}
private:
unsigned __int64 m_HashKey;
public:
template <typename KEY>
unsigned __int64 GetHash(KEY key)
{
m_HashKey = 0;
size_t Length = sizeof(key);
// unsigned __int64 NewKey = (unsigned __int64)key;
for (int i = 0; i < Length; ++i)
{
unsigned char data = key & 0xff;
m_HashKey += data;
key = key >> 8;
}
return m_HashKey;
}
template <>
unsigned __int64 GetHash(const char* key)
{
m_HashKey = 0;
size_t Length = strlen(key);
for (size_t i = 0; i < Length; ++i)
{
if (i % 2 == 0)
m_HashKey += (unsigned __int64)key[i];
else
m_HashKey *= (unsigned __int64)key[i];
}
return m_HashKey;
}
template<>
unsigned __int64 GetHash(std::string key)
{
m_HashKey = 0;
size_t Length = key.length();
for (size_t i = 0; i < Length; ++i)
{
if (i % 2 == 0)
m_HashKey += (unsigned __int64)key[i];
else
m_HashKey *= (unsigned __int64)key[i];
}
return m_HashKey;
}
};
// HashTable.h
#pragma once
#include "Hash.h"
#include "List.h"
template <typename KEY, typename VALUE>
class CHashNode
{
template <typename KEY, typename VALUE, int HASHSIZE>
friend class CHashTable;
private:
CHashNode()
{
}
~CHashNode()
{
typename CList<PNODE>::iterator iter = m_Chain.begin();
typename CList<PNODE>::iterator iterEnd = m_Chain.end();
for (; iter != iterEnd; ++iter)
{
delete *iter;
}
}
private:
typedef CHashNode<KEY, VALUE>* PNODE;
typedef CHashNode<KEY, VALUE> NODE;
public:
KEY first;
VALUE second;
private:
CList<PNODE> m_Chain;
public:
// chain에 노드를 추가해주는 함수
void Add(const KEY& key, const VALUE& value)
{
PNODE NewNode = new NODE;
NewNode->first = key;
NewNode->second = value;
m_Chain.push_back(NewNode);
}
// chain에서 key를 갖고 있는 Node를 찾아서 반환
PNODE Get(const KEY& key)
{
typename CList<CHashNode<KEY, VALUE>*>::iterator iter = m_Chain.begin();
typename CList<CHashNode<KEY, VALUE>*>::iterator iterEnd = m_Chain.end();
for (; iter != iterEnd; ++iter)
{
if ((*iter)->first == key)
{
return *iter;
}
}
return nullptr;
}
};
template <typename KEY, typename VALUE>
class CHashIterator
{
template <typename KEY, typename VALUE, int HASHSIZE>
friend class CHashTable;
public:
CHashIterator()
{
m_Node = nullptr;
}
~CHashIterator()
{
}
private:
CHashNode<KEY, VALUE>* m_Node;
public:
bool operator == (const CHashIterator<KEY, VALUE>& iter) const
{
return this->m_Node == iter.m_Node;
}
bool operator != (const CHashIterator<KEY, VALUE>& iter) const
{
return this->m_Node != iter.m_Node;
}
CHashNode<KEY, VALUE>* operator * () const
{
return m_Node;
}
};
template <typename KEY, typename VALUE, int HASHSIZE = 1223>
class CHashTable
{
public:
CHashTable()
{
}
~CHashTable()
{
}
private:
typedef CHashNode<KEY, VALUE> NODE;
typedef CHashNode<KEY, VALUE>* PNODE;
public:
typedef CHashIterator<KEY, VALUE> iterator;
private:
NODE m_Array[HASHSIZE];
int m_Size;
public:
// hash table내에서 key, value를 가진 Node를 삽입
void insert(const KEY& key, const VALUE& value)
{
CHash hash;
unsigned __int64 HashValue = hash.GetHash<KEY>(key);
int Index = (int)(HashValue % HASHSIZE);
m_Array[Index].Add(key, value);
++m_Size;
}
// hash table내에서 key를 가진 노드를 찾아서 반환
iterator Find(const KEY& key)
{
CHash hash;
unsigned __int64 HashValue = hash.GetHash<KEY>(key);
int Index = (int)(HashValue % HASHSIZE);
PNODE FindNode = m_Array[Index].Get(key);
iterator iter;
iter.m_Node = FindNode;
return iter;
}
int size() const
{
return m_Size;
}
bool empty() const
{
return m_Size == 0;
}
VALUE& operator [](const KEY& key)
{
CHash hash;
unsigned __int64 HashValue = hash.GetHash<KEY>(key);
int Index = (int)(HashValue % HASHSIZE);
PNODE FindNode = m_Array[Index].Get(key);
// 찾는 노드가 없으면 삽입해주기
if (!FindNode)
{
// 어차피 value는 쓰레기 값이어도 무관
VALUE addValue;
m_Array[Index].Add(key, addValue);
FindNode = m_Array[Index].Get(key);
}
return FindNode->second;
}
bool IsValid(const iterator& iter) const
{
return iter.m_Node != nullptr;
}
};
리스트는 저번에 구현한 것을 그대로 이용하였다.
#pragma once
#include <assert.h>
// friend : 아래처럼 Listnode에 List를 friend로 지정하면 List에서는 ListNode의
// private에 접근할 수 있다.
template <typename T>
class CListNode
{
template <typename T>
friend class CList;
template <typename T>
friend class CListIterator;
template <typename T>
friend class CListReverseIterator;
private:
CListNode() :
m_Next(nullptr),
m_Prev(nullptr)
{
}
~CListNode()
{
}
private:
T m_Data;
CListNode<T>* m_Next;
CListNode<T>* m_Prev;
};
/*
iterator : 노드를 순차적으로 방문하기 위해서 iterator를 만들어서 제공한다.
iterator를 이용해서 앞에서 부터 혹은 뒤에서부터 노드를 순차적으로 접근할 수 있게 해준다.
*/
template <typename T>
class CListIterator
{
template <typename T>
friend class CList;
public:
CListIterator() :
m_Node(nullptr)
{
}
~CListIterator()
{
}
private:
CListNode<T>* m_Node;
public:
// iterator끼리 서로 가지고 있는 노드가 같을 경우 같다고 판단한다.
bool operator == (const CListIterator<T>& iter) const
{
return m_Node == iter.m_Node;
}
bool operator != (const CListIterator<T>& iter) const
{
return m_Node != iter.m_Node;
}
bool operator == (const CListNode<T>* Node) const
{
return m_Node == Node;
}
bool operator != (const CListNode<T>* Node) const
{
return m_Node != Node;
}
void operator ++ ()
{
m_Node = m_Node->m_Next;
}
void operator ++ (int)
{
m_Node = m_Node->m_Next;
}
void operator -- ()
{
m_Node = m_Node->m_Prev;
}
void operator -- (int)
{
m_Node = m_Node->m_Prev;
}
T& operator * () const
{
return m_Node->m_Data;
}
};
template <typename T>
class CList
{
public:
CList()
{
m_Size = 0;
// Begin과 End노드를 생성하고 두 노드를 서로 연결한다.
m_Begin = new NODE;
m_End = new NODE;
m_Begin->m_Next = m_End;
m_End->m_Prev = m_Begin;
}
~CList()
{
// Begin노드는 이전노드가 nullptr이다.
// End노드는 다음노드가 nullptr이다.
PNODE DeleteNode = m_Begin;
while (DeleteNode)
{
PNODE Next = DeleteNode->m_Next;
delete DeleteNode;
DeleteNode = Next;
}
}
private:
typedef CListNode<T> NODE;
typedef CListNode<T>* PNODE;
public:
typedef CListIterator<T> iterator;
typedef CListReverseIterator<T> reverse_iterator;
private:
// Begin과 End는 데이터를 저장하기 위한 노드는 아니다.
// 명시적으로 시작과 끝을 의미하는 노드로 사용하기 위해 할당해두고 사용한다.
// 실제 데이터를 저장하는 노드는 Begin과 End노드 사이에 위치하게 될것이다.
PNODE m_Begin;
PNODE m_End;
int m_Size;
public:
void push_back(const T& data)
{
// 데이터를 저장해둘 노드를 생성한다.
PNODE Node = new NODE;
Node->m_Data = data;
// End노드와 End노드의 이전노드 사이에 새로 생성된 노드를 추가해주도록 한다.
PNODE Prev = m_End->m_Prev;
// End노드의 이전노드의 다음노드를 End에서 새로 생성된 노드로 교체한다.
Prev->m_Next = Node;
// 새로 생성된 노드의 이전노드를 End의 이전노드로 지정한다.
Node->m_Prev = Prev;
// 새로 생성된 노드의 다음 노드를 End노드르 지정한다.
Node->m_Next = m_End;
// End노드의 이전노드를 새로 생성한 노드로 지정한다.
m_End->m_Prev = Node;
++m_Size;
}
void push_front(const T& data)
{
PNODE Node = new NODE;
Node->m_Data = data;
// Begin노드와 Begin노드의 다음노드 사이에 새로 생성된 노드를 추가한다.
PNODE Next = m_Begin->m_Next;
// 새로 생성된 노드의 다음노드로 Begin노드의 다음노드를 지정한다.
Node->m_Next = Next;
// Begin노드의 다음노드의 이전노드를 새로 생성된 노드로 지정한다.
Next->m_Prev = Node;
// Begin노드의 다음노드로 새로 생성된 노드를 지정한다.
m_Begin->m_Next = Node;
// 새로 생성된 노드의 이전노드로 Begin노드를 지정한다.
Node->m_Prev = m_Begin;
++m_Size;
}
// 마지막 노드를 제거한다.
void pop_back()
{
if (empty())
{
assert(false);
}
// End노드의 이전 노드를 지워준다. 그러므로 End의 이전노드를 얻어오고
// End의 이전 노드의 이전노드를 얻어와서 End와 연결해준다.
PNODE DeleteNode = m_End->m_Prev;
PNODE Prev = DeleteNode->m_Prev;
// 지울노드의 이전노드의 다음노드를 End노드로 지정한다.
Prev->m_Next = m_End;
// End노드의 이전노드로 지울노드의 이전노드를 지정한다.
m_End->m_Prev = Prev;
// 노드를 제거한다.
delete DeleteNode;
--m_Size;
}
void pop_front()
{
if (empty())
{
assert(false);
}
// Begin노드의 다음 노드를 지워준다.
PNODE DeleteNode = m_Begin->m_Next;
// 지울 노드의 다음 노드를 얻어온다.
PNODE Next = DeleteNode->m_Next;
// Begin노드의 다음노드로 지울 노드의 다음노드를 지정한다.
m_Begin->m_Next = Next;
// 지울노드의 다음노드의 이전노드로 Begin노드를 지정한다.
Next->m_Prev = m_Begin;
// 노드를 제거한다.
delete DeleteNode;
--m_Size;
}
const T& front() const
{
if (empty())
assert(false);
return m_Begin->m_Next->m_Data;
}
const T& back() const
{
if (empty())
assert(false);
return m_End->m_Prev->m_Data;
}
// 클래스의 멤버함수는 함수의 뒤에 const를 붙일 수 있다.
// 멤버함수의 뒤에 const가 붙으면 이 함수에서는 멤버변수의 값을 변경할 수 없게 된다.
// const 객체가 만들어진 경우라면 뒤에 const가 붙은 멤버함수만 호출이 가능하다.
int size() const
{
return m_Size;
}
bool empty() const
{
return m_Size == 0;
}
// 추가된 모든 노드를 제거한다.
void clear()
{
if (empty())
return;
PNODE DeleteNode = m_Begin->m_Next;
// End노드가 아니라면 while을 돌면서 제거해주도록 한다.
while (DeleteNode != m_End)
{
// 지울 노드의 다음노드를 미리 받아둬야 한다.
// 왜냐하면 노드를 먼저 지워버린다면 다음노드를 얻어올 수 없기 때문이다.
PNODE Next = DeleteNode->m_Next;
// 노드를 제거한다.
delete DeleteNode;
// 지울 노드를 다음 노드로 갱신한다.
DeleteNode = Next;
}
// Begin노드와 End노드를 서로 연결해준다.
m_Begin->m_Next = m_End;
m_End->m_Prev = m_Begin;
// 노드의 개수를 0으로 초기화해준다.
m_Size = 0;
}
iterator begin() const
{
iterator iter;
iter.m_Node = m_Begin->m_Next;
return iter;
}
iterator end() const
{
iterator iter;
iter.m_Node = m_End;
return iter;
}
reverse_iterator rbegin() const
{
reverse_iterator iter;
iter.m_Node = m_End->m_Prev;
return iter;
}
reverse_iterator rend() const
{
reverse_iterator iter;
iter.m_Node = m_Begin;
return iter;
}
void operator = (const CList<T>& list)
{
// 기존 정보를 제거한다.
clear();
m_Size = list.m_Size;
PNODE Node = list.m_Begin->m_Next;
while (Node != list.m_End)
{
push_back(Node->m_Data);
Node = Node->m_Next;
}
}
// 삭제한 노드의 다음 노드를 가지고 있는 iterator를 반환해준다.
iterator erase(const T& data)
{
iterator iter = Find(data);
if (iter == end())
return iter;
return erase(iter);
}
iterator erase(const iterator& iter)
{
if (iter == end() || iter == m_Begin)
return end();
// 지울 노드의 이전노드와 다음노드를 얻어온다.
// 지울 노드의 이전노드와 다음노드를 서로 연결시켜준다.
PNODE Prev = iter.m_Node->m_Prev;
PNODE Next = iter.m_Node->m_Next;
Prev->m_Next = Next;
Next->m_Prev = Prev;
delete iter.m_Node;
--m_Size;
iterator result;
result.m_Node = Next;
return result;
}
iterator Find(const T& data)
{
iterator iter = begin();
iterator iterEnd = end();
for (; iter != iterEnd; ++iter)
{
if (*iter == data)
return iter;
}
// 못찾을 경우 end를 리턴한다.
return iterEnd;
}
// 정렬
void sort(bool (*pFunc)(const T&, const T&))
{
iterator iter1 = begin();
iterator iter1End = end();
--iter1End;
iterator iter2;
iterator iter2End = end();
// 예를 들어 10개라면 0 ~ 8 까지만 반복한다.
// 마지막 9번은 반복을 안한다.
for (; iter1 != iter1End; ++iter1)
{
iter2 = iter1;
++iter2;
for (; iter2 != iter2End; ++iter2)
{
if (pFunc(*iter1, *iter2))
{
T data = *iter1;
*iter1 = *iter2;
*iter2 = data;
}
}
}
}
};
#include <iostream>
#include "HashTable.h"
int main()
{
CHashTable<const char*, const char*> Table;
Table.insert("김철수", "010-1234-5678");
Table.insert("최영희", "010-4494-4494");
Table.insert("이준수", "010-1234-8888");
Table.insert("박지훈", "010-3333-4444");
std::cout << Table["김철수"] << std::endl;
std::cout << Table["이준수"] << std::endl;
Table["정민수"] = "010-1111-1111";
std::cout << Table["정민수"] << std::endl;
CHashTable<const char*, const char*>::iterator iter = Table.Find("최영희");
if (Table.IsValid(iter))
std::cout << "최영희는 정상적인 키값입니다." << std::endl;
iter = Table.Find("이미영");
if (Table.IsValid(iter))
std::cout << "이미영은 정상적인 키값입니다." << std::endl;
else
std::cout << "이미영은 잘못된 키값입니다." << std::endl;
return 0;
}
해시 테이블은 탐색이 O(1)에 가능하다는 장점을 갖고 있다. 물론 충돌이 많고, chaining 기법을 이용했을 때 하나의 리스트에 노드가 다 들어가있다면 사실상 hash table의 장점을 전혀 못살리게 될 것이다. 그래서 hashing 함수가 정말 중요하다. 여기선 숫자는 8bit씩 AND연산으로 누적해서 더하는 방식으로, 문자열은 짝수 인덱스는 누적해서 더하고, 홀수 인덱스는 누적해서 곱하는 형식으로 매우 간단하게 구현했지만 많은 노드가 삽입된다면 충돌 피할 수 없을 것이며, 충돌을 최소화 하려면 더 정교한 hashing 함수를 사용해야 할 것이다.
'공부 > Data Structure' 카테고리의 다른 글
자료구조 별 장단점(vector, list, queue, stack, map, hash map, tree) (0) | 2022.09.15 |
---|---|
AVL Tree (0) | 2021.08.06 |
이진 탐색 트리(Binary Search Tree), 화살표 연산자 오버로딩(-> operator overloading, arrow operator overloading) (0) | 2021.08.06 |
Linked list 구현 (0) | 2021.08.02 |