基本概念之算法

什么是算法

算法(Algorithm)定义:

  • 一个有限指令集:一堆指令放在一起做一件事情,而这个指令集一定是有限的
  • 有零个或多个输入(有些情况下不需要输入)
  • 有一个或多个输出(一定有输出)
  • 一定在有限步骤之后终止
  • 每一条指令必须
    • 有充分明确的目标,不可以有歧义
    • 计算机能处理的范围之内
    • 描述不依赖于任何一种计算机语言以及具体的手段

例1:选择排序算法的伪码描述

void SelectionSort(int List[],int N){
    /*将N个整数List[0]...List[N-1]进行非递减排序*/
    for(i=0;i<N;i++){
        MinPosition=ScanForMin(List,i,N-1);
        /*从List[i]到List[N-1]中找最小元,并将其位置赋给MinPosition;*/
        Swap(List[i],List[MinPosition]);
        /*将未排序部分的最小元换到有序部分的最后位置;*/
    }
}

这段伪码的描述是比较抽象的:

  • 抽象点1:List到底是数组还是链表?
  • 抽象点2:swap用函数还是用宏去实现?

这两个抽象点都需要具体的实现,但是在算法中,这种具体的实现过程是不被关心的。



什么是好的算法?

用以下两个指标来衡量:

  • 空间复杂度S(n)——根据算法写成的程序在执行时占用存储单元的长度。这个长度往往与输入数据的规模有关。空间复杂度过高的算法可能导致使用的内存超限,造成程序非正常中断。
  • 时间复杂度T(n)——根据算法写成的程序在执行时耗费时间的长度。这个长度往往也与输入数据的规模有关。时间复杂度过高的算法可能导致我们在有生之年都等不到运行结果。

这两个指标与我们处理数据的规模是直接相关的。

例2:PrintN函数的递归实现

1 void PrintN(int N){
2     if(N){
3         PrintN(N-1);
4         printf("%d\n",N);
5     }
6 }

当N=100000时,程序是非正常跳出了。

分析原因:当传入参数N=100000时,程序判断N不为0,开始递归调用PrintN。在调用之前,系统会把当前的函数所有的现有的状态都存到系统内存的某个地方,

然后执行递归函数,执行完后,系统会把这些存储状态回复回来,接着执行此函数的下一句,

直到递归到N为1的时候。

可以发现程序占用空间的数量实际上与原始N的大小成正比,S(N)=C*N,当N非常大的时候,程序可利用的空间有限,会直接爆掉,所以非正常退出了。

但是在循环程序里面,只用到了一个临时变量和一个for循环,没有涉及任何程序调用的问题,所以不管N多大,它所占用的空间始终是固定的,占用空间的量不会随着N的增长而增长,空间占用始终是一个常量,不会出现问题。

例3:求多项式的值。

1 double f(int n,double a[],double x){
2     int i;
3     double p=a[0];
4     for(i=1;i<=n;i++){
5         p+=(a[i]*pow(x,i));//累加求和
6     }
7     return p;
8 } 
1 double f(int n,double a[],double x){
2     int i;
3     double p=a[n];
4     for(i=n;i>0;i--){
5         p=a[i-1]+x*p;
6     }
7     return p;
8 } 

在分析程序运行效率时,机器执行加减法的效率比执行乘除法的效率要快很多。

第一个函数,每一次for循环执行i次乘法,不要忽略pow(x,i)函数每次执行i-1次乘法,总共执行的乘法次数是:1+2+...+n=(n+1)n/2

第二个函数,每次循环只执行了1次乘法,总共执行的乘法次数是:n

那么,用时间复杂度来衡量时,T1(n)=C1n2+C2n,T2(n)=C?n,当n很大时,第二个程序比第一个程序要快很多很多。

在分析一般算法的效率时,经常关注下面两种复杂度:

  • 最坏复杂度Tworst(n)
  • 平均复杂度Tavg(n)

显然Tavg(n)<=Tworst(n),我们一般倾向于计算Tworst(n),因为“平均”在不同情况下有不同的理解,分析起来比较复杂。



复杂度的渐进表示

 在分析一个算法时,没必要将算法执行了多少次一步步数出来,需要关注的是随着数据规模的增大,复杂度增长的性质是怎样变化的。

  • T(n)=O(f(n))表示存在常数C>0,n0>0,使得当n>=n0时有T(n)<=C·f(n)
  • T(n)=Ω(g(n))表示存在常数C>0,n0>0,使得当n>=n0时有T(n)<=C·g(n)
  • T(n)=θ(h(n))表示同时有T(n)=O(h(n))和T(n)=Ω(h(n))

 当讨论一个函数复杂度的上界和下界时,其实不是唯一的,它可以有无穷多个上界和下界。但是太大的上界和太小的下界对于分析算法而言是没有帮助的,所以用大O记法时,一般取能找到的最小的上界,最大的下界。

输入规模 n
函数 1 2 4 8 16 32
1 1 1 1 1 1 1
logn 0 1 2 3 4 5
n 1 2 4 8 16 32
nlogn 0 2 8 24 64 160
n2 1 4 16 64 256 1024
n3 1 8 64 512 4096 32768
2n 2 4 16 256 65536 4294967296
n! 1 2 24 40326 2092278988000 26313×1033

在表中所列的输入规模n的最大值才取到32,函数中logn以什么为底并不重要,加了底只是多了一个常数倍而已,影响不大。

           

这张图显示了不同函数增长的速率,作为专业的程序员,如果设计的算法复杂度达到n2,要考虑能不能降低算法的复杂度。

每秒10亿指令计算机的运行时间表
n f(n)=n nlogn n2 n3 n4 n10 2n
10 .01us .03us .1us 1us 10us 10sec 1us
20 .02us .09us .4us 8us 160us 2.84hr 1ms
30 .03us .15us .9us 27us 810us 6.83d 1sec
40 .04us .21us 1.6us 64us 2.56ms 121.36d 13d
50 .05us .28us 2.5us 125us 6.25ms 3.1yr 4×1013yr
100 .10us .66us 10us 1ms 100ms 3171yr 32×10283yr
1000 1.00us 9.96us 1ms 1sec 16.67min 3.17×1013yr  
10000 10us 130.03us 100ms 16.67min 115.7d 3.17×1023yr  
100000 100us 1.66ms 10sec 11.57d 3171yr 3.17×1033yr  
1000000 1.0ms 19.92ms 16.67min 31.71yr 3.17×107yr 3.17×1043yr  
us=微秒=10-6 sec=秒 hr=小时 yr=年 ms=毫秒=10-3 min=分钟 d=日  


复杂度分析小窍门

  • 若两段算法复杂度分别有T1(n)=O(f1(n))和T2(n)=O(f2(n)),则

    • T1(n)+T2(n)=max(O(f1(n)),O(f2(n)))
    • T1(n)×T2(n)=O(f1(n)×f2(n))
  • 若T(n)是关于n的k阶多项式,那么T(n)=Θ(nk)
  • 一个for循环的时间复杂度等于循环次数乘以循环体代码的复杂度
  • if-else结构的复杂度取决于if的条件判断复杂度和两个分支部分的复杂度,总体复杂度取三者中最大

时间: 2024-11-09 03:51:43

基本概念之算法的相关文章

01.数据结构概念与算法基础

数据结构概念与算法基础 一.数据结构概念 1.数据:是描述客观事务的符号,是计算机中可以操作的对象,是能被计算机识别,并输入给计算机处理的符号集合.数据不仅仅包括整型.实型等数值类型,还包括字符及声音.图像.视频等非数值类型. 2.数据元素:是组成数据的.有一定意义的基本单位,在计算机中通常作为整体处理,也被成为记录.比如畜类中,牛.马.羊都属于数据元素. 3.数据项:一个数据元素可以由若干个数据项组成,数据项是数据不可分割的最小单位.比如人这样的数据元素,可以有眼.耳.鼻等数据项. 4.数据对

数据结构基本概念及算法和算法分析 -- 引自《新编数据结构习题与解析》(李春葆等著)

本文引自<新编数据结构习题与解析>(李春葆等著)第1章. 1. 数据结构的基本概念 1.1 数据 数据是对客观事物的符号表示,在计算机科学中是指所有能输入到计算机中并被计算机程序处理的符号的总称.例如,整数.实数和字符串都是数据. 1.2 数据元素 数据元素也称为节点,是表示数据的基本单元,在计算机程序中通常作为一个整体进行考虑和处理. 1.3 数据项 数据项是数据的最小单位.数据元素可以由若干个数据项组成.例如,学生记录就是一个数据元素,它由学号.姓名.性别等数据项组成. 1.4 数据对象

