C实现散列表(分离链接法|开放定址法)

168 阅读4分钟

散列表定义

分离链表法

开放定址法

分离链接法实现

通过头文件 fatal.h

#include <stdio.h>
#include <stdlib.h>

#define Error( Str )        FatalError( Str )
#define FatalError( Str )   fprintf( stderr, "%s\n", Str ), exit( 1 )

声明 hashsep.h

/* Interface for separate chaining hash table */
typedef int ElementType;

/* START: fig5_2.txt */
typedef unsigned int Index;
/* END */

/* START: fig5_7.txt */
#ifndef _HashSep_H
#define _HashSep_H

struct ListNode;
typedef struct ListNode *Position;
struct HashTbl;
typedef struct HashTbl *HashTable;

HashTable InitializeTable(int TableSize);
void DestroyTable(HashTable H);
Position Find(ElementType Key, HashTable H);
void Insert(ElementType Key, HashTable H);
ElementType Retrieve(Position P);
/* Routines such as Delete are MakeEmpty are omitted */

#endif  /* _HashSep_H */
/* END */

实现 hashsep.c

#include "fatal.h"
#include "hashsep.h"
#include <stdlib.h>

#define MinTableSize (10)

struct ListNode
{
	ElementType Element;
	Position    Next;
};


typedef Position List;

struct HashTbl
{
	int TableSize;
	List *TheLists;
};

/* Return next prime; assume N >= 10 */
// 获取大于N的第一个素数
static int
NextPrime(int N)
{
	int i;

	// 确保N为奇数
	if (N % 2 == 0)
		N++;
	// 递增后仍然为奇数
	for (; ; N += 2)
	{
		for (i = 3; i * i <= N; i += 2)
			// 从3开始到 N的平方跟之间所有的奇数 如果都不满足被N整除 说明这个N就是个素数
			if (N % i == 0)
				goto ContOuter;  /* Sorry about this! */
		return N;
	ContOuter:;
	}
}


/* Hash function for ints */
// 哈希映射函数 简单的取余 模运算一次即可
Index
Hash(ElementType Key, int TableSize)
{
	return Key % TableSize;
}

// 初始化一个hash表
HashTable
InitializeTable(int TableSize)
{
	HashTable H;
	int i;

	if (TableSize < MinTableSize)
	{
		Error("Table size too small");
		return NULL;
	}

	/* Allocate table */
	H = malloc(sizeof(struct HashTbl));
	if (H == NULL)
		FatalError("Out of space!!!");

	H->TableSize = NextPrime(TableSize);

	/* Allocate array of lists */
	H->TheLists = malloc(sizeof(List) * H->TableSize);
	if (H->TheLists == NULL)
		FatalError("Out of space!!!");

	/* Allocate list headers */
	for (i = 0; i < H->TableSize; i++)
	{
		// 链表数组中每一个元素是一个头结点 它的next指向实际数据结点
		H->TheLists[i] = malloc(sizeof(struct ListNode));
		if (H->TheLists[i] == NULL)
			FatalError("Out of space!!!");
		else
			H->TheLists[i]->Next = NULL;
	}

	return H;
}

// 找到对应key在hash表中的地址指针
Position
Find(ElementType Key, HashTable H)
{
	Position P;
	List L;

	L = H->TheLists[Hash(Key, H->TableSize)];
	P = L->Next;
	while (P != NULL && P->Element != Key)
		/* Probably need strcmp!! */
		P = P->Next;
	return P;
}


void
Insert(ElementType Key, HashTable H)
{
	Position Pos, NewCell;
	List L;

	Pos = Find(Key, H);
	if (Pos == NULL)   /* Key is not found */
	{
		// key此时不存在
		NewCell = malloc(sizeof(struct ListNode));
		if (NewCell == NULL)
			FatalError("Out of space!!!");
		else
		{
			L = H->TheLists[Hash(Key, H->TableSize)];
			// 插入这个新节点或者重复节点到链表的头节点后面 作为一个新节点 每次新来的重复节点都插入到头节点后面
			NewCell->Next = L->Next;
			NewCell->Element = Key;  /* Probably need strcpy! */
			L->Next = NewCell;
		}
	}
}

ElementType
Retrieve(Position P)
{
	return P->Element;
}

void
DestroyTable(HashTable H)
{
	int i;

	for (i = 0; i < H->TableSize; i++)
	{
		Position P = H->TheLists[i];
		Position Tmp;

		while (P != NULL)
		{
			Tmp = P->Next;
			free(P);
			P = Tmp;
		}
	}

	free(H->TheLists);
	free(H);
}

开放定址法实现(平方探测法)

声明 hashquad.h

/* Interface for quadratic probing hash table */
typedef int ElementType;

/* START: fig5_14.txt */
#ifndef _HashQuad_H
#define _HashQuad_H

typedef unsigned int Index;
typedef Index Position;

struct HashTbl;
typedef struct HashTbl *HashTable;

HashTable InitializeTable(int TableSize);
void DestroyTable(HashTable H);
Position Find(ElementType Key, HashTable H);
void Insert(ElementType Key, HashTable H);
ElementType Retrieve(Position P, HashTable H);
HashTable Rehash(HashTable H);
/* Routines such as Delete are MakeEmpty are omitted */

#endif  /* _HashQuad_H */

/* END */

实现 hashquad.c

