绪论
第一部分 线性表
一、顺序表
注意:线性表中的位序是从1开始,而数组中的元素下标从0开始。
储存:一定连续
时间复杂度:O(1)
基本操作1:初始化
基本操作2:取值
基本操作3:按值查找
二、链表
存储:非顺序
时间复杂度:O(n)
两类:带头结点和不带头结点
带头结点单链表为空:
L->next==NULL;
不带头结点单链表为空:
L==NULL;
基本操作1:初始化
基本操作2:取值
基本操作3:插入
基本操作4:删除
第二部分 栈和队列
栈的表尾为栈顶,栈的表头为栈顶。只允许栈顶元素出入栈。先进后出,后进先出。
队列只允许在队尾删除,在队顶插入。先进先出,后进后出。
栈
顺序栈
注意:top指向栈顶元素后一位。
S.top==0 //栈空
S.top==maxsize //栈满
基本操作1:初始化
基本操作2:入栈
基本操作3:出栈
基本操作4:顺序取栈顶元素
栈的操作是线性表操作的特例。
队列
链式存储与线性表类似
循环队列
需要附设front和rear两个指针表示队头和队尾。
注意:
1.front指向队头,rear指向队尾的后一个元素。
2.队列空和队列满时,front和rear都指向同一结点。
如何判断队列是空是满?
1.少用一个元素空间。
2.另设一个标志位。
基本操作1:定义储存结构
基本操作2:初始化
基本操作3:求长度
基本操作4:插入新元素
基本操作5:删除元素
第三部分 串、数组、广义表
串
储存方式:定长顺序存储、堆分配存储、块链存储
1.定长顺序存储:预定义数组长度,为每个串分配固定长度的存储区。
-1.1显式存储串长。
–1.1.1以下标为0的数组分量存放(数组)。
–1.1.2专设一个字段(结构体)。
-1.2隐式存储串长。
2.堆分配存储:一组地址连续的存储单元,但存储空间是动态分配。
3.串的块链存储:采用链式存储。一个结点可以存储多个字符。结点的字符个数就是结点大小。
数组
数组是线性表的推广,特点是结构中的元素本身可以是具有某种结构的数据,但属于同一数据类型。
一维数组:看作线性表或向量。适合随机查找。
二位数组:看作矩阵。不是线性表,是线性表的推广。
广义表
线性表的推广。可以在广义表里面嵌套广义表。
**长度:**包含数据元素个数。
**深度:**匹配最多括号的元素的括号层数。
第四部分 树和二叉树
树和二叉树
树的定义:
树是一种非线性的数据结构,它是由n个有限结点组成有层次关系的集合。
树的特点:
每个结点具有0个或多个子结点
每个子结点只有一个父结点
没有前驱的结点为根结点
除了根结点外,每个子结点又可以由m棵不相关的子树组成
树的基本术语:
1.结点的度:结点拥有的子树数量称为结点的度
2.树的度:树内各结点度的最大值,即上图 D 结点的度就是此树的度
3.叶子:度为 0 的节点称为叶子或终端节点
结点的层次和树的深度
4.森林:m棵互不相交的树的集合
*二叉树的性质:
性质1:在二叉树的第i层上至多有2i-1个结点(i>=1)。
性质2:深度为k的二叉树至多有2k-1个结点(k>=1)。
性质3:对任何一棵二叉树T,如果其终端结点数为n0,度为2的结点数为n2,则 n 0 n_0 n0 = n 2 + 1 n_2+1 n2+1。
性质4:具有n个结点的完全二叉树的深度为 ⌊ l o g 2 n ⌋ + 1 ⌊log_2n⌋ + 1 ⌊log2n⌋+1。
性质5:如果对一颗有n个结点的完全二叉树(其深度为 ⌊ l o g 2 n ⌋ + 1 ⌊log_2n⌋ + 1 ⌊log2n⌋+1)的结点按层序编号(从第1层到第 ⌊ l o g 2 n ⌋ + 1 ⌊log_2n⌋ + 1 ⌊log2n⌋+1 层,每层从左到右),对任一结点i(1<=i<=n)有:
a.如果i=1,则结点i是二叉树的根,无双亲;b.如果i>1,则其双亲是结点 ⌊ i / 2 ⌋ ⌊i/2⌋ ⌊i/2⌋;
c.如果 2i>n ,则结点 i 无孩子(结点i为叶子结点);否则其左孩子是结点 2i;
d.如果 2i+1>n ,则结点 i 无右孩子;否则其右孩子是结点 2i+1。*
二叉树的存储:顺序(通常用于完全二叉树)、链式
*二叉树的遍历方法:先序、中序、后序、层序
1.先序:
访问根结点–先序遍历左子树–先序遍历右子树(根左右)
2.中序:
中序遍历左子树–访问根结点–中序遍历右子树(左根右)
3.后序:
后序遍历左子树–后序遍历右子树–访问根结点(左右根)
4.层序:
按层次(1-k层),每层从左到右依次访问二叉树中的每一个结点。*
哈夫曼树
基本概念:
1.路径:从一个结点到另一个结点之间的分支序列。
2.路径长度:从一个结点到另一个结点所经过的分支数目。
3.结点的权:根据应用的需要可以给树的结点赋权值。
4.结点的带权路径长度:从根到该结点的路径长度与该结点权的乘积。
5.树的带权路径长度:树中所有叶子结点的带权路径之和 ∑ k = 1 n w k ∗ l k \sum_{k=1}^nw_k*l_k ∑k=1nwk∗lk。
6.哈夫曼树:由n个带权叶子结点构成的所有二叉树中带权路径长度最短的二叉树。
注意:
1.n个叶子结点的哈弗曼树有 2n-1 个结点。
2.在构造哈弗曼树时,是从叶子结点向根节点的方向进行的,每次都是两个两个成对的结点来形成一个新的分支结点,所以不存在度为1的结点。
哈夫曼树的构造
对于有n个叶子结点,可以构造出多个二叉树。
但Huffman树是一个带权路径长度最小的二叉树,又称最优二叉树。
方法:
(1)将{w1,w2,…….,wn}看成n个二叉树;
(2)选择 2 个根结点的值最小(权值最小)的二叉树,构造1个新的二叉树;…….;直至剩1个树止。
哈夫曼编码
前缀码:
如果在一个编码系统中,任一个编码都不是其他任何编码的前缀,则称该编码系统中的编码是前缀码。例如,一组编码01,001,010,100,110就不是前缀码,因为01是010的前缀,若去掉01或010就是前缀码。
哈夫曼编码:
对一棵具有n个叶子的哈夫曼树,若对树中的每个左分支赋予0,右分支赋予1,则从根到每个叶子的通路上,各分支的赋值分别构成一个二进制串,该二进制串就称为哈夫曼编码。
第六部分 图
图的定义与基本术语
图G由顶点集V和边集E组成,记为G=(V,E),其中V(G)表示图G中顶点的有限非空集;E(G)表示图G中顶点之间的关系(边)的集合。
注意:线性表可以是空表,树可以是空树,图不可以是空图,图可以没有边,但是至少要有一个顶点。
有向图
若E是有向边(简称弧)的有限集合时,则G为有向图。弧是顶点的有序对,记为<v,w>,其中 v,w 是顶点。当v 是弧尾,w 是弧头时,称为从顶点v到顶点w的弧。
无向图
若E是无向边(简称边)的有限集合时,则G为无向图。边是顶点的无序对,记为 (v,w) 或(w,v) ,且有 (v,w) =(w,v) 。其中 v,w 是顶点。
完全图
1.无向图中任意两点之间都存在边,称为无向完全图;如G1就是无向完全图。无向完全图具有 n(n-1)/2 条边。
2.有向图中任意两点之间都存在方向向反的两条弧,称为有向完全图;有向完全图具有 n(n-1)条弧。
子图:
设两个图G=(V, E)和G’=(V’,E’),如果V’⊆V且 E’ ⊆E,则称G’为G的子图。
顶点的度、入度和出度
顶点的度为以该顶点为一个端点的边的数目
对于无向图,顶点的边数为度,度数之和是顶点边数的两倍。
对于有向图,入度是以顶点为终点(即箭头所指方向),出度是以顶点为起点 (即箭尾巴所指方向)。
有向图的全部顶点入度之和等于出度之和且等于边数。顶点的度等于入度与出度之和。
注意:入度与出度是针对有向图来说的。
路径:
在图 G=( V, E ) 中, 若从顶点 x 出发, 经过一些顶点v1, v2, …… , vm到达顶点y。 则称顶点序列 ( x ,v1, v2, … Vm, y ) 为从顶点x 到顶点 y 的路径。
路径长度 :
非带权图的路径长度是指此路径上边的条数。
带权图的路径长度是指路径上各边的权之和。
简单路径:
序列中顶点不重复出现的路径。
回路(环):
第一个顶点和最后一个顶点相同的路径。
简单回路(环):
除第一个和最后一个顶点,其余顶点不重复出现的路径。
图的存储结构:邻接矩阵
图的邻接矩阵的存储方式是用两个数组来表示图。一个一维数组存储图中顶点信息,一个二维数组(称为邻接矩阵)存储图中的边或弧的信息(可分别称他们为顶点数组和边数组)。
在无向图中,求某个顶点的度,即计算此顶点 v i v_i vi在邻接矩阵中第i行(或第i列)的元素之和。若 v i v_i vi到 v j v_j vj之间有通路,则记为1,反之为0。
在有向图中,求某个顶点vi的出度,即求此顶点所在行的元素之和,若求某个顶点的度,即求顶点所在列的元素之和。
设图G有n个顶点,则邻接矩阵是一个n×n的方阵A。
邻接矩阵表示法的优缺点
优点:
便于判断两个顶点之间是否有边, 即根据A[i] [j] = 0或1来判断。
便于计算各个顶点的度。对于无向图,邻接矩阵第 i 行元素之和就是顶点 i 的度;对于有向图,第 i 行元素之和就是顶点 i 的出度,第 i 列元素之和就是顶点 i 的入度。
缺点:
不便于增加和删除顶点。
不便于统计边的数目,需要扫描邻接矩阵所有元素才能统计完毕,时间复杂度为 O( n 2 n^2 n2)
空间复杂度高。
图的存储结构:邻接表
对图中的每个顶点建立一个单链表,存储该顶点所有邻接顶点及其相关信息。每一个单链表设有一个表头结点。把从一个顶点出发的所有边链接在一个单链表(又名边链表)中。把所有边链表的表头结点放在一个顺序表(又名顶点表)中。
邻接表表示法的优缺点
优点
便于增加和删除结点
便于统计边的数目
空间效率高
缺点
不便于判断顶点之间是否有边
不便于计算有向图各个顶点的度(需要遍历)
图的遍历
深度优先、广度优先
深度优先搜索
思想:类似先序遍历。
广度优先搜索
思想:由近及远,类似层序遍历。
图的应用:拓扑排序
第七部分 查找
顺序查找
查找第i个元素需要的次数:n-i+1
要查找的元素不在表中:n+1
平均查找长度(ASL)=(n+1)/2
时间复杂度O(n)=(n+1)/2
折半查找
三个指针:low,high,middle
low,high,middle全部指向的元素即为所求
ASL= l o g 2 ( n + 1 ) − 1 log_2(n+1)-1 log2(n+1)−1
时间复杂度O(n)= l o g 2 n log_2n log2n
索引查找(分块查找)
块与块间必须有大小关系
要点:索引查找表
ASL:
内部顺序查找: 1 2 ( n s + s ) + 1 {1 \over 2 }({n \over s}+s)+1 21(sn+s)+1
内部折半查找: l o g 2 ( n s + 1 ) + s 2 log_2({n\over s}+1)+{s\over2} log2(sn+1)+2s
第八部分 排序
内排序、外排序
排序方法
一、插入排序
1.直接插入排序:稳定?稳定!
时间复杂度 O ( n ) = n 2 O(n)=n^2 O(n)=n2
2.折半插入排序
3.2-路插入排序
前面三种方法都不可避免的要移动记录
4.表插入排序(希尔排序)
算法思想:每趟取步长成子表排序,下一趟步长为上次的一半(向下取整),直到1。
时间复杂度 O ( n ) = n 1.3 O(n)=n^{1.3} O(n)=n1.3
特点:不移动记录。
增量序列:
eg:5,3,1
表示数据先每间隔5分成1组,再每间隔3分成1组,以此类推。
二、交换排序
1.冒泡排序(稳定)
排序次数:至多n-1次,至少1次
方法:从后两两对比,更小的往前放。
2.快速排序(不稳定)
算法思想:
a.通过快速排序,把待排序记录分成独立的两组。其中,一组的关键字均比另一组小。
b.采用同样的方法,对一趟快速排序产生的两个子序列进行快速排序。
c.递归执行a和b,直到子序列的记录个数为1。
*口诀:
小放枢轴左,大放枢轴右。
高低所指换,换针向枢轴。
高低所遇处,枢轴所落入。
递归再排至,左右仅一头。*
排序次数C(n): C ( n ) = n − 1 + C ( k ) + C ( n − k − 1 ) C(n)=n-1+C(k)+C(n-k-1) C(n)=n−1+C(k)+C(n−k−1)
时间复杂度O(n): O ( n ) ≤ n ∗ l o g 2 n O(n)\le n*log_2n O(n)≤n∗log2n
三、选择排序
基本思想:
每次从当前待排序的记录中选取“关键字第i(i=1,2,…,n)小的记录”, 放置在记录序列的位置i,直到所有n个记录都有序为止。
1.简单选择排序
时间复杂度 O ( n ) = n 2 O(n)=n^2 O(n)=n2
思想:先扫,再找,放入最前。
拓展:树形选择排序和堆排序
a.树形选择排序
思想:
i.借助“淘汰赛”中的对垒过程,对关键字序列进行排序。
该过程可用一棵有n个叶子结点的 完全二叉树表示。每个分枝结点的关键字都等于其左&右孩子结点中较小的关键字,根结点的关键字就是最小的关键字。
ii.输出最小关键字后,根据关系的可传递性,欲选取次小关键字,只需:
将叶子结点中的最小关键字 改为“最大值(inf)”;
然后,重复上述步骤即可。
b.堆排序(不稳定)
时间复杂度O(n)= n ∗ l o g 2 n n*log_2n n∗log2n
空间复杂度S(n)=O(1)
思想:
i.对一组待排序的记录,按堆的定义建立堆。
ii.将堆顶记录和最后一个记录交换位置,则前n-1个记录是无序的,而最后一个记录是有序的。
iii.堆顶记录被交换后,前n-1个记录不再是堆,需将前n-1个待排序记录重新组织成为一个堆;然后,将堆顶记录和倒数第二个记录交换位置,即将整个序列中次小关键字值的记录调整(排除)出无序区。
iv.重复上述步骤,直到全部记录排好序为止。
*口诀:
先建堆,再找数。
找一次,建一次。
找到数,就输出。(和最后的换位置)
输出完,排序完。*
结论:排序过程中,若采用小根堆,排序后得到的是非递减序列;若采用大根堆,排序后得到的是非递增序列。
实现堆排序的2个关键操作:
1.建堆:如何由一个无序序列建成一个堆?
2.堆调整:如何在输出堆顶元素之后,调整剩余元素,使之成为一个新的堆?
思想(后续示例,均以小根堆为例):
输出堆顶元素之后,以堆中最后一个元素替代之;
然后,将根结点值与左、右子树的根结点值进行比较,并与其中较小者进行交换;
重复上述操作,直到 A)是叶子结点 或 B)其关键字值小于等于左&右子树的关键字的值,即获得新的[小根]堆。
称: 这个从堆顶至叶子的调整过程为“筛选” 。
堆排序算法分析:
堆排序的主要时间花销:初始建堆和重新调整成堆。设记录数为n,所对应的完全二叉树深度为h。
初始建堆:每个非叶子结点都要从上到下做“筛选”。
第 i层结点数≤2i-1,结点下移的最大深度是h-i,而每下移一层要比较2次,则比较次数C1(n)为:
筛选调整:每次筛选要将根结点“下沉”到一个合适位置。
第 i 次筛选时:堆中元素个数为n-i+1;堆的深度是㏒2(n-i+1)+1,则进行n-1次“筛选”的比较次数C2(n)为:
∴ 堆排序时间复杂度T(n)=O(n㏒2n);空间复杂度S(n)=O(1) 。
附加空间: 为元素交换时, 所用的临时空间。
四、归并排序(稳定)
两有序并为一有序
方法:另建表,分别从两表头依次对比。
需要注意的是,每次归并后各个区块的长度不一定相等。
归并次数为 l o g 2 n log_2n log2n,时间复杂度 O ( n ) = n l o g 2 n O(n)=nlog_2n O(n)=nlog2n,空间复杂度 n n n。
五、基数排序/桶排序/数字排序
与前面四种的不同点:不需要进行关键字的比较和记录的移动。
查找&搜索
概念:
i.查找表(Search Table):相同类型的数据元素(对象)组成的集合,每个元素通常由若干数据项构成。
ii.关键字(Key,码):数据元素中某个(或几个)数据项的值,它可以标识一个数据元素。
若关键字能唯一标识一个数据元素,则关键字称为主关键字;将能标识若干个数据元素的关键字称为次关键字。
iii.查找/检索(Searching):根据给定的Key值,在查找表中确定一个关键字等于给定值的记录或数据元素。
查找表中存在满足条件的记录:查找成功;
查找结果:所查到的记录信息 或记录在查找表中的位置。
查找表中不存在满足条件的记录:查找失败。
查找的基本形式:静态、动态
a.静态查找(Static Search):在查找时只对数据元素进行查询或检索;
实施静态查找的查找表 称为静态查找表。
b.动态查找(Dynamic Search):在实施查找的同时,插入查找表中不存在的记录,或从查找表中删除已存在的某个记录;
实施动态查找的查找表 称为动态查找表。
采用何种查找方法?
根据存储结构的不同,查找方法可分为3大类:
i.顺序表和链表的查找:将给定的K值与查找表中记录的关键字逐个进行比较, 找到要查找的记录;
ii.散列表的查找:根据给定的K值直接访问查找表, 从而找到要查找的记录;
iii.索引查找表的查找:首先根据索引确定待查找记录所在的块 ,然后再从块中找到要查找的记录。
方法的评价指标
查找过程中主要操作是:关键字的比较。
查找过程中关键字的平均比较次数(平均查找长度ASL:Average Search Length)常作为衡量一个查找算法效率高低的标准。
ASL定义为:
Pi:查找第i个记录的概率。不失一般性,认为查找每个记录的概率相等,即P1=P2=…=Pn=1/n;
Ci:查找第i个记录需要进行比较的次数。
1.静态查找
因为线性表是查找表最简单的一种组织方式,因此本节将介绍 4种(主要的)关于顺序存储结构 的查找方法。
a.顺序查找(Sequential Search)
b.折半查找(Binary Search)
c.分块查找
d.Fibonacci查找
1.1顺序查找
思路:
从表的一端开始逐个将记录的关键字和给定K值进行比较,
若某个记录的关键字和给定K值相等,则查找成功;
若扫描完整个表,仍然没有找到相应的记录,则查找失败。
样例:
查找成功 :
查找第n个元素:1 次
……….
查找第 i个元素: n-i+1 次
查找第1个元素: n 次
**查找失败:**n+1 次(查找元素非a1,a2,…,an)
算法实现:
1.2折半查找
思路:
前提条件:查找表中的所有记录是按关键字有序(升序或降序) 。
查找时,
【首先】确定待查找记录在表中的范围,
【然后】逐步缩小范围(每次将待查记录所在区间缩小一半)
直到:找到 或 找不到 记录为止。
示例:
查找成功:
查找不成功:
算法实现:
1.3分块查找
数据组织与查找思想:
1.查找表(不同于普通的数据记录表)的组织
将查找表分成几块。
块间有序,即第i+1块的所有记录关键字均大于(或小于)第i块记录关键字;块内无序。
在查找表的基础上附加一个索引表,
索引表是按关键字有序的,索引表中记录的构成(如图所示);
2.分块查找思想
A.先确定待查记录所在块, B.再在块内’顺序’查找。
算法实现: