算法導論筆記(一)

第一章:算法在計算機中的做用程序員

  本章是本書的開篇,介紹了什麼是算法,爲何要學習算法,算法在計算機中的地位及做用。算法

  算法(algorithm)簡單來講就是定義良好的計算機過程,它取一個或一組值做爲輸入,併產生出一個或一組值做爲輸出。即算法就是一系列的計算步驟,用來將輸入數據轉換成輸出數據。數組

  書中有一句話很是好:ide

  Having a solid base of algorithm knowledge and technique is one characteristic that separates the truly skilled programmers from the novices.函數

  是否具備紮實的算法知識和技術基礎,是區分真正熟練的程序員與新手的一項重要特徵。學習

以這句話激勵本身要努力學習算法,夯實基礎,成爲真正熟練的程序員。spa

 

第二章:算法入門設計

本章經過介紹插入排序和歸併排序兩種常見的排序算法來講明算法的過程及算法分析,在介紹歸併排序算法過程當中引入了分治(divide-and-conquer)算法策略。3d

一、插入排序blog

  輸入:n個數(a1,a2,a3,...,an)

  輸出:輸入序列的一個排列(a1',a2',a3',...an')使得(a1'≤a2'≤a3'≤...≤an')。

  插入排序的基本思想是:將第i個元素插入到前面i-1個已經有序的元素中。具體實現是從第2個元素開始(由於1個元素是有序的),將第2個元素插入到前面的1個元素中,構成兩個有序的序列,而後從第3個元素開始,循環操做,直到把第n元素插入到前面n-1個元素中,最終使得n個元素是有序的。該算法設計的方法是增量方法。書中給出了插入排序的爲代碼,並採用循環不變式證實算法的正確性。我採用C語言實插入排序,完整程序以下:

void insert_sort(int *datas,int length)
 {
     int i,j;
     int key,tmp;
     //判斷參數是否合法
     if(NULL == datas || 0==length)
     {
         printf("Check datas or length.\n");
         exit(1);
     }
     //數組下標是從0開始的,從第二個元素(對應下標1)開始向前插入
     for(j=1;j<length;j++)
     {
         key = datas[j];  //記錄當前要插入的元素
         i = j-1;  //前面已經有序的元素
       //尋找待插入元素的位置,從小到到排序,若是是從大到小改成datas[i]<key
         while(i>=0 && datas[i] > key)
         {
             /×tmp = datas[i+1];
             datas[i+1] = datas[i];
             datas[i] = tmp;*/這個過程不須要進行交換,由於要插入的值保存在key中,沒有被覆蓋掉
             datas[i+1] = datas[i];
             i--;   //向前移動
         }
         datas[i+1] = key;  //最終肯定待插入元素的位置
   }
}

 插入排序算法的分析

  算法分析是對一個算法所需的資源進行預測,資源是指但願測度的計算時間。插入排序過程的時間與輸入相關的。插入排序的最好狀況是輸入數組開始時候就是知足要求的排好序的,時間代價爲θ(n),最壞狀況下,輸入數組是按逆序排序的,時間代價爲θ(n^2)。

二、歸併排序

  歸併排序採用了算法設計中的分治法,分治法的思想是將原問題分解成n個規模較小而結構與原問題類似的小問題,遞歸的解決這些子問題,而後再去合併其結果,獲得原問題的解。分治模式在每一層遞歸上有三個步驟:

分解(divide):將原問題分解成一系列子問題。

解決(conquer):遞歸地解答各子問題,若子問題足夠小,則直接求解。

合併(combine):將子問題的結果合併成原問題的解。

歸併排序(merge sort)算法按照分治模式,操做以下:

分解:將n個元素分解成各含n/2個元素的子序列

解決:用合併排序法對兩個序列遞歸地排序

合併:合併兩個已排序的子序列以獲得排序結果

  在對子序列排序時,長度爲1時遞歸結束,單個元素被視爲已排序好的。歸併排序的關鍵步驟在於合併步驟中的合併兩個已經有序的子序列,引入了一個輔助過程,merge(A,p,q,r),將已經有序的子數組A[p...q]和A[q+1...r]合併成爲有序的A[p...r]。書中給出了採用哨兵實現merge的僞代碼,課後習題要求不使用哨兵實現merge過程。在這個兩種方法中都須要引入額外的輔助空間,用來存放即將合併的有序子數組,總的空間大小爲n。如今用C語言完整實現這兩種方法,程序以下:

//採用哨兵實現merge
 #define MAXLIMIT    65535
 void merge(int *datas,int p,int q,int r)
 {
     int n1 = q-p+1;  //第一個有序子數組元素個數
     int n2 = r-q;      //第二個有序子數組元素個數
     int *left = (int*)malloc(sizeof(int)*(n1+1));
     int *right = (int*)malloc(sizeof(int)*(n2+1));
     int i,j,k;
     //將子數組複製到臨時輔助空間
     for(i=0;i<n1;++i)
         left[i] = datas[p+i];
     for(j=0;j<n2;++j)
         right[j] = datas[q+j+1];
     //添加哨兵
     left[n1] = MAXLIMIT;
     right[n2] = MAXLIMIT;
     //從第一個元素開始合併
     i = 0;
     j = 0;
     //開始合併
     for(k=p;k<=r;k++)
     {
         if(left[i] < right[j])
         {
             datas[k] = left[i];
             i++;
         }
         else
         {
             datas[k] = right[j];
             j++;
         }
     }
     free(left);
     free(right);
 }

 不採用哨兵實現,須要考慮兩個子數組在合併的過程當中哪個先合併結束,剩下的那個子數組剩下部分複製到數組中,程序實現以下:

 int merge(int *datas,int p,int q,int r)
 {
     int n1 = q-p+1;
     int n2 = r-q;
     int *left = (int*)malloc(sizeof(int)*(n1+1));
     int *right = (int*)malloc(sizeof(int)*(n2+1));
     int i,j,k;
     memcpy(left,datas+p,n1*sizeof(int));
     memcpy(right,datas+q+1,n2*sizeof(int));
     i = 0;
     j = 0;
     for(k=p;k<=r;++k)
     {
         if(i <n1 && j< n2)  //歸併兩個子數組
         {
             if(left[i] < right[j])
             {
                 datas[k] = left[i];
                 i++;
             }
             else
             {
                 datas[k] = right[j];
                 j++;
             }
         }
         else
          break;
     }
     //將剩下的合併到數組中
     while(i != n1)
         datas[k++] = left[i++];
     while(j != n2)
         datas[k++] = right[j++];
     free(left);
     free(right);
 }

 merge過程的運行時間是θ(n),現將merge過程做爲歸併排序中的一個子程序使用,merge_sort(A,p,r),對數組A[p...r]進行排序,實例分析以下圖所示:

C語言實現以下:

 void merge_sort(int *datas,int p,int r)
 {
     int q;
     if(p < r)
     {
         q = (p+r)/2;   //分解,計算出子數組的中間位置         
      merge_sort(datas,p,q); //對第一個子數組排序;    merge_sort(datas,q+1,r); //對第二個子數組排序   merge(datas,p,q,r); //合併; } }

 歸併排序算法分析:

  算法中含有對其自身的遞歸調用,其運行時間能夠用一個遞歸方程(或遞歸式)來表示。歸併排序算法分析採用遞歸樹進行,遞歸樹的層數爲lgn+1,每一層的時間代價是cn,整棵樹的代價是cn(lgn+1)=cnlgn+cn,忽略低階和常量c,獲得結果爲θ(nlg n)。

三、課後習題

  有地道題目比較有意思,認真作了作,題目以下:

方法1:要求運行時間爲θ(nlgn),對於集合S中任意一個整數a,設b=x-a,採用二分查找算法在S集合中查找b是否存在,若是b存在說明集合S中存在兩個整數其和等於x。而二分查找算起的前提是集合S是有序的,算法時間爲θ(lgn),所以先須要採用一種時間最多爲θ(nlgn)的算法對集合S進行排序。能夠採用歸併排序算法,這樣總的運行時間爲θ(nlgn),知足題目給定的條件。

具體實現步驟:

一、採用歸併排序算法對集合S進行排序

二、對集合S中任意整數a,b=x-a,採用二分查找算法b是否在集合S中,若在則集合S中存在兩個整數其和等於x,若是遍歷了S中全部的元素,沒能找到b,即集合S中不存在兩個整數其和等於x。

採用C語言實現以下:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
 
 //非遞歸二叉查找
 int binary_search(int *datas,int length,int obj)
 {
     int low,mid,high;
     low = 0;
     high = length;
     while(low < high)
     {
         mid = (low + high)/2;
         if(datas[mid] == obj)
             return mid;
         else if(datas[mid] > obj)
             high = mid;
         else
             low = mid+1;
     }
     return -1;
 }
 
 //遞歸形式二分查找
 int binary_search_recursive(int *datas,int beg,int end,int obj)
 {
     int mid;
     if(beg < end)
     {
         mid = (beg+end)/2;
         if(datas[mid] == obj)
             return mid;
         if(datas[mid] > obj)
             return binary_search_recursive(datas,beg,mid,obj);
         else
             return binary_search_recursive(datas,mid+1,end,obj);
 
     }
     return -1;
 }
 //合併子程序
 int merge(int *datas,int p,int q,int r)
 {
     int n1 = q-p+1;
     int n2 = r-q;
     int *left = (int*)malloc(sizeof(int)*(n1+1));
     int *right = (int*)malloc(sizeof(int)*(n2+1));
     int i,j,k;
     memcpy(left,datas+p,n1*sizeof(int));
     memcpy(right,datas+q+1,n2*sizeof(int));
     i = 0;
     j = 0;
     for(k=p;k<=r;++k)
     {
         if(i <n1 && j< n2)
         {
             if(left[i] < right[j])
             {
                 datas[k] = left[i];
                 i++;
             }
             else
             {
                 datas[k] = right[j];
                 j++;
             }
         }
         else
          break;
     }
     while(i != n1)
         datas[k++] = left[i++];
     while(j != n2)
         datas[k++] = right[j++];
     free(left);
     free(right);
 }
 //歸併排序
 void merge_sort(int *datas,int beg,int end)
 {
     int pos;
     if(beg < end)
     {
         pos = (beg+end)/2;
         merge_sort(datas,beg,pos);
         merge_sort(datas,pos+1,end);
         merge(datas,beg,pos,end);
     }
 }
 
 int main(int argc,char *argv[])
 {
     int i,j,x,obj;
     int datas[10] = {34,11,23,24,90,43,78,65,90,86};
     if(argc != 2)
     {
         printf("input error.\n");
         exit(0);
     }
   x = atoi(argv[1]);
   merge_sort(datas,0,9);
   for(i=0;i<10;i++)
   {
       obj = x - datas[i];
       j = binary_search_recursive(datas,0,10,obj);
       //j = binary_search(datas,10,obj);
       if( j != -1 && j!= i)  //判斷是否查找成功
       {
            printf("there exit two datas (%d and %d) which their sum is %d.\n",datas[i],datas[j],x);
            break;
       }
   }
   if(i==10)
       printf("there not exit two datas whose sum is %d.\n",x);
   exit(0);
}

  

程序執行結果以下:

 方法2:網上課後習題答案上面給的一種方法,具體思想以下:

一、對集合S進行排序,能夠採用歸併排序算法

二、對S中每個元素a,將b=x-a構造一個新的集合S',並對S’進行排序

三、去除S和S'中重複的數據

四、將S和S'按照大小進行歸併,組成新的集合T,若干T中有兩隊及以上兩個連續相等數據,說明集合S中存在兩個整數其和等於x。

例如:S={7,10,5,4,2,5},設x=11,執行過程以下:

對S進行排序,S={2,4,5,5,7,10}。

S'={9,7,6,6,4,1},排序後S’={1,4,6,6,7,9}。

去除S和S'中重複的數據後S={2,4,5,7,10},S'={1,4,6,7,9}

概括S和S'組成新集合T={1,2,4,4,5,6,7,7,9,10},能夠看出集合T中存在兩對連續相等數據4和7,兩者存在集合S中,知足4+7=11。

 

第三章:函數的增加

  本章介紹了算法分析中的漸進分析符號,幾個重要漸進記號的定義以下:

Θ(g(n))={ f(n): 存在正常數c1,c2和n0,使對全部的n>=n0,有0<=c1g(n)<=f(n)<=c2g(n) }
 
O(g(n))={ f(n): 存在正常數c和n0,使對全部n>=n0,有0<=f(n)<=cg(n) }
 
Ω(g(n))={ f(n): 存在正常數c和n0,使對全部n>=n0,有0<=cg(n)<=f(n) }
 
o(g(n))={ f(n): 對任意正常數c,存在常數n0>0,使對全部的n>=n0,有0<=f(n)<=cg(n) }
 
ω(g(n))={ f(n): 對任意正常數c,存在常數n0>0,使對全部的n>=n0,有0<=cg(n)<f(n) }
 
 
f(n)=Ω(g(n)),表示這個算法是有一個漸近下界的,這個漸近下界爲g(n),算法的運行時間f(n)趨近並大於等於這個g(n)。

f(n)=Θ(g(n)),表示這個算法是有一個漸近確界的,這個漸近確界爲g(n),算法的運行時間f(n)趨近g(n)。

f(n)=O(g(n)),表示這個算法是有一個漸近上界的,這個漸近上界爲g(n),算法的運行時間f(n)趨近並小於等於這個g(n)。

相關文章
相關標籤/搜索