golang閉包遞歸研究

閉包

理解閉包:
閉包是指延伸了作用域的函數, 其中包含函數定義體中引用、但是不在定義體中定義的非全局變量。函數是不是匿名沒有關係, 關鍵是他能訪問定義體之外定義的非全局變量。
閉包基本概念:
閉包是可以包含自由(未綁定到特定對象)變量的代碼塊, 這個變量不在這個代碼塊內或者任何全局上下文中定義,而是在定義代碼塊的環境中定義。要執行的代碼塊(由於自由變量包含在代碼塊中, 所以這些自由變量以及他們引用的對象沒有被釋放)爲自由變量提供綁定的計算環境(作用域)
閉包的價值:
閉包的價值在於可以作爲函數對象或者匿名函數,對於類型系統而言,這意味着不僅要表示數據還要表示代碼。支持閉包的多數語言都將函數作爲第一級對象, 也就是說這些函數可以存儲到變量中作爲參數傳遞給其他函數, 最重要的是能夠被函數動態創建和返回。
閉包注意:
go語言中的閉包同樣也會引用到函數外的變量。閉包的實現確保只要閉包還被使用, 那麼被閉包引用的變量會一直存在。
閉包引用的變量一直存在, 內存消耗比較大, 要注意, 不能濫用閉包

使用閉包實現斐波那契數列

package main
import "fmt"
func fib2() func() int {
	a := 0
	b := 1
	f := func() int {
		s := a
		a = b
		b = s + b
		return s
	}
	return f
}
func main() { // 打印出每一個值
	f := fib2()
	for i := 0; i < 10; i++ {
		fmt.Println(f())
	}
}

使用閉包代替遞歸求斐波那契之和

package main
import "fmt"
// 閉包求和
func fib4() func() int {
	a := 0
	b := 1
	sum := 0
	f := func() int {
		s := a
		a = b
		b = s + b
		sum = sum + s
		return sum
	}
	return f
}
func main() {
	f := fib4()
	for i := 0; i < 10; i++ {
		fmt.Println(f())
	}
}

一些理解可以參見這篇文章
https://www.cnblogs.com/cxying93/p/6103375.html

閉包的使用場景

  • 使用閉包代替全局變量
  • 函數外或在其他函數中訪問某一函數內部的參數(變量)
  • 包裝相關功能
  • 暫停執行

遞歸

先通過程序感性認識
遞歸得階乘

package main
import "fmt"
func j(n int) int {
	if n == 1 || n == 0 {
		return 1
	}
	fmt.Println(n) // 5 4 3 2 這個數據是從前到後變化  逐漸變小
	obj := n * j(n-1)
	//fmt.Println(obj) // 這裏打印可以看出 從小到大 回溯得到 2 6 24 120 這個就是個回溯的過程
	return obj
}

//  閱讀遞歸可以 直接 從出口進行得到結果 一直到傳入參數 的初始值
func main() {
	n := 5
	j(n)
	//fmt.Println(j(n))
}

遞歸得斐波那契數列中的值

func fib(n int) uint {
	if n == 1 {
		return 0 // 遞歸遞到盡頭
	} else if n == 2 {
		return 1
	} else {
		return fib(n-1) + fib(n-2) // 返回的都是單個具體的值 怎麼看返回的到盡頭看實現過程。這是歸併
	}
}
func main() {
	n := 10
	temp := fib(n)
	fmt.Println(temp)
}

遞歸初識中的一些文章
https://www.cnblogs.com/xzxl/p/7364515.html
這個圖比較好
https://www.jianshu.com/p/104187c62e15

遞歸優缺點:

  • 優點:邏輯簡單清晰
  • 缺點: 使用過深會導致棧溢出

遞歸基本認識
遞歸 將這個詞語分開看 遞 和 歸 有去有歸
遞歸的基本思想就是把大規模的問題轉化爲小規模相似的問題來解決。在函數實現時候, 因爲解決大問題的方法和解決小問題的方法往往就是同一個方法, 所以就產生了函數調用自身的情況

遞歸理解圖
在這裏插入圖片描述

