目录
复杂问题的求解过程常常包含基本操作的多次重复进行,重复基本操作的常用方式有迭代和递归。
迭代一般采用循环结构,通过某种递推式,不断更新变量新值,直到得到问题的解
递归则是算法中存在自调用,将大问题化为相同结构的小问题来求解,递归是一种有效的算法设计方法,是解决许多复杂问题的重要方法
一、高级编程语言中的循环结构
重复执行一段代码的最基本方法是将它放在循环(loop)结构中,循环结构有三个要素:循环变量、循环体、循环终止条件
C#中有foreach语句,能简洁遍历,再foreach语句中遍历的数据集合称为可枚举的(enumerable)集合,又称为可迭代的(iterable)集合
1:单重循环
int n=100,sum=0; int n=100,sum=0;
for(int i=0;i<n;i++) sum+=a[i];
foreach(int t in a)
sum+=t;
该循环的时间复杂度为O(n)
2:二重循环
int n=10, psum=0;
for(int i=0 ; i<n ; i++)
for(int j=0; j<i ; j++)
psum += i*j;
外层执行n次,每执行依次外循环,内层循环执行i次,所以时间复杂度为O(n²)
二、迭代
1:迭代的基本概念
迭代(iterate)的原意是一种不断用变量的旧值递推新值的过程,迭代的过程一般采用循环结构
以迭代过程为显著特点的算法,归为迭代算法
迭代与普通循环的区别是:迭代时,循环体代码中参与运算的变量同时也是保存结果的变量,当前保存的结果作为下一次循环计算的初始值,这种变量称为迭代变量
使用迭代思想完成算法要解决三个方面的问题:
1)迭代变量的确定
2)建立迭代公式
3)对迭代过程进行控制
2:迭代算法
例如用来求方程根的牛顿迭代法,用来解联立方程组的高斯迭代消元法,用来求函数最小值的最速下降法等,都是通过迭代使它在一定条件下收敛于原问题的解
例如:求函数最小值的梯度下降法应用的迭代公式是:
其中,dk是从xk出发的函数值下降方向,称为搜索方向,沿着这样的函数值下降方向迭代,在一定条件下收敛于函数的极小值点,ak是控制迭代速度的参数,称为搜索步长
例:利用迭代方法计算阶乘函数f(n)=n!.
以迭代方式计算的方法是,先计算1乘以2,用其部分结果再乘以3,接着再用结果乘以4,依次到n,递推公式为p=p*i,进行乘法直到计数器的值为n,最后返回p为阶乘函数的计算结果,代码如下:
namespace iter_recu
{
public class Program
{
static void Main(string[] args)
{
int n=5;
Console.WriteLine(n+"!="+factorial(n));
}
//迭代部分
public static int factorial(int n)
{
int p=1;
for(int i = 2; i <n ; i++)
{
p*=i;
}
return p;
}
}
}
结果为:5!=24
三、递归
递归(recursion)是数学定义和计算中的一种思维方式,它用对象自身定义一个对象。
在高级编程语言中,如果一个函数直接或间接地调用自己,则成这个函数是递归函数。
1:递归算法
递归算法将待求解的问题推到比原问题更简单且解法相同或类似的问题来求解,然后得到原问题的解
例如:求解阶乘函数f(n)=n!,为计算f(n),将它推到f(n-1),即f(n)=n*f(n-1),而f(n)和f(n-1)算法是一样的。由此可见,用递归算法求解较为复杂的问题过程有下列特点:
1)如果原问题能够分解成几个相对简单且解法相同或类似的子问题,只要子问题能够解决,那么原问题就能用相同或类似的方法求解
2)不断分解原问题,直到分解到某个可以直接解决的子问题时,就停止分解,这些可以直接求解的问题称为递归结束条件
例:阶乘函数n!的递归实现
namespace iter_recu
{
public class Factorial
{
//递归方法
public static int f(int n)
{
if(n==0)
{
return 1;
}
else
{
Console.WriteLine(n+"!="+n+"*"+(n-1)+"!");
return n*f(n-1);
}
}
public static void Main(string[] args)
{
int i=5;
Console.WriteLine(i+"!="+f(i));
}
}
}
运行的结果为:
5!=5*4!
4!=4*3!
3!=3*2!
2!=2*1!
1!=1*0!
5!=120
2:递归与迭代的比较
递归的最大特点是把一个复杂的算法分解成若干相同的可重复的步骤,所以使用递归实现一个计算逻辑往往思路清晰、代码简洁、比较容易理解,缺点是需要大量的调用,当递归深度越深,系统栈空间的占用就会越大,可能造成系统栈的溢出。它的时间和空间效率比较低
迭代算法思路上可能没有递归算法简洁,但是迭代算法的运行时间正比于循环次数,而且没有调用函数引起的额外时间和内存空间开销,所以时间和空间效率都比较高
3:递归数据结构
有些数据结构是可以用递归方式定义的,例如单向链表结点类可以递归定义为:
Node=(Data,Next_Node)
链表也可以看成是一种递归的数据结构
例:单向链表结点递归定义的实现与测试
定义递归式链表结点类RLinkedNode:
using System;
public class RLinkedNode<T>:SIngleLinkedNode<T>
{
//构造值为ch的结点
public RLinkedNode(T ch):base(ch){}
//构造值为ch,且是q的前驱结点
public RLinkedNode(T ch,SingleLinkedNode<T>q)
{
this.items=ch;
this.Next=q;
}
//构造q的后继结点,其值为ch
public static RLinkedNode<T>AddNode(SingleLinkedNode<T>q,T ch)
{
SingleLinkedNode<T> p=q;
RLinkedNode<T>t=new RLinkedNode<T>(ch);
if(q!+null)
{
while(p.Next!=null)
p=p.Next;
p.Next=t;
return(RLinkedNode<T>) q;
}
else return t;
}
}
测试程序如下:
using System;
namespace stackqueuetest
{
public class RLinkedNodeTest
{
public static void Main(string[] args)
{
Console.Write("Input: ");
string str=Console.ReadLine();
int i,count=str.Length;
Console.Write("Show: ");
for(int i = 0; i < count; i++) //输出str元素值
{
Console.Write(" "+str[i]);
Console.WriteLine();
Console.WriteLine("count="+count);//str实际长度
RLinkedNode<char>node=null;
}
for(int i = 0; i < count; i++) //创建链表
{
node=new RLinkedNode<char>(str[i],node);
node.Show(); //输出链表
}
Console.WriteLine();
node=null;
for(int i = 0; i < count; i++) //重创链表
{
node=RLinkedNode<char>.AddNode(node,str[i]);
node.Show(); //输出链表
}
}
}
}
程序运行时,从键盘输入"CSharp"结果为:
Input:CSharp
Show:C S h a r p
count: 6