算法入门教程(七、迭代)

前面教程汇总

第一讲

算法入门教程(一、模拟)

第二讲

算法入门教程(二、枚举)

第三讲

算法入门教程(三、递推与递归)

第四讲

算法入门教程(四、分治)

第五讲

算法入门教程(五、贪心)

第六讲

算法入门教程(六、试探)

迭代

迭代法也称辗转法,是一种不断用变量的旧值递推新值的过程,在解决问题时总是重复利用一种方法。与迭代法相对应的是直接法(或者称为一次解法),即一次性解决问题。迭代法又分为精确迭代和近似迭代。“二分法”和“牛顿迭代法”属于近似迭代法,功能都比较类似。

迭代算法基础

迭代算法是用计算机解决问题的一种基本方法。它利用计算机运算速度快、适合做重复性操作的特点,让计算机对一组指令(或一定步骤)进行重复执行,在每次执行这组指令(或这些步骤)时,都从变量的原值推出它的一个新值。

在使用迭代算法解决问题时,需要做好如下3个方面的工作。

确定迭代变量

在可以使用迭代算法解决的问题中,至少存在一个迭代变量,即直接或间接地不断由旧值递推出新值的变量。

建立迭代关系式

迭代关系式是指如何从变量的前一个值推出其下一个值的公式或关系。通常可以使用递推或倒推的方法来建立迭代关系式,迭代关系式的建立是解决迭代问题的关键。

对迭代过程进行控制

在编写迭代程序时,必须确定在什么时候结束迭代过程,不能让迭代过程无休止地重复执行下去。通常可分为如下两种情况来控制迭代过程:

  1. 所需的迭代次数是个确定的值,可以计算出来,可以构建一个固定次数的循环来实现对迭代过程的控制;
  2. 所需的迭代次数无法确定,需要进一步分析出用来结束迭代过程的条件。

例题与解

P1032 [NOIP2002 提高组] 字串变换

题目背景

本题疑似错题,不保证存在靠谱的多项式复杂度的做法。测试数据非常的水,各种做法都可以通过,不代表算法正确。因此本题题目和数据仅供参考。

题目描述

已知有两个字串 A , B A,B A,B 及一组字串变换的规则(至多 6 6 6 个规则),形如:

  • A 1 → B 1 A_1\to B_1 A1B1
  • A 2 → B 2 A_2\to B_2 A2B2

规则的含义为:在 A A A 中的子串 A 1 A_1 A1 可以变换为 $ B_1 , , A_2$ 可以变换为 B 2 ⋯ B_2\cdots B2

例如: A = abcd A=\texttt{abcd} A=abcd B = xyz B=\texttt{xyz} Bxyz

变换规则为:

  • abc → xu \texttt{abc}\rightarrow\texttt{xu} abcxu ud → y \texttt{ud}\rightarrow\texttt{y} udy y → yz \texttt{y}\rightarrow\texttt{yz} yyz

则此时, A A A 可以经过一系列的变换变为 B B B,其变换的过程为:

  • abcd → xud → xy → xyz \texttt{abcd}\rightarrow\texttt{xud}\rightarrow\texttt{xy}\rightarrow\texttt{xyz} abcdxudxyxyz

共进行了 3 3 3 次变换,使得 A A A 变换为 B B B

输入格式

第一行有两个字符串 A , B A,B A,B

接下来若干行,每行有两个字符串 A i , B i A_i,B_i Ai,Bi,表示一条变换规则。

输出格式

若在 10 10 10 步(包含 10 10 10 步)以内能将 A A A 变换为 B B B,则输出最少的变换步数;否则输出 NO ANSWER!

样例 #1
样例输入 #1
abcd xyz
abc xu
ud y
y yz
样例输出 #1
3
提示

对于 100 % 100\% 100% 数据,保证所有字符串长度的上限为 20 20 20

【题目来源】

NOIP 2002 提高组第二题

