数据结构知识点总结

来源:互联网 发布:汉诺塔问题递归算法 编辑:程序博客网 时间:2024/05/21 17:28

数据结构的基本概念

数据:所有能输入到计算机中并能被计算机程序识别和处理的符号集合。

      数值数据:整数、实数等

      非数值数据:图形、图象、声音、文字等

数据元素:数据的基本单位,在计算机程序中通常作为一个整体进行考虑和处理。

数据项:构成数据元素的不可分割的最小单位。

数据结构:相互之间存在一定关系的数据元素的集合。按照视点的不同,数据结构分为逻辑结构和存储结构。

数据、数据元素、数据项之间的关系

包含关系:数据由数据元素组成,数据元素由数据项组成。数据元素是讨论数据结构时涉及的最小数据单位,其中的数据项一般不予考虑。

逻辑结构:指数据元素之间逻辑关系的整体。数据元素的关系(逻辑结构)可分为四类:集合,线性结构,非线性结构(树形结构,图状或网状结构)。数据的逻辑结构是从具体问题抽象出来的数据模型。数据的逻辑结构在形式上可定义为一个二元组:Data_Structure = (D, R)

其中D是数据元素的有限集合,RD上关系的集合。R可以用次序对集合表示。

存储结构:又称为物理结构,是数据及其逻辑结构在计算机中的表示。存储结构实质上是内存分配,在具体实现时依赖于计算机语言。数据的存储结构又称数据的物理结构------逻辑结构在存储器中的表示(又称映像)

数据的存储结构依赖于计算机语言

 顺序存储表示

 链接存储表示

 索引存储表示

 散列存储表示

1 顺序存储结构:

    以存储位置的相邻表示后继关系(y的存储位置和x的存储位置之间是一个常量C)而C是一个隐含值,整个存储结构中只含数据元素本身的信息。

2  链式存储结构:

    以附加信息(指针)表示后继关系。需用一个和x在一起的附加信息指示y的存储位置

逻辑结构和存储结构之间的关系数据的逻辑结构属于用户视图,是面向问题的,反映了数据内部的构成方式;数据的存储结构属于具体实现的视图,是面向计算机的。一种数据的逻辑结构可以用多种存储结构来存储,而采用不同的存储结构,其数据处理的效率往往是不同的。

抽象数据类型

1.数据类型(Data Type):一组值的集合以及定义于这个值集上的一组操作的总称。(有原子类型和构造类型),例如:C++中的整型变量 。所以,在某种意义上,数据类型可以看成由一种数据结构和定义在其上的一组操作的总称。

2. 抽象数据类型(Abstract Data TypeADT:一个数据结构以及定义在该结构上的一组操作接口的总称。其定义取决于它的一组逻辑特性,  而与其在计算机内如何表示和实现无关。   以后我们用抽象数据类型来描述数据结构.

ADT有两个重要特征

1.数据抽象:用ADT描述程序处理的实体时,强调的是其本质的特征,其所能完成的功能及它和外部用户的接口(即外界使用它的方法)。

 

2.数据封装:将实体的外部特性和其内部实现细节分离,并且对外部用户隐藏其内部实现细节。

线性表的定义

线性表:简称表,是nn0)个具有相同类型的数据元素的有限序列。

线性表的长度:线性表中数据元素的个数。

空表:长度等于零的线性表,记为:L=(  )

非空表记为:L=(a1, a2 ,, ai-1, ai ,, an)

其中,ai1in)称为数据元素;

下角标 i 表示该元素在线性表中的位置或序号 。

线性表的逻辑结构

线性表的特性

1. 有限性:线性表中数据元素的个数是有穷的。

2. 相同性:本章线性表中数据元素的类型是同一的。

3. 顺序性:线性表中相邻的数据元素ai-1ai之间存在序偶关系(ai-1, ai),即ai-1ai的前驱,aiai-1的后继;a1无前驱,an无后继,其它每个元素有且仅有一个前驱和一个后继。

线性表的存储表示

