概念
数据元素之间存在一对一的线性关系。
- 线性结构这种逻辑结构有着两种物理存储结构:顺序存储、链式存储;
- 顺序存储的线性表称为顺序表,存储的物理位置和逻辑一样连续串联;
- 链式存储的线性表称为链表,存储的物理位置不一定连续,靠元素结点即存储该数据元素的空间结点附带相邻元素的地址信息;
- 线性结构又存在两种操作受限的线性表:栈、队列;
- 栈只能在线性表一端操作,FILO,分别又有顺序栈和链栈两种物理存储;
- 队列只能在线性表一端输入而另一端输出,FIFO,分别又有顺序队列和链式队列两种物理存储。

另外再链表中存在一种静态链表,该链表用数组表示,存储结构是连续的,链式物理存储表示线性结构的时候也可以让其相邻的数据元素在物理位置上同样相邻,这是数组表示的静态链表做到的。
性能上的优缺

浅谈数组
一种物理结构,它的存储单元是连续的,即顺序存储。
在计算机科学中,一个数组数据结构,或者简称一个数组,是一个由元素集合(值或变量)组成的数据结构,每个元素由至少一个数组索引或键来标识。一个被存储的数组中的每个元素的位置都可以通过一个数学公式从它的索引元组中计算出来。最简单的数据结构类型是一个线性数组,也称为一维数组。
例如,有一个由10个32位整型变量组成的数组,索引0到9,可以存储10个元素,2000,2004,2008, ……2036。那么该数组中为索引i 的元素位置是2000+4* i.2000是该数组first address的起始位置。
数组是由n(n>=1)个相同类型的数据元素构成的有限序列,每个数据元素称为一个数组元素,每个元素受n个线性关系的约束,每个元素在n个线性关系中的序号称为该元素的下标,并称该元素为n维数组。
数组是线性表的推广,二维数组相当于 一个一维数组而一维数组中的元素由均链接一个一维数组。

行优先的二维数组

列优先的二维数组

数组是最古老和最重要的数据结构之一,几乎每个程序都会使用它。它还被用来实现许多其他的数据结构,比如列表和字符串。数组有效地利用了计算机的寻址逻辑addressing logic of computers。在大多数现代计算机和许多外部存储设备中,内存是一个一维的word数组,其索引是它们的地址。处理器,尤其是向量处理器,通常是针对数组操作进行优化的。
数组之所以有用,主要是因为元素索引可以在运行时计算。除此之外,该特性允许单个迭代程序可以处理数组中的任意多个元素。由于这个原因,数组数据结构的元素必须具有相同的大小,并且应该使用相同的数据表示。有效的索引元组和元素的地址(以及元素寻址公式)通常在当数组被使用时是固定的。
数组通常被用来表示数组数据类型,这是一种由大多数高级编程语言提供的数据类型,它由一组值或变量组成,这些值或变量可以由一个或多个在运行时计算的索引来选择。数组类型通常由数组结构来实现;然而,在某些语言中,它们可能是由哈希表、链表、搜索树或其他数据结构实现的。
在C语言中, 数组属于构造数据类型。一个数组可以分解为多个数组元素,这些数组元素可以是基本数据类型或是构造类型。因此按数组元素的类型不同,数组又可分为数值数组、字符数组、指针数组、结构数组等各种类别。
数组结构形式:
- 栈内存: 在方法中定义的一些基本类型的变量和对象的引用变量都在方法的栈内存中分配,当在一段代码中定义一个变量时,java就在栈内存中为这个变量分配内存空间,当超出变量的作用域后,java会自动释放掉为该变量所分配的内存空间。
- 堆内存: 堆内存用来存放由new运算符创建的对象和数组,在堆中分配的内存,由java虚拟机的自动垃圾回收器来管理。在堆中创建了一个数组或对象后,同时还在栈内存中定义一个特殊的变量。让栈内存中的这个变量的取值等于数组或者对象在堆内存中的首地址,栈中的这个变量就成了数组或对象的引用变量,引用变量实际上保存的是数组或对象在堆内存中的地址,以后就可以在程序中使用栈的引用变量来访问堆中的数组或对象。
对数组操作所花费的运行时间:
假设数组中有 n 个数据,由于访问数据时使用的是随机访问(通过下标可计算出内存地址),所以需要的运行时间仅为恒定的O(1)。但另一方面,想要向数组中添加新数据时,必须把目标位置后面的数据一个个移开。所以,如果在数组头部添加数据,就需要 O(n) 的时间。删除操作同理。
| 访问 | 添加 | 删除 | |
|---|---|---|---|
| 链表 | 慢 | 快 | 快 |
| 数组 | 快 | 慢 | 慢 |
静态链表
定义
用数组描述的链表,即称为静态链表。
在C语言中,静态链表的表现形式即为结构体数组,结构体变量包括数据域data和游标CUR。
用数组代替指针来描述链表叫做静态链表。静态链表是为了给没有指针的高级语言设计的一种实现单链表能力的方法。首先让数组的元素都由两个数据域组成,data和cur,即数组的每一个下标都对应一个data和一个cur。
与单链表不同的是指针域指向(存放)下一个结点的指针(地址),而cur域指向下一个元素所在该数组下标。
插入
找到一个空的结点,存放数据元素;(如何判断为空,需要你在初始化是给空闲位置的游标域赋值某个特殊值如“-2”)

数据链备用链
除了上述提到的用特殊值标记空闲结点的方法去查找利用空节点,还可以使用备用链和数据链的概念来利用空闲空间。
- 我们给定一个数组a[0:9]一共10个位置,把a[0]当成备用链表表头,a[0].cur指向下一个空闲的备用结点,如该数组初始化时a[0:9]都为空,则
a[o].cur==1指向a[1],a[1].cur==2指向a[2]........a[8].cur==0为链尾指向备用链表表头; - a[maxsize]a[9]表示数据链表表头,a[9].cur0也指向备用链表表头,两个表头都不存放数据,实际上a[0:9]只有8个有效空间;
- 当给线性表插入第一个元素时,从备用链表中a[0]指向的第一个空闲节点此时为a[1]供给插入元素,然后将数据链表头a[9]指向a[0]的游标指向a[1],再将a[1]的游标指向a[0].
int j = a[0].cur;
a[0].cur = a[j].cur;
a[maxsize-1].cur = j;
a[j].cur = a[maxsize-1].cur;
a[j].data = e;
顺序表
与静态链表相似,在内存中划分一块空间存储线性表,还是用一维数组来实现这种线性结构。但是与静态链表不同的是,顺序表的数组不需要设置游标域来指向下一个元素在该数组的位置,每一个相邻元素的物理位置也相邻。
假设线性表中有n个元素,每个元素占k个存储单元,第一个元素的地址为Loc(a1),则第i个元素的地址Loc(ai)😗*Loc(ai) = Loc(a1) + (i-1) * k;**其中Loc(a1)称为基地址。
优点:
- 无须为表示表中元素之间的逻辑关系而增加额外的存储空间
- 可以快速地存取表中任一位置的元素
缺点:
- 插入和删除操作需要移动大量元素
- 当线性表长度变化较大时,难以确定存储空间的容量
- 造成存储空间的“碎片” ???
链表
链式存取的数据结构,用一组任意的存储单元存放线性表中的数据元素。线性表中相邻连续的元素在物理位置上不一定相邻连续。与静态链表相似,每个结点空间又分为两个部分,一部分存放数据元素,另一部分存放后继元素的地址(指针)。

