数据结构第十一节(散列表)

散列表

什么是散列表

散列表(Hash table,也叫哈希表),是根据键(Key)而直接访问在内存储存位置的数据结构。也就是说,它通过计算一个关于键值的函数,将所需查询的数据映射到表中一个位置来访问记录,这加快了查找速度。这个映射函数称做散列函数,存放记录的数组称做散列表。

举一个简单的例子,假设有5个数字,他们的个位都不相同,如何把这5个数字保存起来,而且在查找这个数字时足够快呢。在这个简单的例子中由于它们的个位数都不相同,故可以对这些数字对10求余取个位数,放在对应下标的的数组里。({54,76,31,92,109}这个集合,将54保存到array[4],将76保存到array[6],将31保存到array[1],将92保存到array[2],将109保存到array[9])

上面是一个极其简单和特殊的例子,在正常情况下很难遇到上面的简单情况,多数的情况,会出现冲突。对两个不同的数,如果计算到了同样的键值,该如何处理?

所以构造一个好用的散列表,最重要的是做好以下两件事情:

  1. 设计一个"好"的散列函数来计算Key值。(好的哈希函数应尽可能避免冲突的出现,而且计算时应尽可能简洁快速)
  2. 出现了冲突时又该如何调整插入元素。

散列表的实现

散列函数的实现

散列函数的实现主要有以下几种方法:

  1. 直接定址法:取关键字或关键字的某个线性函数值为散列地址。即\(hash(k)=k\)\(hash(k)=a*k+b\),其中\(a\),\(b\)为常数(这种散列函数叫做自身函数)
  2. 数字分析法:假设关键字是以r为基的数,并且哈希表中可能出现的关键字都是事先知道的,则可取关键字的若干数位组成哈希地址。
  3. 平方取中法:取关键字平方后的中间几位为哈希地址。通常在选定哈希函数时不一定能知道关键字的全部情况,取其中的哪几位也不一定合适,而一个数平方后的中间几位数和数的每一位都相关,由此使随机分布的关键字得到的哈希地址也是随机的。取的位数由表长决定。
  4. 折叠法:将关键字分割成位数相同的几部分(最后一部分的位数可以不同), 然后取这几部分的叠加和(舍去进位)作为哈希地址。
  5. 随机数法
  6. 除留余数法:取关键字被某个不大于散列表表长m的数p除后所得的余数为散列地址。即\(hash(k)=k\) \(mod\) \(p\)。不仅可以对关键字直接取模,也可在折叠法、平方取中法等运算之后取模。对p的选择很重要,一般取素数或m,若p选择不好,容易产生冲突。

以上方法精是对数字类型的操作,对字符串类型的数据,可以选择通过相加或者进位转化成数字后,再执行上面的计算方法。

散列表解决冲突的方法

解决冲突主要有两种方式,一种是链地址法,另一种为开放地址法。

对于第1种方法,是将有共同键值的元素串成一条链表的思路。而第2种方法,如果该位置已经有元素了,则换一个地方,当然为了查找时还能找得到他,肯定是不可以随便放的,需要按照指定的增长序列{\(k_1,k_2,k_3,k_4...k_n\)},依次探查离自己距离自己\(k_1,k_2,k_3,k_4...k_n\)的地方是否有空。

增量序列的不同,提供了不同的解决冲突方法。常用的方法有三种,分别为线性探测和平方探测和双散列。

使用线性探测时,如果出现冲突那就向后错一位,看是否有空,直到找到空的(有点像上厕所找坑);使用平方探测时,每次向前向后\(d_i^2\)位。使用这2种方法容易会造成聚集。聚集(Cluster,也翻译做“堆积”)的意思是,在函数地址的表中,散列函数的结果不均匀地占据表的单元,形成区块,造成线性探测产生一次聚集(primary clustering)和平方探测的二次聚集(secondary clustering),散列到区块中的任何关键字需要查找多次试选单元才能插入表中,解决冲突,造成时间浪费。对于开放定址法,聚集会造成性能的灾难性损失,是必须避免的。总体来说使用平方探测要比使用线性探测出现聚集的概率小。

使用双散列时,如果出现冲突,就用当前的键值作为参数用另一个散列函数计算键值,直到找到空位。

哈希表代码实现

#include<cstdio>
#include<cstdlib>
#include<algorithm>
#define null -99999
#define notFound -1
using namespace std;


typedef struct CellNode* Cell;
struct CellNode
{
	int Data;
};

//哈希表结构体
typedef struct HashTlNode* HashTable;
struct HashTlNode
{
	int TableSize;
	Cell Table;
};

//判断一个数是否为素数
bool isPrime(int n) {
	if (n < 2) {
		return false;
	}
	else if (n == 2 || n == 3) {
		return true;
	}
	else if(n%6==1||n%6==5){
		double p = sqrt(n);
		for (int i = 2; i <= p; i++)
		{
			if (n % i == 0) {
				return false;
			}
		}
		return true;
	}
	else {
		return false;
	}
}

//求一个距离最小的素数
int NextPrime(int n) {
	//所以我们去找下一个素数
	if (isPrime(n)) {
		n++;
	}
	//必须是素数,而且可以写成4k+3的形式,这样保证平方探测时效果最好。
	while (!(isPrime(n)&&n%4==3))
	{
		n++;
	}
	return n;
}

//创建空的哈希表
HashTable CreatHashTable(int TableSize) {
	HashTable H = (HashTable)malloc(sizeof(struct HashTlNode));
	H->TableSize = NextPrime(TableSize);
	H->Table = (Cell)malloc(sizeof(struct CellNode) * H->TableSize);
	for (int i = 0; i < H->TableSize; i++)
	{
		H->Table[i].Data = null;
	}
	return H;
}

//散列函数
int HashFunction(int key, HashTable H) {
	return key % H->TableSize;
}

//查找
int find(HashTable H,int Key) {
	int currentPos, NewPos;
	int Cnum = 0;
	currentPos = NewPos = HashFunction(Key, H);
	while (H->Table[NewPos].Data!=null&& H->Table[NewPos].Data != Key)
	{
		if (++Cnum % 2 != 0) {
			NewPos = currentPos + (Cnum + 1) * (Cnum + 1) / 4;
			while (NewPos>=H->TableSize)
			{
				NewPos -= H->TableSize;
			}
		}
		else {
			NewPos = currentPos - Cnum * Cnum / 4;
			while (NewPos <0)
			{
				NewPos += H->TableSize;
			}
		}
	}
	if (H->Table[NewPos].Data == null) {
		NewPos = notFound;
	}
	return NewPos;
}

//插入
void Insert(int key,HashTable H) {
	int currentPos, NewPos;
	int Cnum = 0;
	currentPos = NewPos = HashFunction(key, H);
	while (H->Table[NewPos].Data != null)
	{
		if (++Cnum % 2 != 0) {
			NewPos = currentPos + (Cnum + 1) * (Cnum + 1) / 4;
			while (NewPos >= H->TableSize)
			{
				NewPos -= H->TableSize;
			}
		}
		else {
			NewPos = currentPos - Cnum * Cnum / 4;
			while (NewPos < 0)
			{
				NewPos += H->TableSize;
			}
		}
	}
	H->Table[NewPos].Data = key;
}

int main() {
	int n,t,array[100];
	scanf_s("%d", &n);
	HashTable H = CreatHashTable(n);
	for (int i = 0; i < n; i++)
	{
		scanf_s("%d", &t);
		array[i] = t;
		Insert(t, H);
	}
	printf("The hashtable size is %d\n",H->TableSize);
	for (int i = 0; i < n; i++)
	{
		int p = find(H, array[i]);
		if (p != notFound) {
			printf("%d in the %d\n", array[i], p);
		}
		else {
			printf("%d not found\n", array[i]);
		}
	}
	while(scanf_s("%d", &t)!=EOF)
	{
		int p = find(H, t);
		if (p != notFound) {
			printf("%d in the %d\n", t, p);
		}
		else {
			printf("%d not found\n", t);
		}
	}
	return 0;
}

