数据结构基础01-基本概念和术语/线性表

本文系列

数据结构基础01-基本概念和术语/线性表

数据结构基础02-栈和队列

基本概念和术语

数据(data): 所有能输入到计算机中去的描述客观事物的符号。

  • 数值性数据
  • 非数值性数据(多媒体信息处理)

数据元素(data element): 数据的基本单位,也称结点(node) 或记录(record)

数据项(data item): 有独立含义的数据最小单位,也称域(field)

数据 > 数据元素 > 数据项
例:
学生表 > 个人记录 > 学号、姓名…

数据对象(Data Object): 相同特性数据元素的集合, 是数据的一个子集。
【例1】 整数数据对象
N = { 0, ±1, ±2, … }
【例2】 学生数据对象 学生记录的集合

数据结构(Data Structure) 是相互之间存在一种或多种特 定关系的数据元素的集合。
数据结构是带“结构”的数据元素的集合,“结构”就是指 数据元素之间存在的关系。

逻辑结构: 数据之间的相互关系。

集合 结构中的数据元素除了同属于一种类型外,别无其它关系。
线性结构 数据元素之间一对一的关系
树形结构 数据元素之间一对多的关系
图状结构或网状结构 结构中的数据元素之间存在多对多的关系

物理结构

物理结构/存储结构: 数据在计算机中的表示。

物理结构是描述数据具体在内存中的存储(如:顺序结构、链式结构、索引结构、哈希结构)等

在数据结构中,从逻辑上可以将其分为线性结构和非线性结构

数据结构的基本操作的设置的最重要的准则是,实现应用程序与存储结构的独立。
实现应用程序是“逻辑结构”,存储的是“物理结构”。

逻辑结构主要是对该结构操作的设定,物理结构是描述数据具体在内存中的存储(如:顺序结构、链式结构、索引结构、希哈结构)等。

顺序存储结构中,线性表的逻辑顺序和物理顺序总是一致的。但在链式存储结构中,线性表的逻辑顺序和物理顺序一般是不同的。

什么是数据结构
程序= 数据结构 + 算法
数据结构为算法服务,算法作用于数据结构之上。

数据结构的定义
一门研究非数值计算的程序设计问题中计算机的操作对象以及 它们之间的关系和操作等等的学科。

算法

算法——是能被机械地执行的动作(或称规则、指令)的有序集合。

算法五个特性:

  • 有穷性:算法应在执行有穷步后结束
  • 确定性:每步定义都必须有确定的含义,不能有二义性
  • 可行性:每一条运算应足够基本
  • 输入:有0个或多个输入
  • 输出:有一个或多个输出(处理结果)

算法设计要求:

  • 正确性
  • 可读性:算法应该层次分明,思路清晰,易于人的理解
  • 健壮性
  • 高效率与低存储量需求。(好的算法)

算法的描述有伪程序、流程图、N-S结构图等。E-R图是实体联系模型,不是程序的描述方式。

对算法是否“正确”的理解可以有以下四个层次:
1 程序中不含语法错误;
2 程序对于几组输入数据能够得出满足要求的结果;
3 程序对于精心选择的、典型、苛刻且带有刁难性的几组 输入数据能够得出满足要求的结果;
4 程序对于一切合法的输入数据都能得出满足要求的结果;

设计算法在执行时间时需要考虑: 算法选用的规模、问题的规模

一个特定算法的“运行工作量”的大小,只依赖于问题的 规模(通常用整数n表示),或者说,它是问题规模 n 的 函数。

时间复杂度的渐进表示法
假如,随着问题规模 n 的增长,算法执行时间的增长率和 f(n) 的增长
率相同,则可记作:
T(n) = O(f(n))
称 T(n) 为算法的(渐近)时间复杂度。
渐进符号(O)的定义:当且仅当存在一个正的常数C和n0,使得对所 有的 n ≥ n0 ,有 T(n) ≤ Cf(n),则 T(n) = O(f(n))

时间复杂度: 算法的执行时间与原操作执行次数之和成正比。
时间复杂度由小到大:O(1) < O(logn) < O(n) < O(nlogn) < O(n2) < O(n3)
幂次时间复杂度由小到大:O(2n) < O(n!) < O(nn)

空间复杂度: 若输入数据所占空间只取决于问题本身,和算法无关,则只需要分析除输入和程序之外的辅助变量所占额外空间。

算法的存储量包括:
(1)输入数据所占空间
(2)程序本身所占空间
(3)辅助变量所占空间

算法的空间复杂度定义为:
S(n) = O(g(n)) 渐进空间复杂度
算法运行所需存储空间的增长率与 g(n) 的增长率相同

线性表

结构的存储

  • 元素本身的存储;
  • 元素之间关系的存储。

线性表的特点
(1) 存在唯一的一个被称为“第一个”的数据元素;
(2) 存在唯一的一个被称为“最后一个”的数据元素;
(3) 除第一个外,集合中的每个数据元素均且只有一个前驱;
(4) 除最后一个外,集合中的每个元素均有且只有一个后继。

线性表的定义

  • n个类型相同的数据元素构成的有限序列,记作a1,a2,…,an ;
  • 其中1,2, …,n是元素的序号,表示元素在表中的位置;
  • n为元素的总个数;
  • a1,a2,…,an 为数据元素;
  • a1表示线性起点,an表示线性终点;
  • ai-1是ai的直接前驱,ai+1是ai的直接后继;  当n等于0,称为空表。

同一性、有限性、有序性。

线性表的存储方式
顺序表:用顺序方式存储的线性表
链表:是用链式方式存储的线性表。

顺序表

把线性表的结点按逻辑顺序依次存放在一组地址连续的存储单元里。用这种方法存储的线性表简称顺序表。是一种随机存取的存储结构。
顺序存储指内存地址是一块的,随机存取指访问时可以按下标随机访问,存储和存取是不一样的。如果是存储,则是指按顺序的,如果是存取,则是可以随机的,可以利用元素下标进行。

数组比线性表速度更快的是:原地逆序、返回中间节点、选择随机节点。

便于线性表的构造和任意元素的访问
插入:插入新结点,之后结点后移。平均时间复杂度:O(n)
删除:删除节点,之后结点前移。平均时间复杂度:O(n)

顺序存储元素地址计算
可以在已知第一个元素的存储起始地址,以及每个元素所占用存储单元个
数的基础上,通过一个公式来直接计算表中任意指定元素的存储地址:
LOC(ai)=LOC(a1)+(i-1)*d
d : 一个元素占用的存储单元个数
LOC(ai) : 线性表第i个元素的起始地址
LOC(a1) : 起始地址,基地址

顺序存储的特点
逻辑上相邻 —— 物理地址相邻 
随机存取

顺序表的结构体数据类型

typedef struct{
    Elemtype* elem; // 存储的是数组第一个元素的地址
    int length; // 顺序表的当前长度
}SqList; //定义了结构体数据类型SqList,用于表示顺序表 数据类型

顺序表的查找操作

查找的两种情况
(1) 根据给定元素的序号进行查找(通过数组下标定位)
(2) 根据给定的元素值进行查找
查找的基本思想
将给定的元素e和顺序表中的每个元素依次进行比较:
若找到与e相等的元素,则查找成功,返回其在表中的“位序”值;
若找遍整个顺序表,都没有找到与e相等的元素,则查找失败,返回值0。

int Locate_Sq ( SqList L, ElemType e )
{
	i = 1; n = L.length;
	while ( i <= n && e != L.elem[i] )
		i++;
	if ( i <= n && e== L.elem[i] )
		return(i); 
	else
		return(0); 
}

顺序表的插入算法:

int Insert_Sq(SqList &L, int i, Elemtype x)
{
	if(i<1||i>L.length+1) 
	return ERROR; //i值不合法
	if ( L.length >= MAXSIZE-1 ) 
	return ERROR; // 存储空间已满
	for ( k=L.length; k>=i; k-- ) 
	L.elem[k+1] = L.elem[k];
	L.elem[i] = x; L.length++;
	return OK;
}

顺序表的删除算法:

Status Delete_Sq(SqList &L, int i, Elemtype &e)
{
	if(i<1||i>L.length) 
	return ERROR; //i值不合法
	e = L.elem[i];
	for( k=i; k<=L.length-1; k++ )
	L.elem[k]=L.elem[k+1]; 
	L.Length -- ;
	return OK;
}

链表

