【算法设计zxd】第3章 迭代法 杨辉三角,穿越沙漠,内存移动,竖式相乘(阶乘)

目录

迭代:(辗转法)        一种 不断用变量的旧值递推新值的过程

【例3-1】 输出如图的杨辉三角形。

【例3-2】穿越沙漠问题

【例3-2】内存移动问题

【例3-4】编程求当n<=100时,n!的准确值。

代码


迭代法:(辗转法)
        一种 不断用变量的旧值递推新值的过程

分类:

  •         精确迭代:杨辉三角,内在移动算法
  •         近似迭代:二分法和牛顿迭代法

设计方法:

  • 确定迭代模型
  • 控制迭代过程 

 递推方程:

 

例子:

斐波那契:

 

换元迭代:

 例如二路归并

 解的正确性——验证:数学归纳法

差消法化简高阶递推方程:_化简为一阶方程再求解

快排

输入情况:

每个输入, 划分的比较次数都是n-1。(每个元素都要跟首元素进行比较)

工作量总和:(比较次数)

 平均工作量:

 差消化简:

 

 

【例3-1】 输出如图的杨辉三角形

问题分析:


存储:A[n,n]矩阵

矩阵:A = {a0,0,a1,0,a1,1,…,ai,0,…,ai,i,…,an-1,n-1} 

//元素之间的关系:ai,j=ai-1,j-1+ai-1,j 即当前元素的值,是由上一层同列和上一层同列左侧的元素相加得到。

计算模型:

算法设计与描述算法分析
输入: n (1)输入n ,规模为n
输出:杨辉三角

Yanghui(n)

{

        a[0,0] <- 1,a[1,0]<-1 ,a[1,1]<-1; //3 

        for i <- 2 to n-1 do

        {

                a[i,0]<- 1 ;a[i,i] <- 1; //2 

                for j<- 1 to i-1 do

                        a[i,j] <- a[i-1,j-1]+a[i-1,j]; // 1

        }

        output(a);

}

(2)核心操作:a[i,j] <- a[i-1,j-1]+a[i-1,j]的加法运算及两边的值

(3)依据定理2.5算法的时间复杂度为 

杨辉三角与斐波那契数列:

【例3-2】穿越沙漠问题

用一辆吉普车穿越 1000公里的沙漠。吉普车的总装油量为 500 升,耗油率为 1 升/公里。由于沙漠中没有油库,必须先用这辆车在沙漠中建立临时油库。
该吉普车以最少的耗油 量穿越沙漠,应在什么地方建油库,以及各处的贮油量。

// 1.车到达终点时油箱是空的。2.路上的每一个中转点都没有剩下油。

分析:
用倒推法来解决, 加油站n加油站n+1 倒推(离终点远):吉普车在两加油站之间往返若干次,每次返回n+1时正好耗尽500升油,每次从n+1出发时装满500升,两点之间的距离必须满足在耗油最少的条件下,使n点存储n*500升汽油。
第1加油站:距离终点500公里,储存500升汽油oil[1]=500,以保证车能从1到达0(终点)。
第2加油站:为了在加油站1存储500升,车需要加油站的500和路上的500,oil[2]=500*2=1000。一总往返是3次。三次往返路程的耗油量按最省 为500升,dis(2)-dis(1)=500/3 km。【第一次走:装了500 走了500/3 车剩下2*500/3的油,在加油站存储500/3。返回:车500/3油,返回后无剩余。第二次走:装500 耗500/3 剩余500/3*2  放入加油站500/3*2 此时加油站有500L】

从i-1点往i点运油时,最节省的情况是从i-1点出发时应该油箱装满油,然后在i点放下油后回到i-1点时油箱是空的,这时候运油的效率是最高的。

第3加油站:为了在加油站2存储2*500升,oil[3]=500*3=1500,一总往返是5次。五次往返路程的耗油量按最省 为500升,dis(3)-dis(2)=500/5 km=100km。
第i加油站:为了在加油站i-1存储(i-1)*500升,oil[i]=500*i,一总往返是(2*i+1)次。dis(i)-dis(2i-1)=500/(2*i+1) km。