单链表
头指针:
- 不带头结点的链表,则头指针指向第一个结点
- 带头结点的链表,则头指针指向头结点
typedef struct LNode{ //定义单链表结点类型
ElemType data; //每个结点存放一个数据元素
struct LNode *next; //指针指向下一个结点
}LNode,*LinkList; //LNode表示强调该类型结构的一个结点 *LinkList == LNode表示强调该类型结构是一个单链表
//初始化一个空的不带头结点单链表
bool InitList(LinkList &L){ //LinkList &L == LNode L 表示这是一个指针,地址,并没有创建一个完整的LNode结点
L = Null; //空表,该指针未存地址指向
return true;
}
//初始化一个带头结点的单链表
bool InitList(LinkList &L){
L = (LNode *) malloc(sizeof(LNode));//分配一个头结点存储
if(L == NULL) //存储空间分配失败
return false;
L->next == Null; //指向的下一个地址的空间为空(结点为空)
return true;
}
LNode *GetElem(Linklist L,int i){ //LinkList L ==LNode*L
int j = 1;
LNode *p = L->next; //L->next == L.next
}
头结点:
- 头结点的数据域不存放数据元素,或者存放该线性表的其他信息,例如表长等;
- 头结点对于在第一元素结点前插入结点和删除第一结点,其操作与其它结点的操作就统一了
- 头结点不一定是链表必须要素
单链表和顺序表对比
—存储分配方式
- 顺序存储结构用一段连续的存储单元依次存储线性表的数据元素
- 单链表采用链式存储结构,用一组任意的存储单元存放线性表的元素
—时间性能
- 查找
- 顺序存储结构 O(1)
- 单链表 O(n)
- 插入和删除
- 顺序存储结构需要平均移动表长一半的元素,时间为 O(n)
- 单链表在线出某位置的指针后,插入和删除时间仅为 0(1)
—空间性能
- 顺序存储结构需要预分配存储空间,分大了,浪费,分小了易发生上溢
- 单链表不需要分配存储空间,只要有就可以分配,元素个数也不受限制
循环链表

循环链表和单链表的差异在于循环的判断条件:从p->next!=null到p->next!=head;
循环链表的意义:如果给定了线性表中一个p结点的地址,在单链表中只能找到这个结点后面的结点,前面的结点对于我们就是未知,如果是循环链表中则可以继续从尾结点找回头结点继续遍历到p结点。
双链表
空间换时间
栈
栈的抽象数据类型
ADT 栈(stack)
Data
同线性表。元素具有相同的类型,相邻元素具有前驱和后继关系
Opration
InitStack(*s):初始化操作,建立一个空栈
DestoryStack(*s):若栈存在,则销毁它
ClearStack(*s):将栈清空
StackEmpty(s):若栈为空,返回true,否则返回false
GetTop(S,*e):若栈存在且非空,用e返回S的栈顶元素
Push(*S,e):若栈S存在,插入新元素e到站S中并成为新的栈顶元素
Pop(*s,e):删除S中栈顶元素,并用e返回其值
StackLength(S):返回栈S的元素个数
endADT
共享栈
当一个栈发生上溢之时可以借用另外一个栈的空闲空间,实现方式就是在一片数组空间中给定两个top指针,分别在数组两端
队列
队列的抽象数据类型
ADT 队列(Queue)
Data
同线性表。元素具有相同的类型,相邻元素具有前驱和后继关系
Operation
InitQueue(*Q):初始化操作,建立一个空队列Q
DestroyQueue(*Q):若队列Q存在,则销毁它
ClearQueue(*Q):将队列清空
QueueEmpty(Q):若队列为空,返回true,否则返回false
GetHead(Q,*e):若队列存在且非空,用e返回Q的队头元素
EnQueue(*Q,e):若队列Q存在,插入新元素e到站Q中并成为队尾元素
DeQueue(*Q,e):删除Q中队头元素,并用e返回其值
StackLength(Q):返回栈Q的元素个数
endADT
引入循环队列概念
- 如果队列不循环,当你在队头操作元素出队之时就需要使后面的元素全部向前进一位
- 如果初始化时将队头指针和队尾指针指向数组中间位置,当然也可以解决出队时移动所有元素的问题,但是会发生假溢出(当队头指针没有指向a[0]时队尾指针却指向了a[MAXSIZE-1],数组越界没有办法入队新元素,实际情况却是队头指针之前还有数组空间并没有利用上,这就是假溢出)
循环队列的实现:
-
将队尾指针rear指向a[0]

队空:front == rear
队满:
-
标志变量flag,当front == rear时,判断flag == 0 表队空,flag == 1表队满
-
牺牲一个元素空间,当rear+1 = front时表示队满

