第一章 绪论
1.1 数据结构的基本概念
**数据:**数据是信息的载体,是计算机程序加工的原料。
数据元素、数据项: 数据元素是数据的基本单位,通常作为一个整体进行考虑和处理。
一个数据元素可由若干数据项组成,数据项是构成数据元素的不可分割的最小单位。
数据结构、数据对象:数据结构是相互之间存在一种或多种特定关系的数据元素的集合。
数据对象是具有相同性质的数据元素的集合,是数据的一个子集。
数据结构的三要素:
-
逻辑结构
-
集合
-
线性结构
数据元素之间是一对一的关系
-
树形结构
树形结构之间是一对多的关系
-
图状结构
图状结构之间是多对多的关系
-
-
物理结构(存储结构)
-
顺序存储
把逻辑上相邻的元素存储在物理上也连续的空间
-
链式存储
借助指针来表示数据之间的逻辑关系,物理上可不相邻
-
索引存储
在存储元素信息时,添加一张索引表
-
散列存储
也称哈希存储
-
-
数据的运算
运算的定义针对逻辑结构,运算的实现针对存储结构。
数据类型、抽象数据类型:
数据类型是一个值的集合和定义在此集合上的一组操作的总称。
- 原子类型 其值不可再分的数据类型 (int、char、bool)
- 结构类型 其值可以再分的数据类型(结构体)
抽象数据类型(ADT):用数学化的语言定义逻辑结构、数据运算
1.2.1 算法的基本概念
**算法的特性:**有穷性、确定性、可行性、输入输出
**好算法的特质:**正确性、可读性、健壮性、高效率和低存储量的需求
1.2.2 算法的时间复杂度和空间复杂度
时间复杂度
描述一个算法执行所消耗的时间,是一种屏蔽了不同机器算力的表示方法,常用大O表示法(高等数学中表示同阶无穷小的数学符号)表示,它是算法问题规模n的函数T(n) = O(f(n))。
常见的时间复杂度量级
-
常数阶O(1)
void func(int n){printf("a");}
算法中的语句执行次数和传入的问题规模参数n没有联系
-
线性阶O(n)
void func(int n){ for(int i =0;i < n;i++){ printf("a"); } }
printf()会被执行n次
-
对数阶O(logN)
void func(int n){ for(int i = 0;i < n; i *= 2){ printf("a"); } }
2 x = n ; x = l o g 2 n 2^x = n;\\ x = log_2n 2x=n;x=log2n
printf()会被执行
l o g 2 n log_2n log2n
次 -
线性对数阶O(nlogN)
void func(int n){ for(int j = 0;j < n;j++){ for(int i = 0;i < n; i *= 2){ printf("a"); } } }
将O(logN)的代码执行n次就是O(nlogN)
-
平方阶O(n²)
void func(int n){ for(int j = 0;j < n;j++){ for(int i = 0;i < n; i++){ printf("a"); } } }
-
立方阶O(n³)
void func(int n){ for(int k = 0;k < n;k++){ for(int j = 0;j < n;j++){ for(int i = 0;i < n; i++){ printf("a"); } } } }
-
K次方阶O(n^k)
-
指数阶(2^n)
空间复杂度
一个程序的空间复杂度是指运行完一个程序所需内存的大小。
利用程序的空间复杂度,可以对程序的运行所需的内存有个预先估计。
程序执行时所需存储空间包括以下两部分:
固定部分。这部分空间的大小与输入/输出的数据的个数多少、数值无关。主要包括指令空间(即代码空间)、数据空间(常量、简单变量)等所占的空间。这部分属于静态空间。
可变空间。这部分空间主要包括动态分配的空间,以及递归栈所需的空间等。这部分的空间大小与算法有关。
我们在写代码时,完全可以用空间来换取时间,比如字典树、哈希等都是这个原理。HashMap.get()、put()都是O(1)的时间复杂度。
空间复杂度为O(1):有的算法只需要占用少量的临时工作单元,而且不随问题规模的大小而改变,我们称这种算法是“就地”执行的,是节省存储的算法,空间复杂度为O(1)。
空间复杂度为O(n):有的算法需要占用的临时工作单元与解决问题的规模n有关,它随着n的增大而增大,当n较大时,将占用较多的存储单元,例如快速排序和归并排序算法就属于这种情况。
常见的空间复杂度就是O(1)、O(n)、O(n^2),像是O(logn)、O(nlogn)对数阶的复杂度平时都用不到,而且空间复杂度分析比时间复杂度分析要简单很多。