1. 什么是数据结构
计算机软件是为了解决现实生活中的问题而存在的,生活中的不同个体体现在软件中即是“数据”。生活中的不同个体间势必存在某些联系,所以在软件设计中需要用某种逻辑来表现这些关系,这就是数据结构。
计算机的“数据”有3个重要概念:
数据元素:组成“数据”的基本单元
数据项:一个数据元素由若干个数据组成
数据对象:性质相同的数据元素的集合
听起来比较抽象,可以用“人类”来类比理解。人类是由一个一个人组成,人即数据元素;人由四肢等等器官组成,这些器官即数据项;人类中有程序员、有医生等等群体,程序员即数据对象。数据结构指的时数据对象中的数据元素之间的关系,也就是“人类”中程序员之间的关系。
常见的数据的结构有:
集合结构:数据元素之间没什么特别的关系,仅属相同集合
线性结构:数据元素间是一对一的关系
树形结构:数据元素之间存在一对多的层次关系
图形结构:数据元素间是多对多的关系
上述的几种结构都是指逻辑结构,是程序设计中在程序员思维上对数据元素间关系的描述。与逻辑结构相对应的是物理结构,物理结构描述的是二进制数据在计算机内存中的存储形式:
顺序存储结构:将二进制数据存储在地址连续的存储单元中
链式存储结构:将二进制数据存储到任意的存储单元中,通过保存地址的方式找到相关联的数据元素
2. 算法及其效率度量方式
对于一个高效的程序来说,除了对数据元素采用合理的数据结构加以描述之外,还需要在数据结构的基础上选择设计算法,算法是特定问题求解步骤的描述。除了正确描述求解步骤,效率是算法最受程序员关注的特性。度量算法的效率,一般有:
(1) 执行后统计法:比较不同算法对同一组输入数据的运行处理时间,该方法的缺陷是运行时间严重依赖硬件以及运行时的环境因素,且算法的测试数据的选取存在困难。
(2) 执行前分析法:依据(在程序执行前就可以)对算法的时间/空间复杂度的统计判断效率高低,用大O表示法表示。算法的时间复杂度依赖于程序操作数量,空间复杂度则依赖于程序的空间使用大小。一般大O表示法表示的是算法的时间复杂度(当然,若要表示空间的复杂度,统计办法和统计时间复杂度类似),这里所说的大O表示法也指时间复杂度。
以下面代码为例,算法操作数为2n+4,空间复杂度为n。
int get_sum(int n)
{
//执行2次,分配n个空间
int ret = 0;
int* arr = new int[n];
//执行n次
for (int i = 0; i < n; i++)
arr[i] = i + 1;
//执行n次
for (int i = 0; i < n; i++)
ret += arr[i];
//执行2次
delete[] arr;
return ret;
}
我们关注的是操作数最高阶项,其它次项忽略不计。用大O表示法表示其时间复杂度为O(n)(空间复杂度也为S(n))。
常见的时间复杂度有:
(1) 线性时间复杂度O(n)
for (int i = 0; i < n; i++)
{
ret += i;
}
//循环n次
(2) 对数阶时间复杂度O(logn)
int i = 1;
while (i < n)
{
i *= 2;
}
//循环次数为log2n
i用于累加有多少个2相乘后大于n,即2^x = n,x = log2n,用大O表示法为O(logn)
(3) 平方阶时间复杂度O(n^2)
for (int i = 0; i < n; i++)
{
for (int j = 0; j < n; j++)
{
ret += j;
}
}
//循环n^2
更多常见的空间复杂度如下表格:
从上至下算法效率递减。一般而言,项目中使用的算法其时间复杂度应不超过O(n^3)。
对于一个算法,可能有最坏情况和最好情况,例如:
bool array_find(int a[], int n, int v)
{
bool ret = false;
for (int i = 0; i < n; i++)
{
if (a[i] == v)
{
ret = true;
break;
}
}
return ret;
}
int main(void)
{
int a[] = {1, 2, 3, 4, 5};
array_find(a, 5, 1); //最好情况,只执行1次循环
array_find(a, 5, 5); //最坏情况,执行n次
}
一般,程序的时间复杂度指的是最坏情况下的,在这里即为O(n)。
3. 时间和与空间的策略
在当下的软件设计中,算法的时间复杂度更令人关注,若有必要,会通过增加额外内存空间的策略来降低时间复杂度。
下面代码记得我学校辅修Linux应用程序课程的时候,主讲老师讲过,它就是这样一个空间换时间的例子:
//在元素为1-10的数组中,设计一个算法找出出现次数最多的元素
#define MAX 10
void array_search(int a[], int len)
{
int tmp[MAX] = {0};
int max = 0;
for (int i = 0; i < len; i++)
tmp[a[i] - 1]++;
for (int i = 0; i < MAX; i++)
{
if (tmp[i] > max)
max = tmp[i];
}
for (int i = 0; i < MAX; i++)
{
if (max == tmp[i])
std::cout << i + 1 << std::endl;
}
}