散列表的性能分析

平均查找长度(ASL)用来度量散列表查找效率:成功、不成功
影响产生冲突多少有以下三个因素:
(1)散列函数是否均匀;
(2)处理冲突的方法;
(3)散列表的装填因子α。
根据上面的性质可以知道,装填因子α越大,寻找一个元素越困难。

课后练习(4个小题)

11-散列1 电话聊天狂人 (25point(s))

给定大量手机用户通话记录,找出其中通话次数最多的聊天狂人。

输入格式:
输入首先给出正整数N(≤10^5​),为通话记录条数。随后N行,每行给出一条通话记录。简单起见,这里只列出拨出方和接收方的11位数字构成的手机号码,其中以空格分隔。

输出格式:
在一行中给出聊天狂人的手机号码及其通话次数,其间以空格分隔。如果这样的人不唯一,则输出狂人中最小的号码及其通话次数,并且附加给出并列狂人的人数。

输入样例:

4
13005711862 13588625832
13505711862 13088625832
13588625832 18087925832
15005713862 13588625832

输出样例:

13588625832 3

题解:
提取号码后六位做哈希函数,哈希表开到10^6,虽然空间浪费有点狠,但是时间可以到100ms左右,稳定过。只取4位,emm。。。小概率压线过。

#include<stdio.h>
#include<string.h>
#include<math.h>
#include<stdlib.h>
#include<stdbool.h>
#define notFound -1

typedef struct CellNode* Cell;
struct CellNode
{
	char Data[12];
	int isEmpty;
	int time;
};

//哈希表结构体
typedef struct HashTlNode* HashTable;
struct HashTlNode
{
	int TableSize;
	Cell Table;
};

//判断一个数是否为素数
bool isPrime(int n) {
	if (n < 2) {
		return false;
	}
	else if (n == 2 || n == 3) {
		return true;
	}
	else if (n % 6 == 1 || n % 6 == 5) {
		double p = sqrt(n);
		for (int i = 2; i <= p; i++)
		{
			if (n % i == 0) {
				return false;
			}
		}
		return true;
	}
	else {
		return false;
	}
}

//求一个距离最小的素数
int NextPrime(int n) {
	//如果N已经是素数,那么他填满了,在查找不存在的数时一定会死环
	//所以我们去找下一个素数
	if (isPrime(n)) {
		n++;
	}
	//必须是素数,而且可以写成4k+3的形式,这样保证平方探测时不会出现死环。
	while (!(isPrime(n) && n % 4 == 3))
	{
		n++;
	}
	return n;
}

//创建空的哈希表
HashTable CreatHashTable(int TableSize) {
	HashTable H = (HashTable)malloc(sizeof(struct HashTlNode));
	H->TableSize = NextPrime(TableSize);
	H->Table = (Cell)malloc(sizeof(struct CellNode) * H->TableSize);
	for (int i = 0; i < H->TableSize; i++)
	{
		H->Table[i].isEmpty = 1;
	}
	return H;
}

//散列函数
int HashFunction(char key[], HashTable H) {
	char result[7];
	strncpy(result, key + 5, 7);
	return atoi(result);
}

//插入
void Insert(char key[], HashTable H) {
	int currentPos, NewPos;
	int Cnum = 0;
	currentPos = NewPos = HashFunction(key, H);
	//循环结束条件,找到元素或者找不到(找到了未被使用的空间)。
	while (H->Table[NewPos].isEmpty != 1)
	{
		//判断是否相同,相同直接跳出
		if (strcmp(key,H->Table[NewPos].Data)==0) {
			break;
		}
		else if (++Cnum % 2 != 0) {
			NewPos = currentPos + (Cnum + 1) * (Cnum + 1) / 4;
			while (NewPos >= H->TableSize)
			{
				NewPos -= H->TableSize;
			}
		}
		else {
			NewPos = currentPos - Cnum * Cnum / 4;
			while (NewPos < 0)
			{
				NewPos += H->TableSize;
			}
		}
	}
	//没有找到,创建新的
	if (H->Table[NewPos].isEmpty==1) {
		H->Table[NewPos].isEmpty = 0;
		strcpy(H->Table[NewPos].Data, key);
		H->Table[NewPos].time = 1;
	}
	else {
		H->Table[NewPos].time++;
	}
}