线性表是一种逻辑结构

线性表的基于数组的存储表示叫做顺序表

线性表的基于指针的存储表示叫做链表(单链表、双链表、循环链表等)

顺序表:线性表的顺序存储结构,用一段地址连续的存储单元,依次存储线性表中的数据元素。

用什么属性来描述顺序表?

存储空间的起始位置

顺序表的容量(最大长度)

顺序表的当前长度(数组的长度大于等于当前线性表的长度 )

 

随机存取:在O(1)时间内存取数据元素

什么时候不能插入?

表满:last>=MaxSize-1

合理的插入位置:1ilast+2i指的是元素的序号)

顺序表的表项的插入insert算法(伪代码)

1. 如果表满了,则抛出上溢异常;

2. 如果元素的插入位置不合理,则抛出位置异常;

3. 将最后一个元素至第i个元素分别向后移动一个位置;

4. 将元素x填入位置i处;

5. 表长加1

算法时间复杂度分析

插入位置          移动元素个数
1                                      n
2                                     n-1
i                     n-i+1

n                      1
n+1                    0

时间性能分析

最好情况( i=n+1): 基本语句执行0次,时间复杂度为O(1)

最坏情况( i=1):基本语句执行n+1次,时间复杂度为O(n)

平均情况(1in+1):时间复杂度为O(n)

顺序表的优缺点

顺序表的优点:

⑴ 无需为表示表中元素之间的逻辑关系而增加额外的存储空间;

⑵ 随机存取:可以快速地存取表中任一位置的元素。

顺序表的缺点:

⑴ 插入和删除操作需要移动大量元素;

⑵ 表的容量难以确定,表的容量难以扩充;

⑶ 造成存储空间的碎片。

所以,为了克服顺序表的缺点,采用链接方式来存储线性表,这种线性表成为链表,链表非常适合频繁地插入或删除、存储空间需求变化很大的情形。

单链表 (Singly Linked List)

单链表是最简单的链表,也叫线性链表,它用指针表示结点间的逻辑关系。

特点:

每个元素(表项)由结点(Node)构成。数据域和指针域

线性结构(first为头指针)
结点可以连续,可以不连续存储

结点的逻辑顺序与物理顺序可以不一致

表扩充很方便

存储特点:

1.逻辑次序和物理次序:不一定相同。

2.元素之间的逻辑关系:用指针表示。

单链表的类定义

多个类表达一个概念(单链表)

链表结点(ListNode)

链表(List)

定义方式:复合方式,嵌套方式,继承方式

单链表

头结点:在单链表的第一个元素结点之前附设一个类型相同的结点,以便在表的不同位置,或空表和非空表处理统一。

头指针:指向第一个结点的地址。

尾标志:终端结点的指针域为空。

空表:first=NULL

非空表:

插入:单链表(a1,a2,a3……an)希望在ai之后插入新元素x

第一种情况:在第一个结点前插入

newnode->link = first ; first = newnode;

插入:单链表(a1,a2,a3……an)希望在ai之后插入新元素x

第二种情况:在链表中间插入(p指向ai)

newnode->link = p->link;p->link = newnode

插入:单链表(a1,a2,a3……an)希望在ai之后插入新元素x