遞歸從代碼層面上來講 :函數調用自身
遞歸算法解決問題的特點:
1.遞歸就是方法裏調用自身
2.在使用遞增歸策略時,必須有一個明確的遞歸結束條件,稱爲遞歸出口。
3.遞歸算法解題通常顯得很簡潔,但遞歸算法解題的運行效率較低。所以一般不提倡用遞歸算法設計程序。
4.在遞歸調用的過程當中系統爲每一層的返回點、局部量等開闢了棧來存儲。遞歸次數過多容易造成棧溢出等,所以一般不提倡用遞歸算法設計程序

在需要將一項工作不斷分爲兩項較小的,類似的工作時候, 遞歸非常有用。遞歸有時候被稱爲分而治之策略。

一種理解

首先是思想方法上要轉變,不要試圖解決問題(這是常規的思考方式),而應該“鼠目寸光”地只想解決一點點,要點是,解決一點點之後,剩下來的問題還是原來的問題,但規模要比原問題小了。
思想和語言是密切相關的,所以問題的提法也很重要。一個問題這樣提可能感覺很難寫出遞歸,換種提法,可能就寫出來了。
平時就要注意用遞歸的方式思考,譬如什麼是鏈表?以遞歸來看就是一個指針,指向一個含有鏈表的指針。一旦你用這種方式看待鏈表,你會發現寫鏈表的代碼非常容易。反之,則非常容易拖泥帶水。
從代碼層面看,遞歸就是函數的循環,所以只要透徹理解函數,寫遞歸代碼沒什麼難的。

遞歸 棧幀
調用棧描述的是函數之間的調用關係。它由多個棧幀組成,每個棧幀對應着一個未運行完的函數。棧幀中保存了該函數的返回地址和局部變量,因而不僅能在函數執行完畢後找到正確的返回地址,還很自然地保證了不同函數間的局部變量互不相干(因爲不同函數對應着不同的棧幀)

下面舉個例子加強理解。
皇帝(擁有main函數的棧幀):大臣,你給我算一下f(3)。
大臣(擁有f(3)的棧幀):知府,你給我算一下f(2)。
知府(擁有f(2)的棧幀):縣令,你給我算一下f(1)。
縣令(擁有f(1)的棧幀):師爺,你給我算一下f(0)。
師爺(擁有f(0)的棧幀):回老爺,f(0)=1。
縣令(心算f(1)=f(0)*1=1):回知府大人,f(1)=1。
知府(心算f(2)=f(1)*2=2):回大人,f(2)=2。
大臣(心算f(3)=f(2)*3=6):回皇上,f(3)=6。
皇帝滿意了。

通過這個例子可以說明一些問題, 遞歸調用時候新建了一個棧幀, 並且跳轉到了函數開頭處執行, 就好比皇帝找大臣、大臣找知府這樣的過程。儘管同一時刻可以有多個棧幀(皇帝、大臣、知府同時處於“等待下級回話”的狀態),但“當前代碼行”只有一個

補充
調用棧並不儲存在可執行文件中,而是在運行時創建。調用棧所在的段稱爲堆棧段,它有自己的大小,如果調用次數多了,就會產生若干個棧幀,便會發生越界,這種情況稱爲棧溢出。由於局部變量也是放在堆棧段的,所以局部變量太大也會造成棧溢出,這就是爲什麼要把較大的數組放在main函數外的原因

遞歸就是有去(遞去)有回(歸來)

有去
這要求遞歸的問題需要是可以用同樣的解決辦法來回答除了規模大小不同其他完全一樣的問題
有回
要求 這些問題不斷從大到小,從近及遠的過程中, 會有一個終點, 一個臨界點 一個baseline, 一個你到了那個點不在往更小更遠的地方去的點, 從那個點開始原路返回到原點。

遞歸思想:
遞歸思想,把規模大的問題裝化爲規模小的相似的子問題來解決,在函數實現時,因爲解決大問題的方法和解決小問題的方法往往是同一個方法,所以就產生了函數調用它自身的情況。另外這個解決問題的函數必須有明顯的結束條件,這樣就不會產生無限遞歸的情況了。

需注意的是,規模大轉化爲規模小是核心思想,但遞歸併非是隻做這步轉化,而是把規模大的問題分解爲規模小的子問題和可以在子問題解決的基礎上剩餘的可以自行解決的部分。而後者就是歸的精髓所在,是在實際解決問題的過程

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章