一、  算法设计的要求:

为什么要学算法?

/* 输出Hello word! */

#include "stdio.h"

void main()

{

    printf("Hello word!\n");

}

在此程序中,要综合运用数据结构和算法。数据结构是加工对象,语言是工具,变成需要合适的方法,但没有一个合格的算法,我们称不上合格的开发程序。所以,算法是程序设计的灵魂和核心。

  1. 1.        正确性

正确性:算法应当满足具体问题的需求。

“正确”一词的含义在通常的用法中有很大的差别,大体分为以下四个层次:

程序不含语法错误;

例如,程序片段如下

int a;

float b;

a=3;b=4.5;

printf("%f%d\n",a,b);

编译时不给出出错信息,但运行结果将与原意不符,输出为

0.000000  16402

常见的语法错误,输入输出语句要求变量及格式说明一定要类型不一致。

程序对于合法的输入数据能够产生满足要求的输出结果;

程序能够正常的输出数据。

程序对于非法的输入数据能够得出满足规格说明的结果;

程序对于精心选择的,甚至刁难的测试数据都有满足要求的输出结果;

显然,达到层次4是最困难的,我们几乎不可能之一验证所有的输入都得到正确的结果。一般情况下,通常以第3层意义的正确性作为衡量一个程序是否合格的标准。

  1. 2.        可读性

可读性:算法设计的另一目的是为了便于阅读、理解和交流。

可读性好有助于人们对算法的理解;晦涩难懂的算法往往隐含错误,不易被发现,并难于调试和修改。可读性是算法好坏很重要的标志。

例:a=a+b;

b=a-b;       a=a-b;

此片段表达的意思是什么?难道是a,b互换?

  1. 3.        健壮性

一个好的算法应该能对输入数据不合法的情况做合适的处理。

健壮性:当输入数据不合法时,算法也能做出相关处理,而不是产生异常或莫名其妙的结果。

如下程序片断:

if ((fp=fopen(filename,”w”))==NULL)

{

printf(“cannot open file\n”);

exit();

}
  1. 4.        时间效率高和存储量低

时间效率指的是算法的执行时间,对于同一个问题如果有多个算法可以解决,执行时间短的算法效率高,执行时间长的效率低。

存储量需求指算法执行过程中所需要的最大存储空间。

效率和低存储量需求这两者都与问题的规模有关。

如:求100个人的平均分与求1000个人的平均分所花的执行时间或运行空间显然有一定的差别。

二、  算法效率的度量方法

算法执行时间需通过依据该算法编制的程序在计算机上运行时所消耗的时间来度量。而度量一个程序的执行时间通常有两种方法:事后统计方法和事前分析估算方法。

  1. 1.        事后统计方法

这种方法主要是通过设计好的测试程序和数据,利用计算机计时器对不同算法编制的程序运行时间进行比较,从而确定算法效率的高低。

但这种方法显然有很大的缺陷:

(1)必须先运行依据算法编制的程序,通常需要花费大量的时间和精力;

(2)所得时间的统计量依赖于计算机的硬件和软件等环境因素,有时容易掩饰算法本身的优劣;

基于这样的缺陷,我们常常采用另一种事前分析估算的方法。

  1. 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. 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)。

  1. 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)

  1. 3.        线性阶

线性阶的循环结构会复杂很多。要确定某个算法的阶次,我们常常需要确定某个特定语句或某个语句集运行的次数。因此,我们要分析算法的复杂度,关键就是要分析循环结构的运行情况。

下面这段代码,它的循环的时间复杂度为O(n),因为循环体中的代码需要执行n次。

int i;

for(i = ;i < n;i++)

{

/* 时间复杂度为O(1)的程序步骤序列 */

}
  1. 4.        对数阶

我们以一段代码为例,说明对数阶:

int count = ;

while (count < n)

{

count = count *

/* 时间复杂度为O(1)的程序步骤序列 */

}

