アルゴリズム-再帰を理解し、再帰関数を作成する方法

すべてのプログラマーが再帰を深く理解して生まれているわけではありません。彼が新入生の年に入ったばかりのとき、誰かが最大公約数を見つけるために最初の再帰関数を書いたとき、彼はループなしでどれだけできるか、そしてコードができることに驚いていました。非常に簡潔であるため、ほとんどの場合、コードが非常に短い場合に再帰が実装されます。ほとんどの人は再帰も知っており、基本的に再帰を理解できますが、多くの場合、記述方法がわからないか、記述された再帰が無限のループ。アルゴリズムの記述はしばしば学習されます。これはルーチンであり、アルゴリズムを作成する人はごくわずかであり、ほとんどの人はアルゴリズムを使用し、再帰には従うべきルーチンがあります。

この記事は、いくつかの簡単な例から一般的なルーチンまで、再帰的なZamaのステップから始まり、再帰を段階的に分解します。

1再帰の3つの要素

再帰を書くということは、3つの要素の実現を書くことを意味します。3つの要素は関数、境界、再帰式です。最初にこれを書くことを忘れないでください。いくつかのアルゴリズムを書いた後、なぜこれをしたいかをゆっくりと理解できます。

1.1再帰的な主要要素-機能

関数の目的、関数の入力パラメーター、戻り値、これら3つの質問を明確にし、関数の目的から始めます。f()再帰的実装の各ステップが次のようになっていると仮定して、関数定義できます。そして、実装が何をするのか、少なくともパラメータを入力するために何が必要なのかを明確にするために、戻り値とパラメータの戻り値は、上位の呼び出しまたはグローバルデータの両方に戻るための1つのものとして理解できます。3つについて考えてください。関数の要素、次に関数が定義されます。

1.2再帰境界、再帰外

同様に、最初にこれを行い、次にその理由を考えます。このステップでは、関数の入力パラメーターが判断されnullます。パラメーターが入力されている場合、入力パラメーターは、フィボナッチ数の最初の1桁または2桁などの初期値です。シーケンス。完全に考える必要はありません。次のステップは引き続き改善されるので、ここでは、結論について直接話すのではなく、フィボナッチの最初の1つまたは2つの例を示します。このステップは関数の実現にあるので、パラメータに臨界値、初期値、特殊値が入力されていると仮定するのが考え方です。判断する必要があります。フィボナッチなど初めて書くときは、このように直接書く

if (n == 1)
  return 1;
if (n == 2)
  return 1; 

あなたが考えることは必ずしも正しいとは限りません、あるいはそれはとてもエレガントなので、あなたが境界について考える限り、それは重要ではありません。以下は境界の意味ですか?n <0の方法でこのような問題よりも、2つ、1つ、外れ値の境界、およびもう一方の端の再帰の判断がn == 1ありn == 2ます。もちろん、これら2つは、あなたが仮定すると、完全ではないと見なされる可能性があります。前のコードで、または境界を書き込むときに、プログラムの結果に影響を与えない書き込みまたは冗長性が多すぎることがわかった場合にのみ、再帰式を書き込んだ後、境界の問題を確認しましょう。

1.3漸化式

これは、最初に意味について話し、次に実現について話します。意味は、アルゴリズムのスケールを徐々に縮小するか、入力値を臨界値にできるだけ近づける方法を定義することです。関係見つけない配列との関係、解決すべき溶液を表している。問題の規模、Nより小さい問題の規模の関数値は、再帰関数の重要なステップである。再帰式がなければ、あります再帰。このようなフィボナッチ数列、という再帰的な式として、我々は、この式を観察し、その最初の発見でをし、あなたが任意の整数を入力した場合、それは、私たちが見て、関係を持っている負の値であってもよい、、、あなたは国境見ることができ、負の数値は考慮されていないので、上記の1.2の再帰を振り返って、境界を追加して次のようにします。f(n)f(n-x)f(n)f(n-x)f(n)=f(n-1) + f(n-2)nn-1n-2n-1,n-201+0