JVM虚拟机(四):JVM 垃圾回收机制概念及其算法

垃圾回收概念和其算法 谈到垃圾回收(Garbage Collection)GC,需要先澄清什么是垃圾,类比日常生活中的垃圾,我们会把他们丢入垃圾箱,然后倒掉.GC中的垃圾,特指存于内存中.不会再被使用的对象,儿回收就是相当于把垃圾"倒掉".垃圾回收有很多中算法:如 引用计数法.标记压缩法.复制算法.分代.分区的思想. 垃圾收集算法 引用计数法:就是个比较古老而经典的垃圾收集算法,其核心就是在对象被其他所引用计数器加1,而当引用时效时则减1,但是这种方式有非常严重的问题:无法处理循环引用

JVM垃圾回收概念和算法

GC中的垃圾:特指存在于内存中.不会再使用的对象. 内存泄漏和内存溢出的区别: 内存泄漏:内存空间忘记回收,垃圾对象永远无法被回收 内存溢出:垃圾对象(不满足回收条件)所耗内存持续上升,导致内存溢出. 1 常用的垃圾回收算法 引用计数法.标记压缩法.标记清除法.复制算法和分代.分区 1)引用计数法:

树的基本概念及算法

基本概念: 某个结点的度:该结点的子树个数: 树的度:该树中的任意结点的度的最大值: 高度:叶子结点高度为1,根结点高度最高: 森林:多个树组成(怎么组成?暂时不看). 树的子树的个数没有限制(但是子树之间一定没有相交). 树的转换: 树->二叉树:3步(临时只写最后一步):第一个孩子是该结点的左孩子,该结点的兄弟转换为右孩子. 森林->二叉树:森林的每棵树(分别转换)->二叉树->组合成一颗二叉树:将后一颗二叉树的根结点转换为前一颗二叉树根结点的右孩子. 二叉树->树:1加

垃圾回收的概念与算法

GC中的垃圾,是指的是在内存中不在不再被使用的对象. 常见的垃圾回收算法 1.引用计数算法(无法回收循环引用的对象) 2.标记清除算法分为标记阶段和清除阶段(会产生内存的空间碎片) 3.复制算法(缺点是将系统内存折半,高效性是建立在存活对象少,垃圾对象多的前提下的) 在java新生代串行垃圾回收器中,使用了复制算法的思想,新生代分为eden,from,to三个部分.from,to空间成为survivor空间,用于存放未被回收的对象. 其中:新生代指得是存放年轻对象的空间. 老年代指的是存放垃圾回

(C# Binary Tree) 基本概念和算法

A binary tree is defined as a tree where each node can have no more than two children. Building a Binary Search Tree: 首先创建一个节点Class public class BtNode { public int Data { get; set; } public BtNode Left { get; set; } public BtNode Right { get; set; }

普林斯顿公开课 算法1-7:并查集基本概念

本节讲的是并查集的基本概念. 算法的开发步骤 对问题进行数学建模 寻找一个能够解决问题的算法 运行算法检测速度和内存是否符合要求 如果达不到要求,找出原因 寻找一种方法来解决问题 循环步骤,直到满意为止 以上就是算法开发比较科学的方法.算法开发完成之后需要进行数学分析. 并查集问题 给定N个物体,可以提供两种操作,一种是合并操作,一种是查找操作.合并操作就是将两个节点进行连接,查找操作就是判断两个节点是否连接在一起. 应用中的物体类型 实际应用中,并查集算法可以支持各种各样的物体类型,比如: 图

数据结构与算法 1 :基本概念,线性表顺序结构,线性表链式结构,单向循环链表

[本文谢绝转载] <大纲> 数据结构: 起源: 基本概念 数据结构指数据对象中数据元素之间的关系  逻辑结构 物理结构 数据的运算 算法概念: 概念 算法和数据结构区别 算法特性 算法效率的度量 大O表示法 时间复杂度案例 空间复杂度 时间换空间案例 1)线性表: 线性表初步认识: 线性表顺序结构案例 线性表顺序结构案例,单文件版 线性表的优缺点 企业级线性表链式存储案例:C语言实现 企业级线性表链式存储案例:C语言实现 单文件版 企业级线性表链式存储案例,我的练习  线性表链式存储优点缺点