10.再帰

再帰は非常に広く使用されているアルゴリズム(またはプログラミングスキル)です

多くのデータ構造とアルゴリズムコーディングの実装では、DFSの深さ優先検索、事前中間後順バイナリツリートラバーサルなどの再帰を使用しています。

再帰が満たすべき3つの条件

1.問題の解法は、いくつかの副問題の解法に分解できます。

2.この問題は、データスケールが異なることを除いて、分解後のサブ問題と同じですが、解決策はまったく同じです。

3.再帰的な終了条件があります

分析:

nステップあり、1ステップまたは2ステップを同時に通過できます。これらのnステップをいくつ歩くことができますか?7段あるとすれば、このように2、2、2、1のように上がるか、このように1、2、1、1、2のように上がることができます。行く方法...

1.最初のステップは2つのカテゴリーに分かれています。1つのステップと2つのステップです。

2. nステップの移動= n-1ステップの移動+ n-2ステップの移動、つまりf(n)= f(n-1)+ f(n-2)

3.終了条件:ステップが1つしかない場合は1移動のみ、2ステップしかない場合は移動が2つのみ、つまりf(1)= 1、f(2)= 2

 

次数nはn-1とn-2からのみ取得できるため、f(n)= f(n-1)+ f(n-2)、n = 1、f(1)= 1、n = 2の場合、F(n)= 2。

f(1) = 1;
f(2) = 2; 
f(n) = f(n-1)+f(n-2)

大きな問題を小さな問題に分解する方法のルールを見つけ、これに基づいて再帰的な式を記述し、終了条件について考え、最後に再帰的な式と終了条件をコードに変換します

コンピュータは反復的なことをするのが得意なので、再帰は前向きであり、その欲求は高くなります。そして私たちの人間の脳は単純な考え方を好みます。再帰を見るときは、常に再帰を広げたいと思います。脳は層ごとに循環し、層ごとに戻り、コンピューターが各ステップを実行する方法を理解しようとします、簡単に入ることができます。

再帰的なコードを作成する鍵は、再帰が発生する限り、呼び出しのレイヤーを考慮せずに再帰的な式に抽象化し、人間の脳を使用して再帰の各ステップを分解しようとしないことです。

 

注:

1.再帰的コードはスタックオーバーフローに注意する必要があります

関数呼び出しは、スタックを使用して一時変数を保存します。関数が呼び出されるたびに、一時変数はスタックフレームとしてカプセル化され、メモリスタックにプッシュされます。スタックは、実行後に関数が戻るまでプッシュされません。システムスタックまたは仮想マシンのスタックスペースは通常、大きくありません。再帰的ソリューションのデータサイズが大きく、呼び出しレベルが非常に深く、それがスタックにプッシュされている場合、スタックオーバーフローのリスクがあります。

オーバーフローを回避する方法:

1000などの再帰呼び出しの最大深度を制限します。最大深度が比較的小さい状況でのみ使用されます。

// 全局变量,表示递归的深度。 
depth := 0
func f(n int) (int,error) {
     ++depth
     if depth > 1000 { 
          return 0,error 
     } 
    if n == 1{
         return 1,nil
     } 
    return f(n-1) + 1,nil 
 }

2.二重カウントに注意する

次の図に示すように、ステップが計算され、f(3)が複数回計算されます。

回避する方法:

ハッシュテーブルなどのデータ構造を使用して、解決されたf(k)を保存します。f(k)を再帰的に呼び出すときは、まずハッシュテーブルで解決されているかどうかを確認し、解決されている場合はハッシュテーブルから直接戻ります。

func f(n int) int{
    if n == 1{
        return 1
    }
    if n == 2{
        return 2
    }
    // hasSolvedList 可以理解成一个 Map,key 是 n,value 是 f(n)
   if hasSolvedList.containsKey(n){
        return  hasSovledList.get(n)
  }

   ret := f(n-1) + f(n-2)
   hasSovledList.put(n, ret)
   return ret
}

3.時間コストとスペースコスト

再帰的なコードで関数を複数回呼び出します。呼び出しの数が多いと、時間コストが高くなります。

各呼び出しは、フィールドデータをメモリスタックに保存する必要があります。

再帰的なコードを非再帰的なコードに変更する

func f(n int)int {
    if n == 1{
        return 1
    } 
     if n == 2{
        return 2
     }
  
   ret := 0
  pre := 2
  prepre := 1
  for i := 3;  i <= n; i++ {
    ret = pre + prepre
    prepre = pre
    pre = ret
  }
  return ret
}

Centralでのリファラーの再帰的検出:

検出リングはハッシュテーブルを作成できます。上位のリコメンダーが取得されるたびに、最初にハッシュテーブルでチェックされ、見つからない場合は追加されます。もちろん、各クエリは独自のハッシュテーブルであり、共有できません。

階乗を再帰的に実装する

package Recursion

// 迭代实现阶乘
type Fac struct {
	val map[int]int
}

func  NewFactorial(n int) *Fac {
	return &Fac{
		make(map[int]int, n),
	}
}

func (fac *Fac) Factorial(n int) int {
	if fac.val[n] != 0{
		return fac.val[n]
	}

	if n <= 1{
		fac.val[n] = 1
		return 1
	}else {
		res := n * fac.Factorial(n-1)
		fac.val[n] =res
		return res
	}
}

func (fac *Fac) Print(n int )  {
	println(fac.val[n])
}

一連のデータ収集の完全な配置を実現する

package Recursion

import (
	"fmt"
)
// 实现一组数据集合的全排列
type RangeType struct {
	value []interface{}
}

func NewRangeArray(n int) *RangeType  {
	return &RangeType{
		make([]interface{},n),
	}
}

func (slice *RangeType)RangeALL( start int)  {
	len := len(slice.value)
	if start == len-1{
		// 如果已经是最后位置,直接将数组数据合并输出
		fmt.Println(slice.value)
	}

	for i:=start; i<len; i++{
		// i = start 时输出自己
		// 如果i和start的值相同就没有必要交换
		if i==start || slice.value[i] != slice.value[start]{
			//交换当前这个与后面的位置
			slice.value[i], slice.value[start] = slice.value[start], slice.value[i]
			//递归处理索引+1
			slice.RangeALL(start+1)
			//换回来,因为是递归,如果不换回来会影响后面的操作,并且出现重复
			slice.value[i], slice.value[start] = slice.value[start], slice.value[i]
		}
	}
}

 

公開された127元の記事 ウォン称賛24 ビュー130 000 +

おすすめ

転載: blog.csdn.net/Linzhongyilisha/article/details/100054574