if (n <= 2)
  return 1;

2再帰的な場合

3つの簡単な例を使用して再帰を練習しましょう。これらは、フィボナッチ数列に相当するカエルのジャンプの問題であり、単一リンクリストを再帰的に反転し、ツリーを再帰的にトラバースし、3つをターゲットにします。

2.1カエルのジャンプの問題

1つ目は関数を定義することです。関数には、一意の入力値が1つしかないことは明らかですn。2つ目は、再帰的な終了条件または再帰的な境界を見つけることです。ステップが1または1の場合に最も簡単に取得できることがわかります。 2.再帰型については、毎回カエルがジャンプすることがわかります。そのとき、2つのジャンプ方法があります。どのようにしてカエルはn最初のステップに到達しますか?f(n-1)とにそれぞれ対応する2つのジャンプ方法があるf(n-2)ので、再帰的です。式がf(n)=f(n-1)+f(n-2)、の場合、アルゴリズム全体は次のようになります。

//一只青蛙一次可以跳上1级台阶,也可以跳上2级。求该青蛙跳上一个n级的台阶总共有多少种跳法。
//1。定义函数
public int f2(int n) {
   //2.确定边界
    if (n == 1)
        return 1;
    if (n == 2)
        return 2;
   //3.确定递归式
    return f2(n-1) + f2(n-2);
}

引き続き境界をチェックし、nが1未満の場合、無限ループに陥ることを確認します。プログラムは次のように変更できます。

if (n == 1)
  return 1;

if (n == 2)
  return 2;

if (n < 1)
  return 0;

//当然简单写,可以这样搞

if (n < 1)
  return 0;
if (n <= 2)
  return n; 

2.2単一リンクリストを再帰的に反転する

単一リンクリストの反転は、一般にダブルポインタ反転と見なされます。もちろん、再帰的な書き込みも可能です。同様に、最初に関数を定義し、関数に入力パラメータが1つしかないことを確認します。nodeこれnodeは、ルートノードまたは任意の中間ノードであり、2番目が境界を決定します。単一リンクリストを逆にするとnode.next、境界が失われる可能性があります。現時点では、2つの方法があります。1、冗長な書き込み、それを考慮する限り、これは可能性があります境界になります。もっと書くと間違いはありません。さらに2、3ステップ書くこともできます。2。書く量が少ない場合は、再帰的に記述して、単一リンクリストを逆にするなどしてもう一度確認します。 。node.next空の場合node.next.next、nullポインタの問題が報告されることがわかります。再帰式を記述した後、戻って境界を確認することをお勧めします。ギャップを確認したり、冗長性を削除したり、条件を追加したりできます。

この質問の核心は、チェーンを解く再帰式です。

Node last = f3(node.next); //假设当前节点的下一节点后的链表已经反转
node.next.next = node; //当前节点指向的节点指向当前节点
node.next = null ;//原来当前节点是指向下一节点的,解开当前节点,并把节点指向空
//此处解释,为什么指向空,首先可以将node节点理解为第一个节点,那么第一节点反转后,就是最后一个节点,则指向是null,否则它还是指向2,就有问题哟
//那么如果不是第一个节点呢?这个指针是怎么指向的 

たとえば、単一リンクリストが次の1,2,3,4,5ように再帰的であるとします。

ファイル

写真を見ると、各ステップの現在のノードが、反転リンクリストに追加された後の最後のノードであることがわかります。次に、それを指す必要がありnullます。理解してください。

class Node{
    int data;
    Node next;
} 
public Node f3(Node node) {
    //2.确定返回边界
    if (node == null || node.next == null)
        return node;
    //3.拿到递归推导
    Node last = f3(node.next);
    node.next.next = node;
    node.next = null ;//这个的作用是什么?,解开死循环,最后是有A->B,B->A
    return last;
}