//find 电话聊天狂人
void find(HashTable H) {
	int count = 1;
	int max = 0;
	for (int i = 1; i < H->TableSize; i++)
	{
		if (H->Table[i].isEmpty != 1) {
			if (H->Table[i].time > (H->Table[max].time)) {
				max = i;
				count = 1;
			}
			else if(H->Table[i].time == (H->Table[max].time)){
				if (strcmp(H->Table[i].Data, H->Table[max].Data)<0) {
					max = i;
				}
				count++;
			}
		}
	}
	printf("%s %d", H->Table[max].Data, H->Table[max].time);
	if (count == 1) {
		printf("\n");
	}
	else {
		printf(" %d\n",count);
	}
}
int main() {
	int n;
	char phone[12];
	scanf("%d", &n);
	HashTable H = CreatHashTable(1000000);
	for (int i = 0; i < n; i++)
	{
		scanf("%s", phone);
		Insert(phone, H);
		scanf("%s", phone);
		Insert(phone, H);
	}
	find(H);
	return 0;
}

11-散列2 Hashing (25point(s))

The task of this problem is simple: insert a sequence of distinct positive integers into a hash table, and output the positions of the input numbers. The hash function is defined to be H(key)=key%TSize where TSize is the maximum size of the hash table. Quadratic probing (with positive increments only) is used to solve the collisions.

Note that the table size is better to be prime. If the maximum size given by the user is not prime, you must re-define the table size to be the smallest prime number which is larger than the size given by the user.

Input Specification:
Each input file contains one test case. For each case, the first line contains two positive numbers: MSize (≤10^4) and N (≤MSize) which are the user-defined table size and the number of input numbers, respectively. Then N distinct positive integers are given in the next line. All the numbers in a line are separated by a space.

Output Specification:
For each test case, print the corresponding positions (index starts from 0) of the input numbers in one line. All the numbers in a line are separated by a space, and there must be no extra space at the end of the line. In case it is impossible to insert the number, print "-" instead.

Sample Input:

4 4
10 6 4 15

Sample Output:

0 1 4 -

题解:
题目的意思是让做散列插入,用仅正向平方探测解决冲突,如果解决不了,就是出现了环,就要输出-,否则输出他的位置。题目的难点在于如何判断环的出现。根据数学可以证明,平方探测的次数达到整个表的一半时,就说明无法插入了,如何得来呢:假设现在进行到了第n次(n<size),它的平方探测增长值对应到\(n^2\) \(mod\) \(size\),我们令一个数m(m<size)且m + n = size。那么n 可以被表示成(size - m),它的平方探测增长值可写成\((size-m)^2\) \(mod\) \(size\) => \((size^2+2*m*size+m^2)\) \(mod\) \(size\) => \(m^2\) \(mod\) \(size\)。因为前两项中包含size,故对他们求于一定为0。到现在我们证明了,对于数m,n(m + n = size 且 m,n < size),它们对应的平方探测值相同。也就是,平方探测值关于size/2对称。

#include<cstdio>
#include<cstdlib>
#include<cmath>
#include<algorithm>
#define null -99999
#define notFound -1
using namespace std;


typedef struct CellNode* Cell;
struct CellNode
{
	int Data;
};

//哈希表结构体
typedef struct HashTlNode* HashTable;
struct HashTlNode
{
	int TableSize;
	Cell Table;
};

//判断一个数是否为素数
bool isPrime(int n) {
	if (n < 2) {
		return false;
	}
	else if (n == 2 || n == 3) {
		return true;
	}
	else if (n % 6 == 1 || n % 6 == 5) {
		double p = sqrt(n);
		for (int i = 2; i <= p; i++)
		{
			if (n % i == 0) {
				return false;
			}
		}
		return true;
	}
	else {
		return false;
	}
}

