关于安卓图形解锁密码的设计
关于安卓图形解锁有关的思考
在知乎上回答了一个问题,是关于安卓图形解锁界面的,大致讲如何选择一个较为复杂的安卓解锁图形。略加改善。
本文只考虑图形解锁走过全部9个点的图形,分析关于最长路径、最短路径、最常见路径、最罕见路径等,并给出一种基于Matlab的实现
1.路径规则:
画出的路径不能重复经过同一点,不能经过未访问的点。
2.命名规则
{1,3,7,9}四个顶点是a类;{2,4,6,8}四个边点是b类;{5}中心点是c类。同行/列之间相邻的点距离为1。
3.算法分析
- 我们称走过的点为访问点;没走过的点为未访问点;判决算法运行时,当前位置称为当前点;从当前点出发可以到达的点称为当前可行点;
- a,b,c类之间可以任意访问,如当前点为a类时,任意b/c类未访问点都是可行点
- 当前点为a类点,当前可行点也为未访问a类点的充要条件是两个a类点连线上的b、c类点为访问点;
- 当前点为b类点,当前可行点可以是不同行不同列的未访问b类点,当c类点是访问点时,b类点可以访问所有未访问点;
后两条是比较容易忽视的,这会导致在计算最长路径时出错。以此规则编写程序(程序见文末),可以获得所有可能的路径。
4.仿真结果
通过仿真可知,
4.1最长路径
最长路径为17.78,共8种;也是最少见的,给出其中一种:
4.2最短路径
最短路径为显而易见的8,共40种;给出其中的一种:
4.3 路径种类
一共有304种不同长度的路径;
4.4 长路径
最长路径前3名为17.42 17.60 17.78;给出一种次长路径(17.60)的画法:
给出一种较长路径(17.42)的画法:
4.5 短路径
最短路径前3名为8.00 8.41 8.83; 给出次短路径的画法:
给出较短路径(8.83)的画法
4.6 常见路径与罕见路径
相同长度的路径中,长度为12.30包含的路径最多,有4160种,给出其中一种:
相同长度的路径中,长度为(13.83、13.90、14.66、15.14、15.78、16.30、16.59、16.61、16.83、16.95、17.01、17.13、17.19、17.19、17.24、17.37、17.78)的路径最少,各只有8种,给出其中最短长度13.83的一种:
4.8 统计信息
最后给出统计结果:不同的长度vs画法个数
可以看出长度
L
L
L在8~12之间比较稀疏而
L
>
12
L>12
L>12以后比较密集这是因为长度只有
1
,
2
,
2
,
5
,
2
2
1,\sqrt{2},2,\sqrt{5},2\sqrt{2}
1,2,2,5,22这几种,因此起始时比较稀疏,主要因为包含长度为1的路径比较多。
注:在程序编写时为了计算方便,我只递归计算了1种开始点为a类点和1种开始点为b类点,然后 × 4 \times{4} ×4得到的结果。因为根据对称性和旋转对称性可知,不同a类点(或不同b类点)之间,产生的路径除了在命名上有不同以外没有差异。
附:Matlab程序
主程序(用于统计长度)
[~,count1] = wherecango(5,[5,zeros(1,8)],1,0);
[~,count2] = wherecango(3,[3,zeros(1,8)],1,0);
[~,count3] = wherecango(2,[2,zeros(1,8)],1,0);
total = count3*4+count*4+count1;
disp(total);
dist1 = zeros(1,count1);
dist2 = zeros(1,count2);
dist3 = zeros(1,count3);
[~,count1,dist1] = wherecango_dist(5,[5,zeros(1,8)],1,0,dist1);
[~,count2,dist2] = wherecango_dist(3,[3,zeros(1,8)],1,0,dist2);
[~,count3,dist3] = wherecango_dist(2,[2,zeros(1,8)],1,0,dist3);
dist2 = repmat(dist2,1,4);
dist3 = repmat(dist3,1,4);
dist = [dist1,dist2,dist3];
figure();
axes = gca;
x=unique(dist);
y=zeros(1,length(x));
for i=1:length(dist)
idx = find(x==dist(i));
y(idx) = y(idx)+1;
end
stem(axes,x,y,'Markersize',1);
title('\fontname{宋体}路径统计','FontSize',16);
xlabel('\fontname{宋体}长度','FontSize',14);
ylabel('\fontname{宋体}个数','FontSize',14);
路径递归搜索算法wherecango.m,以此函数统计距离dist数组个数。
function [where_arr,count] = wherecango(now,previous,looptime,count)
% where<0 说明无处可去
% previous 记录当前迭代下的路径
looptime = looptime+1;
if(looptime==10)
disp(previous);
where_arr = -1;
count = count+1;
return;
end
where_arr = judge(now,previous);
if(isempty(where_arr))
where_arr = -1;
else
idx_total = length(where_arr);
idx = 1;
while idx <= idx_total
now = where_arr(idx);
previous(looptime) = now;
temp = where_arr;
[where_arr,count] = wherecango(now,previous,looptime,count);
if(where_arr(1)<0)
idx = idx+1;
where_arr = temp;
end
end
where_arr = -1;
end
end
其中包含的子函数,就是可行点决策函数judge.m
function arr = judge(now,previous)
switch now
case {1}
res = [2 4 5 6 8];
if(ismember(4,previous))
res = [res,7];
end
if(ismember(2,previous))
res = [res,3];
end
if(ismember(5,previous))
res = [res,9];
end
case {3}
res = [2 4 5 6 8];
if(ismember(6,previous))
res = [res,9];
end
if(ismember(2,previous))
res = [res,1];
end
if(ismember(5,previous))
res = [res,7];
end
case {9}
res = [2 4 5 6 8];
if(ismember(6,previous))
res = [res,3];
end
if(ismember(8,previous))
res = [res,7];
end
if(ismember(5,previous))
res = [res,1];
end
case {7}
res = [2 4 5 6 8];
if(ismember(4,previous))
res = [res,1];
end
if(ismember(8,previous))
res = [res,9];
end
if(ismember(5,previous))
res = [res,3];
end
case {2}
res = [1 3 4 5 6 7 9];
if(ismember(5,previous))
res = [res,8];
end
case {8}
res = [1 3 4 5 6 7 9];
if(ismember(5,previous))
res = [res,2];
end
case {4}
res = [1 2 3 5 7 8 9];
if(ismember(5,previous))
res = [res,6];
end
case {6}
res = [1 2 3 5 7 8 9];
if(ismember(5,previous))
res = [res,4];
end
case 5
res = [1 2 3 4 6 7 8 9];
end
arr = setdiff(res,previous);
end
包含距离计算的wherecango_dist.m。因为画gif时,懒得单独再写寻找指定长度的路径脚本,我就在开头的looptime10的判断语句里面设置的条件断点,再用后面的basic_9gongge.m在断点内画的动图(不知道说明白没有)。
function [where_arr,count,dist] = wherecango_dist(now,previous,looptime,count,dist)
% where<0 说明无处可去
% previous 记录当前迭代下的路径
looptime = looptime+1;
if(looptime==10)
disp(previous);
where_arr = -1;
count = count+1;
temp_d = cal_dist(previous);
dist(count) = temp_d;
return;
end
where_arr = judge(now,previous);
if(isempty(where_arr))
where_arr = -1;
else
idx_total = length(where_arr);
idx = 1;
while idx <= idx_total
now = where_arr(idx);
previous(looptime) = now;
temp = where_arr;
[where_arr,count,dist] = wherecango_dist(now,previous,looptime,count,dist);
if(where_arr(1)<0)
idx = idx+1;
where_arr = temp;
end
end
where_arr = -1;
end
end
这里面的两个子函数比较简单:分别是把1~9转换成二维坐标的number2coordinates.m和计算一种画法总长度的cal_dist.m,(注意,生成动图的程序也用到number2coordinates.m了)
function dist = cal_dist(arr)
dist = 0;
for i=1:8
coor1 = number2coordinates(arr(i));
coor2 = number2coordinates(arr(i+1));
temp = sqrt(sum((coor1-coor2).^2));
dist = dist+temp;
end
end
function [coor] = number2coordinates(number)
coor(1) = mod(number,3);
coor(2) = ceil(number/3);
if(coor(1)==0),coor(1)=3;end
end
最后是生成gif的程序basic_9gongge.m
%% basic_9gongge
function [] = basic_9gongge(arr)
dt = .5;
COOR = zeros(9,2);
for ii = 1:9
COOR(ii,:) = number2coordinates(arr(ii));
end
fig1 = figure();
set(fig1,'Position',[100,100,600,600]);
RR = 0.3;
for ii=1:3
for jj=1:3
pos = [ii-RR,jj-RR,2*RR,2*RR];
rectangle('position',pos,'curvature',[1,1]);
hold on;
end
end
axis off;
frame = getframe(gcf);
frame = frame2im(frame);
[frame,map] = rgb2ind(frame,256);
imwrite(frame,map,'gongge9.gif','LoopCount',Inf,'DelayTime',dt);
for ii = 1:8
plot([COOR(ii,1),COOR(ii+1,1)],[COOR(ii,2),COOR(ii+1,2)],'r','LineWidth',3)
scatter(COOR(ii,1),COOR(ii,2),'SizeData',40,'MarkerFaceColor','r','MarkerEdgeColor','r');
scatter(COOR(ii+1,1),COOR(ii+1,2),'SizeData',40,'MarkerFaceColor','r','MarkerEdgeColor','r');
frame = getframe(fig1);
frame = frame2im(frame);
[frame,map] = rgb2ind(frame,256);
imwrite(frame,map,'gongge9.gif','WriteMode','append','DelayTime',dt);
end
end
注意此函数是独立的,没用一个函数依赖于它(但是它需要number2coordinates.m),因此下面给出一个他的用法:
basic_9gongge(1:9)
输出结果自动保存于当前文件夹下(当前帧率2,即半秒一个点):