早期人们都把计算机理解为数值计算工具,就是感受计算机固然是用来计算的,因此计算机解决问题,应该是先从具体问题中抽象出一个适当的数据模型,设计出一个解此数据类型的算法,而后再编写程序,获得一个实际的软件。php
可现实中,咱们更多的不是解决数值计算的问题,而是须要一些更科学有效的手段(好比表、树和图等数据结构)等帮助,才能更好地解决问题。算法
因此数组
数据结构是一门研究非数值计算的程序设计问题中的操做对象,以及它们之间的关系和操做等相关问题的学科。数据结构
说到数据结构是什么,咱们得先来谈谈什么叫作数据。函数
数据结构中,有5个基本概念:数据、数据元素、数据项、数据对象和数据结构。工具
他们之间的关系以下图所示:测试
具体到代码上,参考以下代码:ui
//声明一个结构体类型
struct Teacher{ //一种数据结构
char *name; //数据项--名字
char *title; //数据项--职称
int age; //数据项--年龄
};
int main(int argc, const char * argv[]) {
struct Teacher t1; //数据元素;
struct Teacher tArray[10]; //数据对象;
return 0;
}
复制代码
是描述客观事物的符号,是计算机中能够操做的对象,是能被计算机识别,并输入给计算机处理的符号集合。数据不只仅包括整型、实数等数值类型,还包括字符及声音、图像、视频等非数值类型类型。spa
——《大话数据结构》设计
好比咱们平时使用搜索殷勤,有网页、mp三、图片、视频等分类。MP3 就是声音数据
数据的特色:
组成数据的、有必定意义的基本单位,在计算机中一般做为总体处理,也被称为记录。
好比,在人类中,人就是数据元素。
而在动物类中,牛、马、羊、鸡等动物就是动物类的数据元素了。
一个数据元素由若干数据项组成。
好比人这样的数据元素,能够有眼耳鼻舌口这些数据项,也有姓名、年龄、性别、出生地址、电话等数据项。
数据项上数据不可分割的最小单位。
性质相同的数据元素的集合,是数据的子集。
性质相同的意思,是指数据元素具备相同数量和类型的数据项,好比,人都有姓名、生日、性别等相同的数据项。
是相互之间存在一种或多种特定关系的数据元素的集合。
在现实世界中,不一样数据元素之间不是独立的,而是存在特定的关系,咱们将这些关系称为结构。而在计算机中,数据元素并非孤立、杂乱无序的,而是具备内在联系的数据集合。数据之间存在的一种或多种特定关系,也就是数据的组织形式。
按照观点的不一样,咱们把数据结构分为逻辑结构和物理结构。
是指数据对象中数据元素之间的相互关系
逻辑关系按照类别分为线性结构与非线性结构:
线性结构中的数据元素是一对一的关系
非线性结构中的数据元素是一对多或多对多的关系。
是指数据的逻辑结构在计算机中的存储形式。
数据元素的存储形式有两种:顺序存储和链式存储。
把数据元素存放在抵制连续的存储单元里,其数据间的逻辑关系和物理关系是一致的。
以下图所示:
把数据元素存放在任意的存储单元里,这组存储单元能够是连续的,也能够是不连续的。
数据元素的存储关系并不能反映其逻辑关系,所以须要用一个指针存放数据元素的地址,这样经过地址就能够找到相关联数据元素的位置,如图所示:
数据类型:是指一组性质相同的值的集合及定义在此集合上的一些操做的总称。
数据类型是按照值的不一样进行划分的。在高级语言中,每一个变量、常量的表达式都有各自的取值范围。类型就用来讲明变量或表达式的取值范围和所能进行的操做。
在C语言中,按照趣致的不一样,数据类型能够分为两类:
抽象是指抽出事物具备的广泛型的本质。咱们对已有的数据类型进行抽象,就有了抽象数据类型。
抽象数据类型(Abstract Data Type:ADT):
是指一个数学模型及定义在该模型上的一组操做。
抽象的意义在于数据类型的数字抽象特性。
抽象数据类型体现了程序设计中问题分解、抽象和信息隐藏的特性。
是解决特定问题对求解步骤的描述,在计算机中表现为指令的有限序列,而且每条指令表示一个或多个操做。
什么是算法?算法是描述解决问题的方法。
自唐代以来,历代更有许多专门论述“算法”的专著:
而英文名称“algorithm”来自于9世纪波斯数学家花拉子米(比阿勒·霍瓦里松,波斯语:خوارزمی ,拉丁转写:al-Khwarizmi),由于比阿勒·霍瓦里松在数学上提出了算法这个概念。“算法”原为“algorism”,即“al-Khwarizmi”的音转,意思是“花拉子米”的运算法则,在18世纪演变为“algorithm”。
欧几里得算法被人们认为是史上第一个算法。
算法具备五个基本特征:输入、输出、有穷性、肯定性和可行性。
算法的正确性是指算法至少应该具备输入、输出和加工处理无歧义性、能正确反应问题的需求、可以获得问题的正确答案。
大概分为如下四个层次:
以上这四层含义里,层次1 要求最低,而层次4 时最困难的,实际开发中,咱们几乎不可能逐一验证全部的输入都能获得正确的结果。
算法设计的另外一目的是为了便于阅读、理解和交流。
可读性时算法(也包括实现它的代码)好坏很重要的标志。
当输入数据不合法时,算法也能作出相关处理,而不是产生异常或莫名其妙的结果。
设计算法应该尽可能知足时间效率高和存储量低的特色。
在生活中,人们都但愿花最少的钱,用最短的时间,办最大的事,算法也是同样的思想,最好用最少的存储空间,办成一样的事——就是好的算法。
经过对算法的数据测试,利用计算机的计时功能,来计算不一样算法的效率是高仍是低。
这种方法主要是经过设计好的测试程序和数据,利用计算机计时器对不一样算法编织的程序的运行时间进行比较,从而肯定算法效率的高低。
在计算机程序编制前,依据统计方法对算法进行估算。
咱们发现,一个用高级程序语言编写的程序在计算机上运行时所消耗的时间取决于下列因素:
抛开这些与计算机硬件、软件有关的因素,一个程序的运行时间,依赖于算法的好坏和问题的输入规模。所谓问题输入规模是指输入量的多少。
咱们看看两种求和的算法:
第一种算法
int i, sum = 0 n = 100; /* 执行 1次*/
for(i = 1; i <= n; i++) /* 执行 n + 1 次*/
{
sum += i; /* 执行 n 次*/
}
print("%d", sum); /* 执行 1 次*/
复制代码
第二种算法
int sum = 0, n = 100; /* 执行 1次*/
sum = (1 + n) * n/2; /* 执行 1次*/
printf("%d", sum); /* 执行 1次*/
复制代码
显然,第一种算法,执行了 1 + (n+1) + n + 1 次 = 2n + 3 次
而第二种算法是1+1+1 = 3 次。算法好坏显而易见。
最终,在分析程序的运行时间时,最重要的是吧程序当作是独立于程序设计语言的算法或一系列步骤。
在进行算法分析时,语句总的执行次数T(n)是关于问题规模n的函数,进而分析T(n) 随n 的变化状况并肯定T(n) 的数量级。
算法的时间复杂度,也就是算法的时间量度,记做:T(n) = O(f(n))。它表示随问题规模 n 的增大,算法执行时间的增加率和 f(n) 的增加率相同,称做算法的渐进时间复杂度,简称为时间复杂度。其中f(n) 是问题规模 n 的某个函数。
大写O() 来体现算法复杂度的激发,咱们称之为大O记法。
上面求和算法的时间复杂度,分别为O(n) 和 O(1)
下面这个算法,就是刚刚的第二个算法(高斯算法)。
int sum = 0, n = 100; /* 执行 1次*/
sum = (1 + n) * n/2; /* 执行 1次*/
printf("%d", sum); /* 执行 1次*/
复制代码
这个算法的运行次函数是 f(n) = 3。根据咱们推导大O阶的方法,第一步就是把常数3 改成1,再加上它没有最高阶项,因此这个算法的时间复杂度为O(1)
若是这里的第二行 sum = (1 + n) * n / 2 有10句,会是怎么样?
int sum = 0, n = 100; /* 执行 1次*/
sum = (1 + n) * n/2; /* 执行 1次*/
sum = (1 + n) * n/2; /* 执行 1次*/
sum = (1 + n) * n/2; /* 执行 1次*/
sum = (1 + n) * n/2; /* 执行 1次*/
sum = (1 + n) * n/2; /* 执行 1次*/
sum = (1 + n) * n/2; /* 执行 1次*/
sum = (1 + n) * n/2; /* 执行 1次*/
sum = (1 + n) * n/2; /* 执行 1次*/
sum = (1 + n) * n/2; /* 执行 1次*/
sum = (1 + n) * n/2; /* 执行 1次*/
printf("%d", sum); /* 执行 1次*/
复制代码
事实上,不管n 为多少,上面的代码就说3次和12次执行的差别。这种与问题的大小无关(n) 的多少,执行时间恒定的算法,咱们称之为具备 O(1) 的时间复杂度
咱们要分析算法的复杂度,关键就是要分析循环结构的运行状况。
下面这段代码,它的循环的时间复杂度为O(n),由于循环体中的代码需要执行 n 次
int i;
for(i = 0; i < n; i++)
{
/ *
时间复杂度为O(1)的程序步骤序列
*/
}
复制代码
int count = 1;
while (count < n)
{
count = count * 2;
/* 时间复杂度为 O(1) 的程序步骤序列*/
}
复制代码
上面这行代码,因为每次 count 乘以 2 之后,就距离 n 更近了一份。
也就是说,有多少个2 相乘后大于 n,则会推出循环。
由 2x= n 获得 x = log2n 。因此这个循环的时间复杂度为O(logn)。
下面的例子说一个循环嵌套,它的内循环时间复杂度为O(n)
in 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) | 平方阶 |
5 log2n + 20 | O(logn) | 对数阶 |
2n + 3n log2n + 19 | O(nlogn) | nlogn 阶 |
6n3 + 2 n2 + 3n + 4 | O(n3) | 立方阶 |
2 n | O(2n) | 指数阶 |
经常使用的时间复杂度所消耗的时间从小到大依次是:
O(1) < O(logn) < O(n) < O(nlogn) < O(n2) < O(n3) < O(2n) < O(n!) < O(nn)
咱们查找一个由 n 个随机数字数组中的某个数组,最好的状况是第一个数字就是,那么算法的时间复杂度为O(1),但也有可能这个数字就在最后一个位置上待着,那么算法的复杂度为O(n),这是最坏的一种状况了。
最坏状况运行时间是一种保证,那就是运行时间将不会再坏了。在应用中,这是一种最重要的需求,一般,除非特别指定,咱们提到的运行时间都是最坏时间的运行时间。
平均运行时间是全部状况中最有意义的,由于它是指望的运行时间。也就是说,咱们运行一段程序代码时,实习完看到平均运行时间的。可现实中,平均运行时间很难经过分析获得,通常都是经过运行必定数量的实验数据后估算出来的。
对算法的分析,一种方法是计算全部状况的平均值,这种时间复杂度的计算方法称为平均时间复杂度。
另外一种方法是计算最坏状况下的时间复杂度,这种方法称为最坏时间复杂度。通常在没有特殊说明的状况下,都是指最坏时间复杂度。
算法的空间复杂度经过计算算法所需的存储空间实现,算法空间复杂度的计算公式记做:S(n) = O(ƒ(n)),其中,n 为问题的规模,ƒ(n) 为语句关于 n 所占存储空间的函数。
一般,咱们都适用“时间复杂度”来指运行时间的需求,使用“空间复杂度”指空间需求。当不用限定词地使用“复杂度”时,一般都是指时间复杂度。