第三种情况:在链表末尾插入( p指向ai

newnode->link = p->link;p->link = newnode

删除:在单链表中删除ai结点

第一种情况: 删除表中第一个元素

del=first;first=first->link;delete del;

删除:在单链表中删除ai结点

第二种情况: 删除表中或表尾元素

del=p->link;p->link=del->link;

(或p->link=p->link->plinkdelete del;

1. 在带附加头结点的单链表第一个结点前插入新结点

newnode->link = p->link; p->link = newnode;

2. 在带附加头结点的单链表中删除第一个结点

del = p->link;p->link = del->link;delete del;

单链表的实现———插入(伪代码)

1. 工作指针p初始化;             

2. 查找第i-1个结点并使工作指针p指向该结点;

3. 若查找不成功,则插入位置不合理,抛出插入位置异常;

否则,3.1 生成一个元素值为x的新结点s

      3.2 将新结点s插入到结点p之后;

单链表的输入操作

输入操作有两个实现途径:前插法和后插法

前插法:每次都在链表的前端插入新结点,这会导致链表中各结点的数据物理顺序与逻辑顺序相反

后插法:与前插法相反

前插法从一个空表(带附加头结点)开始,重复读入数据,以此数据作为data域生成新结点,将该结点插入到链表的前端,直至读入结束符。

从单链表中某结点p出发如何找到其前驱?

将单链表的首尾相接,将终端结点的指针域由空指针改为指向头结点,构成单循环链表,简称循环链表。

如何查找开始结点和终端结点?

开始结点:first->link

终端结点:将单链表扫描一遍,时间为O(n)

循环条件:

(单链表)p != NULLèp != first (循环链)

(单链表)p->link != NULLèp->link != first(循环链)

开始结点:last->link->link

终端结点:last

在循环链表里设置rear不仅仅有利于插入,删除也很方便。所以,在后面的循环链表的定义里,封装了两个指针firstlast

循环链表——插入算法描述:

s=new CircLinkNode<T>;

s->data=x;  

s->link=p->link;       

p->link=s;

双向链表简称双链表,它是为了解决单链表的问题②,即它可以在前驱和后继方向都能遍历的线性链表。

双向链表每个结点结构:

双向链表通常采用带附加头结点的循环链表形式。

结点指向
p == p->lLink->rLink == p->rLink->lLink

双向循环链表的插入分前驱方向的插入和后继方向的插入。

要求在第i个结点之后插入新结点newNode,则

后继方向:从前往后找第i个结点current,在current之后插入newNode

前驱方向:从后往前找第i个结点current,在current之前插入newNode

双向链表的后继方向插入算法 (非空表)

newnode->rLink = current->rLink;

current->rLink = newnode;

newNode->rLink->lLink = newNode;

newnode->lLink = current;

双向链表的前驱方向插入算法 (非空表)

newnode->lLink = current->lLink;

current->lLink = newnode;

newNode->lLink->rLink = newNode;

newnode->rLink = current;

结论:双向循环链表的前驱方向插入的操作代码,可以把后继方向插入的代码里的lr全部取反即可获得。

双向循环链表的删除算法

current->rLink->lLink = current->lLink;      

current->lLink->rLink = current->rLink;

delete current ;

顺序表和链表的比较

时间性能比较

时间性能是指实现基于某种存储结构的基本操作(即算法)的时间复杂度。

按位查找:

顺序表的时间为O(1),是随机存取;

链表的时间为O(n),是顺序存取。

插入和删除:

顺序表需移动表长一半的元素,时间为O(n)

链表不需要移动元素,在给出某个合适位置的指针后,插入和删除操作所需的时间仅为O(1)

空间性能比较

空间性能是指某种存储结构所占用的存储空间的大小。

结点的存储密度:

 顺序表:结点的存储密度为1(只存储数据元素),没有浪费空间;

 链表:结点的存储密度<1(包括数据域和指针域),有指针的结构性开销。

结论

⑴若线性表需频繁查找却很少进行插入和删除操作,宜采用顺序表作为存储结构;若线性表需频繁插入和删除时,则宜采用链表做存储结构。

⑵当线性表中元素个数变化较大或者未知时,最好使用链表实现;而如果用户事先知道线性表的大致长度,使用顺序表的空间效率会更高。

静态链表

静态链表:用数组来表示单链表,用数组元素的下标来模拟单链表的指针。

数组元素(结点)的构成:data:存储放数据元素;link:也称游标,存储该元素的后继在数组的下标。

栈和队列

从数据结构角度看,栈和队列是操作受限的线性表,他们的逻辑结构相同,是线性结构。

从抽象数据类型角度看,栈和队列是两种重要的抽象数据类型。

栈的定义

只允许在一端插入和删除的线性表。允许插入和删除的一端称为栈顶(top),另一端称栈底(bottom)

栈的特点:后进先出 (LIFO)

栈有两种典型存储表示,基于数组的存储表示和基于链表的存储表示。

用数组表示的栈称为顺序栈,用链表表示的栈称为链式栈。

栈空:下标top= -1,表示一个元素都没有

栈满:下标top=maxSize-1,此时再入栈会溢出

栈的空间共享

为了避免栈满发生溢出,需要设立足够大的空间,但是如果栈中实际元素很少则又造成了浪费。

在系统中往往存在多个栈,各个栈的所需空间都是动态变化的,且各栈大小也不同。

为了资源共享,试图让两个栈共享一个栈空间,希望借此解决空间问题。

双栈的优缺点

还可以nn>2)个栈共享栈空间,但是在空间利用率提高的同时,处理变得复杂,插入时元素移动量很大,时间代价太高。

在整个栈空间快满的时候插入更明显。

因此,多栈共享空间在解决空间利用率的同时,增加了很多时间代价,所以此种方法不令人满意。

下一个解决办法就是链接方式表示栈的存储。

栈的链接存储表示 — 链式栈

用单链表来表示栈,即为链式栈。

链式栈无栈满问题,空间可扩充

插入与删除仅在栈顶处执行

链式栈的栈顶在链头

思考:链式栈需要附加头结点吗?

答案是否定的。因为栈是特殊的线性表,只能在栈顶(即链表头部)插入或删除,所以不需要附加头结点。

队列 ( Queue )

定义

队列是只允许在一端删除,在另一端插入的线性表

允许删除的一端叫做队头(front),允许插入的一端叫做队尾(rear)

特性

先进先出(FIFO, First In First Out)

队列的链接存储表示 — 链式队列

队头在链头,队尾在链尾。

链式队列在进队时无队满问题,但有队空问题。

队空条件为 front == NULL

优先级队列

每次从队列中取出的是具有最高优先权的元素(查找、插入、删除)

最小优先级队列、最大优先级队列

一维数组

定义

数组是相同类型的数据元素的集合,而一维数组的每个数组元素是一个序对,由下标(index)和值(value)组成。

多维数组

多维数组是一维数组的推广。

多维数组的特点是每一个数据元素可以有多个直接前驱和多个直接后继。

数组元素的下标一般具有固定的下界和上界,因此它比其他复杂的非线性结构简单。

二维数组

一维数组常被称为向量(Vector)。

二维数组 A[m][n] 可看成是由m 个行向量组成的向量,也可看成是由n 个列向量组成的向量。

一个二维数组类型可以定义为其分量类型为一维数组类型的一维数组类型:

二维数组中数组元素的顺序存放

行优先存放:

    设数组开始存放位置 LOC(0, 0) = a,  每个元素占用l 个存储单元

         LOC ( j, k ) = a + ( j * m + k ) * l

列优先存放:

    设数组开始存放位置 LOC(0, 0) = a,  每个元素占用l 个存储单元

     LOC ( j, k ) = a + ( k * n + j ) * l

特殊矩阵

特殊矩阵是指非零元素或零元素的分布有一定规律的矩阵。

特殊矩阵的压缩存储主要是针对阶数很高的特殊矩阵。为节省存储空间,对可以不存储的元素,如零元素或对称元素,不再存储。

对称矩阵

三对角矩阵

对称矩阵的压缩存储:

为节约存储,只存对角线及对角线以上的元素,或者只存对角线或对角线以下的元素。前者称为上三角矩阵,后者称为下三角矩阵。

把它们按行存放于一个一维数组 B 中,称之为对称矩阵A 的压缩存储方式。

数组 B 共有 n + ( n - 1 ) + ......+ 1 =n*(n+1)2个元素。

需要找到一维数据与二维数组元素之间的对应关系。

稀疏矩阵 (Sparse Matrix)

设矩阵 A 中有s 个非零元素,若s 远远小于矩阵元素的总数(即sm×n),则称A 为稀疏矩阵。

e = s/(m*n),e 为矩阵的稀疏因子。

有人认为 e0.05时称之为稀疏矩阵。

在存储稀疏矩阵时,为节省存储空间,应只存储非零元素。但由于非零元素的分布一般没有规律,故在存储非零元素时,必须记下它所在的行和列的位置 ( i, j )

每一个三元组 (i, j, aij) 唯一确定了矩阵A的一个非零元素。

字符串 (String)

串中任意个连续字符组成的子序列称为该串的子串,包含子串的串相应地称为主串。

通常将子串在主串中首次出现时,该子串首字符对应的主串中的序号,定义为子串在主串中的位置。

串的模式匹配

定义 :在主串中寻找子串(第一个字符)在串中的位置

词汇 :在模式匹配中,子串称为模式,主串称为目标。

广义表 (General Lists )

广义表是 n ( 0 )个表元素组成的有限序列,记作

             LS (a1, a2, a3, , an)

LS 是表名,ai是表元素,可以是表(称为子表),可以是数据元素(称为原子)。

n为表的长度。n = 0的广义表为空表。

n > 0时,表的第一个表元素称为广义表 的表头(head),除此之外,其它表元素组成的表称为广义表的表尾(tail)。

广义表长度 :F的长度为2,广义表E 的长度为3,可见广义表中元素的“个数”应由最外层括弧中的“逗号”来定。

广义表的深度:定义为括弧嵌套的最深层次。因此对广义表来说,“空表”的深度为1,广义表EF的深度都为2

对于任意一个非空广义表 LS=(α1,α2,…,αn)它的第一个数据元素α1被定义为广义表的“表头”,而由其余数据元素构成的广义表(α2,…,αn)被定义为广义表的"表尾"

Hash基本概念

哈希方法(建表与查找)

      选取某个函数,依该函数按关键字计算元素的存储位置,并按此存放;查找时,由同一个函数对给定值k计算地址,将k与地址单元中元素关键码进行比较,确定查找是否成功。

冲突

      通常关键码的集合比哈希地址集合大得多,因而经过哈希函数变换后,可能将不同的关键码映射到同一个哈希地址上,这种现象称为冲突。

所以对于散列方法, 需要讨论以下两个问题:

1)构造好的哈希函数

