赞
踩
查找表:
由
同一类型
的数据元素
(记录)构成的集合。
关键字:
是数据元素中
某个数据项
的值。
主关键字:
若此关键字能
唯一标识一个记录
。
次关键字:
识别
多个数据元素
的关键字。
查找:
- 根据
给定的某个值
,在表中确定一个其关键字等于给定值的数据元素
。- 静态、动态
只作查找操作
的查找表;查询
某个特定的数据元素
是否在查找表中;检索
某个特定的数据元素
和各种属性
。
- 插入删除操作;
- 查找所基于的数据结构是集合,而集合的记录之间没有本质关系,可以组织成
表、树
等结构。
#include<stdio.h> #include<stdlib.h> int SeqSearch(int *a, int n, int key){ int i; a[0] = key; i = n; while (a[i] != key){ i--; } return i; } int main(){ system("pause"); return 0; }
- 时间复杂度:
O(n)
;- 平均查找次数:
(n+1)/2
;- 效率低,算法简单。
- 每次取中间记录查找的方法,划分区域,在进行查找;
- 前提:必须是关键字有序,且为顺序存储
/*@ 折半查找 */ int BinarySearch(int *dataList, int len, int key){ int l, h, m; l = 1; // 最低下标为记录的首位 h = len; while (l < h){ m = (l + h) / 2; // 折半 if (key < dataList[m]){ // 判断查找的值在左区域还是右区域 h = m - 1; } else if(key>dataList[m]){ l = m + 1; } else{ return m; // 若相等,则返回该位置 } } return 0; }
- 时间复杂度:
O(logn)
;
公式:
mid = low + (high-low) * (key-a[low]) / (a[high] - a[low])
- 只将折半的查找中的公式替换即可;
- key:要查找的元素;
- 核心:要查找的key与查找表中最大最小记录的关键字比较后的查找方法。
- 利用黄金分割原理
int FibinacciSearch(int *dataList, int n, int key){ int F[] = { 1, 3, 5, 7 };// .... int l, m, h, i, k; l = 1; // 记录首位 h = n; // 记录末尾 k = 0; while (n>F[k]-1){ // 计算n到F中的位置 k++; } for (i = n; i < F[k] - 1; ++i){ // 将不满的数值补全 dataList[i] = dataList[n]; } while (l < h){ m = l + F[k - 1] - 1; // 当前分隔的下标 if (key < dataList[m]){ // 若查找记录小于当前分隔记录 h = m - 1; // 最高下标调整到分隔下标mid-1处 k = k - 1; // 斐波那契数列下标减一位 } else if (key>dataList[m]){ l = m + 1; k = k - 2; } else{ if (m < n){ return m; } else{ return n; } } } return 0; }
- 时间复杂度:
O(logn)
;- 平均性能要优于折半查找;
- 一个关键字与它对应的记录相关联;
- 每个索引项至少要包含
关键字
和其对应的记录
在存储器中的位置;- 索引技术:
组织大型数据库
以及磁盘文件
的重要技术。
- 在线性索引中,将数据集中的每个记录对应一个索引项;
- 对于稠密索引,索引项一定是按照
关键字有序
的排列;- 空间代价大;
- 索引项有序,可优先考虑到
折半、插值、斐波那契
等有序
查找算法。
分块有序,在对每一个块建立一个索引项,来减少索引项的个数。
满足条件:
- 块内无序,减少空间使用,只能顺序查找;
- 块间有序,后续记录的关键字均要比前一个大。
结构:
- 最大关键字,保证下一块的最小关键字都要比上一个最大关键字大;
- 存储块中个数,便于循环时使用;
- 用于指向块首数据元素的指针,并于开始对这一块数据进行遍历。
- 块中平均查找长度:
Lw = (t+1)/2
;- 分块平均查找长度:
ASLw = Lb + Lw = 1/2*(n/t + t) +1
;
n为总记录个数,t为块内记录个数。
最基础
的搜索技术;- 记录号表存储具有
相同次关键字
的所有记录的记录号。
结构:
- 次关键字;
- 记录号表;
- 将一颗二叉树,通过
中序遍历
进行而得到的序列为二叉排序树;- 来
提高查找和插入删除
关键字的速度。
性质:
- 若它的
左
子树不为空,则左子树上所有结点的值均小于
它的根结构的值
;- 若它的
右
子树不为空,则右子树上所有结点的值均大于
它的根结构的值
。
总结
- 插入、删除时,仅需修改链接指针即可;
- 二叉排序树的查找性能取决于二叉排序树的形状;
- 时间复杂度:
O(logn)
;
是一种高度平衡的二叉排序树,左右子树的
个数差
不能超过1
。
- 平衡因子BF:将二叉树上结点的
左子树深度减去右子树深度的值
。- 最小不平衡子树:距离插入结点最近的,且平衡因子的
绝对值大于1
的结点为根的子树。
在构建二叉排序树过程中,每插入一个结点时,先检测是否因插入而破坏了树的平衡性。若是,则找出
最小不平衡子树
。调整个结点的链接关系,进行相应旋转
。
BinarySortTree.h
#include<stdio.h> #include<stdlib.h> #define LH +1 // 左高 #define EH 0 // #define RH -1 // #ifdef __cplusplus extern "C"{ #endif /*@ 二叉树的二叉链表结点结构定义 */ typedef struct BiTNode{ int data; // 结点数据 int bf; // 平衡因子 BiTNode *lChild, *rChild; // 左右孩子 }BiTNode, *BiTree; /*@ 右旋:旋转处理之前的左子树的根结点 */ void RRotate_BiTree(BiTree *T); /*@ 左旋:旋转处理之前的右子树的根结点0 */ void LRotate_BiTree(BiTree *T); /*@ 对以指针T所指结点为根的二叉树作为左平衡旋转处理 */ void LeftBalance(BiTree *T); /*@ 若在平衡的二叉排序树T中不存在和e有相同关键字的结点,则插入一个 数据元素为e的新结点并返回1,否则返回0.若因插入而使二叉排序树失 去平衡旋转处理,taller反映T是否长高。 */ int InsertAVL(BiTree *T, int e, int *taller); /*@ 对以指针T所指结点为根的二叉树作为右平衡旋转处理 */ void LeftBalance(BiTree *T); #ifdef __cplusplus } #endif
BinarySortTree.cpp
#include "BinarySortTree.h" /*@ 右旋:旋转处理之前的左子树的根结点 */ void RRotate_BiTree(BiTree *T){ BiTree temp; temp = (*T)->lChild; // temp指向T的左子树根结点 (*T)->lChild = temp->rChild; // temp的右子树挂接为T的左子树 temp->rChild = (*T); *T = temp; // T指向新的根结点 } /*@ 左旋:旋转处理之前的右子树的根结点0 */ void LRotate_BiTree(BiTree *T){ BiTree temp; temp = (*T)->rChild; // temp指向T的右子树根结点 (*T)->rChild = temp->lChild; // temp的左子树挂接为T的右子树 temp->lChild = (*T); *T = temp; // T指向新的根结点 } /*@ 对以指针T所指结点为根的二叉树作为左平衡旋转处理 */ void LeftBalance(BiTree *T){ BiTree L, Lr; L = (*T)->lChild; switch (L->bf){ /* 检查T的左子树的平衡度,并作相应平衡处理 */ case LH: // 新结点插入在T的左孩子的左子树上,要作单右旋处理 (*T)->bf = L->bf = EH; RRotate_BiTree(T); break; /* 新结点插入在T的左孩子的右子树上,要作双旋处理 */ case RH: Lr = L->rChild; // Lr指向T的左孩子的右子树根 switch (Lr->bf){ // 修改T及其左孩子的平衡因子 case LH: (*T)->bf = RH; L->bf = EH; break; case EH: (*T)->bf = L->bf = EH; break; case RH: (*T)->bf = EH; L->bf = LH; break; } Lr->bf = EH; LRotate_BiTree(&(*T)->lChild); // 左子树左旋 RRotate_BiTree(T); // 右子树右旋 default: break; } } /*@ 对以指针T所指结点为根的二叉树作为右平衡旋转处理 */ void LeftBalance(BiTree *T){ BiTree L, Ll; L = (*T)->rChild; switch (L->bf){ /* 检查T的左子树的平衡度,并作相应平衡处理 */ case RH: // 新结点插入在T的左孩子的左子树上,要作单右旋处理 (*T)->bf = L->bf = RH; LRotate_BiTree(T); break; /* 新结点插入在T的左孩子的右子树上,要作双旋处理 */ case LH: Ll = L->lChild; // Lr指向T的左孩子的右子树根 switch (Ll->bf){ // 修改T及其左孩子的平衡因子 case RH: (*T)->bf = RH; L->bf = EH; break; case EH: (*T)->bf = L->bf = EH; break; case LH: (*T)->bf = EH; L->bf = LH; break; } Ll->bf = EH; RRotate_BiTree(&(*T)->rChild); // 右子树右旋 LRotate_BiTree(T); // 左子树左旋 default: break; } } /*@ 若在平衡的二叉排序树T中不存在和e有相同关键字的结点,则插入一个 数据元素为e的新结点并返回1,否则返回0.若因插入而使二叉排序树失 去平衡旋转处理,taller反映T是否长高。 */ int InsertAVL(BiTree *T, int e, int *taller){ if (!*T){ // 插入新结点,树长高,则taller为1 *T = (BiTree)malloc(sizeof(BiTNode)); (*T)->data = e; (*T)->lChild = (*T)->rChild = NULL; (*T)->bf = EH; *taller = 1; } else{ // 树中已存在和e有相同关键字的结点则不再插入 if (e == (*T)->data){ *taller = 0; return 0; } // 应继续在T的左子树中进行搜索 if (e < (*T)->data){ if (!InsertAVL(&(*T)->lChild, e, taller)) // 未插入 return 0; if (taller){ // 已插入 switch ((*T)->bf) // 检查树的平衡度 { case LH: // 原来的左子树比右子树高,需要左平衡处理 LeftBalance(T); *taller = 0; break; case EH: // 左右子树等高,现因左子树增高而树增高 (*T)->bf = LH; *taller = 1; break; case RH: // 原本右子树比左子树高,现右子树等高 (*T)->bf = EH; *taller = 0; break; } } } // 应继续在T的右子树中进行搜索 else{ if (!InsertAVL(&(*T)->rChild, e, taller)) // 未插入 return 0; if (*taller){ // 插入到右子树且长高了 switch ((*T)->bf) { case LH: (*T)->bf = EH; *taller = 0; break; case EH: (*T)->bf = RH; *taller = 1; break; case RH: LeftBalance(T); *taller = 0; break; default: break; } } } } return 0; }
查找、删除、插入的时间复杂度均为:
O(logn)
;
- 由于内存存取外存次数很多,会在时间效率上存在瓶颈,为此引入多路查找树。
- 概念:其每个结点的
孩子数可以多于俩个
,且每个结点
处可以存储多个元素
。
- 每个结点都具有孩子2个孩子则2结点,3个则3结点。
- 2结点:一个元素2个孩子或没有孩子;
- 3结点:一小一大俩个元素和三个孩子或没有孩子;
插入只在叶子结点上发生。
插入三种情况
- 对于
空树
,插入一个2结点
;- 若插入到一个
2结点
中,则需要升级3结点
;- 若插入到一个
3结点
中,且3结点此时存满了,则需要将此三个元素的其中一个往上移动一层
。- 主要插入会造成层之间的结点调节,并且通过
2、3结点
的互相转换
。
删除三种情况
- 删除
3结点
中的元素,之间删除即可;- 删除
叶子是2结点
的元素,会造成多种情形;
- 1)此结点的双亲也是2结点,且拥有一个3结点的右孩子;
- 2)双亲是2结点,它是右孩子也是2结点;
- 3)双亲是一个3结点;
- 4)满二叉树,需要考虑到层数的减少。
一个3结点包含小中大
三个元素和四个孩子或没有孩子
;
- B树是一种
平衡
的多路查找树;- B树的
阶
:结点最大
的孩子数目;- 为内外存的数据交互准备;
一个m阶B树的属性
- 若
根结点不是叶结点
,则其至少有俩棵子树
;- 每一个非根的分支结点都有
k-1
个元素和k
个孩子,每一个叶子结点n都有k-1个元素
;- 所以叶子结点都位于同一层次;
- 是应
文件系统
所需而处的一种B树的变形树
;- 在B+树中,出现在分支结点中的元素会被当作他们在该分支结点位置的
中序后继者(叶子结点)
中再次列出。另外,每个叶子结点都会保存一个指向后一叶子结点的指针
;灰色关键字即
是根结点中的关键字在叶子结点再次列出
。
B+树与B树的主要差异:
- 有
n棵子树
的结点中包含有n个关键字
;所有的叶子结点包含全部关键字的信息
,及指向含这些关键字记录的指针,叶子结点本身依关键字的大小自小而大顺序链接;- 所有分支结点可以看成是
索引
,结点中仅含有其子树中的最大(最小)关键字。
适合
带有范围
的查找。
存储位置 = f(关键字)
- 散列技术是在记录的
存储位置
和它的关键字
之间建立一个确定的对应关系f
;- f为散列函数,或哈希函数;
- 散列技术将记录存储在一块连续的存储空间;
- 散列技术即是一种存储方法,也是一种查找方法。
- 最适合的求解
冲突:
key1 != key2
但f(key1) = f(key2)
;
避免冲突
尽量让
散列地址均匀分布
,保证存储空间的有效利用
,减少
为处理冲突而耗费时间
。
- 取关键字的某个线性函数值为散列地址;
f(key) = a * key + b
优点:
- 简单、均匀、不会产生冲突;
缺点
- 要事先知道
关键字的分布情况
,适合查找表较小且连续
的情况。
- 适用关键字的位数较多的数字。
例如:手机号可取后四位。
抽取数字中间几位在,平方。
- 将关键字从左到右分割成位数相等的几部分,在叠加求和,在取后几位。
f(key) = key mod p
- 若表长为m,而p一般小于或等于m的最小质数或不包含小于20质因子的合数。
f(key) = random(key)
;
考虑因素:
- 计算散列地址所需的时间;
- 关键字的长度;
- 散列表的大小;
- 关键字的分布情况;
- 记录查找的频率。
- 若发生冲突,则寻找下一个散列地址
fi(key) = (f(key) + di) MOD m
堆积:
争夺一个地址的情况。
二次探测法:
增加
平方运算
的目的是为了不让关键字都聚集在某一块区域。
随机探测法:
在冲突时,对于位移量di采用
随机函数
计算得到。
fi(key) = RHi(key)
RHi
为不同的散列函数
将所有关键字为
同义词
的记录存储再一个单链表中;
为冲突建立一个
公共溢出区
来存放。
- 查找效率最高:
O(1)
;- 散列表的好坏直接影响着出现冲突的频繁程度;
- 装填因子α = 填入表中的记录个数 / 散列表长度:
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。