Loading [MathJax]/jax/element/mml/optable/BasicLatin.js

循环不变量

  1. 循环不变量(分治结束标志量):在循环中定义不发生变化的量,当不满足定义时缩小规模。

    关于循环不变量定义的条件,我们是有充分条件必要条件之分的。

    1. 充分条件:这个条件一旦满足,说明循环不变量满足定义,过程立即结束;
    2. 必要条件:假设这个循环不变量满足其定义,那么一定满足必要条件,否则它必然不满足定义。但是如果加上某个f(),将可以被转换为其对应的充分条件;

    所以我们首先需要满足必要条件(这些必要条件应该是易得到的),并将这些必要条件从大到小依次排序(包含)。

    x[i] = 必要条件[i] from 必要条件[1] to 必要条件[n]: 
    	if f(x[i]) then satisfy 充分条件[i], break;
    	else i++;
    

    或者

    while(i<n){
    	if f(必要条件[i]) then satisfy 充分条件[i], break;
    	else i++;	// 缩小规模(也可以说是“递归”)
    }
    

    较大的必要条件满足f为真,则可以转换为充分条件;否则继续检查较小的必要条件。这一过程直到某一必要条件成功转换,或者所有必要条件都不能转换。若出现后者这样的情况,我们还需要设置边界条件,通常还被称为初始条件(因为是在循环之前设置的特殊值)。

    initialize(n, 充分条件[n]);	// 当必要条件达到第n个时,设置充分条件[n]
    while(i<n){
        if i == n || f(必要条件[i]) then satisfy 充分条件[i], break;
        else i++;	// 缩小规模(也可以说是“递归”)
    }
    

    不过一般的,这样的必要条件组我们是不清楚具体多长,只能通过“上一个不满足,然后规模缩小到下一个必要条件”这样的类似递归的方式前进。

    initialize(border);	// 这时具体问题具体分析边界条件是什么,假设为border
    while(true){// 这表示只是解决一个问题,如果是解决多个性质相同的问题,while里面则是问题规模
        if border(必要条件) || f(必要条件) then satisfy 充分条件, break;
        else 必要条件 = next(必要条件);	// 递归思想,实现自我覆盖
    }
    
  2. KMP算法中的循环不变量分析

    如果在j位上失配,则假设下次匹配在kj处开始。很显然这个kj就是循环不变量,那么我们给出它的定义:“子串[j-1]中最长重复前后缀子串的长度+1”,记为kj=max

    显然k_{j+1}(最大)必要条件就是“子串[k_j-1]=[\max(j-1)]”与“子串[j-1]中等长的后缀”相等(就是下k_j的充分条件),(最小)充分条件是“子串[k_j]=[\max(j)]”与“子串[j-1]中等长的后缀”相等。这两者之间相差的条件是“T[j]=T[k_j]”(第j位与第k_j位匹配)。

    如果不满足转换条件,就需找第二必要条件:“子串[k′_{j+1}-1]”与“子串[j-1]中等长的后缀”相等,而根据k_j的充分条件可知:“子串[j-1]中等长的后缀”与“子串[k_j-1]中等长的后缀”相等,所以其实也就是k_{k_j}的充分条件……依次类推,我们可以得出结论:k_{j+1}的必要条件簇为k_j,k_{k_j},…,k_{…k_j}的充分条件。

    KMP算法

    它的边界条件就是:当1号位都不匹配时,必须规定一个数值,这里我们定义为0。

    void GetNext(MString T, int*& next){
        // 定义当前失配指针(cmm)和下次匹配开始指针(nbm)
        int curMissMatch = 1, nextBeginMatch;
        // 设置边界条件:当第1位不匹配时,规定下一次从0位开始匹配
        nextBeginMatch = next[curMissMatch] = 0;
        while(curMissMatch < StrLen(T)){// 设置问题规模:from 1 to len
            if(nextBeginMatch == 0 || (T[curMissMatch] == T[nextBeginMatch])){
                // 如果达到边界调教或者满足转换条件则:直接+1。移动cmm相当于break,跳出当前问题
                next[++curMissMatch] = ++nextBeginMatch;
            }
            else{
                // 如果没有达到上述条件:向后迭代。不移动cmm:相当于一直在同一个问题中迭代
                nextBeginMatch = next[nextBeginMatch];
            }
        }
    }
    
  3. ColorSort中的循环不变量分析

    很显然有两个分隔指针,一个是zeroCursor,在其左边全是0;另一个是twoCursor,在其右边全是2。所以循环不变量就是它俩。给出的定义也就是“左边全是0”和“右边全是2”(也是充分条件)。

    所以k′_z=k_z+1最大必要条件是“0\sim k_z全是0”(k_z充分条件),最小充分条件为“0\sim k_z+1”全是0,显然有转换条件:“在k_z+1处的值为0”;k′_t=k_t-1最大必要条件是“k_t\sim n全是2”(k_t充分条件),最小充分条件为“k_t-1\sim n”全是2,显然有转换条件:“在k_t-1处的值为2”。

    这里有点儿和前面不一样,之前我们是被动判断是否满足转换条件,这里我们可以主动让其满足

    void sortColors(vector<int>& nums) {
        // 如果数组的长度<2,没必要排序了
        if(nums.size() < 2){
            return ;
        }
    
        // 定义循环不变量zeroCursor和twoCursor:
        //      1. zeroCursor的左边永远只有0;
        //      2. twoCursor的右边永远只有2;
        int zeroCursor = 0, twoCursor = nums.size() - 1;
        // 定义cursor作为循环变量:zeroCursor和cursor之间只有1;
        int cursor = 0;
    
        while(cursor <= twoCursor){// 确定问题规模
            // 假设中间状态为:zeroCursor左边只有0,由于cursor左边都已经排过序,所以zeroCursor与cursor之间没有0
            //                           ↓(cursor)
            //      0,0,0,1,1,1,0,0,1,2,1,2,2,2
            //                 ↑(0)                   ↑(2)
            // 我们要保证定义不变,就要将cursor指向的0移至zeroCursor的左边(交换),然后移动cursor和zeroCursor;
            if(nums[cursor] == 0){
                swap(nums, cursor++, zeroCursor++);// 主动满足zero的转换条件
            }
            // 假设一段时间之后的中间状态为:twoCursor右边只有2,现在要将所有的2移至twoCursor右边
            //                           ↓(cursor)
            //      0,0,1,1,1,1,2,1,2,1,2
            //             ↑(0)            ↑(2)
            // 这是我们要保证定义不变,就要将cursor指向的2移至twoCursor的右边(交换);
            else if(nums[cursor] == 2){
                swap(nums, cursor, twoCursor--);// 主动满足two的转换条件
            }
            else if(nums[cursor] == 1){
                cursor++;
            }
        }
    }
    
posted @   BNTU  阅读(366)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 一个适用于 .NET 的开源整洁架构项目模板
· 【开源】C#上位机必备高效数据转换助手
· .NET 9.0 使用 Vulkan API 编写跨平台图形应用
· MyBatis中的 10 个宝藏技巧!
· [.NET] 使用客户端缓存提高API性能
历史上的今天:
2021-09-29 Dijkstra和Floyd算法遍历图的核心
点击右上角即可分享
微信分享提示