2.3ツリーを再帰的にトラバースする

ツリーの再帰的走査も最も簡単です。これまでに走査コードを見たことがないと仮定して、この問題を最初から検討します。最初に関数を定義し、入力パラメーターが単一リンクリストの反転に類似していることを確認します。1つのTreeNodeノードのみが必要であり、境界を次のように考えます。ではnullなく、null最初に考えましたか?

if (node == null)
  return ;

if (node.left == null && node.right == null) {
  System.out.pritln(node.val);
  return ;
}

今は少し冗長に思えますが、わからないとしたら、次に再帰的に、例として事前注文を取ります

//首先节点本身
System.out.println(node.val);      
//然后节点左
preOrder(node.left);      
//然后节点右 
preOrder(node.right); 

それだけです。次に、前の境界の問題を確認します。上記のコードは2行だけです。ノードがの場合、子ノードを考慮せずにnull単純returnであることがわかります。バイトポイントの境界は境界で考慮されています。もちろん、この境界を記述してもプログラムの動作にはまったく影響しないため、最終的なトラバーサルコードは次のようになります。

 //二叉树前序遍历
public static void preOrder(TreeNode node) {
    if (node == null)
        return;
    System.out.println(node.val);
    preOrder(node.left);
    preOrder(node.right);
}

//二叉树中序遍历
public static void inOrder(TreeNode node) {
    if (node == null)
        return;
    preOrder(node.left);
    System.out.println(node.val);
    preOrder(node.right);
}

//二叉树后序遍历
public static void postOrder(TreeNode node) {
    if (node == null)
        return;
    preOrder(node.left);
    preOrder(node.right);
    System.out.println(node.val);
}

2.4シーケンスを介して二分木を構築する

次に、再帰的アルゴリズムの問​​題を入力し、バイナリツリーシーケンスを入力してバイナリツリーの構築を復元し、前のツリー走査コードをテストします。再帰ルーチンにも慣れたら、コードを記述します。直接

//1.定义函数确认,只需要一个参数,即剩余序列
public static TreeNode createBinaryTree(LinkedList<Integer> inputList) {
        //定义一个空的树节点,此处为了整齐划一,在边界和递归体里面都可以用,所以写在第一行
        TreeNode node = null;
        //2.边界
        if (inputList == null || inputList.isEmpty())
            return node;

        //3.主要递归体,从链表中删除并取第一个元素,构建好左右节点,最后返回当前节点
        Integer data = inputList.removeFirst();
        //data,主要是异常值判断,前面已经判断过链表为空了
        if (data != null) {
            node = new TreeNode(data);
            node.left = createBinaryTree(inputList);
            node.right = createBinaryTree(inputList);
        }
        return node;
}

    public static void main(String[] args) {
        //前序遍历序列
        LinkedList<Integer> inputList = new LinkedList<Integer>(Arrays.asList(new Integer[]{3,2,9,null,null,10,null,null,8,null,4}));

        TreeNode node = createBinaryTree(inputList);

        //前序遍历
        System.out.println("前序遍历:");
        preOrder(node);

    } 

3.まとめ

再帰の書き方は3つのステップです。まず、関数の入力値と戻り値、つまり関数が何をすべきかを確認します。次に、境界を判断するために、考えられるすべての境界を書き留めます。最後に、関数の戻り値を含む再帰本体を記述してから、戻って境界を確認し、境界を追加、削除、および変更します。

ps:アルゴリズムのことを考えていなかったケースも多く、考えてみればシミュレーション手法で全体像を示し、この記事を参考にしてコードを書くことができます。行く。Wu Xie、Xiao San Ye、バックグラウンドの小さな新人、ビッグデータ、人工知能。もっと注意してくださいファイル

おすすめ

転載: blog.csdn.net/hu_lichao/article/details/109966601