计算模型:
设起点到终点距离为distance,终点到加油站的距离为dis,加 油站的油量为oil,加油站编号为
n,计算模型如下:
  • dis1=500        //终点到第一加油站的距离
  • oil1=500        //第一加油站的油量
  • dis(n)= dis(n-1) + 500/(2*n-1)    约束:dis(n-1)<distance  //终点到第n加油站的距离 = 
  • oil(n) = oil(n-1)+500        //下一个加油站的油量多500 也可以用500*i
算法设计与描述算法分析
输入: distance(1)输入distance 
输出:每个加油站的油量 及 到终点的距离规模 distance 与n 

Desert ( distance )

{

     dis <- 500,oil <- 500,n <- 1;
    while dis<distance do
    {
        output(n,dis,oil);
        n <- n+1;
        oil <- oil+500;
        dis <- dis+500/(2*n+1);
    }
    //最后一个加油站可能超范围
    dis <- distance - (dis-500/(2*n-1)) ;
    oil =oil -500 + dis*(2*n-1);
    output(n,distance,oil);

     

}

(2)核心语句:求加油站的距离

规律如下 500 + 500/3 + 500/5 +...+ 500/(2*n-1)=distance

【例3-2】内存移动问题

数组中有 n 个数据,要将它们顺序循环向后移 k位,即前面的元素向后 ( ) k 位,后面的元素则循环向前移 k位,
例: 0 1 2 3 4 5 循环移 3 位后为: 3 4 5 0 1 2
考虑到 n 会很大,不允许用 2*n 及以上个空间来完成此题。
问题分析
(1) 建立存原序列的数组 a[n] 和移位后数列 b[n]
        b[(k+i) mod n]=a[i], i∈ [0, n-1]
时间渐近复杂度 O(n) ,空间渐近复杂度 O(2*n)
(2) 建立存原序列的数组 a[n] 和临时变量 tmp ,令 tmp=a[n-1]
a[n-1]=a[n-2] ->  a[n-i]=a[n-i-1] i [1, n-1]
时间渐近复杂度 O(k*n) ,空间渐近复杂度 O(n+1)=O(n)
n=5 k=3 时, 0 1 2 3 4 移动 3 位后为 2 3 4 0 1,
        一轮移动恰好完任务(0 -> 3-> 1-> 4-> 2-> 0)。
n=6 k=3 时, 0 1 2 3 4 5 移动 3 位后为 3 4 5 0、 1、2,
        用三轮移动完任务(0-> 3-> 0,1-> 4-> 1,2-> 5-> 2)。
x i =(i+k) mod n (公式 3-1
移动的轮数: n k 的最大公约数 Q
每轮移动的元素数量: n/Q
  • 设数列中元素n个,向右移动 k 次 , 位置编号 0... n-1 。
  • 按照(3-1)计算位置:
  • Loc1 :x1 =(x0+k) mod n ,设商为y0 , x1=k+x0 - n*y0 ;
  • Loc2 :x2 =(x1+k) mod n ,设商为y1 , x2= 2*k+x0 - n*(y0 +y1) ;
  • Loc i:xi = (x (i-1) +k)mod n,设商为y(i-1) , xi = i*k +x0-n*(y0+...y(i-1)); 
  • xi= ( x0 +i*k )mod n 
  • 第i次连续移动后,回到初始位置,则一定有 xi = (x0 + i*k ) mod n = x0 (式3-3)
  • 第m论连续移动的起始位置为xm ,则有:xi=(xm+i*k )mod n = xm ;
  • 必须有 i*k mod n =0 成立。
  • 所以可设:i*k/n = y -> i*k = n*y
  • 设k与n的最大公约数Q ,则有 k=a*Q , n=b*Q ,可得 k/n = y/i = (a*Q)/(b*Q) , a,b互质【不互质 就不是最大公约数,,ab就是另一个约数/因数】

计算模型:

(1) 求最大公约数 欧几里得定理 (辗转相除)  r≠0 时,有  

  •   r=a  mod  b  (3  5 )  
  • gcd ( a,  b ) =  gcd ( b,  r ) r≠0  (3  5 )  

(2)  Q=b  移动次数: i =n/Q  

(3)计算元素移动位置:  m = (m+k )  mod n   (3 - 2),(3 - 4)   

算法设计与描述算法分析
输入: n, k ,n个元素的数列 
输出:向右移动 k 位的数列

最大公约数:

Euclid (a,b)

{

        temp <- a%b;

        while temp do

                a=b;

                b=temp;

                temp=a%b;

        return b;//最大公约数

}

f(b) = log1.618( b * 根5 )

Memory_move(n,k,a[n])

{

        Q <- Euclid (n,k);        //求最大公约数 

//例如 2  = Euclid(n=6,k=3); 分成2组

        for( q <- 0  ; q < Q ; q <- q+1) //Q是移动轮数

        {

                temp <- a[q];        //第一次循环 temp=a[0]

                m <- q;        //下标 m=0

                for(i <-0 ;i<n/Q ;i<- i+1)//移动次数 3次

                {

                        m <- (m+k) mod n;  //元素移动位置 m=(0+3)%6=3

                        s <- a[m];   //a[m]和temp交换 a[3]和a[0]交换

                        a[m] <- temp;        //

                        temp <- s;        //

                }        

        }
}

(1)问题规模 是由元素个数n 及 移动次数k 控制

(2)核心语句:内层循环移动

(3)算法时间复杂度(移动次数):

/*

第一次循环 q=0

temp=a[0]

m=0

i 0 to 3

m= (0+3)%6=3  a[3]和temp即a[0]交换 temp=a[3]

m=(3+3)%6=0 a[0]和a[3]交换 temp=a[0]

m= (0+3)%6=3  a[3]和temp即a[0]交换 temp=a[3]

第二次循环 q=1

temp=a[q] = a[1]

m=q=1

i 0 to 3

m= (m+k) mod n=(1+3)%6=4  a[4]和temp即a[1]交换 temp=a[4]

m=(4+3)%6=1 a[1]和a[4]交换 temp=a[1]

m= (m+k) mod n=(1+3)%6=4  a[4]和temp即a[1]交换 temp=a[4]

0 1 2 3 4 5

3 4 5 0 1 2 

*/

改进

i <- n/Q -1 ; p1 <- (m+ i*k) mod n ;temp <- a[p1] ;

for ( i <- i-1 ;i>=0 ;i-- ) // 次数n/Q-1

{        

        p2 <- ( m+i*k ) mod n;

        a[p1] =a[p2];

        p1=p2;

}

a[p2]=temp;

/*

m=0

i =1 , p1=(0+1*3) mod 6 =3 ,temp=a[3];

循环

i=0;p2=(0+0)%6=0 ,a[3]=a[0];p1=0

i--退出

a[0]=a[3]

m=1;

i=1;p1=4,temp=a[4]

*/

内存后移(作业)

算法设计与描述算法分析
输入: n, k ,n个元素的数列 
输出:向右移动 k 位的数列

最大公约数:

Euclid (a,b)

{

        temp <- a%b;

        while temp do

                a=b;

                b=temp;

                temp=a%b;

        return b;//最大公约数

}

f(b) = log_{1.618}(b*\sqrt{5})

Memory_move(n,k,a[n])

{

        Q <- Euclid (n,k);        //求最大公约数 

//例如 2  = Euclid(n=6,k=3); 分成2组

        for( q <- 0  ; q < Q ; q <- q+1) //Q是移动轮数

        {

                temp <- a[q];        //第一次循环 temp=a[0]

                m <- q;        //下标 m=0

                for(i <-0 ;i<n/Q ;i<- i+1)//移动次数 3次

                {

                        m <- (m+n-k) mod n;  //元素移动位置 

                        s <- a[m];   //a[m]和temp交换 

                        a[m] <- temp;        //

                        temp <- s;        //

                }        

        }
}

(1)问题规模 是由元素个数n 及 移动次数k 控制

(2)核心语句:内层循环移动

(3)算法时间复杂度(移动次数):T(n)=\sum_{i=0}^{q-1} \sum_{j=0}^{\frac{n}{q}-1} C = C*\sum_{i=0}^{q-1}(\frac{n}{q}) = C*n = \Theta (n)

改进

i <- n/Q -1 ; p1 <- (m+ i*(n-k)) mod n ;temp <- a[p1] ;

for ( i <- i-1 ;i>=0 ;i-- ) // 次数n/Q-1

{        

        p2 <- ( m+i*(n-k) ) mod n;

        a[p1] =a[p2];

        p1=p2;

}

a[p2]=temp;

找到本轮移动最后一个元素的位置(而不是移动后的位置),从前往后进行覆盖性移动,减少移动次数。

代码:

#include<iostream>
using namespace std;

int Euclid(int a,int b)
{
	int r=a%b;
	while(r)
	{
		a=b;
		b=r;
		r=a%b;
	}
	return b;
} 

int main()
{
	int a[6]={0,1,2,3,4,5};
	int n=6,k=2;//前移
	int q=Euclid(n,k);
	for(int i=0;i<q;i++){
		int temp=a[i];
		int p=i;
		for(int j=0;j<n/q;j++)
		{
			p=(p+n-k)%n;
			int s=a[p];
			a[p]=temp;
			temp=s;
		}
	}
	for(int i=0;i<6;i++)
	{
		cout<<a[i] <<" ";
	}
	return 0;
}

改进

#include<iostream>
using namespace std;

int Euclid(int a,int b)
{
	int r=a%b;
	while(r)
	{
		a=b;
		b=r;
		r=a%b;
	}
	return b;
} 

int main()
{
	int a[6]={0,1,2,3,4,5};
	int n=6,k=2;//前移
	int q=Euclid(n,k);
	for(int i=0;i<q;i++)
	{
		int j=n/q-1;
		int p1=(i+j*(n-k))%n;
		int temp=a[p1]; 
		int p2;
		for(j=j-1;j>=0;j--)
		{
			p2=(i+j*(n-k) )%n;
			a[p1]=a[p2];
			p1=p2;
		}
		a[p2]=temp;
	}
	for(int i=0;i<6;i++)
	{
		cout<<a[i] <<" ";
	}
	//2 3 4 5 0 1
	return 0;
}

【例3-4编程求当n<=100时,n!的准确值。

问题分析
基本数据类型
  • 整型数据:-32768——32767
  • 长整型:-2147483648——2147483647
  • 单精度:六位精度,±(3.4e-38~3.4e+38)
  • 双精度: 17位精确度,±(1.7e-308~1.7e+308)
不够大、不精确
设计要点:
(1) 存储:可用整数或字符类型的数组,每个元素 1 到若干位
(2) 数组长度:由式 log(n!)= Θ(n log n)确定。
如每个元素存储 存 6 位整数, log(n!)= 100log(100)/6≈34 100 !有 27 位)
(3) 计算: 模拟竖式乘法

 竖式乘法原理--实例分析