C++解(1)
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
string st1;
string st2;
const int maxn=15;
int ok=0;
struct method{
	string s1;
	string s2;
}p[maxn];
string ss1,ss2;
int cnt;
int d;                              //迭代加深的深度 
void dfs(int cur,string st){
	if(cur>d) return;                //因为dfs是同层同步的,所以只要有一个触底就全部触底,那么就说明d深度内无法找到答案 
	for(int i=1;i<=cnt;i++){         //尝试每种操作 
    int m=-1;
    while(true){
			m=st.find(p[i].s1,m+1);
	    if(m==-1) break;             //如果find返回-1则说明没有找到 
	    string ss=st;
	    int len=(p[i].s1).size();
	    ss.replace(m,len,p[i].s2);   //函数的用法和m的处理上面算法分析写得很清楚了,这里就不写了 
	    if(ss==st2){
	    	ok=1;
	    	cout<<cur;
	    	return;
			}
      if(!ok) dfs(cur+1,ss);
    }
  }
}

int main(){

	cin>>st1>>st2;                    //读入 
	while(cin>>ss1>>ss2){
		p[++cnt].s1=ss1;
		p[cnt].s2=ss2;
	}
	for(d=1;d<=10 && !ok;d++){
		dfs(1,st1);
	}
	if(!ok) cout<<"NO ANSWER!";
	return 0;
} 
C++解(2)
#include<cstdio>
#include<cstring>
#include<iostream>
using namespace std;
string X,Y,A[9],B[9]; int n,ans;
inline string trans(const string& str,int i,int j){
	static string ans; ans="";
	if(i+A[j].length()>str.length()) return ans;
	for(int k=0,mx=A[j].length();k<mx;++k)
		if(str[i+k]!=A[j][k]) return ans;
	ans=str.substr(0,i),ans+=B[j];
	return ans+str.substr(i+A[j].length());
}
inline string check(const string& str,int i,int j){
	static string ans; ans="";
	if(i+A[j].length()>str.length()) return ans;
	for(int k=0,mx=A[j].length();k<mx;++k)
		if(str[i+k]!=A[j][k]) return ans;
	ans=str.substr(0,i),ans+=B[j];
	return ans+str.substr(i+A[j].length());
}
inline bool dfs(string& now,int stp){ //管他重复不重复,一路搜。(当然你也可以自己加剪枝之类的优化)
	if(now==Y) return 1;
	if(stp>ans) return 0; string tmp;
	for(int i=0,mx=now.length();i<n;++i)
		for(int j=0;j<mx;++j){
			tmp=check(now,j,i);
			if(tmp!=""&&dfs(tmp,stp+1)) return 1;
		} return 0;
}
int main(){
	ios::sync_with_stdio(false),cin>>X>>Y;
	while(cin>>A[n]>>B[n]) ++n;
	for(ans=1;ans<=10;++ans)
		if(dfs(X,1)) return !printf("%d\n",ans);
	return !puts("NO ANSWER!");
}
C++解(3)
#include <cstdio>
#include <cstring>
#include <string>
#include <set>
using namespace std;

struct Pri
{
    char A[25];
    char B[25];
};

int n = 1;
int maxn;      //最大深度(名字很奇怪)
Pri P[10];
char А[25], B[25];

int mins = 11;

bool dfs(int s, char* str)
{
    //puts(str);
    if (s > maxn)            return false;//深度限制
    if (strcmp(str, B) == 0) return true; //找到解立即返回
    
    for (int i = 1; i <= n; i++)
    {
        int= strlen(P[i].А);
        int= strlen(P[i].В);
        int lS = strlen(str);
        int p, q;
        for (p = 0; p < lS; p++)
        {
            for (q = 0; q <; q++)
                if (str[p + q] != P[i].A[q])
                    break;
            if (q == lA)
            {
            	char tmp[500];
            	int k = 0;
            	for (int j = 0; j < p && k < 500; j++)       tmp[k++] = str[j];
            	for (int j = p; j < p + lB && k < 500; j++)  tmp[k++] = P[i].B[j - p];
            	for (int j = p + lA; j < lS && k < 500; j++) tmp[k++] = str[j];
            	if (k != 500)
            	{
                	tmp[k] = '\0';
                	if (dfs(s + 1, tmp)) return true; //找到解立即返回
                }
            }
    	}
    }
    return false;
}

int main()
{
    scanf("%s%s", A, B);
    for(; scanf("%s%s", P[n].Α, P[n].В) > 0; n++){}
    n--;
    
    for (maxn = 1; maxn <= 10; maxn++)//不断加大深度
    {
        if(dfs(0, A))  //找到后输出答案
        {
            printf("%d\n", maxn);
            return 0;
        }
    }
    
    printf("NO ANSWER!");
    return 0;
}
Pascal解
Const maxn=10000; 
      maxq=100000; 
