数据结构介绍
软件工程中有一句话,软件=数据结构+算法。所以数据结构对于开发人员是非常重要的知识。总有人觉得数据结构和算法难,其实就是忘的快,学起来也挺简单的。
以下为学习中的重点总结:
第一章 绪论
- 数据结构是一门重要的基础课程,其中包含软件、硬件、数学等知识。程序=数据结构+算法。
- 顺序存储结构就是把逻辑上相邻的元素存储在物理位置相邻的存储单元中。比如数组。
- 链式存储结构就是逻辑上相邻的元素存储在物理位置不相邻的存储单元,元素间的逻辑关系通过附加的指针字段来表示。
- 术语:
- 数据元素:数据集合中的一个“个体”。可以是一个或多个数据项。
- 数据对象:具有相同数据元素的集合。
- 数据结构:指相互之间存在一种或多种关系的数据元素的集合。元素之间的关系为结构。
- 集合结构、线性结构、树形结构、图形结构
- 逻辑结构、物理结构(顺序结构、链式结构)
- 抽象数据类型:是指一个数学模型以定义在该模型上的一组操作。可以理解为抽象类。一般由元素、关系和操作组成。
- 算法是一组解题步骤,一般具有5种特性输入、有穷性、确定性、可行性、输出。
- 空间复杂度:是指程序运行从开始到结束所需的存储量。
- 时间复杂度:是指程序运行从开始到结束所需的时间。使用“O”记号来表示的称为算法的渐进时间复杂度。
- O(1)<O(log_2n)<O(n)<O(nlong_2n)< O(n^2) < O(2^n)凡是n的对数函数、线性函数或多项式就是好算法。 凡是指数函数、或阶层函数就是坏算法。
第二章 线性表
-
线性表是具有相同类型的n个数据元素组成的有限序列。
-
顺序存储结构:用一组连续的内存单元依照线性表的逻辑顺序存放各个元素,又称顺序表、向量。例如:一维数组。
//# define MAXSIZE 100
typedef int DataType;
typedef struct {
DataType data[MAXSIZE]; --数据数组
int laset; --表长
}Seqlist;
- 创建顺序表:先预估表大小,创建数据。再填入数据,最后把表长值设置为数组最后数据的下标。
- 插入数据:先判断表是否满,再从后向前后移n位到插入位置,再插入数据,最后修改表长数值。时间复杂度为O(n)
- 删除数据:同插入,表数据需向前移动。O(n)
- 按照值查询:循环一遍比较,O(n)
- 按位查询:直接取下标值,O(1)
-
链式存储结构:不要求逻辑上相邻的元素在物理上相邻。
-
2.1单链表:每个节点中只有一个指向后继的指针。
- typedef struct node{
DataType data; --数据
struct node * next; --后继指针
}LNode,*LinkList;
LNode是结点的类型,LinkList是指向LNode类型结点的指针类型。
- LinkList p= (LinkList)malloc (sizeof(LNode)); 指针p指向一块新分配的结点。
- 为链表设置一个头指针,是链表的第一个结点,数据域为空。设置头指针的原因是为了定位链表,方便后继结点的操作。也能方便的表示空链和非空链。
- 建立单链表:O(n)
- 在链表头部插入数据,这样插入方便但之后读取数据顺序是相反的。
- 在链表尾部插入数据,需加一个指针一直指向链表尾。
- 插入数据:后插O(1)、前插O(n)因为需要从头循环找前驱结点、前插的优化(先后插、然后两个结点值互换O(1))
- 删除数据:将要删除结点的前驱指向后继,然后释放空间。O(n) 因为要循环找前驱。
- 单链表的逆置:在头结点后和第一个存放元素的结点之间,顺序不断的插入后边的结点。O(n)
-
2.4 循环链表和双向链表
- 循环链表:对于单链表而言最后一个结点的指针是空,如果将该指针指向表头,则是单循环链表。
- 增加一个尾指针。操作基本与单链表相同,就是判断链表是否为NULL,改为判断尾指针是否指向头指针。
- 双向链表:除了数据域和指向后继的指针,增加一个指向前驱的指针。
- 可以两个方向搜索。空表就是头指针的前驱和后继都是自己。
- 循环链表:对于单链表而言最后一个结点的指针是空,如果将该指针指向表头,则是单循环链表。
-
-
顺序存储结构和链式存储结构的分析
- 顺序存储结构:
- 优点:简单、不用为结点间逻辑关系加额外开销、适于随机访问。
- 缺点:插入、删除需移动表数据、需先预估表大小
- 链式存储结构:与其相反。
- 顺序存储结构:
第三章 栈和队列
-
栈和队列是两种特殊的线性表。
-
3.1栈:特殊的线性表,插入和删除运算限定在表的某一端。先进后出、后进先出。
-
3.2栈的顺序存储结构
-
#define MAXSIZE 100
typedef int DataType;
typedef struct {
DataType elem[MAXSTZE];
int top;
}SqStack;
SqStack S; --声明栈S
一般以top指向位置为栈顶,top=-1时栈为空。top=MAXSIZE-1时为栈满
-
若让多个栈使用同一个向量(连续的存储单元),则管理起来很复杂。涉及空间利用、栈溢出处理等问题。
-
-
3.3 栈的链式存储结构
-
单链表结构:
typedef char DataType;
typedef struct Lsnode{
DataType data;
struct Lsnode *next;
}Lsnode;
Lsnode * top; --声明top栈
- 栈顶设置在链头,方便入栈出栈。当top->next为NULL时为空栈。
- 入栈不用考虑栈满情况,单出栈需要考虑空栈情况。
- 多个链表栈可以放在一个顺序表中,实现同时使用多个栈的情况。
-
-
3.4栈的应用:表达式计算、子程序的嵌套、递归调用、n阶Hanoi塔问题。
-
3.5队列:特殊的线性表,插入和删除在不同的两端。先进先出。
-
3.6队列的顺序存储结构
-
typedef struct {
DataType elem[MAXSIZE];
int front,rear; --头位置与尾位置
}SeQueue;
SeQueue Q;
-
当队列为空时 front=rear
-
但随着头位置上移,顺序表之前的位置就会闲置了,会产生“假溢出”的问题。
- 解决方法:1采用平移元素法 2将队列设置为循环队列
- 但循环队列中 front=rear就不能判断队列为空了,因为队列满时也这样,所以改为头位置不存值,当(rear+1)%MAXSIZE =front 时为队列满
- (rear+1)%MAXSIZE =front 时为队列满。 rear = front 为队列空。
- 插入时下一个值的位置为 rear=(rear+1)%MAXSIZE
- 删除时下一个值位置为 front = (front+1)%MAXSIZE
-
-
3.7队列的链式存储结构
-
typedef struct NodeType
{
DataType data;
struct NodeType *next;
}NodeType;
NodeType *front, *rear;
-
空队列为 front = rear,没有满队列情况。
-
删除从链头(头指针)处删除、插入从尾指针处插入。注意删除最后一个元素时需将尾指针指向头结点。
-
第四章 串
- 串是一种特殊的表,其中每个数据元素仅由一个字符组成。从数据逻辑结构来看,串也是线性结构。也叫字符串
- 4.2串的存储与基本操作实现
- 4.2.1 定长存储:也叫静态存储。是顺序存储结构。用定长数组来存储字符。
- 4.2.2 堆串:串的堆分配存储表示即串的动态分配存储空间。特点是任使用一组连续的地址单元存放字符,但它们的存储空间是在程序执行过程中动态分配而得。即每个串的存储首地址是在算法执行过程中动态分配的。系统提供一个连续的大容量的存储区,称为“堆”,在堆中动态分配多个固定长度的地址给不同的串。
- 4.2.3 链串:使用链式结构实现串。
- 4.3 串的模式匹配:从主串中查询子串位置。
- 朴素模式匹配算法:顺序的从主串中依次与子串字符比较。O(n*m)
- 模式匹配的KMP算法:它是朴素算法的改进,时间复杂度可以到O(n+m)。改进在于:每当一趟匹配过程出现字符比较不等时,不用回溯i指针,而是利用已得到的“部分匹配结果”将模式T向右“滑动”仅可能远的距离,再进行比较。【算法实现外部连接: 字符串匹配的KMP算法 - 阮一峰的网络日志 (ruanyifeng.com)】
第五章 数组和广义表
-
5.1 数组的定义:数组是有n(n>=1) 个相同类型的数据元素组成的有限序列。也叫向量。特点是随机存取。
-
5.2 数组的结构:一般采用顺序结构。
- 一维数组:LOC(a_i)=a+(i-1)*d
- 二维数组:以行序为主序:LOC(a_i)=a+[(i-1)*n+(j-1)] *d 以列为主序:LOC(a_i) = a+[(j-1) *m + (i-1)] *d
-
5.3 特殊矩阵的压缩存储:压缩存储就是使多个元素共享存储单元。
-
特殊矩阵-对称矩阵:对称矩阵中元素时关于对角线对称的。因此,只需存储下三角或上三角部分即可。
-
特殊矩阵-三角矩阵:矩阵中,上三角或下三角(不包括对角线)中元素均为同一数据。
-
特殊矩阵-对角矩阵:所有非零的元素都集中在以主对角线为中心的带状区域中。
-
稀疏矩阵:在矩阵中大多数元素都是0,只有少数的非零元素。使用非零元素的行号、列号三元组(i,j,a_ij)来存储。
-
顺序存储:顺序存储三元表。注意:稀疏矩阵的转置。
-
链式存储:当顺序存储的矩阵元素值变化时,必将引起大量的数据移动。所以用链式存储结构表示稀疏矩阵更合适。
-
使用十字链表的结点结构:结点包括5个域稀疏矩阵的三元组和指向该行下一个元素的指针、和指向该列下一个元素的指针。
-
typedef struct OLNode{
int i,j,value; --三元组
struct OLNode *right, *down; --行指针、列指针
}OLNode;
typedef struct{
OLNode *rhead[] , *chead[]; --稀疏矩阵元素
int mu,nu,tu; --稀疏矩阵行数、列数、非零元素个数
}CrossList;
-
-
-
-
5.5 广义表:广义表是有n(n>=1)个元素组成的有限序列,其中元素a可以是单个元素,也可以是一个广义表。
-
广义表的特性:1递归、2多层。广义表的深度是指其最大的层数。
-
广义表有两个重要的操作,取表头和取表尾。表头可是是单个元素或集合,但表尾必须是集合。
-
5.2.2广义表的存储:因为有不同的结构(有递归),所以难以用顺序存储表示,链式存储比较适合。
-
头尾表示法:一对确定的表头和表尾能唯一的确定一个广义表。
-
表结点:表示列表包括 标志域、表头指针、表尾指针
-
元素结点:表示单个元素,包括 标志域、元素数据
-
typedef enum {ATOM,LIST} DataTag; --ATOM=0单元素 LIST=1子表‘
typedef struct GLNode
{ Datatag tag;
union
{Datatype atom; —元素结点的值域
struct{struct GLNode *hp, *tp;}ptr; —表结点的指针域
};
} *Glist; --广义表类型
-
表结点不存值,存的是包含哪些数据的范围。元素结点存具体值
-
-
孩子-兄弟表示法:
- 有孩子的结点:包含一个标志域、一个指向第一个孩子的指针、一个指向兄弟的指针,
- 无孩子结点:包含一个标志域、元素值域、一个指向兄弟结点的指针。
-
-
第六章 树和二叉树
- 树是一组非线性结构,与线性结构不同的是:线性结构最多有一个后继,而树可以有多个后继。相同的是:都最多只有一个前驱。
- 结点的度:结点的子树的个数。
- 树的层数:根结点层为1,往下累加。
- 树的深度:最大的层数。
- 树的表示法:文氏图法、凹入法、树形图法、广义表法。
- 6.2 二叉树:每个结点不能多于两个孩子的树。
- 重要特性:
- 1二叉树第i(i>=1)层上至多有2^(i-1)个结点。
- 2深度为 k(k>=1)的二叉树至多有2^k -1个结点。
- 3在任意二叉树中,若叶子结点个数为n0,度为1的个数为n1,度为2的为n2,则n0=n2+1
- 深度为k并且含有2^k -1个结点的树称为满二叉树。
- 每个结点的编号与满二叉树的编号相同的树,为完全二叉树。
- 4具有n个结点的完全二叉树树深为[log_2 n+1]+1。[]为向下取整
- 5若对有n个结点的完全二叉树,对于i(i>=1)有
- i=1位根
- i>1时,该结点的双亲为[i/2]
- 若2i<=n,它有编号为2i的左孩子,否则没有左孩子。
- 若2i+1<=n,它有编号为2i+1的右孩子,否则没有右孩子。
- 二叉树的存储结构
- 顺序存储结构:按照完全二叉树的编号,把元素存储在数组里。适合满二叉树、完全二叉树。
- 链式存储结构:
- 二叉链:一个数据域和两个指针域,一个指向左孩子一个指向右孩子。容易找到孩子,不容易找到父节点
- 三叉链:比二叉链多了个指向父节点的指针。
- 二叉树的遍历
- 遍历就是按一定次序将每个元素都访问一般,且只访问一次。
- 先序遍历:先访问根、再左节点、再右节点
- 中序遍历:先左节点、再根、再右。投影
- 后续遍历:先左再右再根。
- 按层遍历:从根开始,从左到右逐层遍历。
- 二叉树的递归与非递归算法:二叉树遍历java
- 二叉树的建立: 二叉树的建立及其递归遍历(C语言实现)_dream0130__的博客-CSDN博客_c语言创建二叉树
- 二叉树与树与森林的转换
- 将树转换为二叉树
- 1加线:在树中所有相邻的兄弟之间加一条线
- 2抹线:对树中的每个结点,只保留它与第一个孩子之间的连线
- 3调整:以每个结点为轴心,将其右侧所有结点按顺时针转动45度。
- 转换后左分支上的各结点在原来的树中是父子关系,右分支上个结点在原来树中是兄弟关系。
- 将二叉树转换为树:
- 1加线:若某结点是其父结点的左孩子,则把该孩子的右孩子,的右孩子。。。与父结点连接起来。
- 2抹线:删去二叉树中所有父结点与右孩子结点的连线。
- 3调整:按层排序
- 森林与二叉树的转换
- 树转换的二叉树没有右子树,所有把其他树的转换二叉树连接在其右子树上。
- 将二叉树转换为森林,反之。
- 树的存储
- 简单的顺序存储结构表示不了树,所有需要使用复杂点的表示方法
- 双亲表示法:顺序存储结构,每个结点有一个数据域和一个父结点地址的域。
- 孩子表示法:链式存储结构,一个数据域和多个指向孩子结点的指针域
- 孩子-兄弟表示法:链式,一个数据域、一个指向第一个孩子结点的指针、一个指向第一个兄弟的指针。孩子-兄弟表示法转换后的树结构就是树转置后二叉树的结构,可以按二叉树的处理方式处理树。
- 树的遍历:在树使用孩子-兄弟表示法时,其存储结构与二叉树相似,所有可以使用二叉树的遍历方法遍历树。
- 先序遍历:与二叉树相同。
- 后序遍历:与二叉树的中序遍历相同,因为树转换二叉树后没有右子树。
- 按层遍历:使用队列,按层遍历。
- 哈夫曼树:保证电报问编码中,每个字母的编码都不是另外一个字母编码的前缀。也叫最右二叉树。
- 建立方法:
- 1对所有n个权值,建立n个结点。
- 2选择2个权值最小的结点合并为一个二叉树,根结点为2个权值的和。
- 3将这个两个结点去掉,并把合成出的二叉树根值做为一个新权值,继续和其他权值进行合并。
- 4直到所有结点合并为一棵树。
- 哈夫曼编码:将形成的哈夫曼树,左分支路径编号为0,右分支路径编号为1。每个叶子结点代表的权值对于的字符,其从根结点到其的路径上的编码就是哈夫曼编码。保证了每个编码都不是另一个编码的前缀。
- 将树转换为二叉树
- 重要特性:
第七章 图
-
图是树的推广,数据间关系可以是任意的。可以定义表示为G=(V,E) 顶点与边的集合。
-
有向图< v1,v2> 无向图( v1,v2)
-
对研究的图有些限制:1不能有自身到自身的边(环),2两个顶点之间的边无向图只有一条有向图只有两条。
- 完全图:有n个顶点,无向图有n(n-1)/2 条边,有向图有n(n-1) 条边。
- 权:边上的权值
- 顶点的度:顶点的边数。
- 路径长度:两个顶点之间的边数
- 简单路径:路径长度最小。
- 连通图:任意两个顶点之间都是联通的。
- 连通分量:极大连通子量
- 强连通:对于有向图来说,任意两个顶点之间有入出两条边。 强连通分量。
-
7.2 图的存储结构
-
邻接矩阵:用一维数组保存图的顶点,用二维数据保存边。
-
无向图的左对角线全是0。
-
无向图按左对角线对称。
-
无向图每行都是该顶点的度,有向图行是出度,列是入度。
-
#define MaxVertices 100
#define MaxWeight 32767
typedef struct{
int Vertices[MaxVertices];
int Edge[MaxVertices] [MaxVertices];
int numv; --顶点数
int nume; --边数
}AdiMatrix;
-
稀疏图时不是很经济
-
-
邻接链表:对每个顶点建立一个单链表,将从同一个顶点出发的边 连接在其中。
-
头结点:出发顶点,包括值域、指向第一个连接顶点的边结点的指针。所有的头结点可以存储在顺序表中。
-
边结点:包括:指向顶点的位置、边的权值、指向头结点下一个连接顶点的边结点的指针。
-
typedef struct Vnode
{VertexType data;
ArcNode *firstarc;
} Vnode; —头结点
typedef struct ArcNode
{int adjvew; --指向顶点位置
struct ArcNode *nextarc; --下一个边结点指针
Infotype *info;
}ArcNode; --边结点
-
因为邻接链表表示的都是出度,所有求一个顶点的入度不方便。解决方法是转换为逆邻接表,以入度表示边。
-
-
图的遍历:
- 广度优先遍历:算法是一个分层搜索的过程,和树的层次遍历算法类同。
- 1从图中某顶点v0出发,首先访问v0
- 2依次访问v0的各个未被访问的邻接顶点。
- 3分别从这些邻接顶点出发,依次访问他们的各个未被访问的邻接点。
- 深度优先遍历:算法是沿着某初始点出发的一条路径,尽可能“深入”的前进。
- 1访问顶点v
- 2从v为访问邻接顶点中选一个顶点w,从w出发进行深度优先遍历。
- 3重复上树过程,直到图中所有和v有路径相通的顶点都被访问为止。
- 图的遍历之 深度优先算法和广度优先算法_hylpeace的博客-CSDN博客
- 广度优先遍历:算法是一个分层搜索的过程,和树的层次遍历算法类同。
-
-
7.4 图的最小生成树
- 一个连通图的生成树是图的极小连通子图。包含所有顶点和尽可能少的边。
- 所有生成树中,权值和最小的为最小生成树。
- 构造最小生成树的MST性质:设N=(V,{E})是一个连通网,U是顶点集的一个非空子集,若(u,v)是一条具有最小权值的边,其中u《U,v《V-U,则存在一颗含边(u,v)的最小生成树。
- 普里姆算法
- 初始TE为0,选择一条权值最小的边,并入TE最小生成树,再从最小生成树没有的顶点中,接的并入权值最小边,直到并入所有顶点。
- 克鲁斯卡算法
- 假设最小生成树为包括所有顶点V,单没有边的集合T。按照边的权值递增的顺序考察网G中的边,若被考察的边的两个顶点属于T的两个不同的连通分量,则将此边加入最小生成树T,同时把两个连通分量连接为一个连通分量。
- 当顶点多,边数少时更优
-
7.5 最短路径:两个结点间的最短路径。
-
7.6 拓扑排序与关键路径
- 用顶点表示活动,用有向边表示活动之间的优先关系的有向图被称为 用顶点表示活动的网络AOV网。
- AOV网不容许出现环。对网构造一个包含所有顶点的线性序列称为拓扑排序。
- 1从AOV网中选择一个没有前驱的顶点,输出
- 2从网中删除该顶点,并删除所有该顶点的边
- 3重复上述步骤,直到网中所有顶点输出。
- 使用链式结构存储图信息,再加个栈存储每次查找到的入度为0的结点。
- 在带权有向图中,以顶点表示事件,以边表示活动,边上的权表示活动的开销,则此图为边表示活动的网AOE网。
- 表示工程的AOE网应该是无环的,且有唯一入度为0的点。
- 具有最大路径长度的路径称为关键路径。关键路径上的活动称为关键活动。
- 求关键路径算法: 10分钟了解关键路径及如何求得关键路径_壮壮不太胖^QwQ的博客-CSDN博客_关键路径怎么求
第八章 查找
- 根据对查找表中的数据所执行的操作,可以分为静态查找表和动态查找表。
- 衡量查找算法效率的标准是平均查找长度。
- 8.2 静态查找表
- 顺序查找:数据集合中的数据依次与关键字比较。平均查找长度:(n+1)/2 时间复杂度:O(n)
- 折半查找:有称二分法查找,要求数据集中数据已排序。思想:先取中间数比较,大于则取右边子集的中间再比较,小于则取左边子集中间数比较。平均查找长度:log2n。
- 分块查找:性能介于顺序查找与折半查找中间,思想:按照数据集中的某些属性把表分成n块(子表),并建立一个相应的“索引表”,索引表记录每块中最大关键字的值和块中第一个记录的位置,且要求后一个块的所有值都要比前一个块中值大,每个块中值可以是无序的。查找时就可以按折半查找先查找索引表,在按顺序查找定位好的块。
- 8.3 动态查找
- 二叉排序树,二叉搜索树
- 左子树中值小于父结点、右子树中值大于等于父结点。
- 中序遍历就是 递增的有序序列。
- 二叉排序树的插入:插入的结点一定是叶子结点,插入后树还是二叉排序树。思想是:关键字先和根结点比较,大于则取根的右孩子比较,小于则取左孩子比较,一直到叶子结点上,小于在左,大于在右。
- 二叉排序树的建立:与插入想一致,取第一个数为根结点,其余按插入的方法来。
- 二叉排序树的删除:
- 首先查找要删除值的位置,若为叶子结点,直接删除。
- 若待删除的结点只有左子树,则用左子树代替,
- 若待删除的结点只有右子树,则用右子树代替。
- 若左右都有,则取左子树中最大的结点来代替它(或用其右子树中最小的结点来代替)。
- 二叉排序树的查找:先和根比较,小于再和左子树比较,大于和右子树比较。性能在O(log2n)—O(n)之间。
- 平衡二叉树 :AVL树
- 平衡二叉树是二叉排序树,且左右子树的深度之差绝对值不超过1.
- 深度之差有称为平衡因子,值为-1、0、1。
- 平衡二叉树的调整:
- 当插入结点导致树不平衡时,先找出最小不平衡子树,在保持二叉排序树的特性下,调整最小不平衡子树的各个结点,使之平衡。
- LL型调整、RR型调整、LR型调整、RL型调整。
- 平衡二叉树
- 二叉排序树,二叉搜索树
第九章 排序
-
当相同的关键字在排序后位置不变,则排序算法是稳定的。
-
9.2 插入排序
- 9.2.1 直接插入排序
- 将一个记录插入到一个长度为n的有序表中,使之任然有序。
- 假设第一个数为有序表,从第二个数开始,插入第一个数表示的有序表中,顺序比较表中数据,插入到合适位置,使其为一个新的有序表。依次往下插入。
- 时间复杂度:O(n^2)当表中数据基本有序或n值较小时为O(n)。稳定的。
- 9.2.2 折半插入排序
- 与之间插入排序类似,只是插入数据与有序表比较时,使用折半查找的方式,这样可以节省时间。
- O(n2)。稳定的。
- 9.2.3 希尔排序
- 先取一个正整数d1,把全部记录分成d1个分组,所有距离为d1的倍数的记录看成一组。若假设d1为4,则距离为4、8、12…的分到一组。
- 对每组数据进行组内排序。
- 然后取d2(d2<d1),一般为d1/2重复上述过程,直到d为1,再进行所有数据的排序。
- 时间复杂度大约O(n^1.3),是不稳定排序。
- 9.2.1 直接插入排序
-
9.3 交换排序
- 9.3.1 冒泡排序
- 假设第一个数为最小的数,然后依次与其后的数比较,若第一个数大于与之比较的数,则交换位置。这样依次比较后,第一位就是集合中的最小的数。然后向后重复,直到所有数据按从小到大排序。
- O(n^2)。当数据有序时O(n)。稳定的。
- 9.3.2 快速排序
- 取第一个数为关键字K1,然后把数据分为2个区,左分区为小于K1的值,右分区为大于等于K1的值。
- 然后记录K1的右分区起始位置与结束位置入栈,对左分区进行第一步操作。
- 重复第一步第二步,直到数据排序完毕。栈为空。分区大小为1个数值。
- O(nlog_2n)。但当源文件有序时O(n^2)。不稳定的。
- 9.3.1 冒泡排序
-
9.4 选择排序
- 9.4.1 简单选择排序:直接选择排序
- 选中最小值,放在未排序的集合之前。O(n^2)。稳定的。
- 9.4.2 堆排序
- 堆是n个元素的有限序列{k1,k2…kn},当且仅当满足如下关系:k_i<=k_2i k_i<=k_(2i+1) 其中i=1,2,…|n/2|。这个一个上小,低大的堆。
- 可以用完全二叉树来表示一个堆。若完全二叉树中任一非叶子结点的值小于等于(或大于等于)其左和右孩子结点的值,则从根结点开始按结点编号排列所得的结点序列就是一个堆。
- 堆排序思想:
- 1.初建堆:先以数据集原有顺序生成完全二叉树。然后从树低开始逐步向上(只遍历一遍),使树满足父结点小于等于(或大于等于)其子树结点值。
- 2.堆排序:首先输出堆顶元素(是第一步筛选出的最小值或最大值),然后让堆中最后一个元素移动到堆顶,在重新恢复成堆。
- 3.一直重复第2步,直到堆只有一个元素。
- O(nlog_2n)。不稳定的。
- 9.4.1 简单选择排序:直接选择排序
-
9.5 归并排序
- 归并排序是和插入、交换、选择不同的排序方式,其含有是将两个或多个有序的表合并为一个有序表。
- 两路归并排序
- 把记录看成n个长度为1的有序表
- 两两合并,使其成为长度为2的有序表,
- 重复第二表,直到记录成为一个长度为n的有序表。
- O(nlog_2n)。稳定。
- 多路归并排序
-
9.6 基数排序
- 基数排序是根据关键字的值,借助于“分配”和“收集”两种操作来实现。
- 当关键字为整数时,一种方式是:先按最高位有效数字进行排序,再依次到最低位。 另一种是先按最低位开始排,叫最低位优先法。O(d(n+rd)) 。稳定。
-
小结:
- 当数据规模不大时,选择直接插入、简单选择、冒泡排序。虽然时间复杂度为O(n^2),但算法简单易懂。且在数据基本有序时,直接插入和冒泡更快。
- 当n值很大,并不强调稳定性,内存不宽裕时,应使用快速排序或堆排序。快速排序对于基本有序数据时时间复杂堆为O(n^2),堆排序无最坏情况。
- 当n值很大,要求稳定,内存宽裕时,应使用归并排序。快且稳定。
- 当n值很大,且关键字位数较小时,采用静态链表基数排序较好。快且稳定。
- 超详细十大经典排序算法总结(java代码)c或者cpp的也可以明白_Top_Spirit的博客-CSDN博客_排序算法总结
第十章 索引结构与散列
-
当数据量非常大时,以致在内存中无法处理时,可以将数据以文件的形式存储在外存中。当进行查找时,将一部分数据调入内存进行处理,在查找的过程中在内存和外存之间进行数据交换。
-
10.1 静态索引结构
- 如之前讲的:分块查找。将数据分成多块,建立索引表(包括块中最大值和块的首个元素位置),后一块中的元素必须都大于前一个块中元素,但块内可以无序。
-
10.2 动态索引结构(B-树和B+树)
- B-树和B+树是一种平衡多叉树,其特点是插入、删除时易于平衡,外部查找效率高,适用于组织磁盘文件的动态索引结构。
- 10.2.1 B-树
- 定义:对于m叉树
- 1树中每个结点最多有m棵子树
- 2除根结点和叶子结点外,其他每个结点至少有m/2(向上取整)棵子树。
- 3根结点要么是一个叶子结点,要么至少有两棵子树。
- 4所有的叶子结点在同一层,叶结点不包含任何关键字信息,叶结点的双亲结点称为终端结点
- 5有K个孩子的非叶子结点包含K-1个关键字。每个结点中关键字递增有序
- B-树是平衡树,所以中序排列是一个递增数组。从根结点所在第一层开始,左子树小于根值,右子树大于根值。依次往下细分。所以就把需表示的集合一层一层按父结点划分为了大小两份。
- B-树是等高的,所以查找时对于每个关键字查找长度趋于平衡。
- 查找:
- 1从根结点开始,先和根结点中关键字比较,不相等时根据大于小于关系,继续查找其右子树或左子树。
- 2一般结点存储在磁盘中,所以查找结点在磁盘中进行,确定结点后将结点内存读到内存中,再遍历结点关键字。
- 查询效率高
- 插入:
- 插入关键字只能在终端结点中进行。有两种情况:
- 1插入关键字后树还满足B-树要求,即结点关键字数量满足要求。则不需其他修改。
- 2若结点已有m-1(m阶树)个结点,即关键字满了,再插入关键字后,则将结点分裂。并取中间值加入父结点中。若父结点也满了,则依次向上分裂。若根结点也满,则需再建一棵树或再加一层。
- 插入关键字只能在终端结点中进行。有两种情况:
- 删除:
- 删除与插入类型,略微复杂:
- 1如果删除的关键字不是终端结点中的,则先把此关键字与它在B-树中的后继关键字的位置对换,然后再删除该关键字。
- 2如果删除的关键字是终端结点中的,则直接去掉。若去掉有使结点不满足B-树条件,即结点关键字数小于(m/2)-1。则从其左或有兄弟结点中移若干关键字过来,或者与其合并结点。
- 删除与插入类型,略微复杂:
- 定义:对于m叉树
- 10.2.2 B+树
- 定义:m阶B+树
- 1有m棵子树的结点中含有m个关键字,即每个结点孩子数与结点数相同。
- 2所有叶子结点包含了全部的关键字信息,以及指向含这些关键字记录的指针。
- 3结点中关键字K对应的是其子树中最大的关键字。
- 4叶子结点本身依赖关键字的大小自小而大顺序连接,形成单链表。
- 与B-树类似,就是只有叶子结点包含关键字,其他结点只是包含其子树中的最大值,已分类关键字。
- 查找、插入、删除与B-树一致。
- 定义:m阶B+树
-
10.3键树
-
键树又称数字搜索树,它是一棵度大于等于2的树,树中每个结点中不是包含关键字值,而是只包含关键值的组成字符。如:第一次结点包含所有关键字的第一个字符,第二层包含第二个字符。。。最后使用特殊字符如“$”表示关键字结束。
-
键树有两种存储结构:双链树和Trie树
-
10.3.2 Trie树
- 以多重链表形式表示的键树。
-
10.3.3 双链树
- 键树是一个根为空的森林结构,所有可以转换为二叉树形式,使用链表存储在系统中。
-
-
10.4 哈希表及其查找
- 哈希表就是对关键字使用一个函数,计算出一个稳定值作为其物理地址的存储方式。以致于查找时不需比对关键字值,按其存储地址就能确认该关键字。
- 哈希函数:又叫散列函数,计算关键字记录在表中的存储位置。
- 哈希地址:哈希函数计算出的相对地址。
- 好的哈希函数应使所有关键字均匀的分布在哈希表中,但不同关键字计算出相同地址是难免的(冲突)。
- 10.4.2 构造哈希函数的常用方法
- 遵循的原则:1计算简单 2均匀分布
- 1平方取中法:把关键字转换为数字后求其平方值,再取中间几位作为地址。
- 2除留余数法:关键字转换为数字,与一个固定值求P余数。P值很关键,当P为小于存储区容量的素数时最好。
- 3数字分析法:根据关键字数值在各位上的分布情况,选取分布比较均匀的若干位作为地址。
- 4截断法、分段叠加法。。。
- 10.4.3 解决冲突的主要方法
- 1开发地址法:哈希值一旦冲突,就按规则向下查找一个位置填入。只有还有空位,就可以实现。寻找空位的过程叫探测,一般由一下3种:
- 1线性探测法:顺序向下查找。缺点:处理溢出问题还需另外编程、删除工作困难、容易产生聚堆现象
- 2二次探测法:再使用另一个哈希函数计算其位置。
- 3随机探测:以随机步长寻找空位。
- 2链地址法:很好的解决了冲突和溢出的问题。哈希表的每一个记录中增加一个链域,链域中存入下一个具有相同哈希函数值的记录的存储地址。
- 建立链表的方法可以分为:内链地址法(在哈希表内存中建立)外链地址法(另外申请一个附加区用于记录冲突和溢出)。外链地址放更优,但需空间增大。
- 1开发地址法:哈希值一旦冲突,就按规则向下查找一个位置填入。只有还有空位,就可以实现。寻找空位的过程叫探测,一般由一下3种:
- 10.4.4 哈希查找的性能问题
- 填装因子:已存在记录与表长之比。填装因子越大越容易冲突。平均查找长度只与填装因子有关,与表中记录个数无直接关联。
另外
其他博主的数据结构总结:
1数据结构介绍
2数据结构介绍
八大数据结构介绍
数据结构介绍
大神博主关于数据结构的整体总结 数据结构_咕叽咕叽小菜鸟的博客-CSDN博客