第一章:算法在计算机中的做用程序员
本章是本书的开篇,介绍了什么是算法,为何要学习算法,算法在计算机中的地位及做用。算法
算法(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。
第三章:函数的增加
本章介绍了算法分析中的渐进分析符号,几个重要渐进记号的定义以下:
f(n)=Θ(g(n)),表示这个算法是有一个渐近确界的,这个渐近确界为g(n),算法的运行时间f(n)趋近g(n)。
f(n)=O(g(n)),表示这个算法是有一个渐近上界的,这个渐近上界为g(n),算法的运行时间f(n)趋近并小于等于这个g(n)。