//求一个距离最小的素数
int NextPrime(int n) {
	while (!isPrime(n))
	{
		n++;
	}
	return n;
}

//创建空的哈希表
HashTable CreatHashTable(int TableSize) {
	HashTable H = (HashTable)malloc(sizeof(struct HashTlNode));
	H->TableSize = NextPrime(TableSize);
	H->Table = (Cell)malloc(sizeof(struct CellNode) * H->TableSize);
	for (int i = 0; i < H->TableSize; i++)
	{
		H->Table[i].Data = null;
	}
	return H;
}

//散列函数
int HashFunction(int key, HashTable H) {
	return key % H->TableSize;
}

//插入
void Insert(int key, HashTable H) {
	int currentPos, NewPos;
	int Cnum = 0,end =H->TableSize;
	currentPos = NewPos = HashFunction(key, H);
	while (H->Table[NewPos].Data != null)
	{
		Cnum++;
		NewPos = (currentPos + (Cnum) * (Cnum))% H->TableSize;
		if (Cnum == end) {
			printf("-");
			return;
		}
	}
	H->Table[NewPos].Data = key;
	printf("%d", NewPos);
}

int main() {
	int n, t;
	scanf("%d %d", &t,&n);
	HashTable H = CreatHashTable(t);
	scanf("%d", &t);
	Insert(t, H);
	for (int i = 1; i < n; i++)
	{
		printf(" ");
		scanf("%d", &t);
		Insert(t, H);
	}
	printf("\n");
	return 0;
}

11-散列3 QQ帐户的申请与登陆 (25point(s))

实现QQ新帐户申请和老帐户登陆的简化版功能。最大挑战是:据说现在的QQ号码已经有10位数了。

输入格式:
输入首先给出一个正整数N(≤10^​5),随后给出N行指令。每行指令的格式为:“命令符(空格)QQ号码(空格)密码”。其中命令符为“N”(代表New)时表示要新申请一个QQ号,后面是新帐户的号码和密码;命令符为“L”(代表Login)时表示是老帐户登陆,后面是登陆信息。QQ号码为一个不超过10位、但大于1000(据说QQ老总的号码是1001)的整数。密码为不小于6位、不超过16位、且不包含空格的字符串。

输出格式:
针对每条指令,给出相应的信息:

1)若新申请帐户成功,则输出“New: OK”;
2)若新申请的号码已经存在,则输出“ERROR: Exist”;
3)若老帐户登陆成功,则输出“Login: OK”;
4)若老帐户QQ号码不存在,则输出“ERROR: Not Exist”;
5)若老帐户密码错误,则输出“ERROR: Wrong PW”。

输入样例:

5
L 1234567890 myQQ@qq.com
N 1234567890 myQQ@qq.com
N 1234567890 myQQ@qq.com
L 1234567890 myQQ@qq
L 1234567890 myQQ@qq.com

输出样例:

ERROR: Not Exist
New: OK
ERROR: Exist
ERROR: Wrong PW
Login: OK

题解:
一道简单的模拟题,我这里的散列函数是取QQ号的前6位,开了一个10的6次方大小的散列表,虽然有些浪费,但是速度凑合。

#include<cstdio>
#include<cstdlib>
#include<algorithm>
#include<cmath>
#include<string.h>
#define _CRT_SECURE_NO_WARNINGS
using namespace std;

typedef struct CellNode* Cell;
struct CellNode
{
	char Data[11];
	int isEmpty;
	char passw[17];
};

//哈希表结构体
typedef struct HashTlNode* HashTable;
struct HashTlNode
{
	int TableSize;
	Cell Table;
};

//判断一个数是否为素数
bool isPrime(int n) {
	if (n < 2) {
		return false;
	}
	else if (n == 2 || n == 3) {
		return true;
	}
	else if (n % 6 == 1 || n % 6 == 5) {
		double p = sqrt(n);
		for (int i = 2; i <= p; i++)
		{
			if (n % i == 0) {
				return false;
			}
		}
		return true;
	}
	else {
		return false;
	}
}

