算法过程:
1 将顶点按x坐标递增排序,若x相同,按y坐标递增排序,然后枚举所有边,对每一条由点p1和p2(根据排序p1 < p2)组成的边按照如下方式可唯一确定一个正方形:
1) 将边绕p1逆时针旋转90度得到点p3
2) 将边绕p2顺时针旋转90度得到点p4
则p1 p2 p3 p4组成一个正方形,设p1 = (x1,y1), p2 = (x2, y2),根据向量的旋转公式可以求出p3, p4的坐标为
p3 = (y1 - y2 + x1, x2 - x1 + y1)
p4 = (y1 - y2 + x2, x2 - x1 + y2)
2 然后搜索点p3和p4是否存在,若存在则找到一个正方形,计数加1,可以发现总是存在两条边确定的正方形是一样的,也就是说每个正方形会被发现2次,所以要将最后的计数结果除以2.
实现的时候关键是如何搜索某个点是否存在,由于所有点都排序过,所以可以用二分查找来搜索,但速度比较慢,至少1000ms, hash的速度更快些,可以达到几百ms,还有hash表如果用开放地址线性探测法解决冲突的话,很容易超时,
而用链地址法解决冲突效果要好很多。下面是二分搜索和hash两种方法的代码实现。
二分查找
#include <stdio.h>
#include <string.h>
//#define DEBUG
#ifdef DEBUG
#define debug(...) printf( __VA_ARGS__)
#else
#define debug(...)
#endif
#define N 1000
struct Point {
int x;
int y;
};
struct Point point[N];
int n; /* 点的个数 */
/* 由于点已经按照坐标排序过,所以采用二分查找
* 搜索点(x,y)是否存在,存在返回1,否则返回0
*/
int bsearch(int x, int y)
{
int m, s, t;
s = 0;
t = n-1;
while (s <= t) {
m = (s+t)/2;
if (point[m].x == x && point[m].y == y) return 1;
if (point[m].x > x || (point[m].x == x && point[m].y > y)) {
t = m-1;
}
else {
s = m+1;
}
}
return 0;
}
int main()
{
int x, y, i, j, count;
while (scanf("%d", &n), n) {
count = 0;
for (i = 0; i < n; i++) {
scanf("%d %d", &x, &y);
//插入法对点排序,按照x从小到大,y从小到大,且x优先排列的方式
for (j = i-1; j >= 0; j--) {
if (point[j].x > x || (point[j].x == x && point[j].y > y)) {
point[j+1] = point[j];
} else {
break;
}
}
point[j+1].x = x;
point[j+1].y = y;
}
/* 枚举所有边,对每条边的两个顶点可以
* 确定一个唯一的正方形,并求出另外两个顶点的坐标
*/
for (i = 0; i < n; i++) {
for (j = (i+1); j < n; j++) {
//计算第三个点的坐标,搜索其是否存在
x = point[i].y-point[j].y+point[i].x;
y = point[j].x-point[i].x+point[i].y;
if (bsearch(x,y) == 0) {
continue;
}
//计算第四个点的坐标,搜索其是否存在
x = point[i].y-point[j].y+point[j].x;
y = point[j].x-point[i].x+point[j].y;
if (bsearch(x, y)) {
count++;
}
}
}
printf("%d\n", count/2);
}
return 0;
}
hash
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#define DEBUG
#ifdef DEBUG
#define debug(...) printf( __VA_ARGS__)
#else
#define debug(...)
#endif
#define N 1000 /* 顶点个数 */
#define M 2999 /* hash表的大小,取素数冲突冲突较少 */
struct Point {
int x;
int y;
};
struct Point point[N];
/* 使用链地址法解决冲突, 表头不存数据 */
struct hash_entry {
int x;
int y;
struct hash_entry *next;
};
struct hash_entry hash_table[M+1];
int conflict;
void insert(int x, int y)
{
unsigned int p;
struct hash_entry *new_entry;
p = (x*x+y*y)%M; /* hash函数 */
//创建一个新的entry
new_entry = (struct hash_entry *)malloc(sizeof(struct hash_entry));
new_entry->x = x;
new_entry->y = y;
/* 把新entry插在最前面,这样,越先插进来的entry在链表的越后面,
*最后一个entry的next指针为空
*/
new_entry->next = hash_table[p].next;
hash_table[p].next = new_entry;
}
int find(int x, int y)
{
unsigned int p;
struct hash_entry *entry;
p = (x*x+y*y)%M; /* hash函数 */
for(entry = hash_table[p].next; entry != 0 && (entry->x != x \
|| entry->y != y); entry = entry->next, conflict++);
if (entry) {
return 1;
}
return 0;
}
int main()
{
int n, x, y, i, j, count;
while (scanf("%d", &n), n) {
memset(hash_table, 0, sizeof(hash_table));
count = 0;
for (i = 0; i < n; i++) {
scanf("%d %d", &x, &y);
//插入法对点排序,按照x从小到大,y从小到大,且x优先排列的方式
for (j = i-1; j >= 0; j--) {
if (point[j].x > x || (point[j].x == x && point[j].y > y)) {
point[j+1] = point[j];
} else {
break;
}
}
point[j+1].x = x;
point[j+1].y = y;
insert(x, y);
}
/* 枚举所有边,对每条边的两个顶点可以
* 确定一个唯一的正方形,并求出另外两个顶点的坐标
*/
for (i = 0; i < n; i++) {
for (j = (i+1); j < n; j++) {
//计算第三个点的坐标,搜索其是否存在
x = point[i].y-point[j].y+point[i].x;
y = point[j].x-point[i].x+point[i].y;
if (!find(x, y)) {
continue;
}
//计算第四个点的坐标,搜索其是否存在
x = point[i].y-point[j].y+point[j].x;
y = point[j].x-point[i].x+point[j].y;
if (find(x, y)) {
count++;
}
}
}
printf("%d\n", count/2);
}
debug("conflict = %d\n", conflict);
return 0;
}
寻找几何正方形
本文介绍了一种通过给定点集寻找所有可能形成的正方形的方法。利用点的坐标排序和旋转公式,算法能高效地找出所有正方形。提供了两种实现方案:二分查找和哈希表,后者使用链地址法解决冲突。
165

被折叠的 条评论
为什么被折叠?



