目錄介紹
- 01.什么是遞歸
- 02.遞歸三個(gè)條件
- 03.斐波那契數(shù)列
- 04.找指定目錄下所有文件
- 05.求1+2+…+N和
- 06.求100的階乘
- 07.有序數(shù)組合并
- 08.求一個(gè)數(shù)乘方
- 09.背包問題
- 10.選擇一支隊(duì)伍
- 11.漢諾塔問題
- 12.二分法查找
- 13.警惕重復(fù)計(jì)算
- 14.開源項(xiàng)目推薦
01.什么是遞歸
- 遞歸:在一個(gè)方法內(nèi)部對自身進(jìn)行調(diào)用。利用遞歸可以用簡單的程序來解決一些復(fù)雜的問題。比如:裴波那契數(shù)列的計(jì)算、漢諾塔、快排等問題。
- 遞歸結(jié)構(gòu)包括兩個(gè)部分:
- 1、定義遞歸頭。解答:什么時(shí)候不調(diào)用自身方法。如果沒有頭,將陷入死循環(huán),也就是遞歸的結(jié)束條件。
- 2、遞歸體。解答:什么時(shí)候需要調(diào)用自身方法。
02.遞歸三個(gè)條件
- 遞歸需要滿足的三個(gè)條件。剛剛這個(gè)例子是非常典型的遞歸,那究竟什么樣的問題可以用遞歸來解決呢?我總結(jié)了三個(gè)條件,只要同時(shí)滿足以下三個(gè)條件,就可以用遞歸來解決。
- 1.一個(gè)問題的解可以分解為幾個(gè)子問題的解
- 何為子問題?子問題就是數(shù)據(jù)規(guī)模更小的問題。比如,前面講的電影院的例子,你要知道,“自己在哪一排”的問題,可以分解為“前一排的人在哪一排”這樣一個(gè)子問題。
- 2.這個(gè)問題與分解之后的子問題,除了數(shù)據(jù)規(guī)模不同,求解思路完全一樣
- 比如電影院那個(gè)例子,你求解“自己在哪一排”的思路,和前面一排人求解“自己在哪一排”的思路,是一模一樣的。
- 3.存在遞歸終止條件+把問題分解為子問題,把子問題再分解為子子問題,一層一層分解下去,不能存在無限循環(huán),這就需要有終止條件。
- 還是電影院的例子,第一排的人不需要再繼續(xù)詢問任何人,就知道自己在哪一排,也就是 f(1)=1,這就是遞歸的終止條件。
03.斐波那契數(shù)列
- 題目要求
- 大家都知道斐波那契數(shù)列,現(xiàn)在要求輸入一個(gè)整數(shù)n,請你輸出斐波那契數(shù)列的第n項(xiàng)。n<=39
- 什么是斐波那契數(shù)列?
- 1,1,2,3,5,8,13,21......斐波那契數(shù)列從第三項(xiàng)開始,每一項(xiàng)都等于前兩項(xiàng)之和。斐波那契數(shù)列是由數(shù)學(xué)家 Leonardoda Fibonacci 以兔子繁殖為例子而提出的,所以也叫做“兔子數(shù)列”。
- 解題思路
- 可以肯定的是這一題通過遞歸的方式是肯定能做出來,但是這樣會(huì)有一個(gè)很大的問題,那就是遞歸大量的重復(fù)計(jì)算會(huì)導(dǎo)致內(nèi)存溢出。另外可以使用迭代法,用fn1和fn2保存計(jì)算過程中的結(jié)果,并復(fù)用起來。下面我會(huì)把兩個(gè)方法示例代碼都給出來并給出兩個(gè)方法的運(yùn)行時(shí)間對比。
-
采用迭代法:
int Fibonacci(int number) { if (number <= 0) { return 0; } if (number == 1 || number == 2) { return 1; } int first = 1, second = 1, third = 0; for (int i = 3; i <= number; i++) { third = first + second; first = second; second = third; } return third; } -
采用遞歸:f(n) = f(n-1) + f(n-2)
public int Fibonacci(int n) { if (n <= 0) { return 0; } if (n == 1||n==2) { return 1; } return Fibonacci(n - 2) + Fibonacci(n - 1); } - 執(zhí)行時(shí)間對比
- 假設(shè)n為40我們分別使用迭代法和遞歸法計(jì)算,計(jì)算結(jié)果如下:
- 1.迭代法:耗費(fèi)時(shí)間1ms
- 2.遞歸法:耗費(fèi)時(shí)間272ms
- 假設(shè)n為40我們分別使用迭代法和遞歸法計(jì)算,計(jì)算結(jié)果如下:
- 為何遞歸效率低
- 如果簡單分析一下程序的執(zhí)行流,就會(huì)發(fā)現(xiàn)問題在哪,以計(jì)算Fibonacci(5)為例
- 從上圖可以看出,在計(jì)算Fib(5)的過程中,F(xiàn)ib(1)計(jì)算了兩次、Fib(2)計(jì)算了3次,F(xiàn)ib(3)計(jì)算了兩次,本來只需要5次計(jì)算就可以完成的任務(wù)卻計(jì)算了9次。這個(gè)問題隨著規(guī)模的增加會(huì)愈發(fā)凸顯,以至于Fib(1000)已經(jīng)無法再可接受的時(shí)間內(nèi)算出。
- 當(dāng)時(shí)使用的是簡單的用定義來求 fib(n),也就是使用公式 fib(n) = fib(n-1) + fib(n-2)。這樣的想法是很容易想到的,可是仔細(xì)分析一下我們發(fā)現(xiàn),當(dāng)調(diào)用fib(n-1)的時(shí)候,還要調(diào)用fib(n-2),也就是說fib(n-2)調(diào)用了兩次,同樣的道理,調(diào)用f(n-2)時(shí)f(n-3)也調(diào)用了兩次,而這些冗余的調(diào)用是完全沒有必要的??梢杂?jì)算這個(gè)算法的復(fù)雜度是指數(shù)級的。
- 遞歸迭代效率比較
- 遞歸調(diào)用實(shí)際上是函數(shù)自己在調(diào)用自己,而函數(shù)的調(diào)用開銷是很大的,系統(tǒng)要為每次函數(shù)調(diào)用分配存儲空間,并將調(diào)用點(diǎn)壓棧予以記錄。而在函數(shù)調(diào)用結(jié)束后,還要釋放空間,彈?;謴?fù)斷點(diǎn)。所以說,函數(shù)調(diào)用不僅浪費(fèi)空間,還浪費(fèi)時(shí)間。它的很多時(shí)間浪費(fèi)在對函數(shù)調(diào)用的處理上。
04.找指定目錄下所有文件
- 問題如下所示:
- 列出(或刪除)指定目錄下的所有文件
- 實(shí)例代碼,如下所示
/** * 找出指定目錄下的所有文件 * 遞歸 * * @param files * @return */ public static List<File> listFiles(File files) { List<File> fileList = new ArrayList<>(); if (files.isDirectory()) { for (File file : files.listFiles()) { fileList.addAll(listFiles(file)); } } else { fileList.add(files); } return fileList; } - 測試代碼
public static void main(String args[]) { List<File> l = listFiles(new File("E:\\yc\\JavaProjectTest\\src")); System.out.println("共" + l.size() + "個(gè)文件"); for (File f : l) { System.out.println(f.getName()); //(這里只打印了文件的文件名) } }
05.求1+2+…+N和
- 題目要求
- 問題如下所示:
- 計(jì)算從1+2+3+…+N的和
- 示例 :
計(jì)算從1+2+3+…+100的和是5050
- 問題如下所示:
- 實(shí)例代碼,如下所示
/** * 獲取從1+到N的和 * * @param num * @return */ public static int getSum(int num) { if (num == 1) { return 1; } return num + getSum(num - 1); } - 測試代碼
public static void main(String args[]) { System.out.println(getSum(100)); } //執(zhí)行結(jié)果 5050
06.求100的階乘
- 問題如下所示:
- 求100的階乘
- 實(shí)例代碼,如下所示
public BigInteger sum(int i) { if (i == 1) { return BigInteger.ONE; } return BigInteger.valueOf(i).multiply(sum(i - 1)); } - 測試代碼
public static void main(String[] args) { LoopMutiply test = new LoopMutiply(); try { System.out.println("yc計(jì)算結(jié)果:" + test.sum(50) + "!"); } catch (Exception e) { e.printStackTrace(); } }
07.有序數(shù)組合并
- 問題如下所示:
- 給你兩個(gè)有序數(shù)組,a是{ 1, 3, 4 },b是{ 2, 3, 5, 6 },請把他合成一個(gè)新的數(shù)組,然后輸出……
- 實(shí)例代碼,如下所示
// 合并數(shù)組 public static int[] mergeArray(int[] a, int[] b) { int result[] = new int[a.length + b.length]; if (checkSort(a) && checkSort(b)) { // 說明ab數(shù)組都是有序的數(shù)組 // 定義兩個(gè)游標(biāo) int i = 0, j = 0, k = 0; while (i < a.length && j < b.length) { if (a[i] <= b[j]) { result[k++] = a[i++]; } else { result[k++] = b[j++]; } } while (i < a.length) { // 說明a數(shù)組還有剩余 result[k++] = a[i++]; } while (j < b.length) { result[k++] = b[j++]; } } return result; } // 檢查一個(gè)數(shù)組是否是有序1 2 3 public static boolean checkSort(int[] a) { boolean flag = false;// 默認(rèn)不是有序的 for (int i = 0; i < a.length - 1; i++) { if (a[i] > a[i + 1]) { // 說明不是有序的 flag = false; break; } else { flag = true; } } return flag; } - 測試結(jié)果
public static void main(String[] args) { int[] a = { 1, 3, 4 }; int[] b = { 2, 3, 5, 6 }; int[] c = mergeArray(a, b); for (int n : c) { System.out.print(n + " "); } }
08.求一個(gè)數(shù)乘方
- 問題如下所示:
- 求一個(gè)數(shù)的乘方
- 示例 :
比如2^8,我們可以會(huì)求表達(dá)式2*2*2*2*2*2*2*2的值,但是如果y的值很大,這個(gè)會(huì)顯得表達(dá)式很冗長。那么由沒有更快一點(diǎn)方法呢? - 問題分析
- 一般稍微復(fù)雜一點(diǎn)的計(jì)算器上面都能求一個(gè)數(shù)的乘法,通常計(jì)算器上面的標(biāo)志是 x^y 這樣的按鍵,表示求 x 的 y 次方。一般情況下我們是如何求一個(gè)數(shù)的乘法的呢?
- 數(shù)學(xué)公式如下是成立的:(Xa)b = Xa*b
- 如果如果求28次方,我們可以先假定22=a,于是28 = (22)4 ,那么就是a4 ;假定 a2 = b,那么 a4 = b2,而b2可以寫成(b2)1。于是現(xiàn)在28就轉(zhuǎn)換成:b*b
- 也就是說我們將乘方的運(yùn)算轉(zhuǎn)換為乘法的運(yùn)算。求xy的值,當(dāng)y是偶數(shù)的時(shí)候,最后能轉(zhuǎn)換成兩個(gè)數(shù)相乘,當(dāng)時(shí)當(dāng)y是奇數(shù)的時(shí)候,最后我們必須要在返回值后面額外的乘以一個(gè)x。
- x^y= (x2)(y/2),定義a=x^2,b=y/2, 則得到形如: x^y= a^b;
- 實(shí)例代碼,如下所示
public static int pow(int x,int y){ if(x == 0 || x == 1){ return x; } if(y > 1){ int b = y/2; int a = x*x; if(y%2 == 1){//y為奇數(shù) return pow(a,b)*x; }else{//y為偶數(shù) return pow(a,b); } }else if(y == 0){ return 1; }else{//y==1 return x; } } - 測試結(jié)果
public static void main(String[] args) { System.out.println("yc計(jì)算結(jié)果:" + pow(2,8) + "!"); }
09.背包問題
- 問題如下所示:
- 背包問題也是計(jì)算機(jī)中的經(jīng)典問題。在最簡單的形式中,包括試圖將不同重量的數(shù)據(jù)項(xiàng)放到背包中,以使得背包最后達(dá)到指定的總重量。
- 示例 :
- 比如:假設(shè)想要讓背包精確地承重20磅,并且有 5 個(gè)可以放入的數(shù)據(jù)項(xiàng),它們的重量分別是 11 磅,8 磅,7 磅,6 磅,5 磅。這個(gè)問題可能對于人類來說很簡單,我們大概就可以計(jì)算出 8 磅+ 7 磅 + 5 磅 = 20 磅。但是如果讓計(jì)算機(jī)來解決這個(gè)問題,就需要給計(jì)算機(jī)設(shè)定詳細(xì)的指令了。
- 問題分析
- 一、如果在這個(gè)過程的任何時(shí)刻,選擇的數(shù)據(jù)項(xiàng)的總和符合目標(biāo)重量,那么工作便完成了。
- 二、從選擇的第一個(gè)數(shù)據(jù)項(xiàng)開始,剩余的數(shù)據(jù)項(xiàng)的加和必須符合背包的目標(biāo)重量減去第一個(gè)數(shù)據(jù)項(xiàng)的重量,這是一個(gè)新的目標(biāo)重量。
- 三、逐個(gè)的試每種剩余數(shù)據(jù)項(xiàng)組合的可能性,但是注意不要去試所有的組合,因?yàn)橹灰獢?shù)據(jù)項(xiàng)的和大于目標(biāo)重量的時(shí)候,就停止添加數(shù)據(jù)。
- 四、如果沒有合適的組合,放棄第一個(gè)數(shù)據(jù)項(xiàng),并且從第二個(gè)數(shù)據(jù)項(xiàng)開始再重復(fù)一遍整個(gè)過程。
- 五、繼續(xù)從第三個(gè)數(shù)據(jù)項(xiàng)開始,如此下去直到你已經(jīng)試驗(yàn)了所有的組合,這時(shí)才知道有沒有解決方案。
- 實(shí)例代碼,如下所示
public class Knapsack { private int[] weights; //可供選擇的重量 private boolean[] selects; //記錄是否被選擇 public Knapsack(int[] weights){ this.weights = weights; selects = new boolean[weights.length]; } /** * 找出符合承重重量的組合 * @param total 總重量 * @param index 可供選擇的重量下標(biāo) */ public void knapsack(int total,int index){ if(total < 0 || total > 0 && index >= weights.length){ return;//沒找到解決辦法,直接返回 } if(total == 0){//總重量為0,則找到解決辦法了 for(int i = 0 ; i < index ; i++){ if(selects[i] == true){ System.out.println(weights[i]+" "); } } System.out.println(); return; } selects[index] = true; knapsack(total-weights[index], index+1); selects[index] = false; knapsack(total, index+1); } } - 測試結(jié)果
public static void main(String[] args) { int array[] = {11,9,7,6,5}; int total = 20; Knapsack k = new Knapsack(array); k.knapsack(total, 0); }
10.選擇一支隊(duì)伍
- 問題如下所示:
- 在數(shù)學(xué)中,組合是對事物的一種選擇,而不考慮他們的順序。
- 示例 :
- 比如有5個(gè)登山隊(duì)員,名稱為A,B,C,D和E。想要從這五個(gè)隊(duì)員中選擇三個(gè)隊(duì)員去登峰,這時(shí)候如何列出所有的隊(duì)員組合。(不考慮順序)
- 問題分析
- 還是以遞歸的思想來解決:首先這五個(gè)人的組合選擇三個(gè)人分成兩個(gè)部分,第一部分包含A隊(duì)員,第二部分不包含A隊(duì)員。假設(shè)把從 5 個(gè)人中選出 3 個(gè)人的組合簡寫為(5,3),規(guī)定 n 是這群人的大小,并且 k 是組隊(duì)的大小。那么根據(jù)法則可以有:
- (n,k) = (n-1,k-1) + (n-1,k)
- 對于從 5 個(gè)人中選擇 3 個(gè)人,
- 有:(5,3) = (4,2)+(4,3)
- (4,2)表示已經(jīng)有A隊(duì)員了,然后從剩下的4個(gè)隊(duì)員中選擇2個(gè)隊(duì)員,(4,3)表示從5個(gè)人中剔除A隊(duì)員,從剩下的4個(gè)隊(duì)員中選擇3個(gè)隊(duì)員,這兩種情況相加就是從5個(gè)隊(duì)員中選擇3個(gè)隊(duì)員。
- 現(xiàn)在已經(jīng)把一個(gè)大問題轉(zhuǎn)換為兩個(gè)小問題了。從4個(gè)人的人群中做兩次選擇(一次選擇2個(gè),一次選擇3個(gè)),而不是從5個(gè)人的人群中選擇3個(gè)。
- 從4個(gè)人的人群中選擇2個(gè)人,又可以表示為:(4,2) = (3,1) + (3,2),以此類推,很容易想到遞歸的思想。
- 實(shí)例代碼,如下所示
public class Combination { private char[] persons;//組中所有可供選擇的人員 private boolean[] selects;//標(biāo)記成員是否被選中,選中為true public Combination(char[] persons){ this.persons = persons; selects = new boolean[persons.length]; } public void showTeams(int teamNumber){ combination(teamNumber,0); } /** * * @param teamNumber 需要選擇的隊(duì)員數(shù) * @param index 從第幾個(gè)隊(duì)員開始選擇 */ public void combination(int teamNumber,int index){ if(teamNumber == 0){//當(dāng)teamNumber=0時(shí),找到一組 for(int i = 0 ; i < selects.length ; i++){ if(selects[i] == true){ System.out.print(persons[i]+" "); } } System.out.println(); return; } //index超過組中人員總數(shù),表示未找到 if(index >= persons.length ){ return; } selects[index] = true; combination(teamNumber-1, index+1); selects[index] = false; combination(teamNumber, index+1); } } - 測試結(jié)果
public static void main(String[] args) { char[] persons = {'A','B','C','D','E'}; Combination cb = new Combination(persons); cb.showTeams(3); }
11.漢諾塔問題
- 問題如下所示:
- 漢諾塔問題是由很多放置在三個(gè)塔座上的盤子組成的一個(gè)古老的難題。所有盤子的直徑是不同的,并且盤子中央都有一個(gè)洞使得它們剛好可以放在塔座上。所有的盤子剛開始都放置在A 塔座上。這個(gè)難題的目標(biāo)是將所有的盤子都從塔座A移動(dòng)到塔座C上,每次只可以移動(dòng)一個(gè)盤子,并且任何一個(gè)盤子都不可以放置在比自己小的盤子之上。
- 示例 :
- 試想一下,如果只有兩個(gè)盤子,盤子從小到大我們以數(shù)字命名(也可以想象為直徑),兩個(gè)盤子上面就是盤子1,下面是盤子2,那么我們只需要將盤子1先移動(dòng)到B塔座上,然后將盤子2移動(dòng)到C塔座,最后將盤子1移動(dòng)到C塔座上。即完成2個(gè)盤子從A到C的移動(dòng)。
- 如果有三個(gè)盤子,那么我們將盤子1放到C塔座,盤子2放到B塔座,在將C塔座的盤子1放到B塔座上,然后將A塔座的盤子3放到C塔座上,然后將B塔座的盤子1放到A塔座,將B塔座的盤子2放到C塔座,最后將A塔座的盤子1放到C塔座上。
- 問題分析
- 如果有四個(gè),五個(gè),N個(gè)盤子,那么我們應(yīng)該怎么去做?這時(shí)候遞歸的思想就很好解決這樣的問題了,當(dāng)只有兩個(gè)盤子的時(shí)候,我們只需要將B塔座作為中介,將盤子1先放到中介塔座B上,然后將盤子2放到目標(biāo)塔座C上,最后將中介塔座B上的盤子放到目標(biāo)塔座C上即可。
- 所以無論有多少個(gè)盤子,我們都將其看做只有兩個(gè)盤子。假設(shè)有 N 個(gè)盤子在塔座A上,我們將其看為兩個(gè)盤子,其中(N-1)~1個(gè)盤子看成是一個(gè)盤子,最下面第N個(gè)盤子看成是一個(gè)盤子,那么解決辦法為:
- ①、先將A塔座的第(N-1)~1個(gè)盤子看成是一個(gè)盤子,放到中介塔座B上,然后將第N個(gè)盤子放到目標(biāo)塔座C上。
- ②、然后A塔座為空,看成是中介塔座,B塔座這時(shí)候有N-1個(gè)盤子,將第(N-2)~1個(gè)盤子看成是一個(gè)盤子,放到中介塔座A上,然后將B塔座的第(N-1)號盤子放到目標(biāo)塔座C上。
- ③、這時(shí)候A塔座上有(N-2)個(gè)盤子,B塔座為空,又將B塔座視為中介塔座,重復(fù)①,②步驟,直到所有盤子都放到目標(biāo)塔座C上結(jié)束。
- 簡單來說,跟把大象放進(jìn)冰箱的步驟一樣,遞歸算法為:
- ①、從初始塔座A上移動(dòng)包含n-1個(gè)盤子到中介塔座B上。
- ②、將初始塔座A上剩余的一個(gè)盤子(最大的一個(gè)盤子)放到目標(biāo)塔座C上。
- ③、將中介塔座B上n-1個(gè)盤子移動(dòng)到目標(biāo)塔座C上。
- 實(shí)例代碼,如下所示
/** * 漢諾塔問題 * @param dish 盤子個(gè)數(shù)(也表示名稱) * @param from 初始塔座 * @param temp 中介塔座 * @param to 目標(biāo)塔座 */ public static void move(int dish,String from,String temp,String to){ if(dish == 1){ System.out.println("將盤子"+dish+"從塔座"+from+"移動(dòng)到目標(biāo)塔座"+to); }else{ move(dish-1,from,to,temp);//A為初始塔座,B為目標(biāo)塔座,C為中介塔座 System.out.println("將盤子"+dish+"從塔座"+from+"移動(dòng)到目標(biāo)塔座"+to); move(dish-1,temp,from,to);//B為初始塔座,C為目標(biāo)塔座,A為中介塔座 } } - 測試結(jié)果
move(3,"A","B","C");
12.二分法查找
- 問題如下所示:
- 一個(gè)一系列數(shù)組,然后找到某個(gè)值的索引
- 問題分析
- 一定是有序表,升序降序都可以
- 實(shí)例代碼,如下所示
/** * @param array 有序數(shù)組,但不限于數(shù)組 * @param start 開始查找的數(shù)組下標(biāo) * @param end 結(jié)束查找的數(shù)組下標(biāo) * @param searchValue 要搜索的值 * @return */ public static int search(int[] array, int start, int end, int searchValue){ if (array != null && array.length > 0){ int middle = (start + end) / 2; int middleValue = array[middle]; if (searchValue == middleValue){ return middle; }else if (searchValue < middleValue){ //查詢值小于中值,在中值前面再次搜索,縮小范圍 return search(array, start, middle-1, searchValue); }else { //查詢值大于中值,在中值后面再次搜索,縮小范圍 return search(array, middle+1, end, searchValue); } }else { return -1; } } - 測試結(jié)果
public static void main(String[] args) { int[] array = {1,3,5,7,9,12,14,15,19,20,22,23,28,30}; System.out.println(search(array, 0, array.length-1, 20)); }
13.警惕重復(fù)計(jì)算
- 除此之外,使用遞歸時(shí)還會(huì)出現(xiàn)重復(fù)計(jì)算的問題。剛才講的第三個(gè)遞歸代碼的例子,如果我們把整個(gè)遞歸過程分解一下的話,那就是這樣的:
- 想要計(jì)算 f(5),需要先計(jì)算 f(4) 和 f(3),而計(jì)算 f(4) 還需要計(jì)算 f(3),因此,f(3) 就被計(jì)算了很多次,這就是重復(fù)計(jì)算問題。
- 為了避免重復(fù)計(jì)算,我們可以通過一個(gè)數(shù)據(jù)結(jié)構(gòu)(比如散列表)來保存已經(jīng)求解過的 f(k)。當(dāng)遞歸調(diào)用到 f(k) 時(shí),先看下是否已經(jīng)求解過了。如果是,則直接從散列表中取值返回,不需要重復(fù)計(jì)算,這樣就能避免剛講的問題了。
- 按照上面的思路,我們來改造一下剛才的代碼:
public int Fibonacci(int n) { if (n == 1) return 1; if (n == 2) return 2; // hasSolvedList 可以理解成一個(gè) Map,key 是 n,value 是 f(n) if (hasSolvedList.containsKey(n)) { return hasSovledList.get(n); } int ret = f(n-1) + f(n-2); hasSovledList.put(n, ret); return ret; } - 除了堆棧溢出、重復(fù)計(jì)算這兩個(gè)常見的問題。遞歸代碼還有很多別的問題。在時(shí)間效率上,遞歸代碼里多了很多函數(shù)調(diào)用,當(dāng)這些函數(shù)調(diào)用的數(shù)量較大時(shí),就會(huì)積聚成一個(gè)可觀的時(shí)間成本。
14.開源項(xiàng)目推薦
- 1.開源博客匯總
- 2.組件化實(shí)踐項(xiàng)目(停止維護(hù))
- 3.視頻播放器封裝庫
- 4.狀態(tài)切換管理器封裝庫
- 5.復(fù)雜RecyclerView封裝庫
- 6.彈窗封裝庫
- 7.版本更新封裝庫
- 8.狀態(tài)欄封裝庫
- 9.輕量級線程池封裝庫
- 10.輪播圖封裝庫
- 11.音頻播放器
- 12.畫廊與圖片縮放控件
- 13.Python多渠道打包
- 14.整體側(cè)滑動(dòng)畫封裝庫
- 15.Python爬蟲妹子圖
- 17.自定義進(jìn)度條
- 18.自定義折疊和展開布局
- 19.商品詳情頁分頁加載
- 20.在任意View控件上設(shè)置紅點(diǎn)控件
- 21.仿抖音一次滑動(dòng)一個(gè)頁面播放視頻庫
- 22.騰訊x5開源庫