但是这里出现了一个问题,如上左图中所示也是队满的情况,但是两者的数值差有整整一圈,与rear+1 == front不符合。 于是就可以换成(rear+1)%QueueSize == front (4+1)%5 == 0 -
设置数组当前长度length;当length == maxsize时队满,length == 0时队空
串
- 由零个(空串)或多个字符组成的有限序列,又名字符串
串的逻辑结构同属于线性结构,但与其他线性结构稍有不同的是,串针对的是字符串,不管是2010还是2021-11-9都理解为长度为4和长度为10的字符串,串中每个元素都用字符标识。而且基本操作也是更倾向于查找子串位置、得到指定位置的子串以及替换指定子串等操作。
基本概念:
- 长度:字符个数
- 空串:string=“”,长度为0
- 空格串:
string Blank = " ",Blank串中仅含一个空格,长度为1 - 字串:串中任何连续字符组成
- 主串:包含子串b的串,称为b的主串
- 真子串:串的所有字串,除了自身以外
- 字串的位置:子串中第一个字符的位置
- 串相等:长度和位置相等
串的存储结构
- 定长顺序存储 (静态数组)
- 堆分配存储 (动态数组)
- 块链存储 (链式存储)
- https://www.jianshu.com/p/65534d21e097
串的抽象数据类型
ADT 串(string)
Data
串中元素仅由一个字符组成,相邻元素具有前驱和后继关系。
operation
StrAssign (T, *chars):生成一个其值等于字符串常量chars的串T。
Strcopy (T,s):串S存在,由串S复制得串T。
clearstring (s):串s存在,将串清空。
StringEmpty(s):若串S为空,返回true,否返回false。
StrLength(s):返回串S的元素个数,即串的长度。
strCompare (s,T):若S>T,返回值>0,若S=T,返回0,若S<T,返回值<0。
Concat (T,s1,s2):用T返回由S1和S2联接而成的新串。
Substring(Sub,S,pos,len);串S存在,1≤pos≤StrLength (s),
且0≤len≤StrLength(s)-pos+1,用 Sub返回串S的第pos个字符起长度为len的子串。
Index (s,T, pos): 串S和T存在,T是非空串,1≤pos≤StrLength (s)。若主串S中存在和串T值相同的子串,则返回它在主串S中第pos个字符之后第一次出现的位置,否则返回0。
Replace (s,T,v):串s、T和V存在,T是非空串。用V替换主串S中出现的所有与T相等的不重叠的子串。
strInsert(s,pos,T):串S和T存在, 1≤pos≤StrLength(s)+1。在串S的第pos个字符之前插入串T。
strDelete (s, pos,len):串S存在,1≤pos≤StrLength(s)-len+1从串S中删除第pos个字符起长度为 len的子串。
endADT
模式匹配算法
slmgr.vbs //可以查看所有的可用参数
slmgr.vbs -dlv //列出详细的激活信息.
//含激活ID、安装ID、激活截止日期。
slmgr.vbs -dli
//列出当前操作系统的版本、一部分产品密钥、激活类型以及许可证状态。
slmgr.vbs -xpr //查看window是否激活
StrLength(s)+1。在串S的第pos个字符之前插入串T。
strDelete (s, pos,len):串S存在,1≤pos≤StrLength(s)-len+1从串S中删除第pos个字符起长度为 len的子串。
endADT
### 模式匹配算法
slmgr.vbs //可以查看所有的可用参数
slmgr.vbs -dlv //列出详细的激活信息.
//含激活ID、安装ID、激活截止日期。
slmgr.vbs -dli
//列出当前操作系统的版本、一部分产品密钥、激活类型以及许可证状态。
slmgr.vbs -xpr //查看window是否激活
win+R : msinfo32
本文详细介绍了数据结构中的线性关系,包括顺序存储和链式存储的线性表,以及栈和队列这两种受限的线性表。数组作为线性结构的特例,提供了快速访问但插入删除效率低的特点。链表则在插入和删除上具有优势,但访问速度较慢。静态链表通过数组实现链表的功能,节省了指针的存储空间。顺序表和链表是线性表的两种主要实现方式,各有优缺点。栈和队列是两种特殊操作受限的线性表,栈遵循后进先出(LIFO)原则,队列遵循先进先出(FIFO)原则。此外,文章还涵盖了串的基本概念和存储结构,以及模式匹配算法的相关知识。
342

被折叠的 条评论
为什么被折叠?



