数据结构·基本概念

Data Structure Notes

1
2
3
4
5
6
Author : "ebxeax"
Version : 1.0
Refresh Date 2020.11.26
Description :
Just record and review some points about Data Structure.
Have mistakes that please correct it yourself.

数据结构的基本概念

1.数据

2.数据元素:

数据的基本单位,一个数据元素可有若干个数据项构成,数据项是不可分割的最小单位

3.数据类型

4.抽象数据类型(ADT[Abstract Data Type]):

数学模型在计算机的一种实现,包括数据对象、数据关系、基本操作,如建立一个有限状态机模型

5.数据结构:数据元素之间的关系称之为结构,数据结构包括三方面:逻辑结构、存储结构、数据运算(程序=算法+数据结构)

6.逻辑结构:数据间的逻辑关系,与数据存储独立,分为线性结构和非线性结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
graph TD
逻辑结构--线性结构
逻辑结构--非线性结构
线性结构--一般线性表
线性结构--受限线性表
线性结构--线性表推广
受限线性表--栈和队列
受限线性表--串
线性表推广--数组
线性表推广--广义表
非线性结构--非线性表
非线性表--集合
非线性表--树形结构
非线性表--图形结构
树形结构--一般树
树形结构--二叉树
图形结构--有向图
图形结构--无向图

7.物理结构:数据元素的表示以及关系的表示,主要有:顺序存储、链式存储、索引存储、散列存储

8.算法评估

(1)特性:有穷、确定、可行、输入、输出

(2)时间复杂度:衡量算法随问题规模的增大,算法执行的时间增长的快慢

T(n)=O(f(n)),f(n)为算法运算频度,一般采用最坏情况下的时间复杂度

计算方法:取算法时间增长最快的函数项,忽略其系数

a加法规则:

$$
T(n)=T_1(n)+T_2(n)=O(f(n))+O(g(n))=O(max(f(n),g(n)))
$$

多项式相加,只保留最高阶的项,且系数变为1

b乘法规则:

$$
T(n)=T_1(n)*T_2(n)=O(f(n))*O(g(n))=O(f(n)*g(n))
$$

多项式相乘,都保留

从左到右性能依次降低:

$$
O(1)<O(log_2n)<O(n)<O(nlog_2n)<O(n^2)<O(n^3)<O(2^n)
$$

单循环体型:

例题1:计算下列程序的时间复杂度

1
2
3
4
int i,sum	//执行1次
sum=0 //执行1次
for(i=0;i<=n;i++)//int i=0执行1次,i<=n执行n+2次,i++执行n+1次
sum+=i; //执行n+1次
时间分析: 该算法执行了3n+6个语句。 假设每个语句执行时间一致,均为常数t。则总时间 $$ T=(3n+6)*t $$ 随着问题规模n的增大,总时间的增长率与n的增长率一致,所以复杂度为 $$ O(n) $$ 结论: 复杂度是关于增长率的,所以可以直接忽视常数项 一般可以直接关注循环段基本操作语句
1
sum+=i;
的执行次数

例题2:

1
2
3
4
int sum,i;
sum=0;
for (i = 1;i <= n;i=2*i){
sum=sum+i;

时间分析:

i 取值:1,2,4,8…
满足条件:2^𝑘 ≤ n
K𝑙𝑜𝑔_2𝑛时, 跳出循环
所以循环体执行次数:⌈𝑙𝑜𝑔_2𝑛⌉ 故时间复杂度为O(logn).i 取值:1,2,4,8

多循环体型

两个运算法则:乘法规则(嵌套循环)、加法规则(若干循环)

例题3:

1
2
3
4
5
6
int x,y,i,j;
for(i=1;i<=n;i++)
x++;
for(i=1;i<=n;i++)
for(j=1;j<=n;j++)
y++;

两个循环体是独立的,采用加法规则:
$$
T(n)=T_1(n)+T_2(n)
$$

$$
=max(T_1(n),T_2(n)) =O(n^2)
$$

例题4:

1
2
3
4
int i,j,sum;
for (i=1;i<=n;i++)
for(j=1;j<=n;j=2*j)
sum=sum+j;

两个循环体是嵌套的,采用乘法规则:

$$
T(n)=T_1(n)*T_2(n)
$$

$$
=O(nlogn)
$$

(3)空间复杂度:衡量算法随问题规模的增大,算法所需空间的快慢

S(n)=O(g(n)),算法所需空间的增长率和g(n)的增长率相同

空间复杂度S(n)指算法运行过程中所使用的辅助空间的大小

线性表

1.定义:线性表是具有相同数据类型的n个数据类型的有限序列,n为表长

线性表中第一个元素称为表头元素,最后一个元素称为表位元素

除第一个元素外,每个元素仅有一个直接前驱,除最后一个元素外,每个元素有且仅有一个直接后继

顺序存储

线性表的顺序存储又称顺序表

使用一组地址连续的存储单元(数组等)依次存储线性表的数据元素,从而使得逻辑相邻的两个元素在物理位置上也相邻

三个属性:

1.存储空间的起始位置

2.顺序表最大存储容量

3.顺序表当前的长度

宏定义

静态分配大小

1
2
3
4
5
6
#define MaxSize 100
typedef int Elemtype
typedef struct{
Elemtype elem[MaxSize];
int length;
}SqList;

动态分配大小(这里动态指空间大小运行时决定,但分配大小后,空间大小被固定)

1
2
3
4
5
typedef int Elemtype
typedef struct{
Elemtype *elem;
int length;
}SqList;

优点:访问效率高、存储密度高

缺点:插入删除操作复杂

顺序存储线性表操作

1.初始化顺序存储线性表

1
2
3
4
5
6
7
int initLinklist(SqList &L){
L.elem=new Elemtype[MaxSize];
if(!L.elem)
exit(OVERFLOWS);
L.length=0;
return OK;
}

(1)创建一个顺序存储表后,需要初始化,首先根据数组大小通过new在堆空间开辟一段连续的空间赋值于先前创建的顺序存储表的elem空间

(2)检查elem是否存在,不存在溢出退出程序

(3)将length元素赋值为0,即设置顺序存储线性表长度为0

2.销毁顺序存储线性表

1
2
3
4
void destroyList(SqList &L){
if(L.elem)
delete(L.elem);
}

如果线性表存在,删除线性表elem开辟的空间

3.清空顺序存储线性表

1
2
3
void clearList(SqList &L){
L.length=0;
}

将线性表的长度置为0

4.判断顺序存储线性表是否为空

1
2
3
4
5
6
bool isEmpty(SqList &L){
if(L.length==0)
return 1;
else
return 0;
}

判断线性表长度是否为0,并返回相应bool值

5.引用类型按下表获取顺序存储线性表元素

1
2
3
4
5
6
int getElem(SqList L,int i,type&e){
if(i<1||iL.length)
return ERROR;
e=L.elem[i-1];
return OK;
}

(1)先检查传递参数下标量是否正确

(2)通过访问elem内数据存入引用类型变量内

6.按下表获取顺序存储线性表元素

1
2
3
4
5
Elemtype getElem(SqList L,int i){
if(i<1||iL.length)
return ERROR;
return L.elem[i-1];
}

(1)先检查传递参数下标量是否正确

(2)通过访问elem内数据并返回

7.引用类型按值查询顺序存储线性表元素下标

1
2
3
4
5
int locateElem(SqList L,Elemtype e,int &i){
for(int i=0;i<L.length;i++)
if(L.elem[i]==e)
return OK;
}

按照elem开辟空间进行迭代,当迭代元素与目标元素值相等时,将迭代量赋值于引用类型下标变量

8.按值获取顺序存储线性表元素下标

1
2
3
4
5
int locateElem(SqList L,Elemtype e){
for(int i=0;i<L.length;i++)
if(L.elem[i]==e)
return i;
}

按照elem开辟空间进行迭代,当迭代元素与目标元素值相等时,将迭代量返回

9.按下标插入元素

1
2
3
4
5
6
7
8
9
int listInsert(SqList &L,type e,int i){
if(i<1||iL.length)
return ERROR;
++L.length;
for(int j=L.length-1;j=i-1;j--)
L.elem[j+1]=L.elem[j];
L.elem[i-1]=e;
return OK;
}

(1)先检查传递参数下标量是否正确

(2)增加线性表长度

(3)按照目标元素位置,将其尾部元素后移1偏移量

(4)将目标元素存入下标位置

时间复杂度分析:

(1)
$$
最好情况:在表尾插入(即i=n+1)
$$

$$
元素后移语句执行的时间复杂度为O(1)
$$

(2)
$$
最坏情况:在表头插入(即i=1)
$$

$$
元素后移语句执行n次,时间复杂度为O(n)
$$

(3)
$$
平均情况:假设p_i(p_i=1/(n+1))
$$

$$
是第i个位置上插入一个结点的概率
$$

$$
则在长度为n的线性表中插入一个节点是需要移动结点的平均次数为
$$

$$
\begin{equation*}

f = \sum_{i=1}^{n+1}p_i(n-i-1)

\end{equation*}
$$

$$
\begin{equation*}

=\sum_{i=1}^{n+1}{\frac{n+1}{n-i+1}}

\end{equation*}
$$

$$
\begin{equation*}

=\frac{1}{n+1} \sum_{i=1}^{n+1}(n-i-1)

\end{equation*}
$$

$$
=\frac{1}{n+1}\frac{n(n+2)}{2}=\frac{n}{2}
$$

$$
因此顺序存储线性表的插入算法平均时间复杂度为O(n)
$$

10.按下标删除元素

1
2
3
4
5
6
7
8
9
int listDelete(SqList &L, int i) {
if ((i < 1) || (i L.length))
return ERROR;
for (int j = i; i <= L.length - 1; j++) {
L.elem[j - 1] = L.elem[j];
--L.length;
}
return OK;
}

(1)先检查传递参数下标量是否正确

(2)按照目标元素位置,将其头部元素前移1偏移量

(3)减少线性表长度

时间复杂度分析:

(1)
$$
最好情况:在表尾插入(即i=n)
$$

$$
无需移动元素,时间复杂度为O(1)
$$

(2)
$$
最坏情况:在表头插入(即i=1)
$$

$$
需移动除第一个元素外的所有元素,时间复杂度为O(n)
$$

(3)
$$
平均情况:假设p_i(p_i=1/(n+1))
$$

$$
是第i个位置上插入一个结点的概率
$$

$$
则在长度为n的线性表中插入一个节点是需要移动结点的平均次数为
$$

$$
\begin{equation*}

f = \sum_{i=1}^{n}p_i(n-i)

\end{equation*}
$$

$$
\begin{equation*}

=\sum_{i=1}^{n}{\frac{n}{n-i}}

\end{equation*}
$$

$$
\begin{equation*}

=\frac{1}{n} \sum_{i=1}^{n}(n-i)

\end{equation*}
$$

$$
=\frac{1}{n}\frac{n(n-1)}{2}=\frac{n-1}{2}
$$

$$
因此顺序存储线性表的插入算法平均时间复杂度为O(n)
$$

11.创建顺序存储线性表

1
2
3
4
5
6
7
8
9
10
int createList(SqList &L, int n) {
type e;
L.length = n;
for (int i = 0; i < n; i++) {
cout << "Please in put an element:";
cin e;
L.elem[i] = e;
}
return OK;
}

11.打印顺序存储线性表内元素

1
2
3
4
5
6
void printList(SqList L) {
printf("\nList's element:\n");
for (int i = 0; i < L.length; i++) {
cout << "elem[" << i << "] =" << L.elem[i] << endl;
}
}

转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 jaytp@qq.com

×

喜欢就点赞,疼爱就打赏