做爲五大算法之一的分治法,可算是最先接觸的一種算法。分治法,與其說是一種算法,不如將其稱爲策略來的更貼切一些。算法的思想就是將大問題分紅小問題,並解決小問題以後合併起來生成大問題的解。java
二分法針對於有序集合的處理來講顯得比按序遍從來得更快一些。記得有次我讀到一篇處理問題的博客:有一個城市到另外一個城市之間的電杆不通電了,該如何排查?做爲經常使用思惟大概就是逐個去查了,若是能肯定兩地間的電杆是串聯的節點,那麼二分法顯然是比較有效率的,由於它跳過了不少沒必要要排查的電杆。python
咱們通常使用二分法去尋找一個流中的特定的數。好比查找有序數列中是否存在一個數,或者使用二分法求一個數的根號。算法
1.找數字數組
假設咱們須要在1-10000裏面找一個數200,使用逐個搜索的方法,咱們會消耗200步。若是計入小數的畫,恐怕就大大超過200這個消耗了。ide
假如使用二分法:ui
第一步咱們找到1-10000中間的那個數:5000。它大於200,因此200應該在1-4999這個區間內,這樣咱們就丟掉了後5000個數。spa
第二步咱們找到2500,也比200要大,200在1-2500這個區間內。線程
第三步找到1250這個數,也比200大。blog
第四步找到750。排序
第五步找到375。
第六步找到167,它比200要小了,說明200在167-375之間。
第七步找到271,它在167-271之間。
第八步找到219,它在167-219之間。
第九步找到193,它在193-219之間。
第十步找到206,它在193-206之間。
第十一步找到199,它在199-206之間。
第十二步找到202,它在199-202之間。
第十三步找到200。
在n=10000的這個問題來說,從200步到13步是一個很大的改進。若是我在這裏寫200步,那我確定是傻了。
使用二分法找數組中的一個值的下標,若是沒有找到則返回-1
int index=-1; public void BisectionFind(int a[],int l,int r,int target){ if(l<r){ int mid=(l+r)/2; if(a[mid]<target) BisectionFind(a,mid+1,r,target); else if(a[mid]>target) BisectionFind(a, l, mid-1, target); else { index=mid; return; } } } public static void main(String[] args) { SqrtDemo sq=new SqrtDemo(); int a[]={5,13,19,21,37,56,64,75,80,88,92}; sq.BisectionFind(a, 0, a.length-1, 21); System.out.println(sq.index); }
2.求根號
對於我來講,最先接觸的有序數列大概就是數軸了,要在數軸上找到一個根號的具體值,就是從數軸上找一個數乘以本身看是否在所求數字的周圍。若是精確度能夠接受的話,那麼就採用這個值爲這個數的根號的近似值。
這回咱們捨棄的是半個數軸,半個數軸按照精確度的不一樣,它會產生不一樣的複雜度。因此二分法的效率,遠遠高於按序查找。
public double sqrt1(double number,double precision){ double up=(number>1?number:1); double down=0; double n; int time=0; while(true){ n=(down+up)/2; if(n*n-number<precision && n*n-number>=0) break; else if(n*n-number>precision) up=n; else if(n*n-number<0) down=n; time++; System.out.println(n); } System.out.println("time="+time); return n; }
SqrtDemo sq=new SqrtDemo(); System.out.println(sq.sqrt1(10, 0.001));
上面兩種問題,它們能肯定這個問題的解就在序列的內部,因此它們在執行的時候都轉換成了尋找子問題的解。在不斷分割問題的過程當中,問題的複雜度急劇降低,效率大大地提升了。
經典的分治法案例,在亂序數組中作到了O(nlogn)的效率,對冒泡法(O(n*n))的一個很大的改進。
快速排序的步驟:1.尋找一個基準元素
2.從右向左尋找大於(小於)基準元素的值
3.從左向右尋找小於(大於)基準元素的值
4.使得基準元素左邊都是小於(大於)它的元素,右邊都是大於(小於)它的元素
5.遞歸的處理基準元素左邊與右邊的模塊
C++版
int Partition1(int a[],int i,int j){ int start=i; int end=j; int x=a[i]; while(start<end){ while(a[end]>=x && end>start) end--; swap(a[start],a[end]); while(a[start]<=x && end>start) start++; swap(a[start],a[end]); } cout<<"中心位置:"<<start<<endl; return start; } void quickSort1(int a[],int p,int r){ if(p<r) { int x=Partition1(a, p, r); quickSort1(a, p, x-1); quickSort1(a, x+1, r); } }
Java版
public int Partition(int a[],int p,int r){ int start=p; int end=r; int x=a[p]; while(start<end){ while(start<end && a[end]>=x) end--; if(start<end) a[start++]=a[end]; while(start<end && a[start]<=x) start++; if(start<end) a[end--]=a[start]; } a[start]=x; return start; } public void quickSort(int a[],int i,int j){ if(i<j){ int p=Partition(a, i, j); quickSort(a, i, p-1); quickSort(a, p+1, j); } }
Python版
def Partition(a,p,r): x=a[p] i=p j=r while(1): while(1): if(a[i]<=x and i<len(a)-1): i=i+1 else: break while(1): if(a[j]>=x and j>0): j=j-1 else: break if(i>=j): break else: a [j], a [i] = a [i], a [j] a[p]=a[j] a[j]=x return j def quickSort(a,i,j): if(i<j): p=Partition(a,i,j) quickSort(a,i,p-1) quickSort(a,p+1,j) def PartitionDemo(a,p,r): x=a[p] start=p end=r while start<end : while start<end and a[end]>=x : end-=1 while start<end and a[start]<x : a[start]=a[end] start+=1 a[end]=a[start] a[start]=x return start def quickSortDemo(a,i,j): if(i<j): q=PartitionDemo(a,i,j) quickSortDemo(a,i,q-1) quickSortDemo(a,q+1,j) a=[9,5,2,4,7,3,6,8,15,18,11,13] quickSortDemo(a,0,len(a)-1) print a
做爲經典排序算法,使用分治策略,歸併排序無疑是最能體現分治思想而且易於理解的一種算法。它在排序以前先將序列分割成最短爲1的小數組。當長度爲1的時候,數組無疑是有序的。合併的時候就如樹形結構逆着生成根節點同樣,子問題排序、合併,最終生成一個有序的數組。
C++版
void merge(int a[],int b[],int p,int mid,int r){ int i=p; int j=mid+1; int t=p; while(i<=mid && j<=r){ if(a[i]<a[j]) b[t++]=a[i++]; else if(a[j]<=a[i]) b[t++]=a[j++]; } if(i!=mid) while(t<=r) b[t++]=a[j++]; else while(t<=r) b[t++]=a[i++]; for(i=p;i<=r;i++) a[i]=b[i]; } void MergeSort(int a[],int b[],int start,int end){ if(start<end){ int mid=(start+end)/2; MergeSort(a,b,start,mid); MergeSort(a,b,mid+1,end); merge(a,b,start,mid,end); } }
Java版
public void merge(int a[],int b[],int start,int mid,int end){ int i=start; int j=mid+1; int k=start; while(i<=mid && j<=end){ if(a[i]<=a[j]) b[k++]=a[i++]; else b[k++]=a[j++]; } while(i<=mid) b[k++]=a[i++]; while(j<=end) b[k++]=a[j++]; for(i=start;i<=end;i++) a[i]=b[i]; } public void MergeSort(int a[],int b[],int start,int end){ if(start<end) { int mid=(start+end)/2; MergeSort(a,b,start,mid); MergeSort(a,b,mid+1,end); merge(a,b,start,mid,end); } }
分治法做爲一個比較重要的算法,思想的理解來講仍是比較簡單的。可是它寫起代碼來確實有些許抽象。從C++到java再到python,雖然我一直按照着它的思想來寫,可是不一樣語言的實現確實有些小小的區別。至今我還描述不出來,可是這給我提了個醒,學算法必定要作題!不只地把經典算法的實現老老實實寫出來,還要認真地體會它的細節,不停在腦子裏畫出程序的執行結構圖,使之造成習慣。