您的位置:首页 > 理论基础 > 数据结构算法

[从今天开始修炼数据结构]基本概念

2019-11-25 18:36 369 查看

[从今天开始修炼数据结构]基本概念

[从今天开始修炼数据结构]线性表及其实现以及实现有Itertor的ArrayList和LinkedList

[从今天开始修炼数据结构]栈、斐波那契数列、逆波兰四则运算的实现

[从今天开始修炼数据结构]队列、循环队列、PriorityQueue的原理及实现

从双十一低价购入了一批书,拖到今天还没开始看,实在不该不该。所以我决定从今天开始修炼数据结构和算法,打下坚实基础!

学习路线:《大话数据结构》程杰 和 《算法》Robert Sedgewick 两本书对照学习。 再加上网上搜集的优秀博文的参考。代码实现使用Java,写下此篇博文作为记录。   —— 2019.11.25  8:49在B326实验室。

      另外,这两本书缺少动态规划这一部分,如果有幸有人看到我这篇随笔,请推荐你觉得优秀的动态规划教程给我~

下面开始正式内容

一、什么是数据结构

  学习数据结构,首先要搞清楚什么是数据,数据相关的概念。

    数据就是计算机可以识别、操作的符号集合。  

    数据元素是组成数据的、有一定意义的基本单位。 也被称为记录。

    数据项:一个数据元素可以由若干个数据项组成。  比如人这个数据元素,可以有眼睛、耳朵、鼻子等若干数据项。

    数据对象是性质相同的数据元素的集合。 比如所有的人,都具有眼睛、耳朵、鼻子,那么人的集合就是数据对象。

  数据结构:相互之间存在一种或多种特定关系的数据元素的集合。 编写一个好的程序,要分析、组织、处理对象的特性和处理对象之间的关系。

  数据结构分为 逻辑结构 和 物理结构。

    逻辑结构:集合结构  线性结构  树形结构  图形结构

    物理结构:顺序存储  链式存储

  在高级语言中,数据类型可以分为 原子类型(不可分解的基本类型) 和结构类型(对象、结构体等)。二者都属于抽象数据类型。下面给出描述抽象数据类型的标准格式。

 

ADT 抽象数据类型名
Data
数据元素之间逻辑关系的定义
Operation
操作1
初始条件
操作结果描述
操作2
……
操作n
……
endAD

 二、什么是算法

  算法是解决特定问题求解步骤的描述。在计算机中表现为指令的有限序列,并且每条指令表示一个或多个操作。简单说算法也就是解决问题的方法

  一个好的算法要满足

  1,正确性

    没有语法错误,对于合法的输入能产生满足要求的输出;对于非法的输入能够得出满足规格说明的结果;对于刁难的测试数据也有满足要求的输出

  2,可读性

    算法设计的目的之一是便于阅读、理解和交流。

  3,健壮性 

     一个好的算法应该能对输入数据不合法的情况做合适的处理,而不是产生异常或者莫名其妙的结果。

  4,时间效率高和存储量低

    时间和空间复杂度分析。事后统计方法有很大缺陷,不科学,不准确,我们一般使用事前分析估算方法来评判算法的效率。在计算机程序编制前,依据统计方法对算法进行估算

    我们在分析一个算法的运行时间时,重要的是把基本操作的数量与输入规模关联起来,即基本操作的数量必须表示成输入规模的函数。随着n值越来越大,它们在时间效率上的差异也就越来越大。

那么我们如何来表示算法在时间上的差异呢?这就引入了算法时间复杂度

 三、时间复杂度

  判断一个算法好不好,我们通过少量的数据是不能做出准确判断的,我们可以对比算法的关键执行次数函数的渐进增长性,得到某个算法随着n的增大,越来越优于(或劣于)另一算法。

  在判断一个算法的效率时,函数中的常数和其他次要项常常可以忽略,而更应该关注主项(最高阶项)的阶数分析高阶项,关键就是分析循环结构的运行情况

  算法的时间复杂度,也就是算法的时间量度,记作:T(n) = O(f(n))。它表示随问题规模n的增大,算法执行时间的增长率和f(n)的增长率相同,称作算法的时间复杂度。其中f(n)是问题规模n的某个函数。

  1,常数阶:对于单纯的分支结构(不包含在循环结构里),执行次数是恒定的,不会随着n的变大而发生变化,其时间复杂度是O(1).

   2,线性阶:对于下面这段代码,因为循环体中的代码要执行n次,所以它的时间复杂度为O(n)

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

  3,对数阶:看下面这段代码。

int count = 1;
while (count < n){
count  = count * 2;
}

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

  4,平方阶:对于常规的循环嵌套

for(i = 0; i < n; i++){
for(j = 0; j < n; j++){
//一段O(1)的程序
}
}

这段代码的时间复杂度为O(n2);如果外循环的循环次数改为m。时间复杂度就变为O(m×n)。

我们可以总结得出:循环的时间复杂度等于循环体的复杂度乘以该循环运行的次数

看下面这个循环嵌套。

for (i = 0; i < n; i++){
for (j = i; j < n; j++){
    //一段O(1)的程序 } }

当i = 0时,内循环执行n次;当i = 1时,内循环执行了n - 1次;…… ; 当i= n -1 时,内循环执行了一次。所以总执行次数为

  n + (n - 1 )+ (n - 2) + …… + 1 = n(n + 1)/2 = n2/2 + n/2 。去除不予考虑的常熟、低阶项等,得到时间复杂度为O(n2).

常用的时间复杂度所耗费的时间从小到大依次是:

  O(1) < O(logn) < O(n) < O(nlogn) < O(n2) < O(n3) < O(2n) < O(n!) < O(nn)

  O(nlogn)会在后面讨论到,待补充。  O(n3) 及更高的时间复杂度太高,一般不讨论。

   在实际情况中,遇到的问题都会有好有坏。比如在数组中顺序查找一个数字,最好的情况一开始找第一个位置就是目标;最坏的情况找到最后一个位置才发现目标。那么对应的最好情况的时间复杂度就是O(1),最坏情况就是O(n)。除非特别指定,我们提到的时间复杂度都是按照最坏情况来考虑的运行时间。那么我们在设计算法的时候希望的是哪个运行时间呢?我们希望的是平均运行时间,对于上面的例子就是n/2.平均运行时间是所有情况中最有意义的,因为它是期望的运行时间。

 四、空间复杂度

  我们在写代码时,完全可以用空间换取时间。 举个例子,要判断某年是否是闰年:常规方法时写一个算法,设置判断条件,通过计算得到是否是闰年。而另一个方法是,设置一个足够长的,比如2050个元素的数组,把所有年份按下标对应,如果是闰年,数组值设为1,如果不是值设为0.这样判断是否是闰年可以直接到数组中按照下标取值。这样我们的运行时间将为了O(1) ,但是硬盘或者内存要存储这样一个很长的数组。这就是空间换取时间的做法。

  到底哪个好?看你要用在什么地方。

  算法的空间复杂度通过计算算法所需的存储空间实现,算法弓箭复杂度的计算公式记作:S(n) = O(f(n))。 n为问题规模,f(n)为语句关于n所占存储空间的函数。

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: