数据结构概念与算法基础
一、数据结构概念
1.数据:是描述客观事务的符号,是计算机中可以操作的对象,是能被计算机识别,并输入给计算机处理的符号集合。数据不仅仅包括整型、实型等数值类型,还包括字符及声音、图像、视频等非数值类型。
2.数据元素:是组成数据的、有一定意义的基本单位,在计算机中通常作为整体处理,也被成为记录。比如畜类中,牛、马、羊都属于数据元素。
3.数据项:一个数据元素可以由若干个数据项组成,数据项是数据不可分割的最小单位。比如人这样的数据元素,可以有眼、耳、鼻等数据项。
4.数据对象:是性质相同的数据元素的集合,是数据的子集。所谓性质相同,是指数据元素具有相同数量和类型的数据项。比如人都有姓名、生日、性别等相同的数据项。从某种意义上来说,数据对象也称之为数据(数据对象是数据的子集)。
5.数据结构:不同数据元素之间不是独立的,二是存在特定的关系,这些关系可被称之为结构。所谓数据结构,即指是相互之间存在一种或多种特定关系的数据元素的集合-----数据的组织形式。
6.逻辑结构与物理结构
(1)逻辑结构:是指数据对象中数据元素之间的相互关系。逻辑结构是针对具体问题的,是为了解决某个问题,在对问题的理解的基础上,选择一个合适的数据结构表示数据元素之间的逻辑关系。
a.集合结构:集合结构中的数据元素除了同属于一个集合外,它们之间没有其他关系
b.线性结构:线性结构中的数据元素之间是一对一的关系。
c.树形结构:树形结构中的数据元素之间存在一种一对多的层次关系。
d.图形结构:图形结构的数据元素是多对多的关系。
注释:我们在用示意图表示数据的逻辑结构时,需注意的是将每一数据元素看作一个结点(用圆圈表示);元素之间的逻辑关系用结点之间的连线表示。
(2)物理结构:是指数据的逻辑结构在计算机中的存储形式,即实际上就是如何把数据元素存储到计算机的存储器(内存)中,数据的存储结构反映数据元素之间的关系。
a.顺序存储结构:是把数据元素存放在地址连续的存储单元里,其数据间的逻辑关系和物理关系是一致的。
b.链式存储结构:是把数据元素存放在任意的存储单元里,这组存储单元可以是连续的,也可以是不连续的。数据元素的存储关系并不能反映其逻辑关系,因此需要用一个指针存放数据元素的地址,这样通过地址就可以找到相关联数据元素的位置。
总结:逻辑结构是面向问题的。而物理结构是面向计算机的,其基本的目标就是将数据及逻辑关系存储到计算机的内存中。
7.数据类型与抽象数据类型
(1)数据类型:是指一组性质相同的值的集合及定义在此集合上的一些操作的总称。
(2)抽象数据类型(ADT):是指一个数据模型及定义在该模型上的一组操作。"抽象"的意义在与数据类型的数学抽象特性,一个抽象数据类型定义了:一个数据对象,数据对象中各数据元素之间的关系及数据元素的操作。总之,抽象数据类型体现了程序设计中问题分解、抽象和信息隐藏的特征。抽象数据类型把实际生活中的问题分解为多个规模很小且容易处理的问题,然后建立一个计算机能处理的数学模型。并把每个功能模块的时间细节作为一个独立的单元,从而使具体实现过程隐藏起来。
描述抽象数据类型的标准格式:
ADT 抽象数据类型名
Data
数据元素之间逻辑关系的定义
Operation
操作1
初始条件
操作结果描述
操作2
....
操作n
....
endADT
二、算法基础
1.什么是算法?
算法是解决特定问题求解步骤的描述,在计算机中表现为指令的有限序列,并且每条指令表示i一个或多个操作。具体的说,为了解决某个或某类问题,需要把指令表示成一定的操作序列,操作序列包括一组操作,每个操作都完成特定的功能,这就是算法了。
2.算法有哪些特性?
(1)输入输出:算法具有零个或多个输入;至少有一个或多个输出。
(2)有穷性:指算法在执行有限的步骤之后,自动结束而不会出现无限循环,并且每一个步骤在可以接受的时间内完成。
(3)确定性:算法的每一步骤都具有确定的含义,不会出现二义性。
(4)可行性:算法的每一步都必须是可行的
3.算法的设计要求有哪些?
(1)正确性
(2)可读性
(3)健壮性:指当输入数据不合法时,算法也能作出相关处理,而不是产生异常或莫名奇妙的结果
(4)时间效率高和存储量低
4.算法效率的度量方法
(1)事后统计方法:这种方法主要是通过设计好测试程序的数据,利用计算机计时器对不同算法编制的程序的运行时间进行比较。
(2)事前分析估算方法:一个用高级程序编写的程序在计算机上运行的所消耗的时间取决于:算法采用的策略和方法(算法好坏的根本);编译产生的代码质量(软件环境决定);问题的输入规模;机器指令执行的速度(硬件决定)。
注意:测定运行时间最可靠的方法就是计算对运行时间有消耗的基本操作的执行次数,运行时间与这个计数成正比。
某个算法,随着n的增大,它会越来越优于另一算法,或者越来越差于另一算法。
三、算法时间复杂度
1.定义:在进行算法分析时,语句的执行次数T(n)是关于问题规模n的函数,进而分析T(n)随n的变化情况并确定T(n)的数量级。算法的时间复杂度,也就是算法的时间量度,记作:T(n)=O(f(n))---它表示随问题规模n的增大,算法执行时间的增长率和f(n)的增长率相同,称作算法的渐进时间复杂度,简称为时间复杂度。其中f(n)是问题规模n的某个函数。
其中,O()来体现算法时间复杂度的记法(大O记法)。一般情况下,随着n的增大,T(n)增长最慢的算法为最优算法。
2.推到大O阶方法:分析一个算法的时间复杂度,其原则具体表现如下:
原则一:用常数1取代运行时间中的所有加法常数-------------------举例:f(n)=3,则根据大O阶方法时间复杂度为O(1)
原则二:在修改后的运行次数函数中,只保留最高阶项--------------举例:f(n)=3n+1
原则三:如果最高阶项存在且不是1,则去除与这个相乘的常数。-----举例:---------则根据大O阶方法时间复杂度为O(n)0
3.常见时间复杂度:根据程序语句执行的次数,可以求得算法的时间复杂度
(1)常数阶
源码:int sum=0,n=100; /*执行一次*/
sum=(1+n)*n/2; /*执行一次*/
printf("%d",sum); /*执行一次*/
程序执行次数为3次,根据原则一可知,时间复杂度为O(1)。
(2)线性阶
源码:
int i;
for(i=0;i<n;<i++)
{
..../*时间复杂度为O(1)的程序步骤序列*/
}
程序的执行次数为(n+a),其中a为常数。根据原则二、三可知,算法的时间复杂度为O(n)。要确定某个算法的阶次,我们常常需要确定某个特定语句或某个语句集运行的次数。因此,我们要分析算法的复杂度,关键就是要分析循环结构的运行情况。
(3)对数阶
源码:int count=1;
while(count<n)
{
count=count*2;
..../*时间复杂度为O(1)的程序步骤序列*/
}
由于每次count乘以2之后,就距离n更近一份,也就是说有多少个2相乘就后大于n,则会退出循环。
由2^x<n得x<log2^n,程序的执行次数为log2^n=log(2*2*2*2*...*n)。根据原则二、三得算法的时间复杂度为O(logn).
(4)平方阶
源码1:
int i、j;
for(i=0;i<m;i++)
{
for(j=0;j<n;j++);
{
..../*时间复杂度为O(1)的程序步骤序列*/
}
}
程序语句执行的次数为f(n)=m*n+1+a,其中a为常数。根据大O阶方法可知,算法的时间复杂度为O(m*n).
源码2:
int i,j;
for(i=0;i<n;i++)
{
for(j=i;j<n;j++)
{
/*时间复杂度为O(1)的程序步骤序列*/
}
}
分析当i=0时,内循环执行n次;当i=1时,执行了n-1次;....当i=n-1时,执行了1次。所以,程序语句执行的次数为f(n)=n+(n-1)+(n-2)+...+1=n(n+1)/2=n^2/2+n/2+a,其中a为常数。根据大O阶方法可知,算法的时间复杂度为O(n^2)。
4.最坏情况和平均情况
(1)最坏情况:最坏情况运行时间是一种保证,那就是运行时间将不会再坏了。在应用中,这是一种最重要的需求。通常,除非特别指定,我们提到的运行时间都是最坏情况的运行时间。
(2)平均情况:平均运行时间是所有情况中最有意义的,因为他是期望的运行时间。
四、算法空间复杂度
算法的控件复杂度通过计算算法所需的存储空间实现,算法控件复杂度的计算公式记作:S(n)=O(f(n)),其中,n为问题的规模,f(n)为语句关于n所占存储控件的函数。"时间复杂度"指运行时间的需求,"空间复杂度"指空间需求。总之,算法的优劣直接决定了程序运行的效率。