用一组任意的存储单元来依次存放线性表的结点,这组存储单元即可以是连续的,也可以是不连续的,甚至是零散分布在内存中的任意位置上的。
因此,链表中结点的逻辑次序和物理次序不一定相同。为了能正确表示结点间的逻辑关系,在存储每个结点值的同时,还必须存储指示其后继结点的地址。
data域是数据域,用来存放结点的值。
next是指针域(亦称链域),用来存放结点的直接后继的地址(或位置)。
不需要事先估计存储空间大小。

顺序表的优点
1 逻辑上相邻的两个元素在物理位置上也相邻;
2 可随机存取;
3 它的存储位置可用一个简单直观的公式来表示。
顺序表的缺点
1 需预分较大空间;
2 插入、删除需移动大量元素;
3 表的容量难以扩充。

链表分类
线性链表(单链表)
循环链表
双向链表

单链表的定义

单链表中每个结点的存储地址是存放在其前趋结点next域中,而开始结点无前趋,故应设头指针head指向开始结点。同时,由于最后一个结点无后继,故结点的指针域为空,即NULL。

头插法建表(逆序)、尾插法建表(顺序)。
增加头结点的目的是算法实现上的方便,但增大了内存开销。

1链式存储的线性表;
2每个结点中只含有一个指针域,用来指出其后继结点的位置;
3最后一个结点没有后继,它的指针域为空(记为NULL或^);
4设置一个表头指针head(H),指向链表的第一个结点。

概念
首结点:用于存储线性表中第一个数据元素的结点
头结点:链表的首结点前附加的一个结点
头指针:是指向单链表的第一个结点的指针。

typedef struct LNode { 
	ElemType data; //数据域 
	struct LNode *next; //指针域
} LNode,* LinkList;

LinkList: 单链表
LNode * :指向单链表中任意结点的指针变量

LNode *p; <==> LinkList p;

查找:只能从链表的头指针出发,顺链域next逐个结点往下搜索,直到搜索到第i个结点为止。因此,链表不是随机存取结构。
插入:先找到表的第i-1的存储位置,然后插入。新结点先连后继,再连前驱。
删除:首先找到ai-1的存储位置p。然后令p–>next指向ai的直接后继结点,即把ai从链上摘下。最后释放结点ai的空间.r=p->next;p->next=r->next;delete r。
判断一个单向链表中是否存在环的最佳方法是快慢指针。

静态链表

用一维数组来实现线性链表,这种用一维数组表示的线性链表,称为静态链表。
静态:体现在表的容量是一定的。(数组的大小);
链表:插入与删除同前面所述的动态链表方法相同。
静态链表中指针表示的是下一元素在数组中的位置。

静态链表是用数组实现的,是顺序的存储结构,在物理地址上是连续的,而且需要预先分配大小。

动态链表是用申请内存函数(C是malloc,C++是new)动态申请内存的,所以在链表的长度上没有限制。
动态链表因为是动态申请内存的,所以每个节点的物理地址不连续,要通过指针来顺序访问。
静态链表在插入、删除时也是通过修改指针域来实现的,与动态链表没有什么分别

循环链表

是一种头尾相接的链表。从表中任一结点出发均可找到表中其他结点,提高查找效率。

在单链表中,将终端结点的指针域NULL改为指向表头结点的或开始结点,就得到了单链形式的循环链表,并简单称为单循环链表。

由于循环链表中没有NULL指针,故涉及遍历操作时,其终止条件就不再像非循环链表那样判断p或p—>next是否为空,而是判断它们是否等于某一指定指针,如头指针或尾指针等。

双向链表

双向链表:在单链表的每个结点里再增加一个指向其直接前趋的指针域prior。
这样就形成的链表中有两个方向不同的链。
双链表一般由头指针唯一确定的,将头结点和尾结点链接起来构成循环链表,并称之为双向链表。
设指针p指向某一结点,则双向链表结构的对称性可用下式描述:
p—>prior—>next=p=p—>next—>prior。
从两个方向搜索双链表,比从一个方向搜索双链表的方差要小。

插入:先搞定插入节点的前驱和后继,再搞定后结点的前驱,最后搞定前结点的后继。

在有序双向链表中定位删除一个元素的平均时间复杂度为O(n)
可以直接删除当前指针所指向的节点。
而不需要像单向链表中,删除一个元素必须找到其前驱。因此在插入数据时,单向链表和双向链表操作复杂度相同,而删除数据时,双向链表的性能优于单向链表

猜你喜欢

转载自blog.csdn.net/u013728021/article/details/84539826
今日推荐