a)所选函数尽可能简单,以便提高转换速度;

b)所选函数对关键码计算出的地址,应在哈希地址集中大致均匀分布,以减少空间浪费。

 

2)制定一个好的解决冲突的方案

   查找时,如果从哈希函数计算出的地址中查不到关键码,则应当依据解决冲突的规则,有规律地查询其它相关单元。

构造哈希函数的基本方法

设关键字集K中有n个关键字,哈希表长为m,即哈希表地址集为[0,m-1],则哈希函数H应满足:

1. 对任意kiKi=1,2,,n,有0H(ki)m-1

2. 对任意kiKH(ki)[0,m-1]中任一值的概率相等。

常用哈希构造方法

直接定址法

除留余数法

乘余取整法

数字分析法

平方取中法

折叠法

随机数法

1、直接定址法

取关键字key的一个线性函数为哈希函数,即:

H(key)=a×key+b,其中ab为常数,且a0

特点:这类散列函数是一对一的映射,一般不会产生冲突。但是,它要求散列地址空间的大小与关键码集合的大小相同,空间效率低。

2、除留余数法

取关键字被某个不大于哈希表表长 m 的数p 除后所得余数为哈希地址。一般情况下,pm且为质数

为什么要对 p 加限制?可见,若p 中含质因子3, 则所有含质因子3 的关键字均映射到“3的倍数”的地址上,从而增加了“冲突”的可能。

