1.1 数据结构与算法概述
1.1.1 什么是数据结构?
数据结构是一门研究非数值计算的程序设计问题中的操作对象,以及他们之间的关系和操作等相关问题的学科。
大白话:数据结构 就是把数据元素按照一定的关系组织起来的集合,用来组织和存储数据。
1.1.2 数据结构分类
传统上,我们可以把数据结构分为 “逻辑结构” 和 “物理结构” 两大类。
逻辑结构分类:
逻辑结构是从具体问题中抽象出来的模型,是抽象意义上的结构,按照对象中数据元素之间的相互关系分类,也是我们后面课题中需要关注和讨论的问题。
-
集合结构:集合结构中数据元素除了属于同一个集合外,他们之间没有任何其它的关系。
-
线性结构:线性结构中的数据元素之间存在一对一的关系。
-
树形结构:树形结构中的数据元素之间存在一对多的层次关系。
它们的本质区别在于,不同的结构 是按照 存储数据之间的关系是不同的,然后来分类的。
4.图形结构:图形结构的数据元素是多对多的关系。(算是 这些结构里 难理解的结构。)
物理结构分类:
逻辑结构在计算机中真正的表示方式(又称为映像)称为 “物理结构”,也可以叫做 存储结构。常见的物理结构有 “顺序存储结构” 和 “链式存储结构”。
- 顺序存储结构:
把数据元素放到地址连续的存储单元里面,其 数据间的逻辑关系和物理关系是一致的。比如我们常用的 “数组” 就是顺序存储结构。
顺序存储结构存在一定的弊端,就像生活中排队时,会有人插队,甚至可能有人突然离开,也就是我们所谓的 “插入数据” 和 “删除数据”,这个时候 整个结构都处于变化当中!那么此时 就需要 链式存储结构。
优点:这种结构,一般都是 具有索引的,所以我们可以直接通过索引 对 元素 进行相关的操作。
插入数据举例:比如在 3 后面插入 一个 18,那么你就需要 把 4 5 6 7 8 9 都往后移动一位,然后 再把 18 这个值 赋予在 以前 4 的 下标位置处!(这是很 低效率的)
- 链式存储结构:
是把数据元素存放在任意的存储单元里面,这组存储单元可以是连续的也可以是不连续的。此时,数据元素之间并不能反映元素之间的逻辑关系。因此在链式存储结构中 引进了一个叫做指针的东西 用来存放数据元素的地址!,这样子,通过指针存储的下一个元素的地址,就可以实现一对一的数据关系了。
链式存储结构存在一定的弊端,查找某个元素的时候,可能需要遍历很多元素,这样效率会极低。并且这种链式存储结构,开发者在编写的时候,很容易出现内存泄漏等一些问题。
*优点:这种结构,因为是随便开辟空间,存储数据的。并且是由 指针建立关系的。所以插入数据的话,很简单。
插入数据:比如在 3 和 4 之间插入一个数据。那就直接开辟一个空间,先存储这个数据。然后 把 3 的指针 存储 这个新开辟空间的内存地址,这个新开辟的空间 指针 存储 4 的那个内存地址。相关的操作,同理!(这样的话,它的效率是非常非常高的!)
1.1.3 什么是算法?
算法是指解决问题提出方案的准确而完整的描述,是解决问题的一系列清晰指令,算法代表着用系统的方法解决问题的策略机制。也就是说,能够对一定规范的输入,在有限时间内获得所要求的输出。
大白话:根据一定的条件,对一些数据进行计算和处理,并且得到需要的结果。(并非是 效率高的,才叫做算法!)
1.1.4 算法初体验
在生活中,我们如果遇到某个问题,常常解决方案不是唯一的。
例如①:从西安到北京,如何去?会有不同的解决方案,我们可以坐飞机,可以坐火车,可以坐汽车,甚至可以步行。但不同的解决方案给我们带来的时间和金钱成本是不一样的!比如说,坐飞机用的时间最少,但费用最贵。步行费用最低,但时间却很长。
例如②:在北京二环买一套四合院,如何付款?也会有不同的解决方案。可以一次性现金付清,也可以通过银行坐按揭,这两种解决方案带来的成本是不一样的。一次性付清,虽然当时付出的钱财很多,压力很大,但没有 利息。按揭虽然付出的钱财少,压力小,但会有利息跟着你。(而且 30年的总利息 几乎是贷款额度的一倍,这意味着 你要付出比原来价格更多的钱!!)
在程序中,我们也可以用不同的算法解决相同的问题,而不同的算法的成本是不同的。所需时间也是不同的。从总体上来看,一个优秀的算法,需要追求以下两个目标:
- 花最少的时间完成需求
- 占用最少的内存空间完成需求
下面我们用一些实际案例体验一些 算法。
1.1.5 需求①
计算 1 到 100 的和
第一种解法:暴力循环
public class 求和{
public static void main(String[] args) {
int sum = 0;
int n = 100;
for(int i = 1;i <= n;++i)
{
sum += i;
}
System.out.println("sum=" + sum);
}
}
第二种解法:高斯公式
public class 求和 {
public static void main(String[] args) {
/*int sum = 0;
int n = 100;
for(int i = 1;i <= n;++i)
{
sum += i;
}
System.out.println("sum=" + sum);*/
int sum = 0;
int n = 100;
sum = (n+1)*n/2;
System.out.println("sum="+sum);
}
}
第一种解法要完成需求,要完成以下几个动作:
- 定义两个整型变量;
- 执行 100 次加法运算;
- 打印结果到控制台;
第二种解法要完成需求,要完成以下几个动作:
- 高斯公式
1.1.6 需求②
计算 10 的阶乘
第一种解法:递归求解
public class 阶乘 {
public static void main(String[] args) {
System.out.println(f(10));
}
public static int f(int n)
{
if(n == 1)
return 1;
return n * f(n-1);
}
}
第二种解法:循环迭代
public class 阶乘 {
public static void main(String[] args) {
//System.out.println(f(10));
int ret = 1;
for(int i = 1;i<=10;++i)
{
ret *= i;
}
System.out.println(ret);
}
public static int f(int n)
{
if(n == 1)
return 1;
return n * f(n-1);
}
}
第一种解法要完成需求,光是 递归为方法压栈开辟的空间,就有 10个!
第二种解法要完成需求,开辟的空间 与 第一种解法对比,仅占 1/10 !
我们将来 研究算法,肯定要采取 第一种解法的类型。既要 少时间,又要 少空间。