Num 33 : 函数递归 [ 全排列 ]

数学上的全排列问题:

给定m个数,可以排列成n位数的所有情况;

例:3 个数 ( 1,2,3 ) 排列成两位数[ 含有重复数字 ]有:

11,12 ,13,21,22,23,31,32,33;

例:2个数( 1,2 ) 排列成三位数:

111, 112, 121, 122, 211, 212, 221, 222;

由上易找到规律

        对于 n 位 m 个要求的数
[ 不妨设为1,2,3的 2位数 ],我们只需从最高位开始,依次冠以第一个数( 11 );

        之后保持高位不变,最低位游历所有要排列的数:11, 12, 13 ;

        最低位游历过之后,次低位变为第二个数继续游历:21, 22, 23 ;

        知道最高位也游历过所有要排列的数为止;

这样的问题通过for循环也是可以实现的,但是对于n位数,需要用n个for循环来游历所有位的数据,

代码长度可想而知;

所以我们想到了用递归的方法来解决;

        问题分析:

    1.

             
因为我们不可能一次将排列的数按整形来输出,所以要把它的每一位存入数组中输出;

              需要定义两个数组 ( arr[ M ]和a[ N ] ) 分别代表了要排列的数和第 i 位[ i从0开始 ]的数;

          2.

              当 i 小于 n 的时候( 即没有达到n位数的要求的时候 ),游历第 i 位;

              当 i 大于等于 n 的时候( 已经满足要求 ) 按 i=0 到 i=n-1( 第一位到最后一位 )输出;

      
实现代码( 含重复数字 ):

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<math.h>
const int M=10;
int n,m,a[M],arr[M]={1 , 2 ,3 ,4};
void dfs(int v)
{
    if(v >= n)
	{
        for(int i = 0;i<n;i++)
            printf("%d ",a[i]);
        printf("\n");
        return ;
    }
    for(int i = 0; i<m;i++)
	{
        a[v] = arr[i];
        dfs(v+1);
    }
}
int main()
{
    while(scanf("%d%d",&n,&m)==2)
	{
        dfs(0);
    }
	return 0;
}

      
代码分析:

    1.

              dfs(0);  表示:从第一个( 第0个 )数开始排列 ;

    2.

              arr[ M ]={ 1,2,3,4 } 表示:要实现全排列的数有M个分别是 1, 2, 3, 4;

          3.

   
          当 i 小于 n 的时候( 即没有达到n位数的要求的时候 ),游历第 i 位;

for(int i = 0; i<m;i++)
	{
        a[v] = arr[i];
        dfs(v+1);
    }

          4.

             
当 i 大于等于 n 的时候( 已经满足要求 ) 按 i=0 到 i=n-1( 第一位到最后一位 )输出;

  if(v >= n)
	{
        for(int i = 0;i<n;i++)
            printf("%d ",a[i]);
        printf("\n");
        return ;
    }

以上的代码是实现含有重复数字的全排列,下面我们来看看不含有重复数字的全排列 :

与含有重复数字的算法基本相同,唯一的不同在于 :

  不含有重复数字的全排列算法需要引入标记数组( mark[M] );

            注意, 当 m<n 的时候( 不重复排列的数小于位数 ),这种情况下是错误的,结果不予输出;

      
实现代码( 含重复数字 ):

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<math.h>
const int M=10;
int n,m,a[M],arr[M]={1,2,3,4},mark[M];
void dfs(int v)
{
    if(v >= n)
	{
        for(int i = 0;i<n;i++)
            printf("%d ",a[i]);
        printf("\n");
        return ;
    }
    for(int i = 0; i<m;i++)
	{
		if(!mark[i])
		{
			mark[i]=1;
			a[v] = arr[i];
        	dfs(v+1);
        	mark[i]=0;
		}
    }
}
int main()
{
    while(scanf("%d%d",&n,&m)==2)
	{
		memset(mark,0,sizeof(mark));
        dfs(0);
    }
	return 0;
}

          1.

   
          利用回溯法:当 i 小于 n 的时候( 即没有达到n位数的要求的时候 ),游历第 i 位,并添加标记mark[i]=1;

   
          当mark[ i ]==1 的时候,说明已经使用过了,进行下一个数的排列;

             
