c语言进阶11-算法设计思想
一、 算法设计的要求:
为什么要学算法?
/* 输出Hello word! */ #include "stdio.h" void main() { printf("Hello word!\n"); }
在此程序中,要综合运用数据结构和算法。数据结构是加工对象,语言是工具,变成需要合适的方法,但没有一个合格的算法,我们称不上合格的开发程序。所以,算法是程序设计的灵魂和核心。
- 1. 正确性
正确性:算法应当满足具体问题的需求。
“正确”一词的含义在通常的用法中有很大的差别,大体分为以下四个层次:
程序不含语法错误;
例如,程序片段如下
int a;
float b;
a=3;b=4.5;
printf("%f%d\n",a,b);
编译时不给出出错信息,但运行结果将与原意不符,输出为
0.000000 16402
常见的语法错误,输入输出语句要求变量及格式说明一定要类型不一致。
程序对于合法的输入数据能够产生满足要求的输出结果;
程序能够正常的输出数据。
程序对于非法的输入数据能够得出满足规格说明的结果;
程序对于精心选择的,甚至刁难的测试数据都有满足要求的输出结果;
显然,达到层次4是最困难的,我们几乎不可能之一验证所有的输入都得到正确的结果。一般情况下,通常以第3层意义的正确性作为衡量一个程序是否合格的标准。
- 2. 可读性
可读性:算法设计的另一目的是为了便于阅读、理解和交流。
可读性好有助于人们对算法的理解;晦涩难懂的算法往往隐含错误,不易被发现,并难于调试和修改。可读性是算法好坏很重要的标志。
例:a=a+b; b=a-b; a=a-b; |
此片段表达的意思是什么?难道是a,b互换?
- 3. 健壮性
一个好的算法应该能对输入数据不合法的情况做合适的处理。
健壮性:当输入数据不合法时,算法也能做出相关处理,而不是产生异常或莫名其妙的结果。
如下程序片断:
if ((fp=fopen(filename,”w”))==NULL) { printf(“cannot open file\n”); exit(); }
- 4. 时间效率高和存储量低
时间效率指的是算法的执行时间,对于同一个问题如果有多个算法可以解决,执行时间短的算法效率高,执行时间长的效率低。
存储量需求指算法执行过程中所需要的最大存储空间。
效率和低存储量需求这两者都与问题的规模有关。
如:求100个人的平均分与求1000个人的平均分所花的执行时间或运行空间显然有一定的差别。
二、 算法效率的度量方法
算法执行时间需通过依据该算法编制的程序在计算机上运行时所消耗的时间来度量。而度量一个程序的执行时间通常有两种方法:事后统计方法和事前分析估算方法。
- 1. 事后统计方法
这种方法主要是通过设计好的测试程序和数据,利用计算机计时器对不同算法编制的程序运行时间进行比较,从而确定算法效率的高低。
但这种方法显然有很大的缺陷:
(1)必须先运行依据算法编制的程序,通常需要花费大量的时间和精力;
(2)所得时间的统计量依赖于计算机的硬件和软件等环境因素,有时容易掩饰算法本身的优劣;
基于这样的缺陷,我们常常采用另一种事前分析估算的方法。
- 2. 事前分析估算的方法
事前分析估算方法:在计算机程序编制前,依据统计方法对算法进行估算。
经分析,我们发现,一个用高级程序语言编写的程序在计算机上运行时所消耗的时间取决于下列因素:
算法采用的策略、方法。
编译产生的代码质量。
问题的输入规模。
机器执行指令的速度
第1条是算法好坏的根本,第2条要有软件来支持,第4条要看硬件性能。就是说,抛开这些与计算机硬件、软件有关的因素,可以认为一个特定算法的“运行工作量”的大小,只依赖于问题的规模(通常用整数量n表示),或者说,它是问题规模的函数。
三、 函数的渐近增长
给定两个算法A和B,假设两个算法的输入规模都是n,算法A要做2n+3次操作,你可以理解为现有一个n次循环,执行完成后,再有一个n次循环,最后有三次赋值或运算,共2n+3次操作。算法B要做3n+1次操作。你觉得它们谁更快呢?
答案是不一定的。
次数 |
算法A(2n+3) |
算法A'(2n) |
算法B(3n+1) |
算法B'(3n) |
n=1 |
5 |
2 |
4 |
3 |
n=2 |
7 |
4 |
7 |
6 |
n=3 |
9 |
6 |
10 |
9 |
n=10 |
23 |
20 |
31 |
30 |
n=100 |
203 |
200 |
301 |
300 |
当n=1时,算法A效率不如算法B(次数比算法B要多一次)。而当n=2时,两者效率相同;当n>2时,算法A就开始优于算法B了,随着n的增加,算法A比算法B越来越好了(执行的次数比B要少)。于是我们可以得出结论,算法A总体上要好过算法B。
此时我们给出这样的定义,输入规模n在没有限制的情况下,只要超过一个数值N,这个函数就总是大于另一个函数,我们称函数是渐近增长的。
函数的渐近增长:给定两个函数f(n)和g(n),如果存在一个整数N,使得对于所有的n>N,f(n)总是比g(n)大,那么我们说f(n)的增长渐近快于g(n)。
四、 算法的时间复杂度
- 1. 算法时间复杂度定义
在进行算法分析时,语句中的执行次数T(n)是关于问题规模n的函数,进而分析T(n)随n的变化情况并确定T(n)的数量级。算法的时间复杂度,也就是算法的时间量度,记作:T(n)=O(f(n))。它表示随问题规模n的增大,算法执行时间的增长率和f(n)的增长率相同,称作算法的渐近时间复杂度,简称为时间复杂度。其中f(n)是问题规模n的某个函数。
一般情况下,随着n的增大,T(n)增长最慢的算法为最优算法。(量增加,时间增长慢)
显然,由此算法时间复杂度的定义可知,我们的三个求和算法的时间复杂度分别为O(n),O(1),O(n2)。我们分别给它们取了非官方的名称,O(1)叫做常数阶、O(n)叫线性阶、O(n2)叫平方阶。
如:
for(i=;i<=n;++i) for(j=;j<=n;++j){ c[i][j]=; for(k=;k<=n;++k) c[i][j]+=a[i][k]*b[k][j]; }
此程序的时间复杂度是O(n3)。
- 2. 常数阶
下面这个算法,即高斯算法,为什么时间复杂度不是O(3),而是O(1)。
int sum = 0,n = 100; /* 执行一次 */
sum = (1+n) *n/2; /* 执行一次 */
printf("%d",sum); /* 执行一次 */
这个算法的运行次数函数是f(n)=3。把常数项3改为1,在保留最高阶项时发现,它根本没有最高阶项,所以这个算法的时间复杂度为O(1)。
另外,我们试想一下,如果这个算法当中的语句sum = (1+n) *n/2有10句,即:
事实上无论n为多少,上面的两段代码就是3次和12次执行的差异。这种与问题的大小无关(n的多少),执行时间恒定的算法,我们称之为具有O(1)的时间复杂度,又叫常数阶。
注意:不管这个常数是多少,我们都记作O(1),而不是O(3),O(12)等其他任何数字,这是初学者常常犯的错误。
对于分支结构而言,无论是真,还是假,执行的次数都是恒定的,不会随着n的变大而发生变化,所以单纯的分支结构(不包含在循环结构中),其时间复杂度也是O(1)。
- 3. 线性阶
线性阶的循环结构会复杂很多。要确定某个算法的阶次,我们常常需要确定某个特定语句或某个语句集运行的次数。因此,我们要分析算法的复杂度,关键就是要分析循环结构的运行情况。
下面这段代码,它的循环的时间复杂度为O(n),因为循环体中的代码需要执行n次。
int i; for(i = ;i < n;i++) { /* 时间复杂度为O(1)的程序步骤序列 */ }
- 4. 对数阶
我们以一段代码为例,说明对数阶:
int count = ; while (count < n) { count = count * /* 时间复杂度为O(1)的程序步骤序列 */ }
由于每次count乘以2之后,就距离n更近了一分。也就是说,有多少个2相乘后大于n,则会退出循环。由2x=n得到x=log2n。所以这个循环的时间复杂度为O(log2n)。
- 5. 平方阶
下面例子是一个循环嵌套,它的内循环刚才我们已经分析过,时间复杂度为O(n2)。
int i,j;
for(i = 0;i < n; i++)
{
for (j = 0;j < n;j++)
{
/* 时间复杂度为O(1)的程序步骤序列 */
}
}
对于外层的循环,不过是内部这个时间复杂度为O(n)的语句,再循环n次。所以这段代码的时间复杂度为O(n2)。
常见的时间复杂度
常见的时间复杂度如下所示。
执行次数函数 |
阶 |
非正式用语 |
12 |
O(1) |
常数阶 |
2n+3 |
O(n) |
线性阶 |
3n2+2n+1 |
O(n2) |
平方阶 |
5log2n+20 |
O(log2n) |
对数阶 |
2n+3nlog2n+19 |
O(nlog2n) |
nlog2n阶 |
6n3+2n2+3n+4 |
O(n3) |
立方阶 |
2n |
O(2n) |
指数阶 |
常用的时间复杂度所耗费的时间从小到大依次是:
1 3 10 30 100 1000 1024
O(1)< O(log2n)< O(n)< O(nlog2n)< O(n2)< O(n3)< O(2n)< O(n!)<O(nn)
最坏情况与平均情况
我们查找一个有n个随机数字数组中的某个数字,最好的情况是第一个数字就是,那么算法的时间复杂度为O(1),最坏的情况是这个数字在最后一个位置上,那么算法的时间复杂度就是O(n)。
最坏情况运行时间是一种保证,那就是运行时间将不会再坏了(在应用中,这是一种最重要的需求,通常除非特别指定,我们提到的运行时间都是最坏情况的运行时间)。
平均运行时间是从概率的角度看,这个数字在每个位置的可能性是相同的,所以平均的查找时间为n/2次后发现这个目标元素。
平均运行时间是期望的运行时间。也就是说,我们运行一段代码时,是希望看到平均运行时间的。可现实中,平均运行时间很难通过分析得到,一般都是通过运行一定数量的实验数据后估算出来的。
对算法的分析,一种方法是计算所有情况的平均值,这种时间复杂度的计算方法称为平均时间复杂度。另一种方法是计算最坏情况下的时间复杂度,这种方法称为最坏时间复杂度。一般在没有特殊说明的情况下,都是指最坏时间复杂度。
例如:冒泡排序法
void bubble_sort(int a[],int n)
{
chang=false;
for(i=n-1;change=TURE;i>1&&change;-i)
for (j=0;j<I;++j)
if(a[j]>a[j+1])
{
a[j]←→a[j+1];change=TURE;}
}
最好情况:0次
最坏情况:1+2+3……+n-1=n(n-1)/2
平均时间复杂度为:O(n2)
4.7算法的空间复杂度
算法的空间复杂度通过计算算法所需的存储空间实现,算法空间复杂度的计算公式记作:S(n)=O(f(n)),表示随着问题规模n的增大,算法运行所需存储量的增长率与f(n)的增长率相同。
算法的存储量包括:
输入数据所占空间;
程序本身所占空间;
辅助变量所占空间。
若输入数据所占空间只取决于问题本身,和算法无关,则只需要分析除输入和程序之外的辅助变量所占额外空间。
若所需额外空间相对于输入数据量来说是常数,则称此算法为原地工作。
若所需存储量依赖于特定的输入,则通常按最坏情况考虑。
希尔排序代码
/*
希尔排序 缩小增量排序----->通俗的讲就是改进后的直接插入排序 增加了k 增量序列 分组的组数 k=MAX/2 增量k的值是越来越小 先分小组,分别对每个组内进行直接插入排序 然后在k=k/2 分组 直到组数为1截止 进行最终的一趟直接插入排序结束
*/
#include "stdio.h"
#define MAX 11
void main()
{
int a[MAX]={,,,,,,,,,,};
int i;//控制循环趟数 以及 待排序元素的下标
int j;//控制有序数组的下标
int temp;//存放 待排序元素 temp数据类型 与数组类型一致
int k;//增量 k代表把元素分为几组
//希尔排序开始
for(k=MAX/;k>=;k=k/) // 缩小增量排序 继续分组 继续进行直接插入排序
{
//直接插入排序开始
for(i=k;i<MAX;i++)
{
temp=a[i];//待排序元素
if(temp<a[i-k])
{
for(j=i-k;a[j]>temp&&j>=;j=j-k)//i-k有序数组最后一个元素的下标
{
a[j+k]=a[j];
}
//当我们结束第二层for循环时候,结束时j=j-k
a[j+k]=temp;
}
}
//直接插入排序结束 }
//希尔排序结束
printf("希尔排序结果:\n");
for(i=;i<MAX;i++)
{
printf("%d\t",a[i]);
}
}
折半法代码
/* 折半查找 前提 顺序存储 记录有顺序 折半查找 low 头下标 high 尾巴下标 mid 中间位置下标=(low+high)/2 拿要查找的值key 和 中间值 比较 key大于 中间值 去右边找 右边有尾巴没有头 按个头 low=mid+1 key小于 中间值 去左边找 左边有头没有尾巴 high=mid-1 key == 中间值 找到了 输出下标 break;终止查找
*/
#include "stdio.h"
#define MAX 10
int a[MAX]={,,,,,,,,,};
//折半查找函数
int zheban(int key) //传递待查找的关键字
{
int low=;
int high=MAX-; //数组最后一个元素下标
int mid;
while(low<=high)
{
mid=(low+high)/; //求出中间值得下标
if(key>a[mid])
{
//去右边找
low=mid+; }else if(key<a[mid])
{
//去左边找
high=mid-; }else{
printf("查找成功\n");
return mid;
}
}
//循环结束后
/*if(low>high)
{
printf("查找失败\n");
} */
return -;
}
void main()
{
int key;//存放待查找的关键件
printf("请输入您要查找的数:");
scanf("%d",&key);
printf("@%d@\n",zheban(key));
}
c语言进阶11-算法设计思想的更多相关文章
- Python数据结构与算法设计(总结篇)
的确,正如偶像Bruce Eckel所说,"Life is short, you need Python"! 如果你正在考虑学Java还是Python的话,那就别想了,选Pytho ...
- 转:从《The C Programming Language》中学到的那些编程风格和设计思想
这儿有一篇写的很好的读后感:http://www.cnblogs.com/xkfz007/articles/2566424.html 读书不是目的,关键在于思考. 很早就在水木上看到有人推荐& ...
- c语言进阶15-数据结构总结
数据结构结论 1.阿基米德说过:“给我一个支点,我就能翘起地球”. 数据结构是指相互之间存在着一种或多种关系的数据元素的集合和该集合中数据元素之间的关系组成.记为:Data_Structure=(D, ...
- Python数据结构与算法设计总结篇
1.Python数据结构篇 数据结构篇主要是阅读[Problem Solving with Python]( http://interactivepython.org/courselib/static ...
- 算法设计与分析(李春保)练习题答案v1
1.1第1 章─概论 1.1.1练习题 1.下列关于算法的说法中正确的有(). Ⅰ.求解某一类问题的算法是唯一的 Ⅱ.算法必须在有限步操作之后停止 Ⅲ.算法的每一步操作必须是明确的,不能有歧义或含义模 ...
- Alink漫谈(一) : 从KMeans算法实现不同看Alink设计思想
Alink漫谈(一) : 从KMeans算法实现不同看Alink设计思想 目录 Alink漫谈(一) : 从KMeans算法实现不同看Alink设计思想 0x00 摘要 0x01 Flink 是什么 ...
- MyBatis 强大之处 多环境 多数据源 ResultMap 的设计思想是 缓存算法 跨数据库 spring boot rest api mybaits limit 传参
总结: 1.mybaits配置工2方面: i行为配置,如数据源的实现是否利用池pool的概念(POOLED – This implementation of DataSource pools JDBC ...
- 用GA算法设计22个地点之间最短旅程-R语言实现
数据挖掘入门与实战 公众号: datadw 相关帖子 转载︱案例 基于贪心算法的特征选择 用GA算法设计22个地点之间最短旅程-R语言实现 ----------------------------- ...
- C语言入门2-程序设计的灵魂—算法及Raptor的应用
一. 什么是算法(5个特性) 算法就是 解决问题的方法和步骤. 算法为解决一个具体问题而采取的确定的 有限的 执行步骤 ,仅指 计算机 能执行的算法. 算法是程序设计的灵魂和核心 ...
随机推荐
- 利用开源软件 Hugin 实现照片的景深合成,使用开源软件 enfuse 做照片的曝光合成
http://blog.csdn.net/liyuanbhu/article/details/53573847 http://blog.csdn.net/liyuanbhu/article/detai ...
- Topshelf结合Quartz.NET实现服务端定时调度任务
这周接受到一个新的需求:一天内分时间段定时轮询一个第三方WebAPI,并保存第三方WebAPI结果. 需求分析:分时段.定时开启.定时结束.轮询.主要工作集中在前三个上,轮询其实就是个Http请求,比 ...
- LOG4NET图文教程
LOG4NET教程 一:简介 从操作系统到大多数的大型软件,都会有自己的程序运行时的日志跟踪API.因为一旦程序被部署以后,就不太可能再利用专门的调试工具了.然而软件开发人员需要一套强大的日志系统来记 ...
- [2017.02.13] linux平台下统计C++项目文件个数和代码行数
#输出排序后文件名 file='find . -name "*.[ch]" | sort' #统计文件个数 filecnt='find . -name "*.[ch]&q ...
- 转载几篇文章URL
读了百伯在线Jobbole的几篇文章,转给需要的朋友.如下: 产品小设计大体验:http://blog.jobbole.com/39593/ 苹果是一家有工程师的设计公司:Google是一家有设计师的 ...
- Spring Schema扩展机制
1:概述 Spring2.0开始,Spring提供XML Schema可扩展机制,用户可以自定义XML Schema文件,并自定义 XML Bean解析器,集成到Spring IOC容器中. 2:步骤 ...
- Markdown教程<3> 数学公式(1)
# Markdown教程<3> 数学公式(1) 1.如何在markdown中使用公式 公式分为行内公式与行间公式,其中: 行内公式使用$ 数学公式 $ 行间公式使用$$ 数学公式 $$ 2 ...
- git push 时:报missing Change-Id in commit message footer的错误
1. 一般而言,按照提示执行以下两个命令即可生成新的Change-id - gitdir=$(git rev-parse --git-dir); scp -p -P 29418 guan@192.16 ...
- 怎么用Hostwinds搭建Wordpress博客网站(超详细图文教程)
Hostwinds 成立于 2010 年,在主机托管行业算是一个比较新的品牌,但是,凭借丰富的产品线.卓越的服务器性能.良好的客户支持,以及低廉实惠的价格,他们受到了广大客户的喜爱,并多次获得行业重要 ...
- Java基础知识了解
第一章 开发前言 一.java语言概述 Java是当下最流行的一种编程语言,至今有20年历史了.Java语言之父是James Gosling. Java是Sun公司(Stanford Universi ...