PTA Hashing - Hard Version 思路分析及代码解析v0.9.1
一、前导
1. 需要掌握的知识
良好的逻辑分析能力 + Hash表 + 拓扑排序
2. 题目信息
2.1 题目来源:PTA / 拼题A
2.2 题目地址: 7-18 Hashing - Hard Version
二、解题思路分析
1. 题意理解
已知Hash表的插入结果(通过线性探测法解决冲突),据此推导出输入数字的插入顺序
输入数据
11 //Hash表大小
33 1 13 12 34 38 27 22 32 -1 21 //Hash表的插入结果
输出数据
1 13 12 21 33 34 38 27 22 32 //输入数字的插入顺序
2. 思路分析
2.1 先看4数字的Hash表,以便更快的找到规律:33%11=0, 1%11=1, 13%11=2, 12%11=1,12本该插在数字1的位置,现在数字 1 13 在它的前面,意味着数字1和数字13一定先于12插入。按照这个规律,Hash表的插入结果可以构建出一个有向图(建议自己画一下,以便加深理解)
11
33 1 13 12
2.2 每次一定是入度为0的结点(Hash后直接插入,没有发生冲突) 且 数值最小的数字先插入(Whenever there are multiple choices, the smallest number is always taken)
2.3 根据2.2的分析,因此使用拓扑排序:每次收集入度为0的结点并挑选出对应数值最小的数字,打印该数字,将与该结点有边关系的结点入度-1,重复上述操作,直到所有有效数字得到打印
三、具体实现
1. 弯路和bug
本题考察了两个知识点:Hash表、拓扑排序,把他们联系到一起还是比较难的
2. 代码框架
2.1 采用的数据结构
二维数组,通过邻接矩阵表示图
#define MAX 1001
int Graph[MAX][MAX];
2.2 程序主体框架
程序伪码描述
int main()
{
1.创建一个空的有向图
2.根据录入的数据,向图中插入边并统计各结点的入度数据
3.进行拓扑排序,相较一般拓扑排序,除满足入度为0的条件外,还需要找到入度为0中数值最小的数字
}
2.3 各分支函数 或 重点步骤
2.3.1 CreateGraph() 构建一个空图,将Hash表的表大小N抽象为N个点,从 0 至 N-1 编号
void CreateGraph()
{
for (int i = 0; i < N; i++)
for (int j = 0; j < N; j++)
Graph[i][j] = noEdge;
}
2.3.2 将N个数字存储在数组Value[ ]中,对数组Value[ ]中的每个数字 模 Hash表大小:如果 Value[i]非负数(意味着有效数字) 且 j!=i(意味着发生冲突) ,说明结点j一定在结点i的前面,结点 j i 之间构成了一条从 j 指向 i 的有向边
for (int i = 0; i < N; i++)
{
int j = Value[i] % N;
while (j!=i && Value[i]>Empty)
{
Graph[j][i] = 1; //构建有向图
inDegree[i]++; //统计入度
if (++j == N)
j=0;
}
}
2.3.3 TopologySort() 拓扑排序,每次收集入度为0的结点 并 挑选出对应数值最小的数字(可以通过Value数组确定数字),打印该数字,将与该结点有边关系的结点入度-1,重复上述操作,直到全部有效数字被打印
void TopologySort()
{
int a[MAX],count=0, HashPrinted=0; //数组a用于存储入度为0的有效数字, count记录数组a有效元素个数 ; HashPrinted记录已打印的数字个数
int index=0; //index记录 被打印数字 对应的 Value数组的下标值(也就是图结点编号)
bool flag = true; //控制空格的打印
while (true)
{
count = 0;
for (int i = 0; i < N; i++)
if (!inDegree[i] && Value[i] > Empty) //所有入度为0的结点且对应的数字为有效值
{
a[count] = Value[i];
count++;
}
sort(a, a + count); //找到入度为0的结点中最小值
if (flag)
{
cout << a[0];
flag = false;
HashPrinted++;
}
else
{
cout << " " << a[0];
HashPrinted++;
}
if (HashPrinted == HashNumber) //HashNumber记录有效数字的总数
break;
//获取对应的Value数组下标
for (int i = 0; i < N; i++)
if (a[0] == Value[i])
index = i;
Value[index] = Empty; //打印之后,擦除
for (int i = 0; i < N; i++)
if (Graph[index][i]) //打印之后,将相关结点的入度-1
inDegree[i]--;
}
}
3. 完整AC编码
如有建议或问题,欢迎留言
#include <iostream>
#include <algorithm> //sort()
using namespace std;
#define MAX 1001
#define Empty -1 //A negative integer represents an empty cell in the hash table
#define noEdge 0
int Graph[MAX][MAX]; //33 1 13 12
int inDegree[MAX];
int Value[MAX];
int N,HashNumber=0;
void CreateGraph();
void Init();
void TopologySort();
int main()
{
int data;
cin >> N;
CreateGraph();
Init();
for (int i = 0; i < N; i++)
{
cin >> data;
if (data > Empty) //非负数就是待插入的数字
HashNumber++;
Value[i] = data;
}
for (int i = 0; i < N; i++)
{
int j = Value[i] % N;
while (j!=i && Value[i]>Empty)
{
Graph[j][i] = 1; //构建有向图
inDegree[i]++; //统计入度
if (++j == N)
j=0;
}
}
TopologySort();
return 0;
}
void TopologySort()
{
int a[MAX],count=0,HashPrinted=0; //数组a用于存储入度为0的数字, count记录其有效元素个数 ; HashPrinted记录已打印的数字
int index=0; //index记录 被打印数字 对应的 Value数组的下标值
bool flag = true;
while (true)
{
count = 0;
for (int i = 0; i < N; i++)
if (!inDegree[i] && Value[i] > Empty) //所有入度为0的结点 且 对应的数字为有效值
{
a[count] = Value[i];
count++;
}
sort(a, a + count); //找到入度为0的结点中最小值
if (flag)
{
cout << a[0];
flag = false;
HashPrinted++;
}
else
{
cout << " " << a[0];
HashPrinted++;
}
if (HashPrinted == HashNumber)
break;
//获取对应的数组下标
for (int i = 0; i < N; i++)
if (a[0] == Value[i])
index = i;
Value[index] = Empty; //打印之后,擦除
for (int i = 0; i < N; i++)
if (Graph[index][i]) //打印之后,将相关结点的入度-1
inDegree[i]--;
}
}
void CreateGraph()
{
for (int i = 0; i < N; i++) //构建一个空图,将==Hash表的表大小N==抽象为N个点,从 0 至 N-1 编号
for (int j = 0; j < N; j++)
Graph[i][j] = noEdge;
}
void Init()
{
for (int i = 0; i < N; i++)
{
inDegree[i] = 0;
Value[i] = Empty;
}
}