数据:能被输入进电脑进行处理的所有信息符号的总称,包括文字,声音,图片视频等等;
数据项:数据的最小单位
数据元素:数据的基本单位 ;一个数据元素可以由若干个数据项组成; 比如:一条书目的信息为 一个数据元素,而其中书的价格,作者,出版社等信息就是一个个数据项
数据对象:性质相同的数据元素的集合; 比如在一个数组中,每一个元素都是数据元素,而一整 个数组是一个数据对象
数据结构:存在特定关系的数据元素的集合
相当于是数据元素的容器,定义了它如何存储如何操作,强调数据的组织和管理方式
数据项 数据元素 数据对象 数据结构都可以被称为数据
前面说到数据结构定义了数据如何进行存储和操作,存储又涉及到逻辑结构和存储结构
逻辑结构就是将数据间的关系抽象成可视化的数学模型,体现数据元素之间的组织方式。我们具体会学四种逻辑结构:
线性:
线性结构(Linear)
特点: 一对一,有一个确定的头和一个确定的尾
例子: 学生成绩单
非线性:
树形结构(Tree)
特点: 一对多;
例子: 文件系统的目录结构,下棋时对应的很多种局势
图状结构/网状结构(Graph)
特点:多对多
例子: 人物关系图,地铁路线图
集合(Set)
特点:和我们高中数学学的集合是一样的,就是一个单纯的包含关系
在计算机中,将数据元素实际地存进计算机有两种方法: 顺序映像 和 链式映像
对应的两种存储结构分为 顺序存储 和 链式存储
顺序映像和顺序存储
顺序映像就是将数据在计算机中用连续的地址来存储数据,使我们可以通过索引来访问数据
最简单直接的例子就是数组
特点:逻辑结构上相邻的位置物理结构上也相邻
优点: 存取元素效率高
缺点: 插入和删除时会引起大量元素移动
应用: 线性表
其实顺序映像和顺序存储这两个概念很像
顺序映像强调的是逻辑上数据之间的顺序关系,而顺序存储强调的是物理存储的布局;
一句话概括就是 顺序映像通过顺序存储进行实现
链式映像和链式存储
链式映像是随机的在计算机中取没有被占用的内存单元存储元素,所以数据的地址是不连续的,需要通过指针来进行连接
优点: 插入删除元素很方便;
缺点:访问元素效率低(不能直接实现随机访问,必须要通过指针进行遍历)
应用:线性表,树,图
指对数据施加的操作 如查找,插入,删除,排序,修改等。
这些操作涉及到算法,而算法的设计主要依赖于数据的逻辑结构,算法的实现主要依赖于数据的存储结构
对一个具体问题解决方法步骤的描述
有穷性,确定性,可行性,输入,输出;
正确: 不仅是不含语法错误,而且无论输入什么都可以偶的符合要求的结果;
可读: 要写注释;
健壮性: 有容错;
效率和低存储: 执行时间短,占用内存;
评估算法的效率 通常从 时间复杂度 和 空间复杂度 两个维度进行考量
标准: 以基本语句的执行次数为基本的度量单位
体现算法执行时间和问题规模(算法输入的数据量的大小,用n来表示)之间的关系,用O来表示
计算时间复杂度时,我们通常只看执行频度最高的语句(一般是循环语句)来确定
时间复杂度的表达式为O(f(n))通常f(n)不需要是一个具体的函数表达式,只要能表达增长率即可(n,,
...)
若问题规模只与问题本身有关,而与算法无关,则只考虑算法占用的额外空间(即不考虑输入数据需要占的内存),表达方式与时间复杂度相同
来看几个例子:
a.线性查找
#include
int linearSearch(int arr[], int n, int key) {
for (int i = 0; i < n; i++) {
if (arr[i] == key) {
return i; // 找到元素,返回索引
}
}
return -1; // 未找到
}
时间复杂度:最好情况下,循环执行一次就return了,但是我们要考虑最坏情况
在最坏情况下,循环要执行n次,时间复杂度为O(n);
空间复杂度: 这个算法并没有使用额外的变量去储存数据,所以空间复杂度为O(1)
b.二分查找
int binarySearch(int arr[], int n, int key) {
int left = 0, right = n - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (arr[mid] == key) {
return mid; // 找到元素,返回索引
} else if (arr[mid] < key) {
left = mid + 1; // 在右半边查找
} else {
right = mid - 1; // 在左半边查找
}
}
return -1; // 未找到
}
时间复杂度:
同样分析最坏情况 每循环一次 数据范围就缩小一半
比如: 问题规模为100,第一次循环结束后是50-100(数据范围为100/2),第二次循环结束之后是75-100 (数据范围为100/2^2)。。。
最后当left == right时 循环结束 此时只剩下一个元素 假设一共循环了k次,则 n/2^k = 1
则 k = log₂(n)
时间复杂度为O()
空间复杂度:
只使用了几个局部变量right,left,mid,它们占用的空间与问题规模无关,空间复杂度为O(1)
(c)冒泡排序
void bubbleSort(int arr[], int n) {
for (int i = 0; i < n - 1; i++) {
for (int j = 0; j < n - 1 - i; j++) {
if (arr[j] > arr[j + 1]) {
// 交换元素
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
}
时间复杂度:重点关注循环体:当i = 0 时,内部循环n-1次, i = 1 时,内部循环n - 2次,所以一共是 (n-1)+(n-2)+(n-3)+....+1 = n(n-1)/2
时间复杂度为
空间复杂度: 同二分查找,为O(1)
(4)最简单的!
x = 0;
y = 0;
while(x<100)
{
y += x;
x++;
}
语句执行次数与问题规模显然无关,时间复杂度为O(1)--常数阶 空间复杂度也为O(1)
(5)将一个字符串在另一个字符串中出现的相同字符删除(这个比较复杂)
int found(char *t, char *c) //在字符串t中寻找与字符c相同的字符
{while(*t&&*t!=*c) t++; //遍历字符串t,找到相同的字符返回,若没有找到返回空
return *t;
}
void delchar(char *s, char *t) //删除字符串s中和t字符串中相同的字符
{ char *p,*q;
p=s;
while(*p) //遍历字符串s
if(found(t,*p)) //s中的字符是否在字符串t中出现
{q=p; //如果找到了,逐个字符向前拷贝
while(*q) *q++=*(q+1);
} else p++;
}
时间复杂度:
设字符串t 长度为n; 字符串s长度为m;
对于外层循环,最坏情况下下循环s字符中的每个字符,共m次;在调用found函数时,最坏情况下要循环t中的每个字符,共n次;在删除字符将后面的字符逐个往前拷贝的循环中,最坏情况下要如果被删除的是第一个字符,第二个字符要移动m-1次,第三个字符要移动m-2次… m-1+m-2+m-3+…1=m*(m-1)/2 字符移动操作的时间复杂度为O(m^2)
所以总的时间复杂度O(n*m + m^2);
空间复杂度:同二分查找,为O(1)