1.数据结构与算法扫盲

1.数据结构与算法基本概念

1.1 数据结构基本知识

首先什么是数据结构?结构其实就是关系,例如公司结构,就是公司里部门、层级的划分,最终就是人与人之间的关系。分子结构就是分子的原子之间的排列方式,我们在中学化学里学过很多奇妙的排列组合方式,这就是组成不同物质的分子结构。数据结构就是数据元素之间关系的集合。在计算机中,数据元素不是孤立的,杂乱的,而是有一定关联关系的。他们之间可能有一种也可能有多种关系。

(1)几个基本概念

数据是对客观事物的符号表示。在计算机科学中是指所有能输入到计算机中并被计算机程序处理的符号的总称。

数据元素是数据的基本单位,在计算机程序中通常作为一个整体进行考虑和处理。 数据对象是性质相同的数据元素的集合,是数据的一个子集。

数据结构是相互之间存在一种或多种特定关系的数据元素的集合。

存储结构是数据结构在计算机中的表示。

数据类型是一个值的集合和定义在这个值集上的一组操作的总称。

(2)抽象数据类型

抽象数据类型是指一个数学模型以及定义在该模型上的一组操作。是对一般数据类型的扩展。

抽象数据类型包含一般数据类型的概念,但含义比一般数据类型更广、更抽象。 一般数据类型由具体语言系统内部 定义,直接提供给编程者定义用户数据,因此称它们为预定义数据类型。抽象数据类型通常由编程者定义,包括定义它所使用的数据和在这些数据上所进行的操作。在定义抽象数据类型中的数据部分和操作部分时,要求只定义到 数据的逻辑结构和操作说明,不考虑数据的存储结构和操作的具体实现,这样抽象层次更高,更能为其他用户提供良好的使用接口。

看一个例子

ADT Complex{
	数据对象:D={r,i|r,i为实数}
	数据关系:R={<r,i>}
	基本操作:
		InitComplex(&C,re,im)
			操作结果:构造一个复数c, 其实部和虚部分别为re和im 
		DestroyCmoplex(&C)
			操作结果:销毁复数C 
		Get(C,k,&e)
			操作结果:用e返回复数C的第k元的值 
		Put(&C,k,e)
			操作结果:改变复数C的第k元的值为e 
		IsAscending(C)
			操作结果:如果复数C的两个元素按升序排列,则返回1,否则返回0 
		IsDescending(C)
			操作结果:如果复数C的两个元素按降序排列,则返回1,否则返回0
		Max(C,&e)
			操作结果:用e返回复数C的两个元素中值较大的一个 
		Min(C,&e)
			操作结果:用e返回复数C的两个元素中值较小的一个
} 

(3)数据关系的表示

数据之间的关系如何表示呢?一般有两种基本的方式来表示:二元组、图形表示。 数据结构的二元组表示为:

数据结构={D,S}

其中D是数据元素的集合,S是D中数据之间的关系集合,并且数据元素只看见的关系是使用序偶来表示的。序偶是由两个元素x和y按一定顺序排列而成的二元组,记作<x,y>,x 是第一个元素,y 是它的第二元素。

当使用图形来表示数据结构时,就是用图形中的电来表示数据元素,用图形之间的连线来表示数据元素之间的关系 。

设有数据结构(D,R), 其中D={d1,d2,d3,d4},R={r},r={(d1,d2),(d2,d3),(d3,d4),(d2,d4),(d1,d3)}

如果用图形表示就是:
在这里插入图片描述

(4)逻辑结构与物理结构

按照视角的不同,我们可以把数据结构分为逻辑结构和物理结构。

什么是物理结构呢?主要就是数据是如何在计算机的存储器里存储的,因此很多书中直接就叫存储结构。存储有两种基本的方式,一种是顺序存储,一种是链式存储。顺序存储就像火车一样,一个一个相互连在一起。而链式存储是分开在很多位置存储,这些位置可能连续,也可能不连续。为了保持连续就需要每个元素保存下一个元素元素的地址,这样从头到尾就能连成一条线。这个一个非常典型的例子就是特工。例如在《无间道》《潜伏》《风筝》
等谍战影视剧中,为了安全,潜伏的人都和自己的上级单线联系,而且不允许相互之间碰面。正常情况下,上级的命令可以下达给自己的所有下属。但一旦中间人断了就会出现大问题。例如梁朝伟扮演的警察在自己的上级牺牲后就无法证明自己是警察,在《风筝》中郑耀先也是如此。而在计算机中,如果断了,那么后面的部分将无法被访问,最终变成垃圾被GC掉。

存储结果主要是计算机的操作系统来维护的,我们更关心的是逻辑结构,也就是我们的代码创建的对象中数据元素的相互关系。逻辑结构主要是四种:集合、线性结构、树形结构和图形结构。

(5)四种常见的逻辑结构

1.集合结构

集合结构中的元素除了属于同一个集合外,它们之间没有任何关系。各个元素是平等的,这与数学中的集合是一致的。现实中最典型的例子就是老乡,你到了一个新公司之后,如果同事和你都是山东人,你们就会感觉笔记亲切。 如果都是山东临沂人,就会感觉更亲切。如果都是临沂市莒南县,甚至是同一个镇子,就感觉像亲人一样,但是你们之前可能并不认识。
在这里插入图片描述

2.线性结构

线性结构中的数据元素是一对一的关系,相互之间只与上一个和下一个连在一起。最典型的就是火车了。数组和链表都属于线性结构,有些地方则称之为线性表,都是一个意思。

线性表经过一些特殊的约束可以形成多种结构,例如只允许一头插入删除就是栈。只允许一头插入一头输出就是队列,使用不同的限制条件,队列也可有多种形式。

3.树形结构

树形结构中的元素存在单向一对多的层次关系。典型的例子有很多,例如族谱、公司部门架构、全国行政区划等等。
在这里插入图片描述

4.图形结构

图形结构的数据元素是多对多的关系,典型的例子就是全国交通网络图等等。
在这里插入图片描述

图形结构的构造和处理都比较复杂,因此代码量会非常大,但是基本的深度优先和广度优先遍历等问题,使用树来考察更方便,因此在面试环节极少出现,所以我们课程也不涉及相关内容。

1.2 算法基本知识

(1)数据结构与算法的关系

大学计算机课里都有《数据结构》课程,也有《算法》课程,但是经常看到很多书叫《数据结构与算法》,那两者到底啥关系呢?我们经常会感觉算法很难很难,但是数据结构却没啥意思。如果抛开算法谈数据结构确实非常简单,无非就是几种逻辑结构的5大操作:创建和增删改查而已。而算法是解决问题的思维过程,计算机的前辈们总结了大量的经典方法让我们可以借助数据结构来解决大量的实际问题,例如回溯、贪心、动态规划、分支限界、滑动窗口等,这些方法使得或者混沌、或者很难的问题变得有章法,甚至变得美妙和神奇。但是这些方法的载体是什么呢?自然是数据结构,所以面试的时候很多题目我们有思路也不出来。

(2)算法的定义和特性

目前比较认可的算法定义是:算法是解决特定问题求解步骤的描述,在计算机中表现为指令的有限序列,并且每条指令表示一个或者多个操作。

这里我们可以看到算法是为了解决一个或者一类具体问题的,没有万能算法。例如回溯是为了解决暴力搜索都不好解决的问题,贪心是为了解决通过最优子问题求解就能得到全局最优的问题等等。

另外一点就是算法是很具体的,为了解决某个或者某类问题,需要一系列完整的步骤,每个步骤对应一定的操作序列,每个操作都完成特定的功能,这就是算法了。

还有一点就是一个问题可能有多种算法能解决。例如求1+2+…+100,可以采用循环一个个算,也可以直接套公式 (100+1)*100/2。不同的方法在耗费的计算机资源、执行时间等方面存在差异,因此在后面我们会看到某些问题会有七八种解法。充分思考一个题目有哪些解决方法,优劣是什么,能让我们学会融会贯通,刷一道顶别人刷10道。

这些特性在算法中有更严谨的表述:输入、输出、有穷、确定性和可执行性五个基本特征。输入输出好理解,算法必须要有0个或者多个输入,必须要有一个或者多个输出。算法一定是要有输出的,不然你算了干嘛。

有穷性就是算法在执行有限的步骤之后,自动结束而不会出现无限循环,并且每个步骤在可接收的时间内完成。

确定性是算法的每一步都具有明确的含义,不会出现二义性。

可行性就是算法的每一步都必须是可行的,也就是能通过执行有限次数来完成。

2.算法的度量

2.1 算法如何度量

看新闻,我们经常听到GDP, 国内生产总值,这个是衡量经济发展状况的基本指标。在公司里,经常听到KPI考评等指标。所以很多事物都需要一个相对客观的衡量标准来评价好坏或者差异。对于算法,最重要就是时间复杂度和空间复杂度。

我们再看上面求累加和的算法,先看依次累加的执行过程:

private static void addOneByOne(){
	// 执行1次
	int sum = 0,n = 100;   
	//执行了n+1 次
	for(int i=0;i<=n;i++) {
		//执行n次
		sum = sum + i;             
	}
	//执行1次
	System.out.println("result is :"+  sum); 
}

再看直接使用公式的执行过程:

private static void addByFormula() {
	//执行1次
	int sum = 0,n = 100;    
	//执行1次  
	sum = (1+n)*n/2; 
	//执行1次
	System.out.println("result is :"+sum);
}

显然第一种执行了1+(n+1)+n+1=2n+3 次,而第二种只执行了3次。好坏立见。

假如上面改成一个100X100 的二维数组,数组的每一行都是1到100,那么这时候该如何做呢?很明显仍然有累加和乘法两种方式。累加法:

private static void addOneByOne(int arr[][], int n) {
	//执行1次
	int sum = 0; 
	//执行n+1次
	for(int i=0;i<n;i++){  
		//执行n+1次
		for(int j=0;j<n;j++) 
			//执行n次
			sum = sum+arr[i][j];              
	}
	//执行1次
	System.out.println("result is:"+sum);
}

而公式法则容易很多:

private static void addByFormula(int arr[][], int n) {
	//执行1次
	int sum = 0;  
	//执行1次
	sum = (1+n)*n*n/2;
	//执行1次
	System.out.println("result is :"+sum);
}

可以看到累加的执行次数为1+(n+1)+(n+1)(n+1)+(n+1)n+1=2n^2+4n+3。

而公式法仍然是3次,这个差异就更明显了。所以执行的步骤数是考察算法好坏的一个重要标准。虽然时间复杂度越小越好,但是这一定代表越快的,我们知道累加是非常快的,而乘除耗费的资源比较大的,所以n小的时候不见得一定是公式法更快。

虽然上面我们找到了非常准确的描述计算次数的方法,但是其实我们更关心当元素数量非常大时的变化情况。

例如y=x,当x非常大时,1就可以忽略不计了。

而y=x^2,当x非常大时,后面的+x作用也不大了

同理y=3x^2 和 y=4x^2,此时前面的系数3和4的影响也不大了。

此时我们可以只考虑不同的阶之间的变化情况,如下可以看到过了1之后,不同的表达式的差异将非常巨大。

所以上面我们可以将表达式分别简化为y=1,y=x 和y=x^2,也就是1阶,线性阶和平方阶。要严格证明其关系需要考虑到渐近函数等问题,在很多算法书中有详细的解释,我们这里不赘述,只认为是简化就好了,这种表示就是 O()记法。例如:

执行次数函数非正式术语
12O(1)常数阶
2n+3O(n)线性阶
3n²+2n+1O(n)平方阶
5log₂n+20O(logn)对数阶
2n+3nlog₂n+19O(nlogn)nlogn阶
6n³+2n²+3n+4O(n³)v立方阶
2"O(2”)指数阶

我们需要记住的是常见的阶耗费时间的关系是:

O(1)<O(logn)<O(n)<O(nlogn)<O(n²)<0(n³)<0(2”)<0(n!)<O(n")

后面我们在表述算法的好坏一般都是直接说是上面的O(*),而不会具体计算到底是多少。

2.2 几种常见的阶

虽然我们不需要证明上面的结论,但是在写算法的时候要能够判断当前代码的阶是多少,特别是O(logn),所以我们本节来看一下如何计算阶。

(1)常数阶

一般顺序执行并且只执行一次的代码就是常数阶。例如上面的sum=(1+n)*n/2。如果是这样的代码:

sum=sum+1; 
sum=sum+2; 
sum=sum+3;
sum=sum+4;

sum=sum+1; 执行了4遍,那还是固定的执行4次,都简化为O(1)来表示

(2)线性阶

线性阶一般是带循环的,例如下面的格式:

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

}

还有while循环:

while(i<n) {
	i++;
}

有一种比较特殊的场景,递归其判断要复杂一些,我们在递归章节再说。

(3)对数阶

对数阶是很多人想不明白的地方,特别是二分查找,二叉树等问题经常会看到0(logn)。其本质其实都可以简化成 如下模型:

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

而二分查找不过是反者来的从n开始,每循环一次都是减半.

(4)平方阶

平方阶主要是双层循环嵌套。

for(i=0;i<n;i++){ 
	for(j=0;j<m;j++){
		//todo  时间复杂度为1的程序
	}
}

其实常用的就这四个。在排序、组合等题目中可能会有一些复杂的情况,我们到时再看。

2.3 空间复杂度

上面谈论的其实都是时间复杂度,还有一个是空间复杂度。其实非常简答,主要是需要多少额外的空间来做现在的事情,例如你需要申请一个或者两三个变量,那么空间复杂度就是O(1),如果需要申请一个数组,链表、队列、栈 或者Hash,其空间复杂度都是O(n),如果需要申请一个二维数组就是O(n^2)。其他的极少用,因为很多时候你申请 O(n^2 就已经到面试官的极限了,很多都要求O(1)。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值