一篇入门算法与数据结构

算法与数据结构开篇

你真的会数据结构吗?

公司开发一个客服电话系统,小菜须要完成客户排队模块的开发,通过三次修改:c++

第一次:小菜使用了数据库设计了一张客户排队表,而且设置了一个自动增加的整型id字段,来一个用户,就在这张表的末尾插入一条数据,等客服系统一空闲,就将表中最前的的客户提交,而后删除这条记录。算法

  • 实时排队模块,在内存中实现便可,无序用数据库

第二次:小菜用数组变量从新实现了这个功能,惧怕数组不够大,选择远大于实际状况的100做为数组长度数据库

  • 数组虽然能够知足必定需求,可是须要考虑溢出问题,以及新增和删除后的数据移动,显然不是很方便

第三次:小菜使用了数据结构中的 “队列结构” 终于知足了需求编程

说明:此例子概括参考自《大话数据结构》数组

为何你的程序比别人的慢?(算法问题)

来看一个问题:bash

公元前五世纪,我国古代数学家张丘建在《算经》一书中提出了“百鸡问题”:鸡翁一值钱五,鸡母一值钱三,鸡雏三值钱一。百钱买百鸡,问鸡翁、鸡母、鸡雏各几何?请设计一个“高效”的算法求解。微信

也就是说:数据结构

买一只公鸡要五文,买一只母鸡要三文,而一文能够买三只小鸡,共有100文,问公鸡母鸡小鸡各能买几只?数据结构和算法

话很少说,咱们先给出一种最容易想到的方式,也就是列两个三元方程组数据库设计

也就是知足鸡的总数为100,同时花钱数也为100,咱们来看一下代码实现

方式一:

//i j k 分别表明公鸡 母鸡 雏鸡的数量 
//20、3四、300 为100元最多能买公鸡、母鸡、小鸡的数量
for (int i = 0; i < 20; i++) {
	for (int j = 0; j < 34; j++) {
		for (int k = 0; k < 300; k = k + 3) { //k = k + 3 是为了知足小鸡实际存在的要求
			if (5*i + 3*j +  k/3 == 100 && i + j + k == 100) {
				cout << "公鸡 母鸡 雏鸡 数量分别为:" << i <<", "<< j <<", "<< k << endl;
			}
		}
	}
}
复制代码

咱们用了三层循环,有没有办法能简化如下代码呢?能不能减小循环层数呢?这显然是可行的,上面小鸡的数量这一层明显能够省略的,由于总数是已知的

方式二

for (int i = 0; i < 20; i++) {
	for (int j = 0; j < 34; j++) {
			
		k_temp = 100 - i - j;
			
		if (k_temp % 3 == 0)
			k = k_temp;
		
		if (5*i + 3*j +  k/3 == 100 && i + j + k == 100) {
			cout << "公鸡 母鸡 雏鸡 数量分别为:" << i <<", "<< j <<", "<< k << endl;
		}
	}
}
复制代码

确实程序更加优化了一些,可是这样是否是就能够了呢?其实还能够优化为一层循环!

方式三

int i, j, k, t;
for (int t = 0; t <= 25/7; t++) {
	i = 4 * t;
	j = 25 - 7 * t;
	k = 75 + 3 * t;
	cout << "公鸡 母鸡 雏鸡 数量分别为:" << i <<", "<< j <<", "<< k << endl; 
} 
复制代码

上例中对程序的优化涉及到了数学的运算

//根据花钱的总数为100列式
5x + 3y + (100 - x - y)/3 = 100

//对上式进行化简
y = 25 -(7/4)x
  = 25 - 2x + x/4
    
//设 x/4 = t,原式子可变为
y = 25 - 7t
    
//能够获得三个不等式
x = 4t >= 0
y = 25 - 7t >= 0
z = 75 + 3t >= 0

//解得
t >= 0
t <= 25/7
复制代码

