什么是数据结构
官方定义没有,相关的三个定义如下:
-
数据结构是数据对象,以及存在于该对象的实例和组成实例的数据元素之间的各种联系,这些联系可以通过定义相关的函数给出。——《数据结构、算法与应用》
-
数据结构是ADT(抽象数据类型)的物理实现——《数据结构与算法分析》
-
数据结构是计算机中存储、组织数据的方式。通常情况下,精心选择的数据结构可以带来最优效率的算法——《中文维基百科》
听起来有一点复杂,我们通过三个例子来了解:
1.关于数据组织——例:图书摆放
例1:如何在书架上摆放图书:
这个问题很不科学,因为这是跟数据规模相关的问题,这个问题难度不在怎么放书,而是难在于放书是为了做什么事情用的。
如果只有几本书放在一个小书架,随便放就很轻松;如果是自家书房几百本书,随便放就很麻烦;而如果要是大图书馆那种成千上万本书,那你如果随便放,你要查找一本书有没有的时候,你不知道有没有就必须从头查找一遍,浪费很多很多时间。
图书的摆放需要2个相关的操作方便实现:
-
新书怎么插入?
-
怎么找到某本指定的书?
①方法1:随便放
- 新书怎么插入?
哪儿有空就放哪儿,一步到位很简单。
- 怎么找到某本指定的书?
如果规模较小还不是很麻烦,如果规模很大,就像图书馆那种,你找一本书得累死,犹如大海捞针一样。
②方法2:按照书名的拼音字母顺序排放
- 新书怎么插入
所有书都得一本一本往后挪,很麻烦!
- 怎么找到某本指定的书
可以用二分查找法,很快就能查到要找的书。
③把书架划分成几块区域,每块区域指定摆放某种类别的书;在每种类别内,按照书名的拼音字母顺序排放
- 新书怎么插入
先定类别,二分查找确定位置,移除空位再插入,虽然还是要移位再插入,但是要移动的书的数量会少很多很多。
- 怎么找到某本指定的书
先定类别,再二分查找法。如在大图书馆里找一本数据结构的书,先在计算机类的书里查,然后再用二分查找法查找,数据规模相比于整个图书馆里书的规模会小很多,速度快很多。
这个例子说明:解决问题方法的效率,跟数据的组织方式有关。
2.关于空间使用——例:printN函数的实现
例2:写程序实现一个函数printN,使得传入一个正整数为N的参数后,能顺序打印从1到N的全部正整数
①方法1:直接for循环打印
void printN(int n) {
for (int i = 0; i < n; ++i) {
cout << i << endl;
}
}
②方法2:递归打印
void printND(int n) {
if (n) {
printND(n - 1);
cout << n << endl;
}
}
int main() {
printN(10000);
printND(10000); //出现中断
}
虽然这个例子所花费的时间差不多,但是递归法十分占用空间,当输入规模十分大时,递归所需要的空间太大,内存不够用出现断点。
这个例子说明:解决问题方法的效率,跟空间的利用率有关
3.关于算法效率——例:对1到n求和
求程序耗费时间的函数:clock():
- clock():捕捉从程序开始运行到clock()被调用时所耗费的时间。这个时间的单位是clock tick,即”时钟打点“。
- 常数CLK_TCK:机器时钟每秒所走的时钟打点数。
通过这两个可以算出一个函数调用所花费的时间:
#include<time.h>
#include<iostream>
int main() {
clock_t start, end;
start = clock();
//所要测试花费时间的代码
end = clock();
cout << "耗费时间为:" << ((double)(end - start)) / CLK_TCK
<< " s" << endl;
}
例如:对1到n求和
①方法1:暴力求解for循环
int sum1(int n) {
int sum = 0;
for (int i = 1; i <= n; ++i) {
sum += i;
}
return sum;
}
②方法2:利用公式sum = (1 + n) / 2
int sum2(int n) {
int sum = 0;
sum = (1 + n) / 2;
return sum;
}
利用上述测算方法对这两个函数调用进行测试:
int main() {
clock_t start, end;
start = clock();
int s1 = sum1(100000000);
//int s2 = sum2(100000000);
end = clock();
cout << "耗费时间为:" << ((long double)(end - start)) / CLK_TCK << " s" << endl;
}
//运行结果为
0.165 s
int main() {
clock_t start, end;
start = clock();
//int s1 = sum1(100000000);
int s2 = sum2(100000000);
end = clock();
cout << "耗费时间为:" << ((long double)(end - start)) / CLK_TCK << " s" << endl;
}
//运行结果为:
0 s
如果一个函数运行速度太快,导致看不出时间,则可以重复调用一个函数,然后除以被调用的次数即可算出一次的时间:
如上述第二次调用速度看不出花费时间,我们对其调用多次
int main() {
clock_t start, end;
start = clock();
//int s1 = sum1(100000000);
int cnt = 100000000;
while (cnt--) {
int s2 = sum2(100000000);
}
end = clock();
cout << "耗费时间为:" << ((float)(end - start)) / CLK_TCK << " s" << endl;
}
//运行结果为:
1.039 s
//实际运行一次的时间为:
1.039 × 10e-8 s
这个例子告诉我们:解决方法的效率跟算法的巧妙程度有关