//求一个距离最小的素数
int NextPrime(int n) {
	//如果N已经是素数,那么他填满了,在查找不存在的数时一定会死环
	//所以我们去找下一个素数
	if (isPrime(n)) {
		n++;
	}
	//必须是素数,而且可以写成4k+3的形式,这样保证平方探测时不会出现死环。
	while (!(isPrime(n) && n % 4 == 3))
	{
		n++;
	}
	return n;
}

//创建空的哈希表
HashTable CreatHashTable(int TableSize) {
	HashTable H = (HashTable)malloc(sizeof(struct HashTlNode));
	H->TableSize = NextPrime(TableSize);
	H->Table = (Cell)malloc(sizeof(struct CellNode) * H->TableSize);
	for (int i = 0; i < H->TableSize; i++)
	{
		H->Table[i].isEmpty = 1;
	}
	return H;
}

//散列函数
int HashFunction(char key[], HashTable H) {
	char result[7];
	strncpy(result, key, 6);
	return atoi(result);
}

//插入
void Insert(char key[], char pass[], HashTable H) {
	int currentPos, NewPos;
	int Cnum = 0;
	currentPos = NewPos = HashFunction(key, H);
	//循环结束条件,找到元素或者找不到(找到了未被使用的空间)。
	while (H->Table[NewPos].isEmpty != 1)
	{
		//判断是否相同,相同直接跳出
		if (strcmp(key, H->Table[NewPos].Data) == 0) {
			break;
		}
		else if (++Cnum % 2 != 0) {
			NewPos = currentPos + (Cnum + 1) * (Cnum + 1) / 4;
			while (NewPos >= H->TableSize)
			{
				NewPos -= H->TableSize;
			}
		}
		else {
			NewPos = currentPos - Cnum * Cnum / 4;
			while (NewPos < 0)
			{
				NewPos += H->TableSize;
			}
		}
	}
	//没有找到,创建新的
	if (H->Table[NewPos].isEmpty == 1) {
		H->Table[NewPos].isEmpty = 0;
		strcpy(H->Table[NewPos].Data, key);
		strcpy(H->Table[NewPos].passw, pass);
		printf("New: OK\n");
	}
	else {
		printf("ERROR: Exist\n");
	}
}

int stringcmp(const char pass1[], const char pass2[]) {
	for (int i = 0; i < max(strlen(pass1),strlen(pass2)); i++)
	{
		if (pass1[i] != pass2[i]) {
			return 0;
		}
	}
	return 1;
}

//登陆
void Login(char key[], char pass[], HashTable H) {
	int currentPos, NewPos;
	int Cnum = 0;
	currentPos = NewPos = HashFunction(key, H);
	//循环结束条件,找到元素或者找不到(找到了未被使用的空间)。
	while (H->Table[NewPos].isEmpty != 1)
	{
		//判断是否相同,相同直接跳出
		if (strcmp(key, H->Table[NewPos].Data) == 0) {
			break;
		}
		else if (++Cnum % 2 != 0) {
			NewPos = currentPos + (Cnum + 1) * (Cnum + 1) / 4;
			while (NewPos >= H->TableSize)
			{
				NewPos -= H->TableSize;
			}
		}
		else {
			NewPos = currentPos - Cnum * Cnum / 4;
			while (NewPos < 0)
			{
				NewPos += H->TableSize;
			}
		}
	}
	//没有找到
	if (H->Table[NewPos].isEmpty == 1) {
		printf("ERROR: Not Exist\n");
	}
	else {
		if (stringcmp(H->Table[NewPos].passw,pass)) {
			printf("Login: OK\n");
		}
		else {
			printf("ERROR: Wrong PW\n");
		}
	}
}
int main() {
	int n;
	char id[11];
	char pas[17];
	char op = ' ';
	scanf("%d", &n);
	HashTable H = CreatHashTable(1000000);
	for (int i = 0; i < n; i++)
	{
		scanf("\n%c", &op);
		scanf("%s", id);
		scanf("%s", pas);
		if (op == 'L') {
			Login(id, pas, H);
			int p = 0;
		}
		else if (op == 'N') {
			Insert(id, pas, H);
			int p = 0;
		}
	}
	return 0;
}