为了增长说服力,咱们测试每个程序的运算时间,不太熟悉的朋友我下面已经贴出了代码

#include<ctime>
clock_t startTime,endTime;
startTime = clock();

......测试代码

endTime = clock();
cout << "The run time is: " << (double) (endTime - startTime) / CLOCKS_PER_SEC << "s" << endl; 
复制代码

咱们将数据放大一些,方便体现差别,咱们将总金额和总数量均改成1000 下面是三种方式的运算时间

The run time is: 0.114s
The run time is: 0.03s
The run time is: 0.026s
复制代码

很显然程序逐步的到了优化,减小了for循环 大大的减小了循环次数,因此节省了时间

##为何要一块儿学习数据结构和算法?

想要回答这个问题,咱们先开看一下数据结构和算法的概念

数据结构概念

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

也就是说,计算机在对数据进行存储的时候并非杂乱无序的而是拥有必定规则的,一个或多个数据元素之间拥有必定的相互关系,因此也能够说数据结构是计算机存储和组织数据的方式

算法的概念

算法是一种能够被计算机使用来解决问题的的方法,这种方法也正是解决问题的具体步骤

对于小型的程序而言,即便这个算法比较差劲,解决问题的步骤比较累赘繁琐,也不会有很大的关系,只要能解决问题的就是好程序,可是若是对于数据量较大的中大型程序,咱们就须要对方法的时间和空间进行有效的利用,也就是设计一个高效的算法,在一样硬件设备的状况下,有时候甚至能够将速度提升十倍甚至百倍

程序 = 数据结构 + 算法

数据结构是对计算机所存储的数据之间的一种组织管理,算法是对数据进行操做从而将你解决问题的方法在计算机中具体实现,也就是说,在计算机中而言,其实这二者单独拿出来讨论是没有什么实际意义的,就像鱼离不开水同样,即便一个优秀的算法,若是数据之间没有任何结构关系可言,算法也就无从实现,也就没有意义了,而即便数据之间有了组织关系,可是不对其进行操做这显然也没有意义。

简单总结:只有数据结构的程序是没有灵魂的,只有算法的程序却只有魂魄,没有躯体

不能否认,数据结构是很是重要的,但我更加倾向于算法为核心,正如 Algorithms(4th.Edition) 中的观点,数据结构是算法的副产品或者结果,在我看来,如何创建一个有效且高效的算法以及模型是更重要一些的,开发的最终目的就是经过程序利用科技设备实现咱们实际生活中的一些需求,咱们遇到问题的第一步都是在现实中对问题进行分析,而后设计出合适的方法,而后就要想办法将这种方法表达给计算机设备,可是这个时候就须要数据结构这样一种知足须要的产物,来支持咱们对咱们的设备具体表达咱们的方法,尤为随着数据量的检验,不一样的算法就会对解决实际问题的效率产生更大的影响,我用一个不是很恰当却很形象的例子表达就是:你的身体(数据结构)死了,你可能还活着,但你的灵魂(算法)死了,你即便活着也已经死了

固然数据结构的重要性也是无可置疑的,一个好的数据结构,能够帮助咱们的程序更加高效,若是何时,模型以及算法的选用已经能够根据数据的特色以及需求选用,咱们就能够将更多的精力投入到数据结构的设计中去,不过这也仅仅是一种美好的假想,因此咱们仍是要学好算法,可是学好算法,咱们也就要对支持咱们算法的数据结构进行必定的研究

说了一些我的的观点,那让咱们赶忙介绍一些入门的必备知识