Var a:array[0..1,0..maxn]of string;//变换规则 
    q:array[0..1,0..maxq]of string;//两个队列 
    step:array[0..1,0..maxn]of longint;//步数
    head,tail:array[0..1]of longint;//两个队列的头指针和尾指针 
    int,aim,s1,s2,s:string; 
    n:longint; 
Procedure split(s:string);//将目标状态和初始状态记录下来 
var k:longint; 
begin 
  k:=pos(' ',s); 
  s1:=copy(s,1,k-1); 
  s2:=copy(s,k+1,length(s)-k); 
end; 
Procedure init; //读入
begin 
  readln(s); 
  split(s); 
  int:=s1;//初始状态
  aim:=s2;//目标状态 
  n:=0; 
  while not eof do 
  begin 
    readln(s); 
    if s='' then exit; 
    inc(n); 
    split(s); 
    a[0,n]:=s1;//初始的可以转换的状态
    a[1,n]:=s2;//由初始状态转换一步得到的目标状态
  end; 
end; 
Function vis(s:string;t:byte):boolean; 
var i:longint; 
begin 
  vis:=false; 
  for i:=1 to tail[t] do//遍历队列
  if q[t,i]=s then exit(true);//如果找到目标状态就返回值true 
end; 
Procedure print(k:longint); 
begin 
  writeln(k);//(如果合法)输出最少变换步数 
  halt; 
end; 
Procedure check(t:byte); 
var i:longint; 
begin 
  for i:=1 to tail[1-t] do //遍历队列(当前的状态保存在队列里)
  if q[1-t,i]=q[t,tail[t]] then //如果两个广搜碰头了
  print(step[1-t,i]+step[t,tail[t]]);//总的步数就是两个广搜步数之和 
end; 
Procedure bfs(t:byte); //广搜(t=0是正着搜,t=1是反着搜)
var i,j,k:longint; 
    pre,tmp:string; 
begin 
  inc(head[t]);//头指针加一
  pre:=q[t,head[t]];//入队 
  for i:=1 to n do//遍历变换规则 
  begin 
    k:=length(a[t,i]); 
    for j:=1 to length(pre)-k+1 do//按照变换规则扩展状态 
    begin 
      if copy(pre,j,k)=a[t,i] then//如果规则符合 
      begin 
        tmp:=copy(pre,1,j-1)+a[1-t,i]+copy(pre,j+k,length(pre)-j-k+1);//扩展下一个状态 
        if not vis(tmp,t) then//如果没有找到目标状态 
        begin 
          inc(tail[t]); 
          q[t,tail[t]]:=tmp; 
          step[t,tail[t]]:=step[t,head[t]]+1;//步数++ 
        end; 
        check(t);//检查是否终止搜索(注意位置,不然就T了)
      end; 
    end; 
  end; 
end; 
Procedure doublebfs;//用数组下标来区分两个队列和两个广搜 
begin 
  head[0]:=0;//第一个队列的头指针
  head[1]:=0;//第二个队列的头指针
  tail[0]:=1;//第一个队列的尾指针
  tail[1]:=1;//第二个队列的尾指针 
  q[0,1]:=int;//初始状态
  q[1,1]:=aim;//目标状态
  step[0,1]:=0;//步数
  step[1,1]:=0;//步数 
  while (head[0]<tail[0])and(head[1]<tail[1])do 
  if tail[1]<tail[0] then 
  bfs(1) else bfs(0);//保持两个广搜的同步 
end; 
Begin 
  init; 
  doublebfs; 
  writeln('NO ANSWER!'); 
End.

本文完。关于迭代(辗转)算法的题还有很多,可以在洛谷上刷。本文由于代码有一些已经加了注释,所以就不在文章里说明了,如果不懂,欢迎在评论区留言。至此,算法入门教程就已经结束了。我为大家讲解了模拟、枚举、递推、递归、分治、贪心、试探(回溯)以及迭代(辗转),总共有8种常见的算法。注意,这只是入门教程,如果要学习更高级的内容,请在CSDN算法技能树学习。

  • 3
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

YoungGeeker

你的鼓励是我最大的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值