递归完毕后,返回,并将改为重新标记为 0;

    for(int i = 0; i<m;i++)
	{
		if(!mark[i])
		{
			mark[i]=1;
			a[v] = arr[i];
        	dfs(v+1);
        	mark[i]=0;
		}
    }

          2.

             
当 i 大于等于 n 的时候( 已经满足要求 ) 按 i=0 到 i=n-1( 第一位到最后一位 )输出;

    if(v >= n)
	{
        for(int i = 0;i<n;i++)
            printf("%d ",a[i]);
        printf("\n");
        return ;
    }

既然已经知道了,重复和不重复的全排列方法了,现在还有一个问题,就是 :

如果不想输出由某个数字开头( 第i位 )的数字怎么办?( 可以在递归之前排除掉一些情况,节省时间 )

例:3 个数 ( 1,2,3 ) 排列成两位数[ 含有重复数字 ][ 不输出由一开头的数字 ]:

21,22,23,31,32,33;

例:3 个数 ( 1,2,3 ) 排列成两位数[ 不含重复数字 ][ 不输出由一开头的数字 ]:

21,23,31,32;

         实际上只需在刚开始加入数组的时候判断开头( 第 i 位 )是否为 k 即可;

①、实现代码如下[ 含有重复数字 ][ 不输出由一开头的数字 ]:

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<math.h>
const int M=10;
int n,m,a[M],arr[M]={1 , 2 ,3 ,4};
void dfs(int v)
{
    if(v >= n)
	{
        for(int i = 0;i<n;i++)
            printf("%d ",a[i]);
        printf("\n");
        return ;
    }
    for(int i = 0; i<m;i++)
	{
        a[v] = arr[i];
        if(a[0]==1) continue;
        dfs(v+1);
    }
}
int main()
{
    while(scanf("%d%d",&n,&m)==2)
	{
        dfs(0);
    }
	return 0;
}

  
代码分析:

             
仅仅加入了判断;

    for(int i = 0; i<m;i++)
	{
        a[v] = arr[i];
        if(a[0]==1) continue;
        dfs(v+1);
    }

②、实现代码如下[ 不含重复数字 ][ 不输出由一开头的数字 ]:

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<math.h>
const int M=10;
int n,m,a[M],arr[M]={1,2,3,4},mark[M];
void dfs(int v)
{
    if(v >= n)
	{
        for(int i = 0;i<n;i++)
            printf("%d ",a[i]);
        printf("\n");
        return ;
    }
    for(int i = 0; i<m;i++)
	{
		if(!mark[i])
		{
			mark[i]=1;
			a[v] = arr[i];
			if(a[0]==1)
			{
				mark[i]=0;
				continue;
			}
        	dfs(v+1);
        	mark[i]=0;
		}
    }
}
int main()
{
    while(scanf("%d%d",&n,&m)==2)
	{
		memset(mark,0,sizeof(mark));
        dfs(0);
    }
	return 0;
}

 

 
代码分析:

             
在回溯法中
加入了判断,需要在跳出之前,将标记取消( 否则所有含一的都不会输出 );

    for(int i = 0; i<m;i++)
	{
		if(!mark[i])
		{
			mark[i]=1;
			a[v] = arr[i];
			if(a[0]==1)
			{
				mark[i]=0;
				continue;
			}
        	dfs(v+1);
        	mark[i]=0;
		}
    }

现在基本上可以对全排列随意操作了吧。 ≥~~≤;

版权声明:本文为博主原创文章,未经博主允许不得转载。

时间: 2024-08-13 21:36:54

Num 33 : 函数递归 [ 全排列 ]的相关文章

HTML-JS 循环 函数 递归

[循环结构的执行步骤] 1.声明循环变量 2.判断循环条件 3.执行循环体操作 4.更新循环变量 然后,循环执行2-4,直到条件不成立时,跳出循环. while循环()中的表达式,运算结果可以是各种类型,但是最终都会转化为真假 转化规则同if结构. while循环特点:先判断,再执行.do-while循环特点:先执行,再判断:即使初始条件不成立,do-while循环也至少执行一次. // var num=1;//1.声明循环变量// while (num<=10){//2.判断循环条件// do