由于每次count乘以2之后,就距离n更近了一分。也就是说,有多少个2相乘后大于n,则会退出循环。由2x=n得到x=log2n。所以这个循环的时间复杂度为O(log2n)

  1. 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-算法设计思想的更多相关文章

  1. Python数据结构与算法设计(总结篇)

    的确,正如偶像Bruce Eckel所说,"Life is short, you need Python"! 如果你正在考虑学Java还是Python的话,那就别想了,选Pytho ...

  2. 转:从《The C Programming Language》中学到的那些编程风格和设计思想

    这儿有一篇写的很好的读后感:http://www.cnblogs.com/xkfz007/articles/2566424.html   读书不是目的,关键在于思考.   很早就在水木上看到有人推荐& ...

  3. c语言进阶15-数据结构总结

    数据结构结论 1.阿基米德说过:“给我一个支点,我就能翘起地球”. 数据结构是指相互之间存在着一种或多种关系的数据元素的集合和该集合中数据元素之间的关系组成.记为:Data_Structure=(D, ...

  4. Python数据结构与算法设计总结篇

    1.Python数据结构篇 数据结构篇主要是阅读[Problem Solving with Python]( http://interactivepython.org/courselib/static ...

  5. 算法设计与分析(李春保)练习题答案v1

    1.1第1 章─概论 1.1.1练习题 1.下列关于算法的说法中正确的有(). Ⅰ.求解某一类问题的算法是唯一的 Ⅱ.算法必须在有限步操作之后停止 Ⅲ.算法的每一步操作必须是明确的,不能有歧义或含义模 ...

  6. Alink漫谈(一) : 从KMeans算法实现不同看Alink设计思想

    Alink漫谈(一) : 从KMeans算法实现不同看Alink设计思想 目录 Alink漫谈(一) : 从KMeans算法实现不同看Alink设计思想 0x00 摘要 0x01 Flink 是什么 ...

  7. MyBatis 强大之处 多环境 多数据源 ResultMap 的设计思想是 缓存算法 跨数据库 spring boot rest api mybaits limit 传参

    总结: 1.mybaits配置工2方面: i行为配置,如数据源的实现是否利用池pool的概念(POOLED – This implementation of DataSource pools JDBC ...

  8. 用GA算法设计22个地点之间最短旅程-R语言实现

    数据挖掘入门与实战  公众号: datadw 相关帖子 转载︱案例 基于贪心算法的特征选择 用GA算法设计22个地点之间最短旅程-R语言实现 ----------------------------- ...

  9. C语言入门2-程序设计的灵魂—算法及Raptor的应用

    一.     什么是算法(5个特性) 算法就是   解决问题的方法和步骤. 算法为解决一个具体问题而采取的确定的  有限的 执行步骤  ,仅指  计算机   能执行的算法. 算法是程序设计的灵魂和核心 ...

随机推荐

  1. Windows RabbitMQ 安装

    操作系统 Win10  企业版 目标: 在win10上安装RabbitMQ 安装步骤 1.安装RabbitMQ需要先安装Erlang语言开发包,下载地址:http://www.erlang.org/d ...

  2. 使用 GNU autotools 改造一个软件项目

    使用 GNU autotools 改造一个软件项目 及永刚 jungle@soforge.com 2006 年 3 月 24 日  版本:0.3 本文不是一篇规范的教程,而是用一个软件项目作为例子,演 ...

  3. C++ 使用回调函数的方式 和 作用。 持续更新

    先看两个demo: 一.在类test1中调用函数print() ,把print()的函数指针传递给test1的函数指针参数 test1.h: #include <stdio.h> #inc ...

  4. iOS11中iOS处理GIF图片的方式

      GIF 五部走如下 :   1 从相册中取出GIF图的Data 2 通过腾讯的IM发送Gif图 3 展示GIF图 4 GIF图URL缓存机制 5 将展示的GIF图存到相册中     一  从相册中 ...

  5. vue-cli脚手架 ,过滤器,生命周期钩子函数

    一.安装vue-cli脚手架 1.淘宝镜像下载 用淘宝的国内服务器来向国外的服务器请求,我们向淘宝请求,而不是由我们直接向国外的服务器请求,会大大提升请求速度,使用时,将所有的npm命令换成cnpm即 ...

  6. 使用fastjson读取超巨json文件引起的GC问题

    项目中需要将巨量数据生成的json文件解析,并写入数据库,使用了 alibaba 的 fastjson,在实践过程中遇到了 GC 问题,记录如下: 数据大约为70万条,文件大小在3~4G左右,使用 f ...

  7. sql server使用公用表表达式CTE通过递归方式编写通用函数自动生成连续数字和日期

    问题:在数据库脚本开发中,有时需要生成一堆连续数字或者日期,例如yearly report就需要连续数字做年份,例如daily report就需要生成一定时间范围内的每一天日期.而自带的系统表mast ...

  8. spring 5.x 系列第16篇 —— 整合dubbo (代码配置方式)

    文章目录 一. 项目结构说明 二.项目依赖 三.公共模块(dubbo-ano-common) 四. 服务提供者(dubbo-ano-provider) 4.1 提供方配置 4.2 使用注解@Servi ...

  9. spring 5.x 系列第8篇 —— 整合Redis客户端 Jedis和Redisson (代码配置方式)

    文章目录 一.说明 1.1 Redis 客户端说明 1.2 Redis可视化软件 1.3 项目结构说明 1.3 依赖说明 二.spring 整合 jedis 2.1 新建基本配置文件和其映射类 2.2 ...

  10. string类总结第二部分实战练习

    第二部门:实战练习 昨天由于时间原因,这个部分应该在同一个文章中的,无奈只能今天再开一个了,今天主要是讲一些面试题 一:equals和==的区别 最简单的面试题,也是最基础的,我估计每个学习java的 ...