数据结构的基本概念和术语

  • 数据:描述客观事物的数字和符号,是可以被计算机识别且进行处理的的符号集合

    • 整型和实数型数据是能够直接进行数值计算的

    • 文字、图像、图形、声音、视频等多媒体信息则能够经过合适的编码保存处理

      例如:文本经过字符编码处理、声音经过储存波形 (WAV) 或在波形分解后存储其振幅特性而后压缩 (MP3)

  • 数据元素:组成数据且有意义的的基本单位 (个体)

    • 例如:猫和狗都是动物群体中的一个数据元素
  • 数据项:组成数据元素具备特定意义的最小不可分割单位

  • 做为人这个类群中具体的个体,一我的而言,其所拥有的手、脚、眼、鼻,亦或者姓名、年龄、身高、等都属于数据项

  • 数据对象:性质相同的数据元素的集合,是数据的子集

    • 例如:人类中一些人的生日、身高相同

逻辑结构和物理结构(存储结构)

逻辑结构

逻辑结构:数据对象中数据元素之间的相互关系

集合结构:数据元素之间的惟一关系就是属于同一个集合

线性结构:数据元素之间存在一对一的关系(除首尾元素均存在前驱和后继)

树形结构:数据元素之间存在一对多的关系

图形结构:数据元素之间存在多对多的关系

  • (每个数据元素看作一个节点,元素之间的逻辑关系用连线表示,若是关系有方向则连线带箭头)

物理结构(存储结构)

物理结构:数据的逻辑结构关系在计算机中的存储形式

顺序存储结构:把元素分别放在地址连续的存储单元中的存储方式

  • 也就是说:元素一个一个有序的排好队,各自占据必定的空间,例如定义一个含有6个浮点型数据的数组:而后内存中的一块大小为6个浮点型数据大小空间就会被计算机所开辟,而后数据存入时,依次顺序摆入

链式存储结构:把元素存储在任意的存储单元中的存储方式

  • 由于数据元素位置不肯定,因此须要经过指针指向到元素的存储地址,从而肯定不一样数据元素之间的位置

  • 举例:200人同时在一个阶梯教室上课,同窗们坐的位置是没有关系的,老师点名签到的时候,你只须要关注你学号前一位的同窗有没有被点到,点到后,你就知道下一个该你了

散列 (哈希) 存储方式:是一种力图将数据元素的存储位置与关键码之间创建肯定对应关系的查找技术

  • 你别慌,我这就来解释了,它的原理就是,将一个节点的关键字key做为自变量,经过一个肯定的函数运算f(key),其函数值做为节点的存储地址,将节点存入到指定的位置上,查找的时候,被搜索的关键字会再次经过f(key)函数计算地址,而后读取对应数据

  • 咱们后面会专篇讲解这个内容,如今作一个简单的了解便可

索引存储方式:存储时,除了存储节点,还附加创建了索引表来表示节点的地址

算法的特征

  • 输入:算法具备零个或者多个输入(零个的状况例如打印输出字符串,或者算法自身已经给定了初始条件)

  • 输出:算法具备一个或者多个输出,用来反映算法对输入数据加工后的结果

  • 有穷性:算法必须在执行有限个步骤后终止

    • “有限” 的定义不是绝对的,而是实际应用中合理的可接受的
  • 肯定性: 算法的每一步骤都具备肯定的含义,不会出现二义性

    • 也就是说,惟一的输入只有惟一的输出
  • 可行性:算法的每一步都是可行的,经过有限步骤能够实现

算法的设计要求

  • 正确性:合理的数据输入下,最终能够输出能解决问题需求的正确答案

    • 对正确的理解:
      • 无语法错误
      • 输入合法和非法的数据都可以获得正确答案
      • 输入刁难的数据依旧能够输出知足须要的答案
  • 可读性:算法便于阅读和理解

    • 算法应该井井有条,易读易懂,方便二次调试和修改

    • 复杂一些的算法,变量的命名尽可能恰当一些,用阿里的开发手册中的一句话就是说:“正确的英文拼写和语法可让阅读者易与理解避免歧义”“为了达到代码自解释的目标,任何自定义编程元素在命名时,使用尽可能完整的单词组合来表达其意”

  • 健壮性:当数据不合理的时候,算法也能对各类状况做出处理,而不是报出异常,或者输出错误的答案

  • 高效性:尽可能知足时间效率高,存储率低的需求

函数的渐进增加

次数 算法A :2n + 4 算法B :3n + 3 算法C :2n 算法D :3n
n = 1 6 6 2 3
n = 2 8 9 4 6
n = 3 10 12 6 9
n = 10 24 33 20 30
n = 100 204 303 200 300

n = 1的时候 算法 A 和 B 的效率一致,可是从n = 2的时候开始算法 A 的效率已经大于算法 B 了,n值变大后,算法 A 相比 B 变的更加快速

  • -因此在n值没有限定的状况下,只要在超过某数值N后,函数值就一直大于另外一个函数,那么咱们称函数是渐进增加的

    函数的渐近增加:给定两个函数f(n)和g(n),若是存在一个整数N,使得对于全部的n > N,f(n)老是比g(n)大,那么,咱们说f(n)的渐近增加快于g(n)

  • 接着咱们分别用 AC 、BD进行对照,你会发现其实后面的加数对算法的影响几乎是忽略不计的,因此咱们通常选择忽略这些加法常数

次数 算法A :2n + 6 算法B :3n² + 3 算法C :n 算法D :n²
n = 1 8 6 1 1
n = 2 10 15 2 4
n = 3 12 30 3 9
n = 10 26 303 10 100
n = 100 206 30003 100 10000

算法 A 和 B 比较的时候,n = 1的时候 B效率更高,从n = 2开始 算法 A 就比较高效,随着数据的输入,体现的越明显,可是咱们同时将这两个算法去掉最高次项的系数,接着能够看到对数据的走向仍然影响不大

  • 最高次项的系数对数据走向的影响不大

  • 最高项的指数大的函数,随着n的增加,结果也会快速增加

  • 判断算法效率的时候,应主要关注最高次项,其余次要项或者常数项经常能够忽略

算法的时间复杂度

算法的时间复杂度是一个函数 T(n),它定性描述该算法的运行时间,一般用大O表示法,记做:T(n) = O(f(n)) 例如3n² + 2n + 1 的时间复杂度为 O(n²)

注意:

  • T(n) = O(f(n)) 解释:随着n增大算法执行时间的增加率和f(n)的增加率相同,同时也考察输入值大小趋于无穷的状况,也称做算法的渐进时间复杂度

  • 在这个过程当中,不考虑函数的低阶项、常数项和最高项系数,

常见的时间复杂度

O(1) < O(logn) < O(n) < O(nlogn) < O(n²) < O(n³) < O(2^n) < O(n!) < O(n^n)

实际开发中,咱们常见到的仍是前几个加粗的

咱们还须要提到两个概念:

最坏状况平均状况

最坏状况就是,运行时间的最大值,状况不能再坏了

平均时间就是

通常状况下,咱们都是指最坏时间复杂度,但平均时间是最有意义的时间,由于它与咱们的指望值更接近

做者的话

这些文章,固然谈不上算什么有深度的技术,可是我在尽量的将一些概念经过举例、图表等方式给对这方面知识有须要的朋友,快速的入门,经过一些通俗的说法,先对一些知识有必定的了解,在此基础上,去看一些深刻的权威书籍,或者大牛的博文,就会不至于劝退,自知技术有限,但仍然想给一些刚刚接触这部分知识的朋友们一些帮助,总得一我的,经历一些难捱的日子,你才会变得更增强大!

结尾:

若是文章中有什么不足,或者错误的地方,欢迎你们留言分享想法,感谢朋友们的支持!

若是能帮到你的话,那就来关注我吧!若是您更喜欢微信文章的阅读方式,能够关注个人公众号

在这里的咱们素不相识,却都在为了本身的梦而努力 ❤

一个坚持推送原创Java技术的公众号:理想二旬不止

相关文章
相关标签/搜索