3、数字分析法

若关键字是r进制数,且可预知全部可能出现的关键字值,则可取关键字中若干位构成哈希地址。

数字分析法仅适用于事先明确知道表中所有关键码每一位数值的分布情况,它完全依赖于关键码集合。如果换一个关键码集合,选择哪几位要重新决定。

4、平方取中法

若关键字较短,则可先对关键字值求平方,然后取运算结果的中间几位为哈希地址。

5.折叠法

将关键字值分割成位数相同的几个部分,然后取这几部分的叠加和(舍去进位)作为哈希地址。

移位叠加

  将各部分按最低位对齐,然后相加;

间界叠加

  将关键字值从一端向另一端沿分界线来回折迭,然后对

齐相加。

一般当关键码的位数很多,而且关键码每一位上数字的分布大致比较均匀时,可用这种方法得到散列地址。

处理冲突的基本方法

处理冲突是指对于一个待插入哈希表的数据元素,若按给定的哈希函数求得的哈希地址已被占用,则按一定规则求下一哈希地址,如此重复,直至找到一个可用的地址以保存该元素。

开放定址法(开地址法)

链地址法(拉链法) (开散列法)

再哈希法(双哈希函数法)

建立一个公共溢出区

1、开放定址法

Hi=(H(key)+di)%mi=1,2,,m-1,其中H(key)为哈希函数,m为哈希表长,di为增量序列。

