每次刷题都觉得自己吃了知识点不全,基础不牢固的亏,刷题的时候目标也不明确,于是看完了算法笔记并把知识点归纳了一下,当然直接看书会更加详细,这个归纳只是学习时加深印象以及方便自己之后回顾而已;之后刷题大概会根据这个大纲归纳一下具体题型,半个月之后看自己能刷多少吧^ _ ^
第二章 C/C++快速入门
2.1 基本数据类型及输入输出
2.1.2 变量类型及输入输出
类型 | 取值范围 | 占用字节 | 格式符 |
---|---|---|---|
int | 10的9次方以内整数 | 4字节(32位) | %d |
long long | 10的10次方~10的18次方 | 8字节(64位) | %lld |
float | 6~7位有效精度 | 32字节 | %f |
double | 15~16位有效精度 | 64字节 | %lf |
char | -128~127 | -128~127 | %c(字符串%s不加&) |
bool | 0/1 | 0/1 |
2.1.3 注意事项:
- 整型
long long赋值后要加LL;
整型加unsigned表示无符号,会把法术范围挪到正数上来;
- 浮点型
用double,放弃float;
- char型
ASCII码——09(4857)、AZ(6590)、az(97122) 小写字母比大写大32;
输出字符串格式scanf("%s",str);
2.1.4 三种实用输出格式
-
%md
不足m位的int变量右对齐输出,高位空格补齐;如果本身超过m位,则保持原样
-
%0md
不足m位的int变量右对齐输出,高位加0补齐;如果本身超过m位,则保持原样
-
%.mf
浮点数保留m位小数输出
2.1.5 getchar/putchar和typedef
- gerchar()——输入单个字符(一般用来识别换行符)
- putchar()——输出单个字符
- typedef long long LL;——起别名
2.2 常用math函数
f a b s ( d o u b l e x ) — — 取 绝 对 值 fabs(double x)——取绝对值 fabs(doublex)——取绝对值
p o w ( d o u b l e r , d o u b l e p ) — — r 的 p 次 方 pow(double r,double p)——r的p次方 pow(doubler,doublep)——r的p次方
f l o o r ( d o u b l e x ) / c e i l ( d o u b l e x ) — — 向 / 下 上 取 整 floor(double x) / ceil(double x)——向/下上取整 floor(doublex)/ceil(doublex)——向/下上取整
p o w ( d o u b l e r , d o u b l e p ) — — 返 回 r p pow(double r,double p)——返回r^p pow(doubler,doublep)——返回rp
s q r t ( d o u b l e x ) — — 算 术 平 方 根 sqrt(double x)——算术平方根 sqrt(doublex)——算术平方根
l o g ( d o u b l e x ) — — 取 e 的 对 数 ( l o g a b = l o g e b / l o g e a ) log(double x)——取e的对数(log_ab=log_eb/log_ea) log(doublex)——取e的对数(logab=logeb/logea)
s i n ( d o u b l e x ) / c o s ( d o u b l e x ) / t a n ( d o u b l e x ) / a s i n ( d o u b l e x ) / a c o s ( d o u b l e x ) / a t a n ( d o u b l e x ) 正 余 弦 sin(double x)/cos(double x) / tan(double x) /asin(double x) / acos(double x) /atan(double x) 正余弦 sin(doublex)/cos(doublex)/tan(doublex)/asin(doublex)/acos(doublex)/atan(doublex)正余弦
r o u n d ( d o u b l e x ) — — 四 舍 五 入 round(double x)——四舍五入 round(doublex)——四舍五入
2.3 结构体(struct)的使用
2.3.1 格式
struct 类型名{
//除自己外的所有数据类型
}结构体变量名;
2.3.2访问方法
“.”(普通变量)or"->’’(指针
2.3.3 初始化
结构体内部可以自定义初始化函数,示例
struct studenInfo{
int id;
char gender;
studenInfo(int _id,char _gender){
id=_id;
gender=_gender;
}
//可以简化为一行
//studenInfo(int _id,char _gender):id(_id),gender(_gender){}
}stu,*p;
2.4 memset赋值
对每个元素赋相同的值(0/-1)
格式:memset(a,-1,sizeof(a));
2.5 补充
2.5.1 浮点数的比较
const double eps=1e-8;
#define Equ(a,b) (fabs((a)-(b))<(eps))
2.5.2 各种复杂度
-
时间复杂度
O ( 1 ) < O ( l o g n ) < O ( n ) < O ( n 2 ) O(1)<O(logn)<O(n)<O(n^2) O(1)<O(logn)<O(n)<O(n2)p s : 两 个 f o r 循 环 的 n 不 能 超 过 1000 , 因 为 空 间 复 杂 度 运 算 次 数 n 2 不 能 超 过 1 0 7 ps:两个for循环的n不能超过1000,因为空间复杂度运算次数n^2不能超过10^7 ps:两个for循环的n不能超过1000,因为空间复杂度运算次数n2不能超过107
ps:两个for循环的n不能超过1000,因为空间复杂度不能超过
-
空间复杂度
看数组大小或者其他数据结构大小
-
编码复杂度
算法冗长,复杂度就大
第三章 入门:入门模拟
3.1 简单模拟
题目怎么说就·怎么做,不涉及算法
3.2 查找元素
给定元素,然后查找某个满足条件的元素。一般简单查找就是两个for遍历,查找算法第四章涉及
3.3 图形输出
-
找规律输出
-
定义二维数组输出
3.4 日期处理*
平年闰年/大月小月问题,需要细心
//判断闰年函数
bool isLeap(int year){
return (year%4==0&&year%100!=0)||(year%400==0)//整除400或者整除4但不整除100
}
3.5 进制转换*
- P进制转换成十进制
//公式法
int y=0,pro=1;//y为最后十进制数,pro为每次的倍数
while(x!=0){
y=y+(x%10)*pro;//获得每次x的个位数
x=x/10;//去掉x的个位数
pro=pro*P;//倍数增长
}
- 十进制转换成Q进制
//除基取余法
int z[40],num=0;//z存放Q进制y的每一位,num为位数
do{
z[num++]=y%Q;
y=y/Q;
}while(y!=0)//商不为0时才循环,z数组从高位到低位为q进制数
3.6 字符串处理
3.6.1 相关函数
仔细分析输入输出格式,会有一些细节和边界情况,积累经验,熟练相关函数
gets | ges(str) | puts | puts(str) |
---|---|---|---|
strcmp(str1,str2) | 比较字符串大小(1<2(-)) | strcpy(ste1,str2) | 复制2—>1 |
strcat(str1,str2) | str2拼接到str1后面 | strlen(str) | 字符个数 |
3.6.2 sscanf和sprintf
sscanf(str,"%d",&n);
sprintf(str,"%d",n);
//可以复杂一点,格式和scanf/printf差不多
第四章 入门:算法初步
4.1 排序
4.1.1 选择排序
//i从[0,n-1]枚举,待排序部分[i,n-1],从小到大,选出最小
int selectSort(){
for(int i=0,i<n,i++){
int k=i;
for(int j=i,j<n,j++){
if(a[j]<a[k])
k=j;
}
int temp=a[i];//交换位置
a[i]=a[k];
a[k]=temp;
}
return 0;
}
4.1.2 冒泡排序
//相邻元素比较,从小到大,有序输出
int bubbleSort(){
for (int i=0; i<n-1; i++){
for (int j = 0; j < n - 1 - i; j++){
if (a[j] > a[j + 1]){
int temp=a[j];//交换位置
a[j]=a[j+1];
a[j+1]=temp;
}//左边数更大就换
}
}
return 0;
}
4.1.3 插入排序
//将数组无序部分插入已有序部分
int insertSort(){
for(int i=1;i<len;i++){
int key=a[i];
int j=i;
while((j>=0) && (key<a[j-1])){
a[j]=a[j-1];//数组后移
j--;
}
a[j]=key;//插入
}
return 0;
}
4.1.4 排序题和sort函数的应用
#include<algorithm>
编写bool cmp()函数
sort(首地址,尾地址的下一个,cmp)
4.2散列
4.2.1散列(hash)定义和整数散列
将元素通过一个函数转换为整数,时该整数可以尽量唯一的代表这个元素
const int maxn=10010;
bool hashTable[maxn]={false};
key是整数:
1.直接定址法 2.平方取中法 3. 除留余数法
冲突三种方法:
1.线性探查法 2.平方探查法 3.链地址法
4.2.2字符串hash初步
举例:将二维坐标P映射成整数H§=x*Range+y;
字符串hash是指将字符串S映射成整数 AZ=025,az=2651,整数52~62或者直接拼接
int hashFunc(char S[],int len){
int id=0;
for(int i=0;i<len;i++){
if(S[i]<='A'&&S[i]>='Z'){
id=id*52+(S[i]-'A');
}else if(S[i]<='a'&&S[i]>='z'){
id=id*52+(S[i]-'a')+26;
}
}
return 0;
}
4.3递归
4.3.1 分治
分解--------解决---------合并
4.3.2 递归
-
递归边界:分解的尽头
-
递归式:将原问题分解成子问题的手段
-
相关问题:---------全排列------n皇后问题
4.4贪心
--------考虑当前状态下局部最优
总的来说,贪心是用来解决一类最优化问题,并希望由局部最优解来推得全局最优解的算法思想.贪心算法适用的问题一定满足最优子结构性质,即一个问题的最优解可以由子问题的最优解有效构造出来.
4.5二分
4.5.1二分查找
#include<stdio.h>
//a[]严格递增,一般采用非递归
int binarySearch(int a[],int left,int right,int x){
int mid;
while(left<=right){
mid=(left+right)/2;
if(a[mid]==x) return mid;
else if(a[mid]>x){
right=mid-1;
}else{
left=mid+1;
}
}
return -1;//查找失败
}
int main(){
const int n=10;
int a[n]={1,2,3,4,5,6,7,8,9,10};
printf("%d",binarySearch(a,0,n-1,6));
return 0;
}
ps:如果二分上界超过int数据一半,可能溢出,此时用mid=left+(right-left)/2代替mid=(left+right)/2
4.5.2二分法拓展
求近似平方根问题/装水问题/木棒切割问题
4.5.3快速幂
快速幂又叫二分幂,基于以下事实:
1. 如 果 b 是 偶 数 , 那 么 a b = a ∗ a b − 1 1.如果b是偶数,那么a^b=a*a^{b-1} 1.如果b是偶数,那么ab=a∗ab−1
2. 如 果 b 是 奇 数 , 那 么 a b = a b / 2 ∗ a b / 2 2.如果b是奇数,那么a^b=a^{b/2}*a^{b/2} 2.如果b是奇数,那么ab=ab/2∗ab/2
- 快速幂的递归写法,时间复杂度O(logb)
typedef long long LL;
//求a^b%m
LL binaryPow(LL a,LL b,LL m){
if(b==0)
return 1;
else if(b%2==1) //可以用if(b&1)代替
return a*binaryPow(a,b-1,m)%m;
else{
int mul=binaryPow(a,b/2,m);
return mul*mul%m;
}
}
- 快速幂的迭代写法(效率差异不高):
typedef long long LL;
//求a^b%m
LL binaryPow(LL a,LL b,LL m){
LL ans=1;
while(b>0){
if(b&1){
ans=ans*a%m;
}
a=a*a%m;
b>>=1;
}
return ans;
}
4.6two points
4.6.1 概念
利用问题本身和序列特性,使用两个下标i,j对序列进行扫描(同向或反向),从而以较低复杂度(一般是O(n))解决问题
//a[n]递增序列,正整数M,求a[i]+a[j]=M,two points思想示例
while(i<j){
if(a[i]+a[j]==m){
ptintf("%d %d\n",i,j);
i++;
j--;
}else if(a[i]+a[j]<m){
i++;
}else{
j--;
}
}//序列合并问题在下一小节有递归和非递归实现
4.6.2归并排序
2-路归并排序思想:------将序列不断两两分组,组内单独排序,然后不断合并.
const int maxn=100;
//将数组的[L1,R1]与[L2,R2]区间合井为有序区间(此处L2即为R1 +1)
void merge(int A[],int L1,int R1,int L2,int R2){
int i=L1,j=L2;//i指向A[L1],j指向A[L2]
int temp[maxn],index=0;//temp临时存放合并后的数组,index为下标
while(i <= R1&&j<=R2){
if(A[i] <= A[j]) {
temp[index++] = A[i++];//将A[i]加入序列temp
}else{
temp[index++] = A[j++];//将A[j]加入序列temp
}
}
while(i <= R1) temp[index++] = A[i++]; //将[L1, R1]的剩余元素加人
while(j <= R2) temp[index++] = A[j++]; //将[L2, R2]的剩余元素加入
for(i = 0; i < index; i++) {
A[L1+i]=temp[i];//将合并后的序列赋值回数组A
}
}
void mergeSort(int A[],int left,int right){
if(left<right){
int mid=(left+right)/2;//取中点
mergeSort(A,left,mid);//递归
mergeSort(A,mid+1,right);//递归
merge(A,left,mid,mid+1,right);//合并
}
}
4.6.3快速排序
把A[1]存入temp,让A[1]左边数都比他小,右边数都比他大的问题:
//递归实现
int Partition(int A[],int left,int right){
int temp=A[left];
while(left<right){
while(left<right&&A[right]>temp) right--;
A[left]=A[right];
while(left<right&&A[left]<=temp) left++;
A[right]=A[left];
}
A[left]=temp;
return left;
}
void quickSort(int A[],int left,int right){
if(left<right){
int pos=Partition(A,left,right);
quickSort(A,left,pos-1);
quickSort(A,pos+1,right);//反复递归排序直到全部有序
}
}
4.6.4生成随机数
#include<stdio.h>//程序必备
#include<stdlib.h>//随机数必备
#include<time.h>//随机数必备
int main(){
srand((unsigned)time(null));//main方法第一句,生成随机数种子
for(int i=0;i<10;i++){
printf("%d",rand());
}
return 0;
}
注意:
-
生成的是[0,RAND_MAX]范围内整数,如果想输出[a,b]范围内,需要使用rand()%(b-a+1)+a;显然rand()%(b-a+1)的范围是[0,b-a],再加上a就是[a,b]
-
想生成更大范围的随机数
(int)(round(1.0rand()/RAND_MAX(b-a)+a)
4.7其他高效技巧与算法
4.7.1打表
- 在程序中一次性计算出所有需要用到的结果,之后的查询直接取这些结果;
- 在程序B中分一次或多次计算出所有需要用到的结果,写在程序A中结果集中,然后在程序A中就可以直接使用这些结果;
- 先暴力计算小范围数据,再找规律.
4.7.2 活用递推
一种思想,细心考虑找出题目中递推关系.
4.7.3随机选择算法
原理:类似于随即快速排序算法,
问题:从一个无序数组中求得第K大的数字
//递归实现
int randPartition(int A[],int left,int right){
int temp=A[left];
while(left<right){
while(left<right&&A[right]>temp) right--;
A[left]=A[right];
while(left<right&&A[left]<=temp) left++;
A[right]=A[left];
}
A[left]=temp;
return left;
}
int randSelect(int a[],int left,int right,int K){
if(left==right) return a[left];
int p=randPartition(a,left,right);
int M=p-left+1;//主元是a[p],第p-left+1大的数
if(K==M) return a[p]
else if(K<M){
randSelect(a,left,p-1,K);//往左找
}else{
randSelect(a,p+1,right,K-M);//往右找
}
}
第五章 入门:数学问题
5.1简单数学
掌握简单的数理逻辑,就是些水题,ccf第一题那种.
5.2最大公约数与最小公倍数
5.2.1最大公约数
--------------------实现原理:欧几里得算法(辗转相除法)
- 递归式: gcd(a,b)=gcd(b,a%b)
- 递归边界: gcd(a,0)=a
- 代码实现:
int gcd(int a,int b){
if(b==0) return a;
else return gcd(b,a%b);
}
//更简洁的写法
int gcd(int a,int b){
return !b?a:gcd(b,a%b);
}
5.2.2最小公倍数
- 公式:a*b/d
- 代码实现
int main(){
int d=gcd(a,b);
printf("%d",(a*b/d));
}
5.3分数的四则运算
所谓的分数的四则运算是指,给定两个分数的分子和分母,求他们加减乘除的结果
5.3.1分数的表示和化简.
-
分数的表示-------对个分数来说, 最简洁的写法就是写成假分数的形式, 即无论分子比分母大或者小,都保留其原数。因此可以使用一个结构体来存储这种只有分子和分母的分数:
于是就可以定义Fraction 类型的变量来表示分数,或者定义数组来表示一堆分数。其中需要对这种表示制订三项规则:
①使down为非负数。如果分数为负,那么令分子up为负即可。
②如果该分数恰为0,那么规定其分子为0,分母为1。
③分子和分母没有除了1以外的公约数。struct Fraction{//分数 int up, down;//分子、分母 }
-
分数的化简-------分数的化简主要用来使Fraction变量满足分数表示的三项规定,因此化简步骤也分为以下三步:
①如果分母down为负数,那么令分子up和分母down都变为相反数。
②如果分子up为0,那么令分母down为1.
③约分:求出分子绝对值与分母绝对值的最大公约数d,然后令分子分母同时除以d.
代码如下:Fraction reduction (Fraction result){ if(result.down < 0) { //分母为负数,令分子和分母都变为相反数 result.up = -result.up; result.down = - result.down; } //如果分子为0,令分母为1 if (result.up == 0) { result.down = 1; } //如果分子不为0,进行约分 else { int d=gcd(abs (result .up), abs (result dow)); //分子分母的最大公约数 result.up /= d;//约去最大公约数 result.down /= d; return result; }
5.3.2分数的四则运算
- 加减:
result=(f1.up*f2.down+f1.down*f2.up)/(f1.down*f2.down)
result=(f1.up*f2.down-f1.down*f2.up)/(f1.down*f2.down)
- 乘除
result=(f1.up*f2.up)/(f1.down*f2.down)
result=(f1.up*f2.down)/(f1.down*f2.up)
- ps:必须当心判断除数不为0
5.3.3分数的输出
-
分数的输出根据题目的要求进行,但是大体上有以下几个注意点:
①输出分数前,先化简
②如果分数r的分母down为1,该分数是整数,一般来说题目会要求直接输出分子而省略分母。
③如果分数r的分子up的绝对值>分母down (想一想分子为什么要取绝对值? ),说明该分数是假分数,此时应按带分数的形式输出,即整数部分为r.up /r.down,分子部分为
abs(r.up) % r.down,分母部分为r.down.
④以上均不满足时说明分数r是真分数,按原样输出即可。 -
以下是一个输出示例:
void showResult (Fraction r) { //输出分数,先分数化简 r=reduction(r) ; //整数 if(r.down==1) printf("%lld", r.up) ; //假分数 else if(abs(r.up) > r.down) { printf("%d %d/%d", r.up / r.down, abs(r.up) %r.down, r.down) ; } else { //真分数 printf ("%d%d", r.up,r.down) ; /*强调一点:由于分数的乘法和除法的过程中可能使分子或分母超过int型表示范围,因 此一般情况下,分子和分母应当使用long long型来存储。*/
5.4素数
1不是素数,也不是合数
5.4.1素数判断
如果存在被整除的数,一定有一个小于sqrt(n),一个大于sqrt(n)
bool isPrime(int n){
if(n==1) return false;
int sqr=(int)sqrt(n*1.0);
for(int i=2;i<=sqr;i++){
if(n%i==0) return false;
}
return ture;
}
5.4.2素数表
//n为10的5次方以内的范围
const int maxn=101;
int Prime[maxn],pNum=0;
bool p[maxn]={0};
int Find_Prime(){
for(int i=1;i<maxn;i++){
if(isPrime[i]){
Prime[pNum++]=i;
p[i]=1;
}
}
}
5.4.3埃氏筛法:
------------如果要求更大的数,则启用
//n为10的5次方以内的范围
const int maxn=101;
int Prime[maxn],pNum=0;
bool p[maxn]={0};
int Find_Prime(){
for(int i=2;i<maxn;i++){
if(p[i]==false){
Prime[pNum++]=i;
for(int j=i+i;j<maxn;j+=i){
//筛去所有i的倍数,循环条件不能写成j<=maxn
p[j]=1;
}
}
}
}
5.5 质因子分解
------------将一个正整数分解成一个或多个质数的乘积的形式,分解步骤:
-
创建一个结构体:
struct factor{ int x,cnt;//x_质因子,cnt_其个数 }fac[10];//fac数组开到10就可以了,不论题目
-
枚举1~sqrt(n)所有质因子p,判断是否是n的质因子
int num=0; if(n%prime[i]==0){//如果是质因子 fac[num].x=prime[i];//增加质因子 fac[num].cnt=0;//初始化个数 while(n%prime[i]==0){ fac[num].cnt++;//计算个数 n/=prime[i]; } num++;//检查下一个质因子 }
-
上述步骤结束后仍然大于1,还有个大于sqrt(n)的质因子n
if(n!=1){//无法被除尽 fac[num].x=n;//把n放进去 fac[num++].cnt=1; }
5.6大整数运算
-----------定义:高精度的整数,就是无法用基础数据类型存储其精度的整数
5.6.1大整数的存储
- 定义一个整数数组存储,高位存高位,低位存低位的顺序存储,为了方便获取长度,一般定义一个结构体:
struct bign{
int len;
int d[1000];
bign(){
memset(d,0,sizeof(d));
int len=0;
}//每次结构体变量被定义都会自动初始化
}
- 但每次输入大整数一般用字符串先读入,然后再把字符串另存为bign结构体。由于char数组读入会翻转,高位存低位,低位存高位的顺序存储,即我们需要再翻转回来
bign change(char str[]){
bign a;
a.len=strlen(str);
for(int i=0;i<a.len;i++){
a.d[i]=str[a.len-i-1]-'0';//逆着赋值
}
return 0;
}
- 如果要比较两个大整数大小的规则:1.判断len大小;2.从高到低比较数字大小。
5.6.2大整数的四则运算
类似于列竖式:
//高精度加法
bign add(bign a,bign b){
bign c;
int carry=0;
for(int i=0;i<a.len||i<b.len;i++){
int temp=a.d[i]+b.d[i]+carry;
c.d[c.len++]=temp%10;//个位为结果
carry=temp/10;//10位为进位
}if(carry!=0){
c.d[c.len++]=carry;
}
return c
}
//注意point:这个模板针对非负整数,如果有单个负数,可以转换时去负号,用减法
//如果都是负数,取绝对值用加法,然后结果加上负数
//高精度减法
bign sub(bign a,bign b){
bign c;
int carry=0;
for(int i=0;i<a.len||i<b.len;i++){
if(a.d[i]<b.d[i]){//不够减
a.d[i+1]--;//向高位借位
a.d[i]+=10;//加10
}
c.d[c.len++]=a.d[i]-b.d[i];//减法结果为当前位结果
while(c.len-1>=1&&c.d[c.len-1]==0){//去除最高位的0且至少保留一位最低位
c.len--;
}
}
return c
}
//注意point:使用sub前比较两个数的大小,如果被减数小于减数,则调换数组相减,结果加上负号
//高精度与低精度乘法
bign multi(bign a,int b){
bign c;
int carry=0;
for(int i=0;i<a.len;i++){
int temp=a.d[i]*b+carry;
c.d[c.len++]==temp%10;
carry=temp/10;
}whiel(carry!=0){
c.d[c.len++]=carry%10;
carry=carry/10;
}
return c
}
高精度与低精度除法
bign multi(bign a,int b,int &r){
//r为余数
bign c;
c.len=a.len;//被除数与商位数一一对应,长度相等
for(int i=a.len-1;i>=0;i--){
r=r*10+a.d[i];
if(r<b) c.d[i]=0//不够除
else{
//够除
c.d[i]=r/b;
r=r%b;
}
}
while(c.len-1>=1&&c.d[c.len-1]==0){
//去除最高位的0且至少保留一位最低位
c.len--;
}
return c
}
5.7扩展欧几里得算法
暂时不学,有需要再回来
5.8组合数
5.8.1关于n!的一个问题
n ! 中 有 ( n / p + n / p 2 + n / p 3 + . . . ) 个 质 因 子 n!中有(n/p+n/p^2+n/p^3+...)个质因子 n!中有(n/p+n/p2+n/p3+...)个质因子
//n!中有多少个质因子p
int cal(int n,int p){
int ans=0;
while(n){
ans+=n/p;
n/=p;
}
return ans;
}
上面这个可以推广计算n!的末尾有多少个0(等于n!中因子10的个数)
一个概念:
n!中质因子p的个数,实际上等于1~n中p的倍数的个数n/p加上n/p!中质因子p的个数
//因此得出cal的递归版本
int cal(int n,int p){
if(n<p) return 0;//n<p时,不可能再存在质因子
else return n/p+cal(n/p,p)//返回n/p!中质因子p的个数**
}
5.8.2组合数的计算
方法一:根据定义式计算------暴力破解,容易溢出,忽略
方法二:根据递推式计算
递 归 公 式 : C n m = C n − 1 m + C n − 1 m − 1 递归公式:C_n^m=C_{n-1}^m+C_{n-1}^{m-1} 递归公式:Cnm=Cn−1m+Cn−1m−1
递 归 边 界 : C n 0 = C n n = 1 递归边界:C_n^0=C_n^n=1 递归边界:Cn0=Cnn=