循环不变量
-
循环不变量(分治结束标志量):在循环中
定义
不发生变化的量,当不满足定义时缩小规模。关于循环不变量定义的条件,我们是有
充分条件
和必要条件
之分的。- 充分条件:这个条件一旦满足,说明循环不变量满足定义,过程立即结束;
- 必要条件:假设这个循环不变量满足其定义,那么一定满足必要条件,否则它必然不满足定义。但是如果加上某个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(必要条件); // 递归思想,实现自我覆盖 }
-
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}的充分条件。它的边界条件就是:当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]; } } }
-
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++; } } }
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】博客园携手 AI 驱动开发工具商 Chat2DB 推出联合终身会员
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 一个适用于 .NET 的开源整洁架构项目模板
· 【开源】C#上位机必备高效数据转换助手
· .NET 9.0 使用 Vulkan API 编写跨平台图形应用
· MyBatis中的 10 个宝藏技巧!
· [.NET] 使用客户端缓存提高API性能
2021-09-29 Dijkstra和Floyd算法遍历图的核心