type
status
date
slug
summary
tags
comment
category
icon
password
photos
📝数据结构
1.数据结构的基本概念
数据
- 数据是信息的载体,是描述客观事物的数,字符及所有能输入到计算机内并被计算机程序识别和处理的符号的集合,数据是计算机程序加工的原料
数据元素与数据项
- 数据元素是数据的基本单位,通常作为一个整体进行考虑和处理,一个数据元素可由多个数据项组成,数据项是构成数据元素的不可分割的最小单位
如一个账号就是一个数据元素,它的昵称,性别,生日等就是数据项,生日中的年、月、日作为一个组合项
结构
- 各个元素之间的关系
数据结构、数据对象
- 数据结构是相互之间存在一种或多种特定关系的数据元素的集合
- 数据对象是具有相同性质的数据元素的集合,是数据的一个子集
数据结构的三要素
- 1.逻辑结构 逻辑结构是指数据元素之间的逻辑关系,即从逻辑关系上描述数据。它与数据的存储无关,是独立于计算机的
- 集合:各个元素同属于一个集合,别无其他关系
- 线性结构:数据元素间是一对一的关系,除了第一个元素,所有元素都有唯一前驱;除了最后一个元素,所有元素都有唯一后继(如烤串,排队)
- 树状结构:数据元素间是一对多的关系(如储存位置E:/1/2/3/…)
- 图状结构(网状结构):数据元素间是多对多的关系(微信好友)
- 2.物理结构(存储结构) 存储结构是指数据结构在计算机中的表示(又称映像),也称物理结构。它包括数据元素的表示和关系的表示。数据的存储结构是逻辑结构用计算机语言的实现,它依赖于计算机语言。数据的存储结构主要有:顺序存储、链式存储、索引存储和散列存储
- 顺序存储:把逻辑上相邻的元素存储在物理位置上也相邻的存储单元中,要求分配一块连续的存储空间(p.s. 物理位置即信息在计算机中的位置,内存。)
- 链式存储:逻辑上相邻的元素在物理位置上可以不相邻,借助指示元素存储地址的指针来表示不同元素之间的逻辑关系。(p.s. 使用指针表示下一个数据元素的存储地址)
- 索引存储:在存储元素信息的同时,还建立附加的索引表。索引表中的每项称为索引项,索引项的一般形式是(关键字,地址),需要消耗一部分的存储空间建立索引表
- 散列存储:根据元素的关键字直接计算出该元素的存储地址,又称哈希(Hash)存储
- 后三者称为非顺序结构,若采取顺序存储,则各数据元素在物理上必须是连续的;若采用非顺序存储,则各元素在物理上可以是离散的
- 数据的存储结构会影响存储空间分配的方便程度
- 数据的存储结构会影响数据运算的速度
- 3.数据的运算
施加在数据上的运算包括运算的定义与实现。运算的定义是针对逻辑结构的,指出运算的功能;运算的实现是针对存储结构的,指出运算的具体操作步骤
数据类型、抽象数据类型:
- 数据类型:是一个值的集合和定义在此集合上的一组操作的总称
- 原子类型:其值不可再分的数据类型(bool,int…)
- 结构类型:其值可以再分解为若干成分(分量)的数据类型(结构体)
- 抽象数据类型(Abstract Data Type, ADT):是抽象数据组织及与之相关的操作
- ADT用数学化的语言定义数据的逻辑结构,定义运算,与具体的实现无关,不关心物理结构(存储结构)
算法(Akgorithm)
- 算法是对特定问题求解步骤的一种描述,它是指令的优先序列,其中的每条指令表示一个或多个操作
算法的特性
- 有穷性:一个算法必须总在执行有穷步后结束,且每一步都在有穷时间内完成。 (p.s.算法必须是有穷的,而程序可以是无穷的)
- 确定性:算法中,每条指令必须有确切的含义,对于相同的输入,只能得到相同的输出
- 可行性:算法中描述的操作都可以通过已经实现的基本运算执行有限次来实现
- 输入:一个算法有零个或多个输入,这些输入取自于某个特定的对象的集合
- 输出:一个算法有一个或多个输出,这些输出是与输入有着某种特定关系的量
好的算法应能够正确的解决问题(正确性),具有良好的可读性以帮助人们理解(可读性),输入非法数据时,算法能适当地作出反应或进行处理,而不会产生莫名其妙的输出结果(健壮性),并且具有高效率(时间复杂度低)与底存储量需求(空间复杂度低)
算法的复杂度
- 时间复杂度:
- 它用来衡量算法随着问题规模增大,算法执行时间增长的快慢;
- 是问题规模的函数:T(n)是时间规模函数 时间复杂度主要分析T(n)的数量级
- T(n)=O(f(n)) f(n)是算法中基本运算的频度 一般我们考虑最坏情况下的时间复杂度
时间复杂度可以只考虑阶数高的部分
- 空间复杂度:
- 它用来衡量算法随着问题规模增大,算法所需空间的快慢;
- 是问题规模的函数:S(n)=O(g(n)) ;算法所需空间的增长率和g(n)的增长率相同。
- 算法原地工作:算法所需内存空间为常数
空间复杂度可以只分析与问题规模相关的部分,例如定义变量int i可以忽略
1.加法规则:多项相加,只保留最高阶的项,且系数变为1
2.乘法规则:多项相乘,都保留
2.线性表(Linear List)
线性表的逻辑结构
- 定义:线性表是具有相同数据类型的n(n>0)个数据元素的有限序列,其中n为表长,当n=0时线性表是一个空表。若用L命名线性表,则其一般表示为:
- 概念:是线性表中的“第i个”元素线性表中的位序(从1开始!);是表头元素;是表尾元素。除第一个元素外,每个元素有且仅有一个直接前驱;除最后一个元素外,每个元素有且仅有一个直接后驱
线性表的基本操作
- InitList(&L):初始化表。构造一个空的线性表L,分配内存空间。
- DestroyList(&L):销毁操作。销毁线性表,并释放线性表L所占用的内存空间
- ListInsert(&L,i,e):插入操作。在表L的第i个位置上插入指定元素e
- ListDelete(&L,i,&e):删除操作。删除表L的第i个位置上的元素,并用e返回该删除元素的值
- LocateElem(L,e):按值查找操作。在表L中查找具有给定关键字值的元素
- GetElem(L,e):按位查找操作。获取表L中第i个位置的元素的值
- Length(L):求表长。返回线性表L的长度,即L中数据元素的个数
- PrintList(L):输出操作。按前后顺序输出线性表L中的所有元素值
- Length(L):判空操作。若L为空表,则返回true,否则返回false
线性表的顺序存储方式(顺序表)
- 定义:把逻辑上相邻的元素存储在物理位置上也相邻的存储单元中,元素之间的关系由存储单元的邻接关系来体现
- 通过C语言提供的sizeof(ElemType)可以得到存放数据元素类型的数据元素大小;如线性表第一个元素的存放位置是LOC(L),则后续的元素位置则为: LOC(L)+数据元素的大小,LOC(L)+2*数据元素的大小,…,LOC(L)+n*数据元素的大小
顺序表的静态分配
给各个内存元素分配连续的存储空间,大小为Maxsize * sizeof(ElemType)
顺序表的动态分配
(ElemType *)malloc(InitList&sizeof(ElemType)
malloc函数会开辟一块连续的存储空间,并且会返回这片存储空间的起始地址,再转换成与数据元素相同类型的指针类型,指针指向顺序表的起始地址、
int *p后,指针p的类型就是int,p[i]的时候,就相当于p+i个int类型的长度,p[i] = p + i * sizeof(int)
p.s.在增加顺序表长度时,L.data=(int *)malloc((L.MaxSize+len)*sizeof(int));相当于重新申请了一块新的存储空间,并不是在原来的基础上延长。再获得一片新的存储空间后,再把指针指向新的存储空间的起始地址,将原来的旧数据复制进来,然后释放旧内存;
为什么不能“直接延长原来的那块内存”? malloc() 申请的是一整块 连续的内存,操作系统不会保证原来的内存后面一定是空的。
顺序表的特点
- 随机访问:即可以在O(1)时间内找到第i个元素
- 存储密度高,每个节点只存储数据元素
- 拓展容量不方便,即便采用动态分配的方式实现,拓展长度的时间复杂度也比较高
- 插入、删除操作不方便,需要移动大量元素
顺序表的操作
- ListInsert(&L,i,e):插入操作。在表L的第i个位置上插入指定元素e
循环将从i开始的每个元素向后移动,然后把第i个位置上插入元素e,将顺序表长度加1
- ListDelete(&L,i,&e):删除操作。删除表L的第i个位置上的元素,并用e返回该删除元素的值
删除的元素赋给e,然后将第i个位置后的元素前移
- GetElem(L,e):按位查找操作。获取表L中第i个位置的元素的值
指针跟[i]代表访问从当前位置开始向后i个sizeof该数据类型大小的位置
顺序表中的各个元素在内存中连续存放,因此可以根据起始地址和数据元素大小立即找到第i个元素——随机”存取“特性(时间复杂度为O(1))
- LocateElem(L,e):按值查找操作。在表L中查找具有给定关键字值的元素
p.s.若顺序表元素的数据类型为结构体,则不能用==来比较两个结构体,必须判断结构体中的变量是否相等
线性表的链式存储结构(链表)
- 单链表的定义:线性表的链式存储是指通过一组任意的存储单元来存储线性表中的数据元素。每个节点中除了存放数据元素外,还存储指向下一个节点的指针
- 头指针:指向单链表的第一个结点
- 优点:不要求大片连续空间,改变容量方便; 缺点:不可随机存取,要消耗一定空间存放指针
- 如何用代码定义一个单链表?
- 头结点和头指针的区别?
- 不管带不带头结点,头指针始终指向链表的第一个结点,而头结点是带头结点链表中的第一个结点,结点内通常不存储信息
- 为什么要设置头结点?
- 1.处理操作起来方便 例如:对在第一元素结点前插入结点和删除第一结点起操作与其它结点的操作就统一了
- 2.无论链表是否为空,其头指针是指向头结点的非空指针,因此空表和非空表的处理也就统一了。