计算模型:

设存储大数结果的数组为 a[m] m= n log n/6,计算过程如 下:
  • b = a[j] *i +d   ( 1 <= j <= m , 1< i <= n)
  • a[j]= b mod 1000000  ( 1<= j <=m)
  • d = b /1000000

其中,b表示每个数组元素相乘的实际结果,d是进位。

注意:输出时 补0 。

算法设计与描述算法分析
输入: n(1)
输出:n!问题规模 是 n次乘法 

Bigdigital (n)
{
    a[m] <- {0};
    a[1] <- 1 ;
    len =1;
    
    for( i <- 2 ;i<=n;i <- i+1 ) //从2乘到n 
    {
        d <- 0;
        for( j<- i ;j<=len;j<- j+1)
        {
            b=a[j]*i +d;
            a[j]= b mod 1000000;
            d <- b /1000000;
            
        }
        if( d != 0)
        {
            a[j]=d;
            len <- len +1;
        }    
    }
    output(a[]);

(2)核心语句:内层的竖式乘法

(3)算法时间复杂度是

T(n)=\sum_{i=2}^{n} \sum_{j=1}^{m} C =\sum_{i=2}^{n}C*m=C*m*(n-1) =C*n*\Theta (nlog_n)/ = O(n^2log_n)

代码

#include<iostream>
#include<cmath>
using namespace std;
#define maxlen 34 //将数组长度近似设为(100!)所需数组长度 

void output(long a[],int len)
{
	int k;
	for(int i=len;i>=1;i--)
	{
		if(a[i]==0) k=6;
		else if(i==len)k=0;//最高位不需要补零 
		else k= 6 - ceil(log(a[i])) ;
		
		for(int j=1 ;j<k;j++)//注意还有原数的位置 因此需要减一 
		{
			cout<<"0";
		}
		cout<<a[i]<<endl;
	}
}
 
int main() 
{
	int n,i,j;cin>>n;
	int m= ceil(n*log(n)/6 );
	long a[maxlen];//六位数 需要长整型
	long b,d;//暂存 
	a[m]=0;
	a[1]=1;
	int len=1;//有效数组长度
	
	for(i=2;i<=n;i++) //阶乘 从2到n  
	{
		d=0;
		for(j=1;j<=len;j++)//有效数组长度 
		{
			b=a[j]*i+d;
			a[j]=b % 1000000;
			d=b/1000000;
			 
		}
		if(d!=0)//需要进位 
		{
			a[j]=d;
			len++;
		}
	}
	output(a, len);
	return 0;
}

测试:100!

100
93
326215
443944
152681
699238
856266
700490
715968
264381
621468
592963
895217
599993
229915
608941
463976
156518
286253
697920
827223
758251
185210
916864
000000
000000
000000
000000


大数乘法:

问题分析:

/*
unsigned int 0~4294967295
int -2147483648~2147483647
unsigned long 0~4294967295
long -2147483648~2147483647
long long的最大值:9223372036854775807
long long的最小值:-9223372036854775808
unsigned long long的最大值:18446744073709551615

__int64的最大值:9223372036854775807
__int64的最小值:-9223372036854775808
unsigned __int64的最大值:18446744073709551615
*/

考虑到数的位数不确定,所以采用string字符串存储。从最低位开始逐位相乘处理进位 注意需要进行字符和数字的转换,一定要转换。

计算模型:

两大数字符串为a,b,存储乘积结果的字符串为sum。其中temp 暂存结果  jin存进位。

 temp <- (sum[i+j]-'0'+(a[i]-'0')*(b[j]-'0')+jin); (0<=i<=a.size()-1,0<=j<=b.size()-1)

sum[i+j]<-temp%10+'0';

jin<-temp/10;

算法设计与描述算法分析
输入: a,b(1)
输出:sum问题规模 是 a.size()*b.size()次运算 

zhuwei(a,b)
{
    sum <- (a.size()+b.size(),'0');
    int i,j,temp ,jin=0;
    for( i <- a.size()-1 ; i>=0 ; i <- i-1 ) 
    {
        
        for( j<- b.size()-1 ;j>=0;j<- j-1)
        {

                temp <- (sum[i+j]-'0'+(a[i]-'0')*(b[j]-'0')+jin);

                sum[i+j]<-temp%10+'0';

                jin<-temp/10;
            
        }
        while( jin != 0)
        {
            sum[i+j] <-(sum[i+j]-'0'+jin)%10+'0';
            j<- j-1;

            jin<-jin/10;
        }    
    }
    //output

    for(i<-0;i<a.size()+b.size()-1;i++)

    {

        cout<<sum[i];
     }

(2)核心语句:内层的竖式乘法

(3)算法时间复杂度是

T(a,b)=\sum_{i=a.size()-1}^{0} \sum_{j=b.size()-1}^{0} C =C*a.size()*b.size() =C*\Theta (loga*logb) = O((logb)^2)

 /*

假设其中较大的数为b 

*/

  • 6
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值