<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    收藏的算法好文章(轉)

    算法
    開放分類: 編程數學計算機算法數據結構

    參考出處:http://blog.csdn.net/ctu_85/archive/2008/05/11/2432736.aspx
    一、什么是算法
    算法是一系列解決問題的清晰指令,也就是說,能夠對一定規范的輸入,在有限時間內獲得所要求的輸出。算法常常含有重復的步驟和一些比較或邏輯判斷。如果一個算法有缺陷,或不適合于某個問題,執行這個算法將不會解決這個問題。不同的算法可能用不同的時間、空間或效率來完成同樣的任務。一個算法的優劣可以用空間復雜度與時間復雜度來衡量。
    算法的時間復雜度是指算法需要消耗的時間資源。一般來說,計算機算法是問題規模n 的函數f(n),算法執行的時間的增長率與f(n) 的增長率正相關,稱作漸進時間復雜度(Asymptotic Time Complexity)。時間復雜度用“O(數量級)”來表示,稱為“階”。常見的時間復雜度有: O(1)常數階;O(log2n)對數階;O(n)線性階;O(n2)平方階。
    算法的空間復雜度是指算法需要消耗的空間資源。其計算和表示方法與時間復雜度類似,一般都用復雜度的漸近性來表示。同時間復雜度相比,空間復雜度的分析要簡單得多。

    二、算法設計的方法
    1.遞推法
    遞推法是利用問題本身所具有的一種遞推關系求問題解的一種方法。設要求問題規模為N的解,當N=1時,解或為已知,或能非常方便地得到解。能采用遞推法構造算法的問題有重要的遞推性質,即當得到問題規模為i-1的解后,由問題的遞推性質,能從已求得的規模為1,2,…,i-1的一系列解,構造出問題規模為I的解。這樣,程序可從i=0或i=1出發,重復地,由已知至i-1規模的解,通過遞推,獲得規模為i的解,直至得到規模為N的解。
    【問題】 階乘計算
    問題描述:編寫程序,對給定的n(n≦100),計算并輸出k的階乘k!(k=1,2,…,n)的全部有效數字。
    由于要求的整數可能大大超出一般整數的位數,程序用一維數組存儲長整數,存儲長整數數組的每個元素只存儲長整數的一位數字。如有m位成整數N用數組a[ ]存儲:
    N=a[m]×10m-1+a[m-1]×10m-2+ … +a[2]×101+a[1]×100
    并用a[0]存儲長整數N的位數m,即a[0]=m。按上述約定,數組的每個元素存儲k的階乘k!的一位數字,并從低位到高位依次存于數組的第二個元素、第三個元素……。例如,5!=120,在數組中的存儲形式為:
    3 0 2 1 ……
    首元素3表示長整數是一個3位數,接著是低位到高位依次是0、2、1,表示成整數120。
    計算階乘k!可采用對已求得的階乘(k-1)!連續累加k-1次后求得。例如,已知4!=24,計算5!,可對原來的24累加4次24后得到120。細節見以下程序。
    # include <stdio.h>
    # include <malloc.h>
    ......
    2.遞歸
    遞歸是設計和描述算法的一種有力的工具,由于它在復雜算法的描述中被經常采用,為此在進一步介紹其他算法設計方法之前先討論它。
    能采用遞歸描述的算法通常有這樣的特征:為求解規模為N的問題,設法將它分解成規模較小的問題,然后從這些小問題的解方便地構造出大問題的解,并且這些規模較小的問題也能采用同樣的分解和綜合方法,分解成規模更小的問題,并從這些更小問題的解構造出規模較大問題的解。特別地,當規模N=1時,能直接得解。
    【問題】 編寫計算斐波那契(Fibonacci)數列的第n項函數fib(n)。
    斐波那契數列為:0、1、1、2、3、……,即:
    fib(0)=0;
    fib(1)=1;
    fib(n)=fib(n-1)+fib(n-2) (當n>1時)。
    寫成遞歸函數有:
    int fib(int n)
    { if (n==0) return 0;
    if (n==1) return 1;
    if (n>1) return fib(n-1)+fib(n-2);
    }
    遞歸算法的執行過程分遞推和回歸兩個階段。在遞推階段,把較復雜的問題(規模為n)的求解推到比原問題簡單一些的問題(規模小于n)的求解。例如上例中,求解fib(n),把它推到求解fib(n-1)和fib(n-2)。也就是說,為計算fib(n),必須先計算fib(n-1)和fib(n-2),而計算fib(n-1)和fib(n-2),又必須先計算fib(n-3)和fib(n-4)。依次類推,直至計算fib(1)和fib(0),分別能立即得到結果1和0。在遞推階段,必須要有終止遞歸的情況。例如在函數fib中,當n為1和0的情況。
    在回歸階段,當獲得最簡單情況的解后,逐級返回,依次得到稍復雜問題的解,例如得到fib(1)和fib(0)后,返回得到fib(2)的結果,……,在得到了fib(n-1)和fib(n-2)的結果后,返回得到fib(n)的結果。
    在編寫遞歸函數時要注意,函數中的局部變量和參數知識局限于當前調用層,當遞推進入“簡單問題”層時,原來層次上的參數和局部變量便被隱蔽起來。在一系列“簡單問題”層,它們各有自己的參數和局部變量。
    由于遞歸引起一系列的函數調用,并且可能會有一系列的重復計算,遞歸算法的執行效率相對較低。當某個遞歸算法能較方便地轉換成遞推算法時,通常按遞推算法編寫程序。例如上例計算斐波那契數列的第n項的函數fib(n)應采用遞推算法,即從斐波那契數列的前兩項出發,逐次由前兩項計算出下一項,直至計算出要求的第n項。
    【問題】 組合問題
    問題描述:找出從自然數1、2、……、n中任取r個數的所有組合。例如n=5,r=3的所有組合為: (1)5、4、3 (2)5、4、2 (3)5、4、1
    (4)5、3、2 (5)5、3、1 (6)5、2、1
    (7)4、3、2 (8)4、3、1 (9)4、2、1
    (10)3、2、1
    分析所列的10個組合,可以采用這樣的遞歸思想來考慮求組合函數的算法。設函數為void comb(int m,int k)為找出從自然數1、2、……、m中任取k個數的所有組合。當組合的第一個數字選定時,其后的數字是從余下的m-1個數中取k-1數的組合。這就將求m個數中取k個數的組合問題轉化成求m-1個數中取k-1個數的組合問題。設函數引入工作數組a[ ]存放求出的組合的數字,約定函數將確定的k個數字組合的第一個數字放在a[k]中,當一個組合求出后,才將a[ ]中的一個組合輸出。第一個數可以是m、m-1、……、k,函數將確定組合的第一個數字放入數組后,有兩種可能的選擇,因還未去頂組合的其余元素,繼續遞歸去確定;或因已確定了組合的全部元素,輸出這個組合。細節見以下程序中的函數comb。
    【程序】
    # include <stdio.h>
    # define MAXN 100
    int a[MAXN];
    void comb(int m,int k)
    { int i,j;
    for (i=m;i>=k;i--)
    { a[k]=i;
    if (k>1)
    comb(i-1,k-1);
    else
    { for (j=a[0];j>0;j--)
    printf(“%4d”,a[j]);
    printf(“\n”);
    }
    }
    }

    void main()
    { a[0]=3;
    comb(5,3);
    }
    3.回溯法
    回溯法也稱為試探法,該方法首先暫時放棄關于問題規模大小的限制,并將問題的候選解按某種順序逐一枚舉和檢驗。當發現當前候選解不可能是解時,就選擇下一個候選解;倘若當前候選解除了還不滿足問題規模要求外,滿足所有其他要求時,繼續擴大當前候選解的規模,并繼續試探。如果當前候選解滿足包括問題規模在內的所有要求時,該候選解就是問題的一個解。在回溯法中,放棄當前候選解,尋找下一個候選解的過程稱為回溯。擴大當前候選解的規模,以繼續試探的過程稱為向前試探。

    【問題】 組合問題
    問題描述:找出從自然數1,2,…,n中任取r個數的所有組合。
    采用回溯法找問題的解,將找到的組合以從小到大順序存于a[0],a[1],…,a[r-1]中,組合的元素滿足以下性質:
    (1) a[i+1]>a,后一個數字比前一個大;
    (2) a-i<=n-r+1。
    按回溯法的思想,找解過程可以敘述如下:
    首先放棄組合數個數為r的條件,候選組合從只有一個數字1開始。因該候選解滿足除問題規模之外的全部條件,擴大其規模,并使其滿足上述條件(1),候選組合改為1,2。繼續這一過程,得到候選組合1,2,3。該候選解滿足包括問題規模在內的全部條件,因而是一個解。在該解的基礎上,選下一個候選解,因a[2]上的3調整為4,以及以后調整為5都滿足問題的全部要求,得到解1,2,4和1,2,5。由于對5不能再作調整,就要從a[2]回溯到a[1],這時,a[1]=2,可以調整為3,并向前試探,得到解1,3,4。重復上述向前試探和向后回溯,直至要從a[0]再回溯時,說明已經找完問題的全部解。按上述思想寫成程序如下:
    【程序】
    # define MAXN 100
    int a[MAXN];
    void comb(int m,int r)
    { int i,j;
    i=0;
    a=1;
    do {
    if (a-i<=m-r+1
    { if (i==r-1)
    { for (j=0;j<r;j++)
    printf(“%4d”,a[j]);
    printf(“\n”);
    }
    a++;
    continue;
    }
    else
    { if (i==0)
    return;
    a[--i]++;
    }
    } while (1)
    }

    main()
    { comb(5,3);
    }

    4.貪婪法
    貪婪法是一種不追求最優解,只希望得到較為滿意解的方法。貪婪法一般可以快速得到滿意的解,因為它省去了為找最優解要窮盡所有可能而必須耗費的大量時間。貪婪法常以當前情況為基礎作最優選擇,而不考慮各種可能的整體情況,所以貪婪法不要回溯。
    例如平時購物找錢時,為使找回的零錢的硬幣數最少,不考慮找零錢的所有各種發表方案,而是從最大面值的幣種開始,按遞減的順序考慮各幣種,先盡量用大面值的幣種,當不足大面值幣種的金額時才去考慮下一種較小面值的幣種。這就是在使用貪婪法。這種方法在這里總是最優,是因為銀行對其發行的硬幣種類和硬幣面值的巧妙安排。如只有面值分別為1、5和11單位的硬幣,而希望找回總額為15單位的硬幣。按貪婪算法,應找1個11單位面值的硬幣和4個1單位面值的硬幣,共找回5個硬幣。但最優的解應是3個5單位面值的硬幣。
    【問題】 裝箱問題
    問題描述:裝箱問題可簡述如下:設有編號為0、1、…、n-1的n種物品,體積分別為v0、v1、…、vn-1。將這n種物品裝到容量都為V的若干箱子里。約定這n種物品的體積均不超過V,即對于0≤i<n,有0<vi≤V。不同的裝箱方案所需要的箱子數目可能不同。裝箱問題要求使裝盡這n種物品的箱子數要少。
    若考察將n種物品的集合分劃成n個或小于n個物品的所有子集,最優解就可以找到。但所有可能劃分的總數太大。對適當大的n,找出所有可能的劃分要花費的時間是無法承受的。為此,對裝箱問題采用非常簡單的近似算法,即貪婪法。該算法依次將物品放到它第一個能放進去的箱子中,該算法雖不能保證找到最優解,但還是能找到非常好的解。不失一般性,設n件物品的體積是按從大到小排好序的,即有v0≥v1≥…≥vn-1。如不滿足上述要求,只要先對這n件物品按它們的體積從大到小排序,然后按排序結果對物品重新編號即可。裝箱算法簡單描述如下:
    { 輸入箱子的容積;
    輸入物品種數n;
    按體積從大到小順序,輸入各物品的體積;
    預置已用箱子鏈為空;
    預置已用箱子計數器box_count為0;
    for (i=0;i<n;i++)
    { 從已用的第一只箱子開始順序尋找能放入物品i 的箱子j;
    if (已用箱子都不能再放物品i)
    { 另用一個箱子,并將物品i放入該箱子;
    box_count++;
    }
    else
    將物品i放入箱子j;
    }
    }
    上述算法能求出需要的箱子數box_count,并能求出各箱子所裝物品。下面的例子說明該算法不一定能找到最優解,設有6種物品,它們的體積分別為:60、45、35、20、20和20單位體積,箱子的容積為100個單位體積。按上述算法計算,需三只箱子,各箱子所裝物品分別為:第一只箱子裝物品1、3;第二只箱子裝物品2、4、5;第三只箱子裝物品6。而最優解為兩只箱子,分別裝物品1、4、5和2、3、6。
    若每只箱子所裝物品用鏈表來表示,鏈表首結點指針存于一個結構中,結構記錄尚剩余的空間量和該箱子所裝物品鏈表的首指針。另將全部箱子的信息也構成鏈表。以下是按以上算法編寫的程序。
    }

    5.分治法
    任何一個可以用計算機求解的問題所需的計算時間都與其規模N有關。問題的規模越小,越容易直接求解,解題所需的計算時間也越少。例如,對于n個元素的排序問題,當n=1時,不需任何計算;n=2時,只要作一次比較即可排好序;n=3時只要作3次比較即可,…。而當n較大時,問題就不那么容易處理了。要想直接解決一個規模較大的問題,有時是相當困難的。
    分治法的設計思想是,將一個難以直接解決的大問題,分割成一些規模較小的相同問題,以便各個擊破,分而治之。
    如果原問題可分割成k個子問題(1<k≤n),且這些子問題都可解,并可利用這些子問題的解求出原問題的解,那么這種分治法就是可行的。由分治法產生的子問題往往是原問題的較小模式,這就為使用遞歸技術提供了方便。在這種情況下,反復應用分治手段,可以使子問題與原問題類型一致而其規模卻不斷縮小,最終使子問題縮小到很容易直接求出其解。這自然導致遞歸過程的產生。分治與遞歸像一對孿生兄弟,經常同時應用在算法設計之中,并由此產生許多高效算法。
    分治法所能解決的問題一般具有以下幾個特征:
    (1)該問題的規模縮小到一定的程度就可以容易地解決;
    (2)該問題可以分解為若干個規模較小的相同問題,即該問題具有最優子結構性質;
    (3)利用該問題分解出的子問題的解可以合并為該問題的解;
    (4)該問題所分解出的各個子問題是相互獨立的,即子問題之間不包含公共的子子問題。
    上述的第一條特征是絕大多數問題都可以滿足的,因為問題的計算復雜性一般是隨著問題規模的增加而增加;第二條特征是應用分治法的前提,它也是大多數問題可以滿足的,此特征反映了遞歸思想的應用;第三條特征是關鍵,能否利用分治法完全取決于問題是否具有第三條特征,如果具備了第一條和第二條特征,而不具備第三條特征,則可以考慮貪心法或動態規劃法。第四條特征涉及到分治法的效率,如果各子問題是不獨立的,則分治法要做許多不必要的工作,重復地解公共的子問題,此時雖然可用分治法,但一般用動態規劃法較好。
    分治法在每一層遞歸上都有三個步驟:
    (1)分解:將原問題分解為若干個規模較小,相互獨立,與原問題形式相同的子問題;
    (2)解決:若子問題規模較小而容易被解決則直接解,否則遞歸地解各個子問題;
    (3)合并:將各個子問題的解合并為原問題的解。
    6.動態規劃法
    經常會遇到復雜問題不能簡單地分解成幾個子問題,而會分解出一系列的子問題。簡單地采用把大問題分解成子問題,并綜合子問題的解導出大問題的解的方法,問題求解耗時會按問題規模呈冪級數增加。
    為了節約重復求相同子問題的時間,引入一個數組,不管它們是否對最終解有用,把所有子問題的解存于該數組中,這就是動態規劃法所采用的基本方法。以下先用實例說明動態規劃方法的使用。
    【問題】 求兩字符序列的最長公共字符子序列
    問題描述:字符序列的子序列是指從給定字符序列中隨意地(不一定連續)去掉若干個字符(可能一個也不去掉)后所形成的字符序列。令給定的字符序列X=“x0,x1,…,xm-1”,序列Y=“y0,y1,…,yk-1”是X的子序列,存在X的一個嚴格遞增下標序列<i0,i1,…,ik-1>,使得對所有的j=0,1,…,k-1,有xij=yj。例如,X=“ABCBDAB”,Y=“BCDB”是X的一個子序列。
    考慮最長公共子序列問題如何分解成子問題,設A=“a0,a1,…,am-1”,B=“b0,b1,…,bm-1”,并Z=“z0,z1,…,zk-1”為它們的最長公共子序列。不難證明有以下性質:
    (1) 如果am-1=bn-1,則zk-1=am-1=bn-1,且“z0,z1,…,zk-2”是“a0,a1,…,am-2”和“b0,b1,…,bn-2”的一個最長公共子序列;
    (2) 如果am-1!=bn-1,則若zk-1!=am-1,蘊涵“z0,z1,…,zk-1”是“a0,a1,…,am-2”和“b0,b1,…,bn-1”的一個最長公共子序列;
    (3) 如果am-1!=bn-1,則若zk-1!=bn-1,蘊涵“z0,z1,…,zk-1”是“a0,a1,…,am-1”和“b0,b1,…,bn-2”的一個最長公共子序列。
    這樣,在找A和B的公共子序列時,如有am-1=bn-1,則進一步解決一個子問題,找“a0,a1,…,am-2”和“b0,b1,…,bm-2”的一個最長公共子序列;如果am-1!=bn-1,則要解決兩個子問題,找出“a0,a1,…,am-2”和“b0,b1,…,bn-1”的一個最長公共子序列和找出“a0,a1,…,am-1”和“b0,b1,…,bn-2”的一個最長公共子序列,再取兩者中較長者作為A和B的最長公共子序列。
    代碼如下:
    # include <stdio.h>
    # include <string.h>
    # define N 100
    char a[N],b[N],str[N];

    int lcs_len(char *a, char *b, int c[ ][ N])
    { int m=strlen(a), n=strlen(b), i,j;
    for (i=0;i<=m;i++) c[0]=0;
    for (i=0;i<=n;i++) c[0]=0;
    for (i=1;i<=m;i++)
    for (j=1;j<=m;j++)
    if (a[i-1]==b[j-1])
    c[j]=c[i-1][j-1]+1;
    else if (c[i-1][j]>=c[j-1])
    c[j]=c[i-1][j];
    else
    c[j]=c[j-1];
    return c[m][n];
    }

    char *buile_lcs(char s[ ],char *a, char *b)
    { int k, i=strlen(a), j=strlen(b);
    k=lcs_len(a,b,c);
    s[k]=’’;
    while (k>0)
    if (c[j]==c[i-1][j]) i--;
    else if (c[j]==c[j-1]) j--;
    else { s[--k]=a[i-1];
    i--; j--;
    }
    return s;
    }

    void main()
    { printf (“Enter two string(<%d)!\n”,N);
    scanf(“%s%s”,a,b);
    printf(“LCS=%s\n”,build_lcs(str,a,b));
    }
    7.迭代法
    迭代法是用于求方程或方程組近似根的一種常用的算法設計方法。設方程為f(x)=0,用某種數學方法導出等價的形式x=g(x),然后按以下步驟執行:
    (1) 選一個方程的近似根,賦給變量x0;
    (2) 將x0的值保存于變量x1,然后計算g(x1),并將結果存于變量x0;
    (3) 當x0與x1的差的絕對值還小于指定的精度要求時,重復步驟(2)的計算。
    若方程有根,并且用上述方法計算出來的近似根序列收斂,則按上述方法求得的x0就認為是方程的根。上述算法用C程序的形式表示為:
    程序如下:
    【算法】迭代法求方程組的根
    { for (i=0;i<n;i++)
    x=初始近似根;
    do {
    for (i=0;i<n;i++)
    y = x;
    for (i=0;i<n;i++)
    x = gi(X);
    for (delta=0.0,i=0;i<n;i++)
    if (fabs(y-x)>delta) delta=fabs(y-x); } while (delta>Epsilon);
    for (i=0;i<n;i++)
    printf(“變量x[%d]的近似根是 %f”,I,x);
    printf(“\n”);
    } 具體使用迭代法求根時應注意以下兩種可能發生的情況:
    (1)如果方程無解,算法求出的近似根序列就不會收斂,迭代過程會變成死循環,因此在使用迭代算法前應先考察方程是否有解,并在程序中對迭代的次數給予限制;
    (2)方程雖然有解,但迭代公式選擇不當,或迭代的初始近似根選擇不合理,也會導致迭代失敗。
    8.窮舉搜索法
    窮舉搜索法是對可能是解的眾多候選解按某種順序進行逐一枚舉和檢驗,并從眾找出那些符合要求的候選解作為問題的解。
    【問題】 將A、B、C、D、E、F這六個變量排成如圖所示的三角形,這六個變量分別取[1,6]上的整數,且均不相同。求使三角形三條邊上的變量之和相等的全部解。如圖就是一個解。
    程序引入變量a、b、c、d、e、f,并讓它們分別順序取1至6的整數,在它們互不相同的條件下,測試由它們排成的如圖所示的三角形三條邊上的變量之和是否相等,如相等即為一種滿足要求的排列,把它們輸出。當這些變量取盡所有的組合后,程序就可得到全部可能的解。程序如下:
    按窮舉法編寫的程序通常不能適應變化的情況。如問題改成有9個變量排成三角形,每條邊有4個變量的情況,程序的循環重數就要相應改變。

    posted on 2008-07-02 18:38 liujg 閱讀(239) 評論(0)  編輯  收藏 所屬分類: 算法


    只有注冊用戶登錄后才能發表評論。


    網站導航:
     
    <2025年5月>
    27282930123
    45678910
    11121314151617
    18192021222324
    25262728293031
    1234567

    導航

    統計

    常用鏈接

    留言簿(1)

    隨筆分類

    隨筆檔案

    文章分類

    文章檔案

    相冊

    收藏夾

    boddiy

    搜索

    最新評論

    閱讀排行榜

    評論排行榜

    主站蜘蛛池模板: 一级女性全黄生活片免费看| 亚洲国产精品成人午夜在线观看| 久久不见久久见免费影院www日本| 永久免费视频v片www| 亚洲综合校园春色| 久久久久免费看黄A片APP| 亚洲导航深夜福利| 免费精品国产自产拍在| 亚洲va在线va天堂成人| 毛片免费观看的视频在线| 亚洲欧美一区二区三区日产| 永久免费毛片手机版在线看| 国产精品亚洲二区在线| 亚洲国产专区一区| a级精品九九九大片免费看| 亚洲天堂久久精品| 中文字幕无码不卡免费视频 | 四虎影院在线免费播放| 在线观看亚洲免费视频| 亚洲精品在线视频| 久久久久久成人毛片免费看| 久久亚洲AV成人出白浆无码国产| 黄+色+性+人免费| 亚洲国产aⅴ成人精品无吗| 全亚洲最新黄色特级网站| 国产色无码精品视频免费| 亚洲精品中文字幕麻豆| 女人18毛片水最多免费观看| 色哟哟国产精品免费观看| 亚洲av永久无码精品漫画| 国产成人免费网站| 成年网站免费入口在线观看| 亚洲成色www久久网站夜月| 18禁网站免费无遮挡无码中文| 国产精品国产亚洲区艳妇糸列短篇| 国产亚洲色婷婷久久99精品91| 99在线热视频只有精品免费| 亚洲人成未满十八禁网站| 亚洲精品国产精品乱码不99| 91成年人免费视频| 亚洲免费视频一区二区三区|