排列組合:排序、排位、排
作者 白寧超
2015年10月15日18:30:07
摘要:程序員眼中的統計學系列是作者和團隊共同學習筆記的整理。首先提到統計學,很多人認為是經濟學或者數學的專利,與計算機並沒有交集。誠然在傳統學科中,其在以上學科發揮作用很大。然而隨着科學技術的發展和機器智能的普及,統計學在機器智能中的作用越來越重要。本系列統計學的學習基於《深入淺出統計學》一書(偏向代碼實現,需要讀者有一定基礎,可以參見后面PPT學習)。正如(吳軍)先生在《數學之美》一書中闡述的,基於統計和數學模型對機器智能發揮重大的作用。諸如:語音識別、詞性分析、機器翻譯等世界級的難題也是從統計中找到開啟成功之門鑰匙的。尤其是在自然語言處理方面更顯得重要,因此,對統計和數學建模的學習是尤為重要的。最后感謝團隊所有人的參與。( 本文原創,轉載注明出處:排列組合:排序、排位、排 )
目錄
【程序員眼中的統計學(1)】 信息圖形化:第一印象
【程序員眼中的統計學(2)】集中趨勢度量:分散性、變異性、強大的距
【程序員眼中的統計學(3)】概率計算:把握機會
【程序員眼中的統計學(4)】離散概率分布的運用:善用期望
【程序員眼中的統計學(5)】排列組合:排序、排位、排
【程序員眼中的統計學(6)】幾何分布、二項分布及泊松分布:堅持離散
【程序員眼中的統計學(7)】正態分布的運用:正態之美
【程序員眼中的統計學(8)】統計抽樣的運用:抽取樣本
【程序員眼中的統計學(9)】總體和樣本的估計:進行預測
【程序員眼中的統計學(10)】假設檢驗的運用:研究證據
【程序員眼中的統計學(11)】卡方分布的應用
【程序員眼中的統計學(12)】相關與回歸:我的線條如何?
0 問題
1 基本計數法則
1 分類加法計數原則
定義:若具有性質A的事件有m個,具有性質B的事件有n個,則具有性質A或性質B的事件有m+n個。
例子:事件A:a點到b點乘火車有3條線路。
事件B:a點到b點乘汽車有2條線路。
則a點到b點共有(2+3)=5條不同線路
解釋說明:完成一件事,有n類方法。在第1類辦法中有m1種不同的方法,在第2類方法中有m2種不同的方法,……,在第n類方法中有mn 種不同的方法,則完成這件事的不同方法共有:N=m1+m2+...+mn
注意點:各類方法相互獨立的。
2 分步乘法計數原則
定義:若具有性質A的事件有m個,具有性質B的事件有n個,則具有性質A與性質B的事件有m*n個。
例子:事件A:a點到b點有3條不同的路徑。
事件B:b點到c點有2條不同的路徑。
則a點到b點共有3*2=6條不同的路徑。
解釋說明:完成一件事,需要分成n個步驟。做第1步有m1種不同的方法,做第2步有m2種不同的方法,……,做第n步與mn種不同的方法,則完成這件事的不同方法共有:N=m1*m2*mn
注意點:各個步驟相互依存。
3 分類計數原理與分布計數原理相同點與不同點
- 相同點: 分類計數原理與分布計數原理都是涉及完成一件事的不同方法的種數問題。
- 不同點: 分類計數原理與“分類”有關,各種方法相互獨立,用其中任何一種方法都可以完成這件事。 分布計數原理與“分布”有關,各個步驟相互依存,只有各個步驟都完成了,這件事才算完成。
2 排列
1 排列描述
排列的定義
設 A={a1,a2,...,an}是n個不同元素的集合,m滿足0≤m≤n。任取A中m個元素按順序排成一列,稱為從A中取m個的一個無重排列。
例子:
排列符號定義
從n個不同元素中,任取m個元素的所有排列的個數叫做從n個元素的排列數,用符號表示。
排列計算公式
選排列:當m<n時的排列稱為選排列。選排列數公式:
全排列:當m=n時的排列稱為全排列。全排列公式:
2 排列的優缺點
優點
可以輔助計算排列的概率問題。
缺點
此公式只適用m個元素是不同的情況,如果當m個元素有重復的時候就不能用此方法,使用受到條件限制。
排列應用
可以計算彩票的中獎概率,分析彩票的購買的排列情況,如七位數等。
3 圓周排列
1 圓周排列描述
圓周排列定義
從n個不同的數中不重復的取出m個沿一圓圈排列,稱為一個圓周排列,表示為 。
圓周排列符號定義
從n個不同的數中不重復的取出m個沿一圓圈排列,得到的排列數,符號表示為 。
例子
圓周排列計算公式
2 圓周排列優缺點
優點
可以快速計算圓周排列的排列數。
缺點
不區分圓周排列是逆時針還是順時針排列。
圓周排列應用
在圓形的馬場比賽跑道,可以計算賽馬的中獎概率。
4 組合
1 組合描述
組合的定義
從n個不同元素中不重復的選取m個元素,組成一組(不管其順序),稱為從n個不同元素中選取m個元素的無重組合。
組合符號定義
從n個不同元素中不重復的選取m個元素,組成一組(不管其順序),得到的排列數用符號 表示。
例子:
組合計算公式
組合數的兩個性質: ,
2 組合的優缺點
優點
生活中經常計算概率的時候經常需要用到組合輔助計算。
缺點
輸入的n個元素必須互不相同。
組合應用
可以計算彩票的中獎概率,分析彩票的購買的組合情況,如組合3。
5 排列組合算法
1 排列算法
遞歸法、字典序法、遞增進位數制法、遞減近位數制法、鄰位交換法、n進制法……
遞歸算法
輸入:{1,2,3}。輸出:123,132,213,231,312,321
算法思想:第一位取1,對2,3全排列。第一位取2,對1,3全排列。第一位取3,對1,2全排列。
字典序法
算法思想:設p是集合{1、2、3、4……n}的一個全排列:p=p1p2.....pn
1、從排列的右端往左,找出第一個比右邊數字小的數字序號j,也就是第一個順序兩位數。
2、從右端開始到j.找出第一個比pj大的數字pk。
3、交換pj,pk
4、對交換后的集合中p(j+1)......pn順序逆轉,也就是順序排列。
2 排列算法描述
排列算法輸入數據、輸出結果
* @param object 輸入可選元素
* @param m 選取m個數排列
* @param out 輸出排列后的元素集
* @return 輸出排列總數
類和方法描述
類名:Permutation.java
方法:
getCountByLine(Object[] object, long m)獲取無重排列數目
getCountByCircle(Object[] object, int m)獲取無重圓周排列數目
getAllPermutation(Object[] in, List<Object[]> out, int index)得到所有排列
java代碼實現
/**
* 獲取無重線排列總數目
* 描述:從n個元數中選取m個元數進行全排列,得出一共有多少種排法
* 公式:A(m,n)=m!/(n-m)!
* 優缺點:輸入數據必須互不相同。求階乘時,使用了for循環,避免了遞歸方法導致內存溢出的風險。
* (object數組中元素總數為n,從object數組中選出的m個數排列)
* @param object 輸入可選元素
* @param m 選取m個數排列
* @return 輸出排列總數
*/
public static BigInteger getCountByLine(Object[] object, long m) {
long n = object.length;
BigInteger result = MathUtils.factorial(n-m+1, n);//計算n-m+1到n的階乘
return result;
}
/**
* 獲取無重圓周排列數目
* 描述:從n個數中選取m個數進行圓周排列,獲取一共有多少種排法
* 公式:Q(m,n)=A(m,n)/m=m!/(m*(n-m)!)
* 優缺點:輸入數據必須互不相同。m定義了int類型,限制了計算范圍。
* (object數組中元素總數為n,從object數組中選出的m個數圓周排列)
* @param object 輸入可選元素
* @param m 選取m個數排列
* @return 輸出排列總數
*/
public static BigInteger getCountByCircle(Object[] object, int m) {
int n = object.length;
BigInteger result = MathUtils.factorial(n-m+1, n).divide(BigInteger.valueOf(m));//計算n-m+1到n的階乘除以m
return result;
}
/**
* 全排列(遞歸)
* 例:輸入{1,2,3}輸出{1,2,3}{1,3,2}{2,1,3}{2,3,1}{3,1,2}{3,2,1}
* 描述:輸入一個數組,對數組中的所有元素進行全排列,把每種排列放入out集合中。
* 優缺點:輸入數據必須互不相同。使用了遞歸的算法,當輸入數據比較大時,會存在內存溢出的危險。
* @param in 輸入數據
* @param out 輸出數據
* @param index 默認為0
*/
public static void getAllPermutation(Object[] in, List<Object[]> out, int index) {
if(index == in.length-1) {
Object[] outTemp = in.clone();
out.add(outTemp);
}
for(int i=index; i<in.length; i++) {
swap(in,index,i);
getAllPermutation(in,out,index+1);//遞歸
swap(in,index,i);
}
}
/**
* 獲取所有排列
* 例:輸入{1,2,3}輸出{1}{2}{3}{1,2}{2,1}{1,3}{3,1}{2,3}{3,2}{1,2,3}……
* 描述:輸入一個數組,先從數組中選取一個數全排列,再選取兩個兩個數全排列,再選取三個數全排列,把所有的得到的排列放入out集合中。
* 優缺點:輸入元素必須互不相同。
* @param in 輸入數據
* @param out 輸出數據
*/
public static void getSelectPermutaion(Object[] in, List<Object[]> out) {
List<Object[]> outTemp = new ArrayList<Object[]>();
for(int i=0; i<in.length; i++) {
Combination.getCombination(in, outTemp, i+1);//獲取所有組合
}
for(int i=0; i<outTemp.size(); i++) {
getAllPermutation(outTemp.get(i), out, 0);//對每種組合進行全排列
}
}
/**
* 交換下標為i,j兩個元素
* 描述:對object數組的i,j位置的元素進行交換
* @param object
* @param i
* @param j
*/
public static void swap(Object[] object, int i, int j) {
Object temp = object[i];
object[i] = object[j];
object[j] = temp;
}
排列算法異常和誤差
輸入數據時檢查數據類型,輸出的數據被轉換成Object類型了。
適用或不適用場景
輸入的元素是互不相同的,並且有序排列。可以據此分析彩票的中獎率,比如七位數。
3 組合算法描述
二進制法
算法思想:首先初始化,將數組前n個元素置1,表示第一個組合為前n個數。然后從左到右掃描數組元素值的“10”組合,找到第一個“10”組合后將其變為 “01”組合,同時將其左邊的所有“1”全部移動到數組的最左端。當第一個“1”移動到數組的m-n的位置,即n個“1”全部移動到最右端時,就得到了最后一個組合。
組合算法輸入數據、輸出結果
* @param in 輸入可選元素
* @param m 選取m個數排列
* @param out 輸出組合后的元素集
* @return 返回組合后的數目
類和方法描述
類名:Combination.java
方法:
getCount(Object[] in, long m)獲取組合數
getCombination(Object[] in, List<Object[]> out, int m)獲取組合后的所有情況
java代碼實現
/**
* 獲取無重組合數目
* 描述:從n個數中選取m個元素組成一組,得到一共有多少種不同的組合
* 公式:C(m,n)=n!/(m!*(n-m)!)
* @param in 輸入可選元素
* @param m 每次選m個
* @return 返回組合后的總數目
*/
public static BigInteger getCount(Object[] in, long m) {
long n = in.length;
BigInteger result = BigInteger.valueOf(1);
if(m<2/n) {//增加計算效率
result = MathUtils.factorial(n-m+1, n).divide(MathUtils.factorial(1, m));//計算公式
} else {
result = MathUtils.factorial(m+1, n).divide(MathUtils.factorial(1, n-m));//計算公式
}
return result;
}
/**
* 組合(二進制法)
* 例:輸入{1,2,3}從中選2個的數組合。輸出:{1,2}{1,3}{2,3}
* 描述:從輸入的元素中,選m個元素組合,把所有的組合存在out中
* @param in 輸入數組
* @param out 輸出組合后的數組集
* @param m 取出m個數
*/
public static void getCombination(Object[] in, List<Object[]> out, int m) {
int n = in.length;
int[] array = new int[n];
for(int i=0; i<n; i++) {//初始化
if(i<m)
array[i] = 1;
else
array[i] = 0;
}
List<int[]> list = new ArrayList<int[]>();
boolean flag = true;
while(flag) {
int[] intTemp = array.clone();
list.add(intTemp);
for(int i=0; i<n-1; i++) {
if(array[i]==1 && array[i+1]==0) {
array[i]=0;//交換i,i+1的值
array[i+1]=1;
if(array[0]==0) {//把i位置左邊的1都移到最左邊
for(int k=0,j=0;k<i;k++) {
if(array[k]==1) {
array[j]=1;
array[k]=0;
j++;
}
}
}
break;
}
if(i==n-2) flag = false;
}
}
for(int i=0; i<list.size(); i++) {//把二進制中的1轉換成對應的數組
Object[] objTemp = new Object[m];
for(int j=0,k=0;j<n;j++) {
if(list.get(i)[j]==1) {
objTemp[k] = in[j];
k++;
}
}
out.add(objTemp);
}
}
組合算法異常和誤差
輸入數據時檢查數據類型,輸入數據的可選范圍是受限的。輸出的數據為Object類型了,可以通過類型轉換變為與輸入數據一致的類型。
適用或不適用場景
輸入的元素必須是互不相同的,並且屬於抽取不放回的情況,抽出是無序的。可以據此分析彩票的中獎率,比如組合3。
6 本章總結
排列與組合的區別