数据结构笔记,参考王道数据结构
查找
ASL 平均查找长度 Average Search Length
查找过程中进行关键字比较次数的平均值
- 顺序查找
- 顺序查找(有序表优化,提前停止)
- 优化-关键字出现概率高的排前面
顺序表折半查找 O(log2n)
折半查找判定树满足二叉排序树定义是`平衡二叉树`
右子树节点数-左子树节点数=0或1
树高为`h = log2(n+1)(向上取整)`
失败节点为 `n+1` 个
typedef struct{
ElemType *arr;
int length;
}
//折半查找
int Binary_search(SSTable L,ElemType key){
int low=0,high=L.length-1,mid;
while(low<=high){
mid = (low+high)/2;
if(L.arr[mid]==key)
return mid;
else if(L.arr[mid]>key)
high = mid-1;
else
low = mid+1;
}
return -1;
}
分块查找
每个块存最小索引和最索引,块内无序,块间有序。
最优分块方案:n个元素,分为根号n块,每块放根号n个元素
二叉排序树(二叉查找树)BST
中序遍历是一个递增的有序序列
二叉查找树删除
- 叶结点直接删除
- 结点只有一颗子树,让子树代替被删除节点的位置
- A结点有两个子树,找到该结点左子树的最大结点B或右子树的最小结点B,数值覆盖被删子树,然后删去B结点
代码实现
typedef struct BSTNode {
int key;
struct BSTNode *lchild,*rchild;
}BSTNode,*BSTree;
// 非递归比递归好
BSTNode *BST_Search(BSTree T,int key){
while(T!=NULL&&key!=T->key){ // 树为空或找到结果,退出循环
if(key<T->key){T = T->lchild;}
else T = T->rchild;
}
return T;
}
// 递归实现(空间复杂度O(h))
BSTNode *BSTSearch(BSTNode T,int key){
if(T==NULL)return NULL;
if(key==T->key)return T;
if(key<T->key)return BSTSearch(T->lchild,key);
return BSTSearch(T->rchild,key);
}
// 插入节点(递归)
int BST_Insert(BSTree &T, int key){
if(T==NULL){
T=(BSTree)malloc(sizeof(BSTree));
T->key=key;
T->lchild = T->rchild = NULL;
}
if(key==T->key){return 0;}
if(key < T->key){
BST_Insert(T->lchild,key);
}else{
BST_Insert(T->rchild,key);
}
return 1;
}
// 构建二叉树
void Create_SBT(BSTree &T,int arr[],int n){
T = NULL;
int i =0;
while(i<n){
BST_Insert(T->lchild,arr[i++]); // 每次插入T都会更新
}
}
平衡二叉树
平衡二叉树(Balanced Binary Tree),简称平衡树(AVL树(人名))
书上左子树和右子树的高度差不超过1
平衡因子=左子树高-右子树高
当插入结点导致不平衡后,找到最小不平衡子树,进行LL/RR/LR/RL旋转
LL和RR是让中间节点当父节点
LR和RL是让最后的节点当父节点
Nh表示深度为h的平衡树中最少结点数
N0=0,N1=1,N2=2,Nh = Nh-1 + Nh-2 + 1
n个节点的平衡树最大深度、平均查找长度为 O(log2n)
代码实现
typedef struct AVLNode {
int key;
int balance;
struct AVLNode *lchild,*rchild;
}AVLNode,*AVLTree;
红黑树
插入新结点不容易破坏红黑树结构,红黑树允许最长子树和最短子树相差一半长度,即便破坏了结构也可在常数级别恢复。
而平衡树更严格,因此插入结点很容易破坏平衡树结构,在多次调整结构的过程影响效率。
红黑树查找效率为2log2n 近似为O(log2n)
,实用效果比平衡树要好
以查找为主使用平衡二叉树
频繁插入删除,使用红黑树(使用居多)
红黑树特性:左根右,根叶黑,不红红,黑路同
B树
又称多路平衡查找树,B数结点的孩子个数最大值为B数的阶
m阶B树满足以下特性:
1. 每个结点最多m个子树,最多m-1个关键字
2. 若根结点不是终端结点,至少两个子树(左右)
3. 除根节点外任何非叶结点至少有m/2(上取整)
个分 叉,即m/2(上取整)-1
个关键字,保证树更矮,提高效率。
4. 所有叶节点在同一层,且结点指针全为空,表示查找失败。
n个关键字的m阶B树高度满足以下公式logm(n+1) ≤ h ≤ logm/2((n+1)/2) + 1
B树的插入
按查找位插入,若插入结点个数超出m-1,则将中间结点推向父结点,两边分裂成两个结点,若父结点不存在,则创建新的结 点,若父结点也满了,就继续分裂。
B树的删除
- 删除关键字后,该结点符合m阶B树结点关键字范围要求
- 删除关键字后,低于要求:
- 该节点为非终端结点(有子结点在下面),则将左侧指针最大关键字或右侧指针最小关键字的元素覆盖被删除结点,然后删去覆盖结点
- 该节点为终端结点,
- 若右兄弟结点关键字有多,则让父节点右关键字覆盖被删除关键字,右兄弟的最小关键字代替父节点对应关键字,删除右兄弟最小关键字。
- 若左兄弟结点关键字又多,则让父节点左关键字覆盖被删除关键字,左兄弟的最大关键字代替父节点对应关键字,删除左兄弟最大关键字。
- 若左右结点关键字都不足,则左右结点与父结点对应关键字合并成新结点,删除父结点该关键字,若父结点仍小于要求,则继续向上合并。
// 5叉排序树
struct Node{
ElemType keys[4]; //最多4个关键字
struct Node * child[5]; // n个关键字n+1个分叉
int num;//结点有几个关键字
}
B+树
一棵m阶B+树满足以下条件:
1. 每个结点最多m个子树
2. 非叶根节点,至少两个子树,其他每个结点至少m/2(上取整)
个子树
3. 结点个数与关键字个数相同
4. 所有叶子结点包含所有关键字,以及相应记录的指针,相邻叶子结点按大小顺序互相连接,即可以顺序查找。
5. 非叶子结点的关键字为对应分支结点关键字的最大值。非叶节点仅起索引作用,不存放具体记录的指针,即在查找的过程中,所有结果都只在叶子结点中找到。
B+树 非根节点结点关键字个数范围:
[m/2(上取整),m]
B+树 非叶结点出现过的关键字都会在叶结点中出现。
B+树的非叶子结点占空间更少,单个结点可以存放更多关键字,使得B+树更矮,进而减少IO次数,查找比B树更快,数据库索引经常使用。
B树与B+树对比
不同点
B树 | B+树 |
---|---|
二叉查找树 => m叉查找树 | 分块查找树 => 多集分块查找 |
n个关键字对应n+1个分叉 | n个关键字对应n个分叉 |
所有节点都包含信息 | 只有叶节点包含信息 |
所有节点都仅出现一次 | 所有节点都在叶节点出现(非叶节点重复) |
不支持顺序查找,查找速度不稳定,可能中途找到结果 | 支持顺序查找,查找顺序稳定 |
相同点
除根节点外,最少m/2(上取整)个分叉,以保证树更低。
任何一个结点的子树都一样高,保证绝对平衡。
散列表 Hash Table
空间换时间,元素关键字与存储地址直接相关
Addr = H(key)
如果不同key 通过散列函数H 得到同一Addr,则为”哈希冲突”
如果没找到结果,一般认为查找长度为0
散列函数
- 除留余数法
H(Key) = key % p
散列表表长为m,取不大于m,但最接近或等于m的质数p
质数的冲突可能性更小,合数的公因子可能与元素关键字相同。- 直接定址法
H(key) = key 或者 H(key) = a*key + b
例如学生学号20191112601-20191112652
H(key) = key - 20191112600
若关键字不连续,可能会导致空间浪费- 数字分析法
假设关键字是r进制数(假设10进制),某些数位分布均匀,可以选分布均匀的部分作为散列地址,适合已知的关键字集合。
例如 手机号后四位作为关键字设计- 平方取中法
将关键字的平方值的中间几位作为散列地址,中间几位的值域原值各位都有关
冲突处理办法
拉链法(链地址法)(常用)
所有冲突的元素放在同一个链表中。
ASL(成功)= 每一个元素查找次数之和 / 元素总数
ASL(失败)= 元素总数 / 数组长度 (也称为装填因子)
装填因子越大查找效率越低开放定址法 (常考)
每个数组位只存一个元素,如果冲突后,冲突值加上一个增量序列,再次与m取模。
i为第i次发生冲突
Hi = (H(key) + d) % m
注意这里m是指表长,与哈希函数的分母p不一样d的取值常用方法
- 线性探测法
d固定为1,即发生冲突后去下一位检查是否冲突,直到找到空位置。- 平方探测法
d动态变化为±(1的平方)、±(2的平方)、±(3的平方)、±(k的平方)又称2次探测法
,其中k ≤ m/2
散列表长度必须是一个可以表示成4j+3
的素数,才能探测到所有位置- 伪随机序列法
定义d为一个伪随机的序列
使用开放定址法时若要删除元素,不能真删,而是逻辑删除,不然会导致被删元素后面的元素被判断未找到。
- 再散列法
除了原始的散列函数之外,多准备几个散列函数,再遇到冲突时,使用下一个散列函数计算一个新地址,直到不冲突为止
k的平方)又称2次探测法
,其中k ≤ m/2
散列表长度必须是一个可以表示成4j+3
的素数,才能探测到所有位置
- 伪随机序列法
定义d为一个伪随机的序列
使用开放定址法时若要删除元素,不能真删,而是逻辑删除,不然会导致被删元素后面的元素被判断未找到。
- 再散列法
除了原始的散列函数之外,多准备几个散列函数,再遇到冲突时,使用下一个散列函数计算一个新地址,直到不冲突为止