数据结构是计算机存储、组织数据的方式 目前只需要记住这点就可以了
一般的数据结构都有以下四种操作: 读取,查找,插入,删除我们一般说明一个程序的运行速度,就是看以上四种操作的步数的多少
同时,我们也知道了第一个重要理论:操作的速度,并不按时间来计算,而是按步数计算(由于新旧计算机性能的差距,我们不能直接按时间来计算快慢,两个计算机可能就会有两个时间,而直接用步数计算是最合适的)
操作的速度,也常常被称为时间复杂度
我们来看一下一个普通数组的四种操作,普通数组也可以称为是一个数据结构 int arr[] = {1,5,4,2,3}; 1) 读取
一个普通数组的读取只需要一步就够了,因为数组本身就是用一串连续的地址存储这数据,计算机有直接跳到任意一个内存地址的能力
2)查找
相对于读取,查找就要复杂一些,比如我们想要找到值为3的那个位置,虽然我们可以一眼直接看出来,但是计算机不行,一般来说需要一个一个地址的查看直到找到为止.一个长度为5的数组最多需要5步,随着数组的长度变化而变化,假设数组有N个元素,查找需要的最多步数就是N
3)插入
如果我们想要在数组中插入元素,需要将插入位置后面的元素全部往后面移动,那么我们最坏的情况就是在第一个位置插入,后面所有的元素都进行移动,共需要N+1步(N次移动,1次插入),同样是与数组长度有关
4)删除
删除其实与插入类似,因为删除一个元素要把后面的元素向前移动,最坏情况同样是删除第一个,N步(1次删除,N-1次移动)
以上就是一些最基本的概念,接下来我们就要想办法缩小查找需要的步数
假如我们要查找上面数组中 3 的位置,按照正常从头到尾查找的办法我们需要把数组全部遍历一遍,因为3其实是在最后一位,那么这个时候我们不妨用另外一种格式来存储这个数组再看一看
int arr[] = {1,2,3,4,5}和第一个数组存储的数据相同,但是只是把它变成了一个有序的数组,从小到大依次排列,这样只需要查找3次就可以找到,虽然最坏情况,假如要查找5,依然需要用5次,实际情况还是与数组长度有关,但是平均情况是要好于无序数组的,相对而言上面这个数组在插入的时候就要进行相对复杂的操作了,需要先遍历找到他自己的位置(大于前一个,小于后一个)
所以要根据实际情况决定用哪一种,频繁查看尽量用有序存储,插入多而查看少可以用无序
只看到这里,你可能并不觉的换了一种存储方式有多么有用,至少上面的例子并不明显,那么下面,我们就要引用一个名为算法的概念来侧面展示有序数组比无序数组好在哪里
算法通俗来讲就是解决问题的步骤
我们小时候可能都玩过一种游戏,我心里想1-100任意一个数,让你来猜,如果猜错我回答大了还是小了,知道猜对
这个游戏最好的猜法就是二分法,顾名思义,我先猜50,如果大了就只需要猜比50小的数字就可以了,每次将范围缩小一半直到猜中,这种方式的最坏情况是 要查找7次 ,100/2 最多除以7次就能除尽,而上一节用的线性查找从头开始查到尾,最多就需要100步
线性查找: 100步二分查找: 7步而这种方式只可以用于有序数组,无序数组肯定是不可以用这种比大小看范围的操作的,这样一来是不是就可以理解一些数据结构对于性能的影响了呢
//二分查找法的简单实现 int main() { //一个存储着1-100的数组 int arr[100]; for (int i = 0; i != 100; ++i) { arr[i] = i + 1; } int begin = 0; //第一个下标 int end = 99; //最后一个下标 int middle = (end + begin) / 2; //二分后的下标 int a = 66; //要查找的元素 //如果没有找到就一直循环 while (arr[middle] != a) { //如果中间数比要查找的a大,把范围缩小到前半部分,就是让end=middle if (arr[middle] > a) { end = middle; } else { //比a小就反过来 begin = middle + 1; } //找到新的中间数,继续判断 middle = (end + begin) / 2; } cout << "要查找的" << a << " 在第" << middle + 1 << "位置" << endl; system("pause"); return 0; }