#include "fatal.h"
#include "hashquad.h"
#include <stdlib.h>

#define MinTableSize (10)

// 正常存在 空的位置 被删除过的位置
enum KindOfEntry { Legitimate, Empty, Deleted };

struct HashEntry
{
	ElementType      Element;
	enum KindOfEntry Info;
};

typedef struct HashEntry Cell;

struct HashTbl
{
	int TableSize;
	Cell *TheCells;
};

static int
NextPrime(int N)
{
	int i;

	if (N % 2 == 0)
		N++;
	for (; ; N += 2)
	{
		for (i = 3; i * i <= N; i += 2)
			if (N % i == 0)
				goto ContOuter;  /* Sorry about this! */
		return N;
	ContOuter:;
	}
}

Index
Hash(ElementType Key, int TableSize)
{
	return Key % TableSize;
}

/* START: fig5_15.txt */
HashTable
InitializeTable(int TableSize)
{
	HashTable H;
	int i;

	/* 1*/      if (TableSize < MinTableSize)
	{
		/* 2*/          Error("Table size too small");
		/* 3*/          return NULL;
	}

	/* Allocate table */
	/* 4*/      H = malloc(sizeof(struct HashTbl));
	/* 5*/      if (H == NULL)
		/* 6*/          FatalError("Out of space!!!");

	/* 7*/      H->TableSize = NextPrime(TableSize);

	/* Allocate array of Cells */
	/* 8*/      H->TheCells = malloc(sizeof(Cell) * H->TableSize);
	/* 9*/      if (H->TheCells == NULL)
		/*10*/          FatalError("Out of space!!!");

	/*11*/      for (i = 0; i < H->TableSize; i++)
						// 默认每个位置的状态都是empty
		/*12*/          H->TheCells[i].Info = Empty;

	/*13*/      return H;
}
/* END */


/* START: fig5_16.txt */
Position
Find(ElementType Key, HashTable H)
{
	Position CurrentPos;
	int CollisionNum;

	/* 1*/      CollisionNum = 0;
	/* 2*/      CurrentPos = Hash(Key, H->TableSize);
				// empty 状态节点可以直接返回作为一个可以插入的位置 
				// 非empty状态且值不等于key说明这是一个放置了其他值的位置 那就进行平方探测去找下一个可以放的位置
	/* 3*/      while (H->TheCells[CurrentPos].Info != Empty &&
		H->TheCells[CurrentPos].Element != Key)
		/* Probably need strcmp!! */
	{	
						// 采用的平方探测法 2个相邻的节点之间差 (i+1)平方 - i平方 =  2*i + 1 意味着下个测试位置是在当前位置上加 1 3 5 7 等等
		/* 4*/          CurrentPos += 2 * ++CollisionNum - 1;
		/* 5*/          if (CurrentPos >= H->TableSize)
								// 模拟取余
			/* 6*/              CurrentPos -= H->TableSize;
	}
	/* 7*/      return CurrentPos;
}
/* END */

void
Insert(ElementType Key, HashTable H)
{
	Position Pos;

	// Find返回的位置是一个可以直接插入的位置
	Pos = Find(Key, H);
	if (H->TheCells[Pos].Info != Legitimate)
	{
		/* OK to insert here */
		H->TheCells[Pos].Info = Legitimate;
		H->TheCells[Pos].Element = Key;
		/* Probably need strcpy! */
	}
}

/* START: fig5_22.txt */
// 扩展新的一倍大小的hash表 把原来的数据值插入到新hash表中
HashTable
Rehash(HashTable H)
{
	int i, OldSize;
	Cell *OldCells;

	/* 1*/      OldCells = H->TheCells;
	/* 2*/      OldSize = H->TableSize;

	/* Get a new, empty table */
	/* 3*/      H = InitializeTable(2 * OldSize);

	/* Scan through old table, reinserting into new */
	/* 4*/      for (i = 0; i < OldSize; i++)
		/* 5*/          if (OldCells[i].Info == Legitimate)
			/* 6*/              Insert(OldCells[i].Element, H);

	/* 7*/      free(OldCells);

	/* 8*/      return H;
}
/* END */


ElementType
Retrieve(Position P, HashTable H)
{
	return H->TheCells[P].Element;
}

void
DestroyTable(HashTable H)
{
	free(H->TheCells);
	free(H);
}

测试代码 testhash.c

#define SepChain    /* Define the appropriate hash algorithm */

#ifdef SepChain
#include "hashsep.h"
#endif

#ifdef QuadProb
#include "hashquad.h"
#endif


#include <stdio.h>

#define NumItems 400

main()
{
	HashTable H;
	Position P;
	int i;
	int j = 0;
	int CurrentSize;

	H = InitializeTable(CurrentSize = 13);

	for (i = 0; i < NumItems; i++, j += 71)
	{
#ifdef QuadProb
		if (i > CurrentSize / 2)
		{
			H = Rehash(H);
			printf("Rehashing...\n");
			CurrentSize *= 2;
		}
#endif
		Insert(j, H);
	}

	for (i = 0, j = 0; i < NumItems; i++, j += 71)
#ifdef SepChain
		if ((P = Find(j, H)) == NULL || Retrieve(P) != j)
#endif
#ifdef QuadProb
			if (Retrieve((P = Find(j, H)), H) != j)
#endif
				printf("Error at %d\n", j);

	printf("End of program.\n");
	return 0;
}