專題:分治法

  • 分治法(Divide and Conquer)

    做爲五大算法之一的分治法,可算是最先接觸的一種算法。分治法,與其說是一種算法,不如將其稱爲策略來的更貼切一些。算法的思想就是將大問題分紅小問題,並解決小問題以後合併起來生成大問題的解。java

     分治法的精髓:
      分--將問題分解爲規模更小的子問題;
      治--將這些規模更小的子問題逐個擊破;
      合--將已解決的子問題合併,最終得出「母」問題的解;
    分治法的做用,天然是讓程序更加快速地處理問題。好比一個n的問題分解成兩個n/2的問題,並由兩我的來完成,效率就會快一些。固然單線程的程序的分治法,就是把n的問題剔除掉能夠省略的步驟,從而提升程序運行的速度。
                                                            
  • 二分法(Bisection)

    二分法針對於有序集合的處理來講顯得比按序遍從來得更快一些。記得有次我讀到一篇處理問題的博客:有一個城市到另外一個城市之間的電杆不通電了,該如何排查?做爲經常使用思惟大概就是逐個去查了,若是能肯定兩地間的電杆是串聯的節點,那麼二分法顯然是比較有效率的,由於它跳過了不少沒必要要排查的電杆。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));

  

 

   上面兩種問題,它們能肯定這個問題的解就在序列的內部,因此它們在執行的時候都轉換成了尋找子問題的解。在不斷分割問題的過程當中,問題的複雜度急劇降低,效率大大地提升了。

    

 

 

  • 快速排序(QuickSort)

    經典的分治法案例,在亂序數組中作到了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
  • 歸併排序(MergeSort)

    做爲經典排序算法,使用分治策略,歸併排序無疑是最能體現分治思想而且易於理解的一種算法。它在排序以前先將序列分割成最短爲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,雖然我一直按照着它的思想來寫,可是不一樣語言的實現確實有些小小的區別。至今我還描述不出來,可是這給我提了個醒,學算法必定要作題!不只地把經典算法的實現老老實實寫出來,還要認真地體會它的細節,不停在腦子裏畫出程序的執行結構圖,使之造成習慣。

相關文章
相關標籤/搜索