问题描述
在某图形操作系统中,有 N 个窗口,每个窗口都是一个两边与坐标轴分别平行的矩形区域。窗口的边界上的点也属于该窗口。窗口之间有层次的区别,在多于一个窗口重叠的区域里,只会显示位于顶层的窗口里的内容。
当你点击屏幕上一个点的时候,你就选择了处于被点击位置的最顶层窗口,并且这个窗口就会被移到所有窗口的最顶层,而剩余的窗口的层次顺序不变。如果你点击的位置不属于任何窗口,则系统会忽略你这次点击。
现在我们希望你写一个程序模拟点击窗口的过程。
输入格式
输入的第一行有两个正整数,即 N 和 M。(1 ≤ N ≤ 10,1 ≤ M ≤ 10)
接下来 N 行按照从最下层到最顶层的顺序给出 N 个窗口的位置。 每行包含四个非负整数 x1, y1, x2, y2,表示该窗口的一对顶点坐标分别为 (x1, y1) 和 (x2, y2)。保证 x1 < x2, y1 < y2。
接下来 M 行每行包含两个非负整数 x, y,表示一次鼠标点击的坐标。
题目中涉及到的所有点和矩形的顶点的 x, y 坐标分别不超过 2559 和 1439。
问题分析:这个问题可以用链式线性表来实现。
输出格式:输出包括 M 行,每一行表示一次鼠标点击的结果。如果该次鼠标点击选择了一个窗口,则输出这个窗口的编号(窗口按照输入中的顺序从 1 编号到 N);如果没有,则输出"IGNORED"(不含双引号)。
样例输入
3 4
0 0 4 4
1 1 5 5
2 2 6 6
1 1
0 0
4 4
0 5
样例输出
2
1
1
IGNORED
样例说明
第一次点击的位置同时属于第 1 和第 2 个窗口,但是由于第 2 个窗口在上面,它被选择并且被置于顶层。
第二次点击的位置只属于第 1 个窗口,因此该次点击选择了此窗口并将其置于顶层。现在的三个窗口的层次关系与初始状态恰好相反了。
第三次点击的位置同时属于三个窗口的范围,但是由于现在第 1 个窗口处于顶层,它被选择。
最后点击的 (0, 5) 不属于任何窗口。
问题分析
类比
想想当我们使用鼠标点击时的情景,可以把一个个窗口看做一叠纸叠合(不一定是整齐叠 放)而成,这叠纸放在桌面上,把鼠标看成一根针,现在拿针去刺那叠纸,若是针直接扎到桌面上,说明“点击无效”;若扎到了纸,说明“点击有效”,则将第一张与针尖接触的纸抽出来,放在最顶部。联系数据结构,可将每张纸抽象成一个节点,使用单向链表存储
数据结构
逻辑结构:链式结构
物理结构:单向带头结点链表
节点设计:两组坐标,一个编号
算法
对于每一组鼠标点击坐标,若从顶窗口开始遍历窗口链表,若找到符合条件的窗口,立马返回该窗口编号;若无符合条件窗口,返回-1。返回值是为了便于判断输出时的值
代码实现
该题是为了练习数据结构中的单向带头结点链表,所以会一步步构建链表及其基本操作,按顺序实现如下函数(以下也是编程时步步为营的思想,反复迭代,最终解决问题):
- 初始化带头结点的空链表
- 添加指定个数的链表(使用头插法,为了适应题目:最后输入为顶窗口)
- 遍历链表(仅仅是为了测试链表是否创建成功)
- 模拟鼠标点击事件(核心功能)
- 主函数中的输出函数(比较简单)
C/C++
#include<iostream>
using namespace std;
#include<stdlib.h>
//窗口节点:包含一组对角坐标,窗口编号
typedef struct LNode
{
int x1, y1;
int x2, y2;
int id;
struct LNode *next;
}LNode, *LinkList;
//初始化空链表
void InitList(LinkList &L)
{
L = (LinkList)malloc(sizeof(LNode));
L->next = NULL;
}
//创建指定个数的链表
//使用头插法建立链表,因为依题意可知最后输入的窗口作为顶层窗口
//而鼠标点击也是从最顶开始考虑的 ,若是使用尾插法则遍历有些困难
void CreateList(LinkList &L, int n)
{
LNode *p;
for(int i = 0; i < n; i++)
{
p = (LNode*)malloc(sizeof(LNode));
cin >> p->x1 >> p->y1;
cin >> p->x2 >> p->y2;
p->id = i + 1;
//插入头结点后第一个位置
p->next = L->next;
L->next = p;
}
}
//遍历链表:在此仅仅是为了测试链表是否建立成功
void TraverseList(LinkList L)
{
if(L->next == NULL)
{
cout<<"空表!"<<endl;
return;
}
LNode *pcur;
pcur = L->next;
while(pcur)
{
cout<<" id: "<<pcur->id<< " x1: "<<pcur->x1<<" x2: "<<pcur->y1
<<" x2: "<<pcur->x2<<" y2: "<<pcur->y2<<endl;
pcur = pcur->next;
}
}
//模拟鼠标点击事件
//从顶窗口开始遍历,若找到符合条件的窗口,立马返回该窗口编号;若无符合条件窗口,返回-1
//返回值是为了便于判断输出时的值
int SimuClick(LinkList &L, int x, int y)
{
LNode *pcur, *ppre;
ppre = L;
pcur = ppre->next;
//从顶开始遍历窗口链表,这也是为什么使用头插法建立链表的原因
while(pcur)
{
//是否在当前窗口内(等于也包括在内)
//若该点击在当前窗口内,先将其移到头结点后第一位
if(x >= pcur->x1 && x <= pcur->x2 && y <= pcur->y2 && y >= pcur->y1)
{
ppre->next = pcur->next;//防止断链
pcur->next = L->next;//移接到头结点后
L->next = pcur;
return pcur->id;//直接返回编号
}
else//不在当前窗口内,继续查找
{
ppre = pcur;
pcur = pcur->next;
}
}
return -1;//若是执行到此步,说明没有找到一个窗口包含此次点击,返回一个标志
}
int main()
{
LinkList L;
int n, m, x, y;
InitList(L);
cin >> n >> m;
CreateList(L , n);
// TraverseList(L);
int state[m+1];//为了记录输出时的内容
//输入一次点击就计算一次
for(int i = 0; i < m; i++)
{
cin >> x >> y;
state[i] = SimuClick(L, x, y);
}
//输出结果
for(int i = 0; i < m; i++)
{
if(state[i] == -1)
{
cout<<"IGNORED"<<endl;//英文单词千万别打错,最好直接复制
}
else
{
cout<<state[i]<<endl;
}
}
return 0;
}
体会
- 读题后,立马跟着用例跑一遍,确认理解无误,若是比较难理解,可以先使用类比方法帮助理解
- 抽象出对象,找出关系,再选择数据结构,再调整算法,代码实现是需清醒,考虑边界问题
- 做题时需注意每个细节,如输出的单词不可拼写错误