若取di=1,2,3,,m-1,则称线性探测再散列;

若取di=12,-12,22,-22,±k2,则称二次探测再散列;

若取di=伪随机数序列,则称伪随机探测再散列。

线性探测法的优点:只要哈希表未被填满,保证能找到一个空地址单元存放有冲突的元素;

线性探测法的缺点:可能使第i个哈希地址的同义词存入第i+1个哈希地址,这样本应存入第i+1个哈希地址的元素变成了第i+2个哈希地址的同义词,……,

     因此,可能出现很多元素在相邻的哈希地址上“堆积”起来,大大降低了查找效率。

解决方案:可采用二次探测法或伪随机探测法,以改善“堆积”问题。

2、再哈希法(双哈希函数法)

Hi=RHi(key)       i=1, 2,  …,k

RHi均是不同的哈希函数,当产生冲突时就计算另一个哈希函数,直到冲突不再发生。

优点:不易产生聚集;

缺点:增加了计算时间。

3、链地址法

将所有按给定的哈希函数求得的哈希地址相同的关键字存储在同一线性链表中,且使链表按关键字有序。

4、公共溢出区法

若关键字所对应的哈希地址已被占用,则保存到公共溢出区中。

开散列法优于闭散列法;在散列函数中,用除留余数法作散列函数优于其它类型的散列函数,最差的是折叠法。

在哈希表中查找元素

在哈希表中查找数据元素的过程与将数据元素插入哈希表的过程基本一致,即:根据待查关键字值,按给定的哈希函数,求哈希地址;若该地址上无数据元素,则查找失败;若该地址上有数据元素,则进行关键字值间的比较;若相等,则查找成功;若不等,则按冲突处理方法求下一可能的存储地址。

虽然哈希表在关键字值与存储位置间建立了映象,但由于冲突的存在,查找时仍需进行关键字之间的比较,因此仍以查找成功时的平均查找长度和查找不成功时的比较次数作为衡量查找效率的依据。

决定哈希表查找效率的是哈希函数、处理冲突的方法和哈希表的装填因子。

哈希表的装填因子越小,发生冲突的可能就越小,而存储空间的利用率也就越低。

排序算法

排序:将一组杂乱无章的数据按一定的规律顺次排列起来。

数据表(datalist):它是待排序数据元素的有限集合。

排序算法的稳定性: 如果在元素序列中有两 个元素r[i]r[j],

它们的关键码 k[i] == k[j] , 且在排序之前,元素r[i]排在r[j]前面。

如果在排序之后, 元素r[i]仍在元素r[j]的前面,  则称这个排序方法是稳定的,否则是不稳定的。

排序的时间开销: 是衡量算法好坏的最重要的标志。排序的时间开销可用算法执行中的数据比较次数数据移动次数来衡量。

算法执行时所需的附加存储:   评价算法好坏的另一标准。


原创粉丝点击