通过“”斐波那契数列“”学习函数递归

斐波那契数列: f(0) = 0  f(1) = 1 f(2) = 1 f(3) = 2  f(4) = 3   f(5) = 8 .......f(n) = f(n - 2) + f(n - 1) 实现方法一: #这个方法使用列表,将结果保存def fbis(num): result = [0,1] for i in range(num - 2): result.append(result[-2] + result[-1]) return result 实现方法二: def fbis_othe

C语言-第36课 - 函数递归与函数设计技巧

第36课 - 函数递归与函数设计技巧 一. 递归 递归概述 (1) 递归是数学领域中的概念在程序设计中的应用. (2) 递归是一种强有力的程序设计的方法. (3) 递归的本质为函数内部在适当的时候调用自身. 组成部分 (1)递归点:以不同参数调用自身. (2)出口:不在递归调用 下面就是求一个数的阶乘的函数: #include <stdio.h> int func(int x) { if( x > 1 ) { return x * func(x - 1);  //递归点 } else {

函数递归与二分法(python3入门)

1 import sys 2 3 print(sys.getrecursionlimit()) # 查询递归保护限制次数 4 5 # 函数递归:在调用函数的过程中又 直接或者间接的调用该函数本身,称之为函数的递归调用 6 7 # 函数递归必须满足: 8 # 1 必须有一个明确的结束条件 9 # 2 每进入下一层递归,问题的规模都应该有所减少 10 11 # 由上述两个条件可以推导出递归应该有两个明确的阶段: 12 # 1 回溯:一层一层的递归调用下去 13 # 2 递推:在某一层结束掉递归,开始

十一、函数递归,算法二分法,三元表达式,列表字典生成式,匿名函数,内置函数

一.函数递归: 函数的递归:函数在调用阶段直接或间接的又调用自身 递归分为两个阶段 1.回溯:就是一次次重复的过程,这个重复的过程必须建立在每一次重复问题的复杂度都应该下降 直到有一个最终的结束条件 2.递推:一次次往回推导的过程 3.递归函数在运行过程中一定要有出口,否则会无限循环下去 # 1.求4以内的和: def sum_numbers(num): if num == 1: return 1 return num + sum_numbers(num - 1) # 递归特点:函数内部自己调用

函数递归及面向过程编程

函数递归及面向过程编程 一.函数递归 1.1什么是递归 递归就是函数调用函数本身,然后有结束条件. 一般解决无法求解但不断靠近的值,比如利用二分法找具体的数字,汉诺塔等 让我们举个栗子,比如要实现0-99的和,用while循环为: num = 0 count = 0 while count<100: print(count) count += 1 num +=count print(num) 接下来我们用递归函数对他进行一个实现 count = 0 def f1(): global count

函数递归调用过程中的调用堆栈的情况

为了加深对函数递归调用过程中的理解,本Demo程序特意在VS2008 C#控制台程序实现了阶乘的计算功能,用于观察函数递归调用过程中的调用堆栈的情况. 源码如下: using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace RecursiveTset { class Program { //阶乘的定义:n!=n*(n-1)!,特别的,1!=1:0!=1 //阶乘的实

函数递归

函数递归就是一个函数引用另一个函数.def func (n): n+=1 if n>=8: return "and" return func(n)n=func(1)print(n)def a(): print(123)def b (): r=a() return rdef c (): r=b() return rc()

iOS函数,函数递归

#import <Foundation/Foundation.h> #import "Function.h" int main(int argc, const char * argv[]) { //函数定义 /*返回值类型 函数名(参数1, 参数2, 参数3, 参数4) { 函数体; return 返回值; } */ //函数四种基本类型: //无参无返回值 //实参 到形参的过程是一个拷贝的过程 //函数的值,要哟过对应的数据类型进行接收 //c语言允许函数嵌套调用,不允