11-散列4 Hashing - Hard Version (30point(s))

Given a hash table of size N, we can define a hash function H(x)=x%N. Suppose that the linear probing is used to solve collisions, we can easily obtain the status of the hash table with a given sequence of input numbers.

However, now you are asked to solve the reversed problem: reconstruct the input sequence from the given status of the hash table. Whenever there are multiple choices, the smallest number is always taken.

Input Specification:
Each input file contains one test case. For each test case, the first line contains a positive integer N (≤1000), which is the size of the hash table. The next line contains N integers, separated by a space. A negative integer represents an empty cell in the hash table. It is guaranteed that all the non-negative integers are distinct in the table.

Output Specification:
For each test case, print a line that contains the input sequence, with the numbers separated by a space. Notice that there must be no extra space at the end of each line.

Sample Input:

11
33 1 13 12 34 38 27 22 32 -1 21
Sample Output:
1 13 12 21 33 34 38 27 22 32

题解:
题目的大概意思是,先读入一个 n,然后传给你一个大小为N的散列表,如果散列表中的元素为负值,说明该位置是空的,而且该散列表在创建时,用的是散列表大小求余的散列函数,解决冲突的方式为线性探索。
要求输出散列表的插入顺序,数字小的优先靠前。
整个题目可以用一个拓扑排序,再加上一个优先队列(最小堆)来解决,不过优先队列比较麻烦而且该题数据量不大,就直接线性扫描了。对于一个位置是否对另一个位置会造成影响(也就是说这个位置的存在,导致了元素冲突),可以通过下面的方式来判别,在初始时我们有计算过它的初始偏移量p = i - t % n, p大于哈希表尺寸时,p-=n。在我们输出一个元素后,循环遍历计算该元素到其他所有元素的距离,如果这个距离小于等于初始偏移量,就将该位置的元素偏移量减1。

#include<cstdio>
#include<cstdlib>
#include<algorithm>
#include<cmath>
#include<vector>
#define INFINITY 999999
#define _CRT_SECURE_NO_WARNINGS
using namespace std;

typedef struct CellNode Cell;
struct CellNode
{
	int Data;
	int pos;
	int firstpos;
};

int main() {
	int n, t, invalid = 0;
	scanf("%d", &n);
	vector<Cell> v(n);
	for (int i = 0; i < n; i++)
	{
		scanf("%d", &t);
		v[i].Data = t;
		if (t < 0) {
			v[i].firstpos = -1;
			v[i].pos = -1;
			invalid++;
		}
		else {
			int p = i - t % n;
			if (p < 0) {
				p += n;
			}
			v[i].firstpos = p;
			v[i].pos = p;
		}
	}
	//判断是否为第一个输出,是前面不输出空格
	bool isf = true;
	for (int i = 0; i < n-invalid; i++)
	{
		int p=0, minnum = INFINITY;
		//扫描全序列,找偏移为0且最小的点。
		for (int j = 0; j < n; j++)
		{
			if (v[j].pos == 0 && v[j].Data < minnum) {
				minnum = v[j].Data;
				p = j;
			}
		}
		//输出且删除该点
		if (isf) {
			printf("%d", minnum);
			isf = false;
		}
		else {
			printf(" %d", minnum);
		}
		//因为p点而偏移的数,偏移量减去1
		for (int j = 0; j < n; j++)
		{
			int dis = j - p;
			if (dis < 0) {
				dis += n;
			}
			//因为p点而偏移
			if (v[j].firstpos >= dis) {
				v[j].pos--;
			}
		}
		v[p].pos = -1;
	}
	printf("\n");
	return 0;
}
发表评论

相关文章