第三課:遞迴的三個例子

來源:互聯網
上載者:User
這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。

概述

前文中講解了遞迴的運行機制,並講解了簡單的遞迴情況。從程式設計的角度來將,遞迴其實是現代程式設計不可或缺的一種方法,對於很多問題,用遞迴可能是一種較為合適的解決方案。但是如何設計遞迴需要設計或者求解出遞推公式,才能轉換為遞迴,其中的重點是設計遞推公式。本文如下部分講解三個例子,一個是人人皆知的漢諾塔,一個是求排列,一個是求整數劃分,重點講解如何設計遞迴公式。更詳細的代碼請見我的github

例1:漢諾塔

漢諾塔是學程式設計的童鞋必備的一環,其約束為:始終保持小圓盤不能在大圓盤之上,從一根柱子一到另一根柱子,中間可以藉助一根柱子,1所示:


圖1:漢諾塔,摘自維基百科

直覺上這應該是一個遞迴問題,但是如何設計遞推公式是很多童鞋覺得比較頭疼的問題。設計遞推公式的本質就是形式化,尋找規模為n的問題和規模為n-1(或者小於n-1)的問題之間關係。

那如何對漢諾塔來設計遞推公式呢?先對其中涉及的對象形式化描述,假設三根柱子分別編號為A, B, C,漢諾塔中的圓盤數為n,初始時n個圓盤在A中,求解的問題為:藉助B,將A中的n個圓盤移動到C,且要適中保持小盤不能在大盤之下。形式化為:F(n, A, B, C);接下來就要分析其中的過程,必然如下所述(肯定要先想辦法把A中最底部的最大圓盤移到C盤上):

  • 將A中上面的n-1個圓盤藉助C盤放在B盤上,其對應的問題形式化為F(n-1, A, C, B)
  • 將A中最下面的圓盤放在C中;
  • 將B中的n-1個圓盤藉助A放置在C上,其對應的問題形式化為F(n-1, B, A, C)。

當然,這還沒完,還必須定義終止條件,否則就是無限迴圈,這個問題中的終止條件為:當n=1時,直接移動,不用接住中間柱子。其對應的golang程式為:
func Hannoi(n int, first, second, third string){ if n == 1{ fmt.Println(first, "->", third) }else{ Hannoi(n - 1, first, third, second) fmt.Println(first, "->", third) Hannoi(n - 1, second, first, third) }}

例子2: 求排列

問題如下:

設計一個演算法,實現{r1,r2,...,rn}的全排列

這個問題相對例1來講,困難一些(一般情況下,涉及純數字遊戲類的演算法會困難一些)。先以例子來看,假設要求{1, 2, 3}的排列,我們一般情況下的思維邏輯是什麼。通常情況下(天才不在此列),應該是:

123, 132 | 213, 231 | 312, 321,這樣思考會比較規整

也就是說,假設數字集合為{r1,r2,...,rn},要求這n個數字組成的集合S的排列F(S),可以先取出其中的一個數rm,然後將rm放在剩下的n-1個數組成的排列F(S-{rm})之前,則rm在最前面的排列全部列玩了;如果取m = 1, 2, ...n,則列出了所有的排列。這個描述本身已經遞迴了,終止條件為:當S中的元素為1時,其直接返回該數字(一個數字只有一個排列)。對應的golang程式如下所示:
func Permutation(input string) []string { ret := make([]string, 0) if len(input) == 1 { ret = append(ret, input) return ret } for idx, v := range input { var left string = "" if idx == 0 { left = input[1:] } else { pre := input[:idx] post := input[idx+1:] left = strings.Join([]string{pre, post}, "") } leftPermutaions := Permutation(left) for _, per := range leftPermutaions { oneUnit := strings.Join([]string{string(v), per}, "") ret = append(ret, oneUnit) } } return ret}

例3:求整數的劃分

數字劃分的問題描述為:

對於任意整數n,給出其劃分的個數,如n=5,則其劃分為:

  • 5
  • 4 + 1
  • 3 + 2
  • 3 + 1 + 1
  • 2 + 2 + 1
  • 2 + 1 + 1 + 1
  • 1 + 1 + 1 + 1 + 1
    即n=5時,有7種劃分

相比例1和例2,這個問題更難一些,很難直接找出其中的遞推關係,而且似乎也沒有特別好的辦法。如果定義F(n)為整數n的劃分數,F(n-1)為n-1的劃分數,則很難直接找出二者之間的關係。如果再觀察上述列出5的劃分的過程,貌似有一些規律,每個劃分中的最大的數從大往小在排。設每個劃分中最大的數為m,用P(n, m)表示整數n的劃分中最大數不超過m的劃分數,則有什麼關係呢?

  • a) 當n=m時,顯然只有1個劃分
  • b) 當1 < m < n時,可以這樣考慮兩種情況:

    • 情況1: 先取出m作為劃分中最大的數,則整數n-m的劃分個數(劃分中的最大數不能超過m,想想為什麼)即為此種情況的個數,為P(n-m, m)
    • 情況2:再考慮劃分中最大數為m - 1的情況,此種情況的劃分個數為:P(n, m - 1).

      因此, b)情境下P(n, m) = P(n, m - 1) + P(n - m, m)

  • c) 當 m > n時,顯然,這種情況出現在b)中情況1中繼續執行時,根據語義:n的劃分個數,且劃分中最大數不能超過m(在這種情況下,這個條件顯然是成立的),因此,這種情況的劃分數為P(n, n)

  • d) 當m = 1時,P(n, m) = 1, 顯然成立。

對應的golang稱為為:
// 求整數的不同劃分數, 要求n > 0// n: 待劃分的整數,m: 劃分中最大的整數func DivideInteger(n, m int) int { if n <= 0 { return 0 } if m == 1 { return 1 } if n < m { return DivideInteger(n, n) } if n == m { return 1 + DivideInteger(n, n - 1) } if n > m && n > 1 && m > 1{ return DivideInteger(n, m - 1) + DivideInteger(n - m, m) } // 當前面的條件都不成立時,程式能執行到這裡,必然是m為1的時候,顯然,對於任意的n,必然只有一種分法,即都為1 return 0}

總結

雖然遞迴很常用,並且在一般的業務處理中,其邏輯也很簡單,但是並不意味著尋找遞迴中的遞推模式很簡單。特別是在大資料當道的今天,數字求解已成為資料採礦、機器學習很重要的一部分,如何快速尋找相應的遞推公式公式需要一定的數學基礎和經常的思維鍛煉。總之,遞推公式是遞迴的核心。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.