zoukankan      html  css  js  c++  java
  • 最长回文

    最长回文字符串

     

    回文串就是一个正读和反读都一样的字符串,比如“level”或者“noon”等等就是回文串。回文子串,顾名思义,即字符串中满足回文性质的子串。比如输入字符串 "google”,由于该字符串里最长的对称子字符串是 "goog”,因此输出4。

    1.问题解决的基本方法

    分析:可能很多人都写过判断一个字符串是不是对称的函数,这个题目可以看成是该函数的加强版。 
    要判断一个字符串是不是对称的,不是一件很难的事情。我们可以先得到字符串首尾两个字符,判断是不是相等。如果不相等,那该字符串肯定不是对称的。否则我们接着判断里面的两个字符是不是相等,以此类推。

    复制代码
    /*
     *作者:侯凯
     *说明:求最长回文字符串
     *日期:2013-10-15
    */
    #include<iostream>
    using namespace std;
    //字符串是否对称
    bool isAym(char *cbegin, char *cend)
    {
        if(cbegin == NULL || cend ==NULL || cbegin > cend)
        {
            return false;
        }
        while(cbegin<cend)
        {
            if(*cbegin!=*cend)
            {
                return false;
            }
            cbegin++;
            cend--;
        }
        return true;
    }
    复制代码

    现在我们试着来得到对称子字符串的最大长度。最直观的做法就是得到输入字符串的所有子字符串,并逐个判断是不是对称的。如果一个子字符串是对称的,我们就得到它的长度,最后经过比较,就能得到最长的对称子字符串的长度了。

    复制代码
    //O(n*n*n)复杂度的子字符串
    int getMaxSym(char * str)
    {
        if(str == NULL)
            return 0;
        int maxlength = 0, strlength = 0;
        char *pFirst = str;
        char *strEnd = str + strlen(str);
        while(pFirst < strEnd)
        {
            char *pLast = strEnd;
            while(pLast > pFirst)
            {
                if(isAym(pFirst, pLast))
                {
                    strlength = pLast - pFirst + 1;
                    if(strlength > maxlength)
                    {
                        maxlength = strlength;
                    }
                }
                pLast --;
            }
            pFirst ++;
        }
        return maxlength;
    }
    复制代码

    上述方法的时间效率:由于需要两重while循环,每重循环需要O(n)的时间。另外,我们在循环中调用了IsSym,每次调用也需要O(n)的时间。因此整个函数的时间效率是O(n^3)。 
    假设输入:abcddcba,按照上述程序,要分割成 'abcddcba’, 'bcddcb’, 'cddc’, 'dd’…等字符串,并对这些字符串分别进行判断。不难发现,很多短子字符串在长些的子字符串中比较过,这导致了大量的冗余判断,根本原因是:对字符串对称的判断是由外向里进行的。 
    换一种思路,从里向外来判断。也就是先判断子字符串(如dd)是不是对称的。如果它(dd)不是对称的,那么向该子字符串两端各延长一个字符得到的字符串肯定不是对称的。如果它(dd)对称,那么只需要判断它(dd)两端延长的一个字符是不是相等的,如果相等,则延长后的字符串是对称的。

    2.改进的解决方案

    根据从里向外比较的思路写出如下代码:

    复制代码
    //改进后的程序
    int getMaxSym2(char * str)
    {
        if(str == NULL)
            return 0;
        int maxlength = 0;
        char *ptag = str;
        while(*ptag !='')
        {
            //奇数子字符串
             char *left = ptag - 1;
            char *right = ptag + 1;
            int oddlenght = 1;
            while(left >= str && *right != '' && *left == *right)
            {
                left--;
                right++;
                oddlenght += 2;
            }
            if(oddlenght > maxlength)
            {
                maxlength = oddlenght;
            }
            //偶数子字符串
             left = ptag;
            right = ptag + 1;
            int evenlength = 0;
            while(left >= str && *right != '' && *left == *right)
            {
                left--;
                right++;
                evenlength += 2;
            }
            if(evenlength > maxlength)
            {
                maxlength = evenlength;
            }
    
            ptag++;
        }
        return maxlength;
    }
    复制代码

    由于子字符串的长度可能是奇数也可能是偶数。长度是奇数的字符串是从只有一个字符的中心向两端延长出来,而长度为偶数的字符串是从一个有两个字符的中心向两端延长出来。因此程序中要把这两种情况都考虑进去。 
    由于总共有O(n)个字符,每个字符可能延长O(n)次,每次延长时只需要O(1)就能判断出是不是对称的,因此整个函数的时间效率是O(n^2)。 
    上述方法称为朴素算法,关于字符串的题目常用的算法有KMP、后缀数组、AC自动机,这道题目利用扩展KMP可以解答,其时间复杂度也很快O(N*logN)。但是,这里介绍一个专门针对回文子串的算法,其时间复杂度为O(n),这就是manacher算法。

    3.manacher算法

    算法的基本思路是这样的:把原串每个字符中间用一个串中没出现过的字符分隔#开来(统一奇偶),同时为了防止越界,在字符串的首部也加入一个特殊符$,但是与分隔符不同。同时字符串的末尾也加入''。算法的核心:用辅助数组p记录以每个字符为核心的最长回文字符串半径。也就是p[i]记录了以str[i]为中心的最长回文字符串半径。p[i]最小为1,此时回文字符串就是字符串本身。 
    示例:原字符串 'abba’,处理后的新串 ' $#a#b#b#a#’,得到对应的辅助数组p=[0,1,1,2,1,2,5,2,2,1]。 
    程序如下,对应的变量解释在后面

    复制代码
    //预处理,将str:abba转换为: $#a#b#b#a#(从1开始)
    char * pre(char *str)
    {
        int length = strlen(str);
        char *prestr = new char[2*length + 4];
        prestr[1] = '$';
        for(int i=0;i<length;i++)
        {
            prestr[2*(i+1)] = '#';
            prestr[2*(i+1)+1] = str[i];
        }
        prestr[2*length+2]='#';
        prestr[2*length+3]='';
        return prestr;
    }
    复制代码

    以下是manacher算法的具体实现,包括:辅助数组的构建、最大字符串长度的获取。

    复制代码
    //manacher算法
    int getMaxSym3(char *str)
    {
        char *prestr = pre(str);
        int mx =0, pi=1;//边界和对称中心
        int len = strlen(prestr);
        //辅助数组
        int *p = new int[len];
        p[0] = 0;
        for(int i=1;i<len;i++)
        {
            if(mx>i)
            {
                p[i]=min(mx-i,p[2*pi-i]);//核心
            }
            else
            {
                p[i]=1;
            }
            while(prestr[i-p[i]]==prestr[i+p[i]]&&i-p[i]>0&&i+p[i]<len)
            {
                p[i]++;
            }
            if(i+p[i] > mx)
            {
                mx = p[i] + i;
                pi = i;
            }
        }
        //最大回文字符串长度
        int maxlen = 0;
        for(int i=0;i<len;i++)
        {
            if(p[i]>maxlen)
            {
                maxlen = p[i];
            }
        }
        delete []prestr;
        delete []p;
        return maxlen - 1;
    }
    复制代码

    上面几个变量说明:pi记录具有遍历过程中最长半径的回文字符串中心字符串。mx记录了具有最长回文字符串的右边界。 
    image 
    pi是最长回文字符串(淡蓝色)的中心,如果以j为中心的最大回文串如上如所示,那么i处的情况与j处相同(关于pi的两侧是对称的)。这样便减少了运算量,i的对称位置是2*pi-i。 
    但是有另外一种情况,就是j的一部分超出蓝色部分,这时p[i]=p[j]就不一定对了,如下图 
    image 
    这就为什么有取最小值这个操作:

    if(mx>i)
    {    
        p[i]=min(mx-i,p[2*pi-i]);//核心
    }

    剩下的代码就很容易看懂了。

    最后遍历一边p数组,找出最大的p[i]-1就是所求的最长回文字符串长度,说明如下:
    (1)因为p[i]记录插入分隔符之后的回文字符串半径,所以以i为中心的回文字符串长度为2*p[i]-1。例如:bb=>#b#b#,中间#的半径为3,回文字符串长度为2*3-1; 
    (2)注意上面两个串的关系。 #b#b#减去一个#号的长度就是原来的2倍。即((2*p[i]-1)-1)/2 = p[i]-1,得证。

     

    ASP.NET MVC4+EasyUI+EntityFrameWork权限管理系统——数据库的设计(一)

     

      快一年没写博客了,这段时间感觉好迷茫,写点博客,记录一下自己的成长过程,希望对大家也有帮助

      先上图

      一个用户可以有多个角色,一个用户可以属于多个部门,这些都可以控制到权限,有时特殊要求,同样的部门和角色不一定有同样的权限(虽然可以定义多个角色来实现,但是如果这种情况多了就有点麻烦了),所以还弄了个基于用户的权限,这样就可以很灵活的控制了。

      每个模块的包含那些可控制的权限,用一个int类型字段控制,下边用一个枚举位标示出了系统所有的可能的权限(根据实际情况)

      

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    using System;
     
    namespace Mydream.Permission.Common
    {
        [Flags]
        public enum Authority : int
        {
            /// <summary>
            /// 浏览权限
            /// </summary>
            Index = 1,
            /// <summary>
            /// 添加权限
            /// </summary>
            Add = 2,
            /// <summary>
            /// 编辑权限
            /// </summary>
            Edit = 4,
            /// <summary>
            /// 删除权限
            /// </summary>
            Delete = 8,
            /// <summary>
            /// 搜索权限
            /// </summary>
            Search = 16,
            /// <summary>
            /// 审核权限
            /// </summary>
            Verify = 32,
            /// <summary>
            /// 移动权限
            /// </summary>
            Move = 64,
            /// <summary>
            /// 打印权限
            /// </summary>
            Print = 128,
            /// <summary>
            /// 下载权限
            /// </summary>
            Download = 256,
            /// <summary>
            /// 备份权限
            /// </summary>
            Backup = 512,
            /// <summary>
            /// 授权权限
            /// </summary>
            Grant = 1024,
            /// <summary>
            /// 查看详细权限
            /// </summary>
            View = 2048,
            /// <summary>
            /// 导出权限
            /// </summary>
            Export = 4096
        }
    }

    这些枚举值全部用的2的次幂,之所有这样赋值,是为了用2进制来控制权限

    比如某个模块(模块A)有浏览、添加、编辑、删除、打印的权限,那么这个模块的int值=Index|Add|Edit|Delete|Print

     00000001(Index=2^0=1)

     00000010 (Add=2^1=2)

     00000011 3

     00000100(Edit=2^2=4)

     00000111 7

     00001000(Delete=2^3=8)

     00001111 15

     1000000 (Print=2^7=128)

     10001111 143

        最后的值其实就是1+2+4+8+128=143 所以这个模块对应的权重就是143(这个只是说明这个模块包含哪些功能)

      如果一用户属于角色A,角色A包含模块A中的浏览和添加权限(1+4=5),那么这个角色A 就对应了  (角色A的ID 模块A的ID 权重=5)

      我们的Action的命名按照枚举那样命名,然后每次操作的时候用这个权限INT值,然后if(INT值&Action命名的枚举==Action命名的枚举)如果为True说明有执行这个Action的权限

          

      由于权限管理系统还在业余学习研究中,很多还没有实现,上面主要讲解了数据库的设计,后边的实现会慢慢呈现,最近都很忙,也没怎么写这个业余学习的例子了,后续有更新会慢慢放上来,写得不好请包涵,如有好的意见请赐教,谢谢!

     
     
    分类: 权限管理
     
  • 相关阅读:
    活动投票
    人品问题
    网站记录
    浅谈底层常数优化及编译器优化
    透过用户思维谈程序员的进阶之路
    我们为什么要学习?写给我的组员们
    原来你是这样的Websocket--抓包分析
    我看依赖注入
    使用反射+策略模式代替项目中大量的switch case判断
    JavaScript 词法作用域不完全指北
  • 原文地址:https://www.cnblogs.com/Leo_wl/p/3373081.html
Copyright © 2011-2022 走看看