加工处理的对象:纯粹的数值
工业检查
过程控制
管理系统
文字处理
...
各种符号的集合
数据的基本单位(用于完整描述一个对象)
构成数据元素的最小单位
(常用单位)性质相同的数据元素的集合,是数据的一个子集
线性表
栈(特殊线性表)
队列(特殊线性表)
字符串、数组、广义表
树形结构
图形结构
顺序结构
链式结构
检索、排序、插入、删除、修改等
一组性质相同的值的集合,以及定义这个值集合上的一组操作的总称
从具体问题抽象出来的一个数学模型,以及定义在此数学模型上的一组操作
包含三部分:数据对象、数据对象上关系的集合、对数据对象的基本操作的集合
抽象数据类型的实现
对特定问题的求解方法和步骤的一种描述
有穷性
确定性
可行性
输入
输出
正确性
健壮性
可读性
高效性
好的算法首先符合算法设计的要求
变量t——
数组b[n]——
问题的规模
待处理数据的初态
事前分析估算法
事后统计法
不考虑计算机的软硬件等环境因素,影响算法时间代价的最主要因素是问题规模
设每条语句执行依次所需的时间均是单位时间,则一个算法的执行时间可用该算法中所有语句频度之和来度量
其中元素的个数 定义为线性表的长度
时称为空表
InitList(&L)
ListLength(L)
GetElem(L,i,&e)
LocateElem(L,e)
ListInsert(&L,i,e)
ListDelete(&L,i)
顺序存储结构/顺序映像:用一组地址连续的存储单元依次存储线性表的数据元素
特点:逻辑上相邻的数据元素,其物理次序也是相邻的
...
只要确定了存储线性表的起始位置,线性表中任一数据元素都可随机存取
所以线性表的顺序存储结构是一种随机存取的存储结构
通常用数组来描述数据结构中的顺序存储结构
数组是将相同类型的元素存储于连续内存空间的数据结构,其长度不可变
// 初始化一个长度为 5 的数组 array
int array[5];
// 元素赋值
array[0] = 2;
array[1] = 3;
array[2] = 1;
array[3] = 0;
array[4] = 2;
int array[] = {2, 3, 1, 0, 2};
1、为顺序表 动态分配一个预定义大小的数组空间,使 elem 指向这段空间的基地址
2、将表的当前长度设为 0
1、判断制定的位置序号 i 值是否合理 (1<=i<=L.length) 若不合理,则返回 ERROR
2、若 值合理,则将第 i 个数据元素 L.elem[i-1]赋给参数 e ,通过 e 返回第 i 个数据元素的传值
由于顺序存储结构具有随机存取的特点,可以直接通过下标定位得到
顺序表取值算法的时间复杂度为
1、从第一个元素起,依次和 e 相比较,若找到与 e 相等的元素 L.elem[i],则查找成功,则返回该元素的序号
2、若查遍整个顺序表都没有找到,则查找失败,返回 0
顺序表按值查找算法的平均时间复杂度为
一般的,在第 个位置插入一个元素,需从最后一个元素即第 个元素开始,依次向后移动一个位置,直至第 个元素,仅当插入位置 时,才无须移动结点
顺序表插入算法的平均时间复杂度为
1、判断删除位置 i 是否合法,若不合法则返回ERROR
2、将第 i+1 个至第 n 个的元素依次向前移动一个位置(i=n时无须移动)
3、表长-1
顺序表删除算法的平均时间复杂度为
用一组任意的存储单元存储线性表的元素。这组存储单元可以是连续的,也可以是不连续的
单链表的每个结点中只包含一个指针域
单链表是非随机存取的存储结构。要取第i个据元素必须从头指针出发顺链进行寻找也称为顺序存取的存储结构
便于首元结点的处理
并于空表和非空表的统一处理
首元结点是指链表中存储的一个数据元素的结点
头结点是在首元节点之前附设的一个节点,其指针域指向首元结点
头指针是指链表中第一个结点的指针
单链表的初始化操作就是构造一个空表
1)生成新的结点作为头结点。用头指针L指向头结点。
2)头结点的指针域置空
1)用指针 P 指向首元结点,用 j 做计数器,初值赋为 1
2)从首元结点开始依次顺着链域 next 向下访问,只要指向当前结点的指针 P 不为空,并且没有达到序号 i 的结点,则循环执行以下操作
p 指向下一个结点
计数器 j 相应加 1
3)退出循环时,如果指针 p 为空或者计数器 j 大于 i,说明指定的序号 i 值不合法(i 大于表长 n 或 i 小于等于 0)取值失败返回 ERROR;否则取值成功。此时若 j 等于 i,p 所指向的节点就是要找的第 i 个结点,用参数 e 保存当前节点的数据域,返回OK。
算法平均时间复杂度为 O(n)
链表中按值查找的过程和顺序表类似。从链表首元结点出发,依次将节点值的和给定值 E 进行比较,返回查找结果。
1)用指针P指向首元结点
2)从首元结点开始,一次顺着链域next向下查找,只要指向当前结点的指针P不为空。并且P所指结点的数据域不等于给定值E,则循环执行操作:P指向下一个节点
3)返回P,若查找成功批次实际为节点的地址值,若查找失败,P的值即为null
算法平均时间复杂度为 O(n)
s->next=p->next;p->next=s
1)查找结点 a(i-1) 并由指针 p 指向该结点
2)生成一个新结点 *s
3)将新结点 *s 的数据域置为e
4)将新结点 *s 的指针域指向a(i)
5)将结点 *p 的指针域指向新结点 *s。
算法平均时间复杂度为 O(n)
要删除单链表中指定位置的元素,同插入元素一样。首先应该找到该位置的前驱节点
1)查找结点 a(i-1) 并由指针 p 指向该结点
2)临时保存待删除结点 a(i) 的地址在 q 中。以备释放。
3)将结点 *p 的指针域指向 a(i) 的直接后继结点
4)释放节点 a(i) 的空间
p->next=p->next->next
算法平均时间复杂度为 O(n)
前插法:是通过将新结点逐个插入链表的头部(头结点之后)来创建链表
后插法:将新结点逐个插入到链表的尾部来创建链表
p.next ——> q.next
s.next ——> a2
p.next ——> x
1、t->prior->next = t->next
2、t->next->prior = t->prior
原则:保证链不断
在循环链表中,从任意一个节点出发都可以找到表中其他节点称作单链的循环链表,简称单循环链表
在链表的每一个结点中设置两个指针,一个指向后继结点,一个指向前驱结点。形成双链表,简称双链表。
当线性表的长度变化比较大,难以预估存储规模时,宜采用链表作为存储结构
当线性表的长度变化不变,易于事先确定其大小时,以采用顺序表作为存储结构
顺序表是由数组实现的,它是一种随机存取结构,取值操作的效率高
链表是一种顺序存取结构,按位置访问。取值操作的效率低
若线性表的主要操作是和元素位置紧密相关的取值操作,很少做插入或删除时已采用顺序表作为存储结构。
对于频繁进行插入或删除操作的线性表一采用链表作为存储结构
1)分别获取 LA 表长 m 和 LB 表长 n
2)从 LB 中第 1 个数据元素开始,循环 n 次执行以下操作
从 LB 中查找第 i 个元素赋给 e
在 LA 中查找元素一如果不存在,则将 E 插入在 LA 的最后
1)创建一个表长为 m+n 的空表 LC
2)指针 pc 初始化,指向 LC 的第一个元素
3)指针 pa 和 pb 初始化,分别指向 LA 和 LB 的第一元素
4)当指针 pa 和 pd 均未到达相应表尾时,则依次比较 pa 和 pd 所指向的元素值,从 LA 或 LB 中取元素值较小的结点插入到 LC 的最后
5)如果 pb 已到达 LB 的表尾,依次将 LA 的剩余元素插入到 LC 的最后
6)如果 pa 已到达 LA 的表尾,依次将 LB 的剩余元素插入到 LC 的最后
1)指针 pa 和 pd 初始化,分别指向 LA 和 LB 的第一结点
2)LC 的结点取值为 LA 的 头结点
3)指针 pc 初始化,指向 LC 的头结点
4)当指针 pa 和 pb 均未到达相应表尾时,则依次比较 pa 和 pb 所指向的元素值,从 LA 或 LB 中取元素值较小的结点插入到 LC 的最后
5)将非空表的剩余部分插入 pc 所指结点之后
6)释放 LB 的头结点
栈是一种具有 「后进先出」 特点的抽象数据结构,可使用数组或链表实现。
栈是限定仅在表尾进行插入或删除操作的线性表,他也有两种存储表示方法
存储空间预分配,可能会出现空间闲置或栈满溢出现象
是指采用链式存储结构实现的栈,通常采用单链表来表示
存储空间动态分配,不会出现空间闲置或栈满溢出现象
元素不一定排好队连续地进栈,有时某一元素可能会进栈后立刻出栈,然后下一个元素才会进栈。 也就是说元素进栈后,不一定会依次出栈
为顺序栈动态分配一个预定义大小的数组空间
构造一个空栈,直接将栈顶指针置空即可
入栈操作是指在栈顶插入一个新的元素,需要判断栈是否满
通过常用操作「入栈 push()」,「出栈 pop()」,展示了栈的先入后出特性
stk.push(1); // 元素 1 入栈
stk.push(2); // 元素 2 入栈
stk.pop(); // 出栈 -> 元素 2
stk.pop(); // 出栈 -> 元素 1
与顺序栈不同的是,链栈在入栈前不需要判断栈是否满,只需要为入栈元素动态分配一个结点空间
出栈操作是将栈顶元素删除,需要判断栈是否空
与顺序栈一样,链栈在出栈前也需要判断栈是否为空,不同的是,链栈在出栈后需要释放出栈元素的栈顶空间
当栈非空时,此操作返回当前栈顶元素的值,栈顶指针保持不变
与顺序栈一样,当栈非空时,此操作返回当前栈顶元素的值,栈顶指针S保持不变
StackEmpty(S)
(1)能够将一个问题转变成一个新问题,而新问题与原问题的解法相同或类同,不同仅是处理的对象,并且这些处理对象更小,而且变化有规律。
该问题的规模缩小到一定的程度就可以容易地解决
该问题可以分解为若干个规模较小的相同问题
利用该问题分解出的子问题的解可以合并为该问题的解
该问题所分解出的各个子问题是相互独立的
(2)必须有一个明确的递归出口,或称递归的边界
分解——解决——合并
数据结构是递归的
问题的解法是递归的
当有多个函数构成嵌套调用时,按照“后调用先返回”的原则,调用函数和被调用函数之间的链接及信息交换必须通过栈来实现。
递归算法:
计算Hanoi塔递归算法的时间复杂度:
递归算法:
计算阶乘问题、Hanoi塔问题的递归算法的空间复杂度
利用栈将递归转换为非递归的方法
递归:通过重复将问题分解成同类的子问题而解决问题的方法
通过调用函数自身来进行递归
优缺点:结构清晰、可读性强;但效率低,调用栈层数太多时可能会溢出
队列是一种具有 「先进先出」 特点的抽象数据结构,可使用链表实现。
队列又称先入先出表,FIFO表
队列是限定只在表头(队头)进行删除操作,只在表尾(队尾)进行插入的线性表
队列也有两种存储表示,顺序和链式表示
queue que;
通过常用操作「入队 push()」,「出队 pop()」,展示了队列的先入先出特性
que.push(1); // 元素 1 入队
que.push(2); // 元素 2 入队
que.pop(); // 出队 -> 元素 1
que.pop(); // 出队 -> 元素 2
循环队列可以解决顺序队列的“假溢出”问题
对于非循环队列,尾指针和头指针的差值就是队列长度
循环队列则:(tail-head+size)%size
入队:队尾插入一个新的元素
出队:将队头元素删除
取队头元素
链队是指采用链式存储结构实现的队列
链队的初始化:构造一个只有一个头结点的空队
链队再出队后需要释放队头元素的所占空间
取队头元素
一个以集合为基础的抽象数据类型,其结点必定包括一种性质:有序
优先队列中的每一个元素都有一个优先级
有序链表
二叉搜索树
无序链表
优先级树
优先级堆
左偏树
左偏树的合并运算
串是由零个或多个字符组成的有限序列
串中所包含的字符个数
串中所包含的字符个数+1('\0')
零个字符的串称为空串,其长度为零,它不包含任何字符,是任意串的子串
串中任何个连续字符组成的子序列
包含子串的串
由一个空格串组成的串称为空格串,其长度为串空格字符的字数
空格串不是空串
当且仅当两个串的值相等时称两个串是相等的
主串:n
子串:
(1)求串长:int strlen(char *s)
(2)串复制:char *strcpy(char *to,char *from)
(3)串联接:char *strcat(char *to,char *from)
(4)串比较:int strcmp(char *s1,char *s2)
简称“链串”
在串匹配中,将主串称为目标串,子串称为模式串
通常用递归的形式进行定义
线性和非线性之间的一种过渡结构
长度为 3
深度为 3
D=(a,D)=(a,(a,(a,(...))))
长度为 2
深度为
E是一个空表,长度为0
L是长度为2的广义表,两个元素都是原子,因此它是一个线性表
A是长度为2的广义表,第一个元素是原子x,第二个元素是子表L
B是长度为2的广义表,第一个元素是表A,第二个元素是原子y
C=(A,B)=((x,(a,b)),((x,(a,b)),y))
C是长度为2的广义表,两个元素都是子表
(2)广义表可为其他广义表所共享
D=(a,D)=(a,(a,(a,(...))))
D是一个递归的表,长度为2,第一个元素是原子a,第二个元素是D自身,展开后它是一个无限的广义表
深度为 1 的表的第一个元素 为表头
可以是广义表或单个元素
除表头外的所有元素 为表尾
其结果必定是广义表(子表)的形式
高阶矩阵包含许多相同的元素和零元素,为节省空间
采用 多个值相同的元素只分配一个空间、零元素不存储的策略
有且只有一个称之为根的结点
除根之外的其余结点可以分为m(m>0)颗子树,称为根的子树
树中的一个独立单元,包含一个数据元素及若干指向其子树的分支
树中一结点的子节点的数量
一颗树中的所有结点的度的最大值
终端结点,指度为 0 的结点(无子结点的结点)
度不为 0 的结点,也称为内部结点
非根结点、非叶子结点
一个结点的所有子结点
结点的子树的根称为该结点的孩子,相应地,该节点称为孩子的双亲或父亲
同一个双亲的孩子之间互称兄弟
双亲再同一层的结点互为堂兄弟
树中结点的各子树从左至右是有次序的
树中结点的各子树从左至右无次序
根为第一层,根的孩子为第二层,以此类推,某结点在第 i 层,其孩子结点在第 i+1 层
一棵树的最大层数
m(m>=0)棵互相不相交的树的集合
二叉树是每个结点最多有两个子树的有序树,通常子树的根被称作“左子树”和“右子树”
有且仅有一个称之为根的结点
除根结点之外的其余结点分为两个互不相交的左子树和右子树,且左右子树本身也是二叉树
1)空二叉树
2)只有一个根结点的二叉树
3)只有左子树的二叉树
4)只有右子树的二叉树
5)左、右子树结点均非空的二叉树
深度为 k 的二叉树有 个结点,称为满二叉树
可以对二叉树中的结点进行连续编号,编号从根结点起,自上而下、从左至右依次进行
除了最后一层,其余各层都是满的,
且再最后一层上的结点必须从左到右依次放置,不能留空(最后一层不要求满)
例如:第 3 层上最多有 4 个结点
例如:深度为 3 的二叉树最多有 7 个结点
(3)对任何一颗二叉树,如果其叶子结点数为 ,度为 2 的节点数为 ,则
度为 1 的结点
(4)叶子结点比度为2的结点多1个
(5)具有n个结点的完全二叉树深度为
建立此二叉树需要实例化每个节点,并构建各节点的引用指向
// 初始化节点
TreeNode *n1 = new TreeNode(3); // 根节点 root
TreeNode *n2 = new TreeNode(4);
TreeNode *n3 = new TreeNode(5);
TreeNode *n4 = new TreeNode(1);
TreeNode *n5 = new TreeNode(2);
// 构建引用指向
n1->left = n2;
n1->right = n3;
n2->left = n4;
n2->right = n5;
将二叉树的所有结点,按照一定的线性次序存储到连续的存储单元中,使得结点在这个序列中的互相位置能反映处结点之间的逻辑关系
顺序存储结构适合用于完全二叉树
链式存储结构适合用于一般二叉树
空结点:n+1
空结点:n+2
表示二叉树的链表中的结点至少包括3个域“二叉链表”
左指针域
数据域
右指针域
有时为了便于找到结点的双亲,还可在结点结构中增加一个指向其双亲结点的指针域,“三叉链表”
左指针域
数据域
双亲指针域
右指针域
遍历二叉树算法描述:是指按某条搜索路径访问树中每个结点,使得每个结点均被访问一次,且仅被访问一次
先访问根节点——>前序遍历左子树——>前序遍历右子树
前缀表达式(+ab)
中缀表达式(a+b)一般式
可利用栈进行求值
相当于二叉树的后序遍历
ab-c5+*
保存(表现出)二叉树在遍历(前、中、后序)的动态过程中得到一个结点的前驱和后继信息
0:lchild域指示结点的左孩子
1:lchild域指示结点的前驱
0:rchild域指示结点的右孩子
1:rchild域指示结点的后继
若需经常查找结在所遍历线性序列中的前驱和后继,则应采用线索链表作为存储结构
前序遍历:ABDEHCFGI
中序遍历:DBHEAFCGI
后序遍历:DHEBFIGCA
树、森林与二叉树之间存在一一对应的关系,它们之间可以相互转换
“关键在于遍历的顺序与第一个孩子”
R-A-D-E-B-C-F-G-H-K
D-E-A-B-G-H-K-F-C-R
1、非空
2、第一棵树的根结点
3、前序遍历第一棵树根节点的子树森林
4、前序遍历除第一颗树外剩余的树构成的森林
A-B-C-D-E-F-G
1、非空
2、中序遍历第一棵树的根节点的子树森林
3、访问第一棵树的根结点
4、中序遍历除去第一棵树外剩余的树构成的森林
B-C-D-A-F-E-G
所谓最优二叉树(哈夫曼树)就是该树带权路径最短的树
一个结点到另一个与之直接相的连结点的路径为 1
结点权
边权
权值 * 树的路径长度
所有叶子结点的带权路径(从根开始)长度之和
记作WPL
享做笔记
1、将有权值的叶子结点按照从小到大的顺序排列成一个有序序列。
2、选取与合并:在有序序列中选出两个权值最小的树作为一棵新二叉树的左、右子树(左 < 右),新二叉树的根结点的权值为这两个子树根结点的权值之和。然后从有序序列中删除这两棵子树,并把新建的二叉树加入到序列中。重复这个过程直到序列中只剩下一棵树为止。
3、构建好Huffman树后,规定将树中每个分支结点的左分支标上“0”,右分支标上“1”,把从根到每个叶子的路径符号(“0”或“1”)连接起来构成一个二进制编码,作为该叶子的哈夫曼编码。
(1)哈夫曼编码是前缀编码
(2)哈夫曼编码是最优前缀编码
任意结点的左右子树深度相差不超过 1
-1
0
1
(根结点)平衡度 = 左子结点深度 - 右子结点深度
左子结点小于根
右子结点大于根
1、若该键值结点已存在,则不再插入
2、若查找二叉树为空树,则以新结点为查找二叉树
3、将要插入结点键值与插入后父结点键值比较,就能确定新结点是左子结点还是右子结点
1、若待删除结点是叶子结点,则直接删除
2、若待删除结点只有一个子结点,则将这个子结点与待删除结点的父结点直接连接
3、若待删除的结点p有两个子结点,则在其左子树上,用中序遍历寻找键值最大的结点s,用结点s的值代替结点p的值
输入次序:45,24,53,12,90
图是一种非线性数据结构,由「节点(顶点)vertex」和「边 edge」组成,每条边连接一对顶点。根据边的方向有无,图可分为「有向图」和「无向图」。
所有边都是无方向的图
有向边
有向边的起点
有向边的终点
无向完全图:n(n-1)/2
有向图中每一对顶点之间都有两条方向相反的弧(有向边)相连,可称完全图 无向完全图:n(n-1)
有很少边或弧的图
任一顶点所关联的边的数目
若为有向图中的顶点,顶点的度表示该顶点的出度与入度之和 以该顶点为终点的有向边的数目
以该顶点为起点的有向边的数目
边(弧)带权值的图称为网
第一个顶点和最后一个顶点相同的路径称为回路或环
无向图中任意两个点都是连通的
有向图中任意两个点都是连通的
若连通图有n个顶点,则至少有n-1条边,其生成树恰好有n-1条边 连通图的生成树是该图的极小连通子图
所谓树,不能存在回路
(c)就存在回路,所以不是生成树
不同的顶点
选择不同的存储方式
用不同的求解方法
适合于求边稠密的网的最小生成树
1、代价最小的边所连的顶点加入 A 集合,剩余的顶点与边为 B 集合,
2、寻找 A 集合中顶点的相连且代价最小的边,连通其连接的另一顶点加入 A 集合,
3、重复第二步,直到集合B再无顶点
(注意:生成树中不能存在回路)
每次选择权值最小的时,可能存在多条同样权值的边,任选一条即可
适合于求边稀疏的网的最小生成树
1、由小到大依次寻找代价最小的边,直到所有顶点连通
(注意生成树中不能存在回路)
对于无向图,邻接矩阵第 i 行元素之和就是顶点 i 的度
数据域
链域
边表
邻接表
十字链表
邻接多重表
(1)设置搜索指针 p,使 p 指向顶点 v
(2)访问 p 所指结点,并使 p 指向与其相邻接的且尚未被访问过的顶点
存在则重复步骤(2)
否则执行步骤(4)
(4)沿着刚才访问的次序和方向回溯到一个尚有邻接顶点且未被访问过的顶点,然后重复步骤(2),直到所有结点均被访问为止
使用栈对图进行深度优先遍历
类似于树的先序遍历,是树的先序遍历的推广
遍历顺序:123456
使用队列对图进行广度优先遍历
类似于树按层次遍历的过程
具有n个顶点、e条边的图采用邻接表存储结构,进行深度优先遍历(DFS)和广度优先遍历(BFS)运算的时间复杂度都为 O(n+e)
适用于从某个源点到其余个顶点的最短路径
1、从第一个对角线元素(左上)开始不断画十字(往右下)
2、十字的值和十字上 部分所在的行列值不变
3、更新值与路基,取下一个对角线继续重复
注:值比原来元素小才更新
适用于每一对顶点之间的最短距离
用有向图,以顶点表示活动,用有向边表示活动之间的优先关系,则称这样的有向图为一顶点表示活动的网,简称AOV网
用顶点表示事件,用弧表示活动,权表示活动持续的时间
通常用AOE网用来估算工程的 完成时间
在AOE网中,关键路径是指从源点到汇点的最长路径,即完成整个工程所需的最长时间。
关键路径上的活动称为关键活动,关键活动的最大特征是该活动的最早开始时间等于该活动所允许的最迟开始时间
是将AOV网中的所有顶点排成一个线性序列的过程
(1)在AOV网中选择一个入度为 0(没有前驱)的顶点其输出它
(2)从网中删除该顶点以及与该顶点有关的所有弧
(3)重复上述两步,直到网中不再存在入度为 0 的顶点为止
散列表是一种非线性数据结构,通过利用 Hash 函数将指定的「键 key」映射至对应的「值 value」,以实现高效的元素查找。
设想一个简单场景:小力、小特、小扣的学号分别为 10001, 10002, 10003 。
现需求从「姓名」查找「学号」。
则可通过建立姓名为 key ,学号为 value 的散列表实现此需求
// 初始化散列表
unordered_map dic;
// 添加 key -> value 键值对
dic["小力"] = 10001;
dic["小特"] = 10002;
dic["小扣"] = 10003;
// 从姓名查找学号
dic.find("小力")->second; // -> 10001
dic.find("小特")->second; // -> 10002
dic.find("小扣")->second; // -> 10003
假设需求:从「学号」查找「姓名」,构建以学号为 key 、姓名对应的数组索引为 value 的散列表
1、将三人的姓名存储至以下数组中,则各姓名在数组中的索引分别为 0, 1, 2
string names[] = { "小力", "小特", "小扣" };
2、构造一个简单的 Hash 函数( % 为取余符号 )
hash(key)=(key−1)%10000
int hash(int id) {
int index = (id - 1) % 10000;
return index;
}
names[hash(10001)] // 小力
names[hash(10002)] // 小特
names[hash(10003)] // 小扣
实际的 Hash 函数还需保证低碰撞率、 高鲁棒性等
将待查找的关键字为key的元素从头到尾与表中元素进行比较,如果中间存在关键字为key的元素,则返回成功;否则失败 顺序查找适用于线性表的顺序存储结构、链式存储结构
缺点:查找效率低,元素较多时不宜采用
为确定记录在查找表中的位置,需和给定值进行比较的关键字的个数的期望值,成为查找算法在查找成功时的平均查找长度
时间复杂度:O(n)
使用该算法的前提:
查找表的元素已经按关键字递增方式排序(键值有序的顺序表)
优点:比较次数少,查找效率高
时间复杂度:O(log₂ n)
平均查找长度:
查找成功时,关键字的比较次数最多为(log₂ n) + 1 次
(索引顺序查找)(块内有序查找)一种将顺序查找和二分查找思想结合在一起的查找办法
动态查找表(Dynamic Search Table)是一种支持动态更新(插入、删除)和高效查找的数据结构。与静态查找表(如有序数组)不同,动态查找表在数据频繁变化时仍能保持较优的时间复杂度,适用于实时数据处理、数据库索引等场景。
散列(哈希)表:有限连续的地址空间,用以存储按散列函数计算得到的散列地址的数据记录
哈希表通过计算一个以记录的关键字为自变量的函数(称为哈希函数)来得到该记录的存储地址 :散列(哈希)函数
:散列地址
关键字:数据元素中的某个数据项的值,可以标识一个数据元素
主关键字:可以唯一标识一个数据元素的关键字
一般可以选 为小于表长的最大质数
2、数字分析法
通常用在选定散列函数时,不一定能知道关键字的全部情况
位移叠加法
边界叠加法
根据设定的哈希函数 H(key) 和处理冲突的方法,将一组关键字映射到一个有限的连续的地址集(区间)上,并以关键字在地址集中的“像”作为记录在表中的存储地址 这一映射过程称为哈希造表 或 散列
所得的存储位置称为哈希地址 或 散列地址
用同一哈希函数计算得到待查记录的存储地址,然后到相应的存储单元去判定查找是否成功
对于某个哈希函数 H 和两个关键字 K1 和 K2,若K1 != K2,而H(K1)=H(K2),则称为冲突
具有相同哈希函数值的关键字对该哈希函数来说称为同义词
冲突只能尽量减少而不能完全避免,哈希函数是关键字集合到地址集合的一个压缩映像
公式:
顺序往下
2次往两边
( +伪随机数)%m=?
链地址法
处理冲突的过程中发生的多个第一个散列地址不同的记录争夺同一个后继散列地址的现象
压缩性,应节省存储空间
应具有较好的散列性,尽量减少冲突
线性探测再散列
二次探测再散列
伪随机数探测再散列
再查找表的每一个记录中增加一个链域,链域中存放下一个具有相同哈希函数值的记录(同义词)的存储地址
同义词发生地址冲突时计算另一个哈希函数地址,知道冲突不再发生
一旦发生冲突,都填入公共溢出区
排序:按关键字的非递减或非递增顺序对一组记录重新进行排序的操作
何时停止排序:本趟到下一趟没有进行过交换操作,则认为完成排序
相同关键字排序前排序后次序一致的排序方法为稳定的,否则为不稳定的
指待排序记录全部存放再内存中进行排序的过程
内部排序的过程是一个逐步扩大记录的有序序列长度的过程
指待排序记录的数量很大,以至于内存不能容纳全部记录,在排序过程中尚需对外存进行访问的排序过程
将数组 data[0]~data[n-1]中的 n 个整数按照非递减有序的方式进行排序 从未排序的序列中依次取出元素与已排序序列中的元素进行比较,将其放入已排序序列的正确位置上
data[1] 与 data[0] 的值交换
data[1] 与 data[0] 的值交换位置
3、data[3] 以此类推,直到 data[n]
(1)算法简便,且容易实现
(2)也适用于链式存储结构
(3)更适合于初始记录基本有序的情况
一步到位
又称“缩小增量排序”,是堆直接插入排序方法的改进
基本思想:先将整个待排记录序列分割成若干个序列,然后分别进行直接插入排序,待整个序列中的记录基本有序时,再对全体记录进行一次直接插入排序
(1)记录跳跃式地移动导致排序不稳定
(2)只能用于顺序结构,不能用于链式结构
(3)增量序列可以有各种取法,序列中的值的公因子只能为1,并且最后一个增量值为1
(4)n 越大效果越明显,适用于记录无序,n 较大
折半插入排序
基本思想:通过相邻元素之间的比较和交换,将排序码较小的元素逐渐从底部移向顶部。整个排序的过程就像水底下的气泡一样逐渐向上冒 第一和第二比,第二和第三比,第三和第四比...
可用于链式存储
当初始序列无序,n较大时,不宜使用
采用分治法+递归的基本思想:将原问题分解成若干个规模更小,结构与原问题相似的子问题。通过递归地解决这些子问题,然后再将这些子问题的解组合成原问题的解 快速排序方法中的一次交换可能消除多个逆序
基于分治法
得到的两个子序列,再递归执行快速排序,递归直至顺序
以57为基准,分开两组序列
记录的移动是非顺次、跳跃式的,所以快速排序不稳定
排序过程需要定位表的上下界,所以适用于顺序结构,很难用于链式结构
使用于初始记录无序,记录较多的情况
每一趟从待排序的记录中选出关键字最小的记录,按顺序放在已排序的记录序列的最后,直到全部排完
即,每次遍历比较当前数的后面所有数,把最小的数和当前数进行交换
数组 A={1,3,4,5,7,2,6,8,0}初建大顶堆
首先建成完全二叉树(自上而下,从左至右)
从最深层的非叶子结点入手:(5) (5)比其孩子结点小
与其最大的一个孩子结点(8)交换
以达到大顶堆的要求 往上走,(3) 比其孩子结点小
与其最大的一个孩子结点(8)交换
以达到大顶堆的要求 (3)还是比其孩子结点小
与其最大的一个孩子结点(5)交换
以达到大顶堆的要求 往上走,(1)比其孩子结点小
与其最大的一个孩子结点(8)交换
以达到大顶堆的要求 (1)还是比其孩子结点小
与其最大的一个孩子结点(7)交换
以达到大顶堆的要求 初建堆达成
将顺序表 R ={80,60,16,50,45,10,15,30,40,20}进行堆排序
首先建成完全二叉树(自上而下,从左至右)
因为是顺序表所以是大顶堆 将堆顶的最大关键字输出(摘出到结果序列)
将编号为最后一个的结点(序列中的最后一个结点/最底层最右子结点)放至堆顶 (20) 比其孩子结点小
与其最大的一个孩子结点(60)交换
以达到大顶堆的要求 (20) 还是比其孩子结点小
与其最大的一个孩子结点(50)交换
以达到大顶堆的要求 (20) 还是比其孩子结点小
与其最大的一个孩子结点(40)交换
以达到大顶堆的要求 (20)写入为叶子结点
此时一趟排序过程结束,新堆顶为(60),可以输出(摘出至结果序列) 结果序列:{80,60}
以此类推,进行n趟直至遍历完成
只能用于顺序排序,不能用于链式结构
记录少时不宜采用,记录较多时较为高效
树形选择排序
归并也称为合并,是将两个或两个以上的有序子表合并成一个新的有序表
若将两个有序表合并成一个有序表称为二路合并
把一个有 n 个记录的无序文件看成是由n个长度为1的有序子文件组成的文件,然后进行两两归并,得到 n/2 个长度为 2 或者 1 的有序文件,再两两归并,如此反复,直到最后形成包含n个记录的有序文件为止
基于分治法
按组成关键字的各个位数的值进行排序,分配排序的一种
除了基数排序外,其他均按顺序表的方式存储
(2)链表
待排序记录本身存储在一组地址连续的存储单元内
直接选择(简单选择)可稳可不稳,优先选稳定
时间复杂度:关键字的比较次数和记录移动次数
空间复杂度:理想为O(1)