Java数据结构与算法——基本概念

一、数据结构

1、基本概念

(1)数据——描述客观事物的符号,是计算机可以操作的对象,是能被计算机识别并输入给计算机处理的符号集合 。

(2)数据元素——组成数据的、有一定意义的基本单位,在计算机中通常被当作整体处理。

(3)数据项——一个数据元素可以由若干个数据项组成。比如,人可以有鼻子、眼睛、耳朵等数据项,也可以有姓名、年龄、性别等数据项。

(4)数据结构——相互之间存在一种或多种特定关系的数据元素的集合。分为逻辑结构与物理结构。

2、逻辑结构与物理结构

(1)逻辑结构——数据对象中数据元素之间的相互关系

  • 集合结构——集合结构中的数据元素除了同属一个集合外,他们之间没有其他关系。
  • 线性结构——线性结构中的数据元素之间是一对一的关系。
  • 树形结构——树形结构中的数据元素之间存在一对多的层次关系。
  • 图形结构——图形结构中的数据元素之间是多对多的关系。

(2)物理结构——指数据的逻辑结构在计算机中的存储形式(顺序存储和链式存储)

  • 顺序存储结构——把数据元素存放在地址连续的存储单元里,其数据间的逻辑关系和物理关系是一致的。
  • 链式存储结构——把数据元素存放在任意的存储单元里,这组存储单元可以是连续的,也可以是不连续的。数据元素的存储关系并不能反映其逻辑关系,因此需要用一个指针存放数据元素的地址,通过地址就可以找到相关联的数据元素的位置。
3、抽象数据类型

(1)数据类型——指一组性质相同的值的集合及定义在此集合上的一些操作的总称。数据类型是按照值的不同进行划分的。每个变量、常量和表达式都有各自的取值范围,类型就用来说明变量和表达式的取值范围和所能进行的操作。

(2)抽象数据类型——指一个数学模型及定义在该模型上的一组操作。如整型,在不同的计算机中的实现方法都是不一样的,但由于其定义的数学特性相同,在编程者的角度来看,他们都是一样的。因此,“抽象”的意义在于数据类型的数学抽象特性。

二、算法

1、定义

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

2、算法的特性
  • 输入输出——算法具有零个或多个输入;至少有一个或多个输出;
  • 有穷性——指算法在执行有限的步骤之后,自动结束而不是出现无限循环,并且每个步骤尽可能在可接受的时间内完成;
  • 确定性——算法的每一步都有确定的含义;
  • 可行性——算法的每一步都必须可行,即每一步都能通过执行有限次数完成。
3、算法的设计要求
  • 正确性——正确反映问题的需求,能够得到正确的答案;
  • 可读性——便于阅读和理解;
  • 健壮性——当输入数据不合法时,算法也能做出相关处理。
  • 时间效率高和存储量低
4、函数的渐近增长

输入规模 n 在没有限制的情况下,只要超过一个数值 N,这个函数就总大于另一个函数,我们称这个函数是渐近增长的。如:给定两个函数 f(n) 和 g(n),如果存在一个整数 N,使得对于所有的 n > N,f(n)总比 g(n)大,那么我们就说f(n)的渐近增长快于 g(n)。

(1)假设两个算法 A 和 B,A 要做 2n + 3 次操作,B 要做 3n + 1 次操作。当 n 很大时,后面的 +3 和 +1 不影响最终的算法变化,即,可以忽略这些加法常数

(2)再比如两个算法 A 和 B,A 要做 4n + 8 次操作,B 要做 2n^2 + 1 次操作。随着 n 的增长,算法 A 的次数总是远小于算法 B 的次数。即,与最高次项相乘的常数并不重要

结论:

判断一个算法的效率时,函数中的常数和其他次要项常常可以忽略,而更应该关注主项(最高阶项)的阶数。

5、算法的时间复杂度
(1)定义

在进性算法分析时,语句的总执行次数 T(n)是关于问题规模 n 的函数,进而分析 T(n)随 n 的变化情况并确定 T(n)的数量级。

算法的时间复杂度记作 T(n)= O(f(n)),它表示随问题规模 n 的增大,算法执行的增长率和 f(n)的增长率相同,称作算法的渐近时间复杂度。其中 f(n)是问题规模 n 的某个函数。该方法称为大 O 记法

(2)推导大 O 阶的方法
  • 用常数 1 取代运行时间中的所有加法常数;
  • 在修改后的运行次数函数中,只保留最高阶项;
  • 如果最高阶项存在且不是 1,则去除与这个项相乘的常数。

将一个算法的运行次数 f(n)经过以上三步处理后,得到的结果就是该算法的时间复杂度。

(3)常数阶

顺序结构的时间复杂度:

考虑下面这段代码,时间复杂度为 O(1),而不是 O(3)。这段代码的运行次数函数为 f(n)= 3,由推导的大 O 阶方法,把常数项 3 改为 1,所以这段代码的时间复杂度为 O(1)。

int sum = 0, n = 100;
sum = (1 + n) * n / 2;
System.out.println(sum);

再考虑下面这段代码:

int sum = 0, n = 100;
sum = (1 + n) * n / 2;
sum = (1 + n) * n / 2;
sum = (1 + n) * n / 2;
sum = (1 + n) * n / 2;
sum = (1 + n) * n / 2;
sum = (1 + n) * n / 2;
sum = (1 + n) * n / 2;
sum = (1 + n) * n / 2;
sum = (1 + n) * n / 2;
sum = (1 + n) * n / 2;
System.out.println(sum);

无论 n 为多少,上面两段代码就是执行 3 次和 12 次的差异。这与问题的大小无关(n 的大小),执行时间恒定的算法,称之为具有 O(1)的时间复杂度,又叫常数阶

注意:不管这个常数是多少,我们都记作 O(1),而不是 O(3)、O(12)等其他任何数字。

分支结构的时间复杂度:

对于分支结构而言,无论是真是假,执行的次数都是恒定的,不会随着 n 的变大而发生变化,所以单纯的分支结构(不包含在循环结构中),其时间复杂度也是 O(1)。

(4)线性阶
for(i = 0; i < n; i++){
	System.out.println("test");
}

上面这段代码的时间复杂度为 O(n)。

(5)平方阶
for(int i = 0; i < n; i++){
	for(int j = 0; j < n; j++){
		System.out.println("test");
	}
}

上面这个循环嵌套的时间复杂度为 O(n^2)。若把外层循环的循环次数改成 m,则时间复杂度变为 O(m*n)。

对比下面这段代码(将 j = 0 改为 j = i):

for(int i = 0; i < n; i++){
	 for(int j = i; j < n; j++){
	  	System.out.println("test");
	 }
}

这个算法的运行次数函数为 f(n)= 1 + 2 + … … + n = (n + n^2) / 2,对 f(n)进行处理后,得到这段代码的时间复杂度仍为 O(n^2)。

(6)对数阶

下面这段代码的时间复杂度就为对数级别的,O(logn)。

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

分析:

着重看执行次数最多的内层代码语句。每循环一次,sum 就给自身乘以 2,乘了多少次就跳出循环了呢(大于等于n)?假设乘了 x 次,则有 2^x = n,解出 x = logn。这说明随着 n 的增大,最消耗时间的内层语句是呈对数变化的。

(7)常见的时间复杂度

在这里插入图片描述

6、空间复杂度

算法的空间复杂度通过计算算法所需的存储空间实现。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值