关于安卓图形解锁密码的设计

  • 关于安卓图形解锁有关的思考
  • 1.路径规则:
  • 2.命名规则
  • 3.算法分析
  • 4.仿真结果
  • 4.1最长路径
  • 4.2最短路径
  • 4.3 路径种类
  • 4.4 长路径
  • 4.5 短路径
  • 4.6 常见路径与罕见路径
  • 4.8 统计信息
  • 附:Matlab程序


关于安卓图形解锁有关的思考

在知乎上回答了一个问题,是关于安卓图形解锁界面的,大致讲如何选择一个较为复杂的安卓解锁图形。略加改善。
本文只考虑图形解锁走过全部9个点的图形,分析关于最长路径、最短路径、最常见路径、最罕见路径等,并给出一种基于Matlab的实现

1.路径规则:

画出的路径不能重复经过同一点,不能经过未访问的点。

2.命名规则

Android密码锁 安卓密码锁解除绘画图_算法

{1,3,7,9}四个顶点是a类;{2,4,6,8}四个边点是b类;{5}中心点是c类。同行/列之间相邻的点距离为1。

3.算法分析

  1. 我们称走过的点为访问点;没走过的点为未访问点;判决算法运行时,当前位置称为当前点;从当前点出发可以到达的点称为当前可行点;
  2. a,b,c类之间可以任意访问,如当前点为a类时,任意b/c类未访问点都是可行点
  3. 当前点为a类点,当前可行点也为未访问a类点的充要条件是两个a类点连线上的b、c类点为访问点
  4. 当前点为b类点,当前可行点可以是不同行不同列的未访问b类点,当c类点是访问点时,b类点可以访问所有未访问点
    后两条是比较容易忽视的,这会导致在计算最长路径时出错。以此规则编写程序(程序见文末),可以获得所有可能的路径。

4.仿真结果

通过仿真可知,

4.1最长路径

最长路径为17.78,共8种;也是最少见的,给出其中一种:

Android密码锁 安卓密码锁解除绘画图_matlab_02

4.2最短路径

最短路径为显而易见的8,共40种;给出其中的一种:

Android密码锁 安卓密码锁解除绘画图_算法_03

4.3 路径种类

一共有304种不同长度的路径;

4.4 长路径

最长路径前3名为17.42 17.60 17.78;给出一种次长路径(17.60)的画法:

Android密码锁 安卓密码锁解除绘画图_算法_04


给出一种较长路径(17.42)的画法:

Android密码锁 安卓密码锁解除绘画图_Android密码锁_05

4.5 短路径

最短路径前3名为8.00 8.41 8.83; 给出次短路径的画法:

Android密码锁 安卓密码锁解除绘画图_Android密码锁_06

给出较短路径(8.83)的画法

Android密码锁 安卓密码锁解除绘画图_Android密码锁_07

4.6 常见路径与罕见路径

相同长度的路径中,长度为12.30包含的路径最多,有4160种,给出其中一种:

Android密码锁 安卓密码锁解除绘画图_算法_08


相同长度的路径中,长度为(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的一种:

Android密码锁 安卓密码锁解除绘画图_最长路_09

4.8 统计信息

最后给出统计结果:不同的长度vs画法个数

Android密码锁 安卓密码锁解除绘画图_最大路径_10


可以看出长度Android密码锁 安卓密码锁解除绘画图_最长路_11在8~12之间比较稀疏而Android密码锁 安卓密码锁解除绘画图_matlab_12以后比较密集这是因为长度只有Android密码锁 安卓密码锁解除绘画图_Android密码锁_13这几种,因此起始时比较稀疏,主要因为包含长度为1的路径比较多。

注:在程序编写时为了计算方便,我只递归计算了1种开始点为a类点和1种开始点为b类点,然后Android密码锁 安卓密码锁解除绘画图_最大路径_14得到的结果。因为根据对称性和旋转对称性可知,不同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,即半秒一个点):

Android密码锁 安卓密码锁解除绘画图_最大路径_15