文章目录
查找
- 查找表 Search Table是由同一类的数据元素(或记录)构成的集合。
- 关键字 Key是数据元素中某个数据项的值。
若此关键字可以唯一的标识一个记录,则称此关键字为主关键字(Primary Key).
对于可以识别多个数据元素(或记录)的关键字,则称为次关键字(Secondary Key)。
查找 Searching就是根据给定的某个值,再查找表中确定一个其关键字等于给定值的数据元素(或记录)。
静态查找表Static Searching Table:只作查找操作的查找表。主要操作有:
(1)查询某个”特定的“数据元素是否再表中;
(2)检索某个“特定的”数据元素和各种属性。
动态查找表 Dynamic Searching Table :再查找过程中同时插入查找表中不存在的数据元素,或者从查找表中删除已经存在的某个数据元素。 主要操作:
(1)查找时插入数据元素;
(2)查找时删除数据元素。
顺序表查找
顺序查找(Sequential Search)又叫线性查找,是最基本的查找技术。
顺序查找算法
public static int Search(int[] array, int a)
{
for (int i = 0; i < array.Length; i++)
{
if (array[i] == a)
return i;
}
return -1;
}
算法时间复杂度:O(n);
###有序表查找
折半查找
折半查找(Binary Search):又称二分查找。前提是线性表中的记录必须是关键码有序(通常从小到大有序),线性表必须采用顺序存储。
/// <summary>
/// 折半查找
/// </summary>
/// <param name="array"></param>
/// <param name="a"></param>
/// <returns></returns>
public static int Search(int[] array, int a)
{
int low, mid, high;
low = 0;high = array.Length - 1;
while (low <= high)
{
mid = (low + high) / 2;
if (a < array[mid])
high = mid - 1;
else if (a > array[mid])
low = mid + 1;
else
return mid;
}
return -1;
}
时间复杂度: O(logn);
插值查找(Interpolation Search)
插值查找:是跟根据要查找的关键字key与查找表中最大最小记录的关键字比较后的查找方法,核心在于插值计算公式:(key - a[low])/(a[high]-a[low]).
时间复杂度: O(logn);
斐波那契查找
private static void CalculateFibonacci()
{
_fibonacciArray[0] = 1;
_fibonacciArray[1] = 1;
//计算斐波那契数,使用数组保存中间结防止重复计算,
//注意MAXSIZE为48时,斐波那契数将会溢出整型范围。
//1,1,2,3,5,8,13,21,34,55,89...
for (int i = 2; i < _fibonacciArray.Length; i++)
_fibonacciArray[i] = _fibonacciArray[i - 1] + _fibonacciArray[i - 2];
}
private static int FibonacciSearch(int[] array, int key)
{
int length = array.Length;
int low = 0, high = length - 1, mid, k = 0;
//先查找到距离最近的斐波那契数,本案例为k=6时,值13
int[] banlance;
while (length > _fibonacciArray[k])
k++;
//数组的数量必须为_fibonacciArray[k],所以使用一个中间平衡数组
if (length < _fibonacciArray[k])
{
banlance = new int[_fibonacciArray[k]];
for (int i = 0; i <= length - 1; i++)
banlance[i] = array[i];
}
else
{
banlance = array;
}
//平衡数组中的后半部分用前面的最后一个值补全
for (int i = length; i < _fibonacciArray[k]; i++)
banlance[i] = banlance[length - 1];
//接下来的过程和二分查找类似
while (low <= high)
{
mid = low + _fibonacciArray[k - 1] - 1;
if (banlance[mid] > key)
{
high = mid - 1;
k--;
}
else if (banlance[mid] < key)
{
low = mid + 1;
k -= 2;
}
else
{
//防止索引出界
if (mid <= length - 1)
return mid;
return length - 1;
}
}
//查找不到时返回-1
return -1;
}
线性索引查找
索引:就是把一个关键字与它对应的记录相关联的过程。
线性索引:就是将索引项集合组织为线性结构,也称为索引表。
稠密索引
稠密索引:再线性索引中,将数据集中的每个记录对应一个索引项。
对于稠密索引这个索引表来说,索引项一定是按照关键码有序的排列。
分块索引
分块有序:是把数据集的记录分成了若干块,并且满足这两个条件:
- 块内无序:即每一块内的记录不要求有序(如果能有序更好);
- 块间有序:块与块之间有序。
分块索引:对于分块有序的数据集,将每一块对应一个索引项,这种索引方法叫做分块索引。
分块索引的索引项结构分三个数据项: - 最大关键码,它存储每一块中的最大关键字,这样的好处就是可以使得再它之后的下一块中的最小关键字也能比这一块最大的还要大;
- 存储块中的记录的个数,以便遍历时使用;
- 用于指向首块数据元素的指针,便于开始对这一块中记录进行遍历。
在分块查找表中查找,分两步进行:
1.再分块索引表中查找要查关键字所在的块。
2.根据块首指针找到相应的块,并在块内顺序查找关键码。
倒排索引(Inverted index)
索引项的通用结构是:
- 此关键码;
- 记录号表
其中记录号表存储具有相同次关键字的所有记录的记录号(可以是指向记录的指针或者是该记录的主关键字)。这样的索引方法就是倒排索引。
二叉排序树(Binary Sort Tree)
二叉排序树:又称二叉查找树,它或者是一颗空树,或者是具有以下性质的二叉树。
- 若它的左子树不空,则左子树上所有结点的值均为小于它的根节点的值;
- 若它的右子树不空,则右子树上所有结点的值均大大于它的根节点的值;
- 它左、右子数也分别为二叉排序树。
二叉排序树查找操作
/// <summary>
/// 二叉树的二叉链表结点结构定义
/// </summary>
class BiTNode //结点结构
{
public int data;//数据域
public BiTNode rChild, lChild;//左右孩子
public BiTNode(int data, BiTNode r, BiTNode l)
{
this.data = data;
this.rChild = r;
this.lChild = l;
}
}
/// <summary>
/// 递归查找key
/// </summary>
/// <param name="T">根结点</param>
/// <param name="key">查找的值</param>
/// <returns></returns>
public static BiTNode SearchBST(BiTNode T, int key)
{
if (T == null)//查找不成功
return null;
else if (key == T.data)//查找成功
return T;
else if (key < T.data)
return SearchBST(T.lChild, key);//在左子树查找
else
return SearchBST(T.rChild, key);//在右子树查找
}
二叉排序树的插入操作
/// <summary>
/// 当二叉排序树中不存在关键字等于key的数据元素时
/// 插入key 并返回true,否则返回 false
/// </summary>
/// <param name="T"></param>
/// <param name="key"></param>
/// <returns></returns>
public static bool InsertBST(ref BiTNode T, int key)
{
if ((T == null))
{
T = new BiTNode(key, null, null);
return true;
}
else if (T.data == key)
return false;//已经存在该结点了
else if (key < T.data)
return InsertBST(ref T.lChild, key);
else
return InsertBST(ref T.rChild, key);
}
二叉排序树删除操作
删除结点的三种情况:
- 叶子结点;
- 仅有左或右子树的结点;
- 左右子树都有的结点
/// <summary>
/// 若二叉排序树中存在关键字等于key的数据元素时,则杉树该数据元素结点
/// 并返回true,否则返回false
/// </summary>
/// <param name="T"></param>
/// <param name="key"></param>
/// <returns></returns>
public static bool DeleteBST(ref BiTNode T, int key)
{
if (T == null)//不存在关键字等于key的数据元素
{
return false;
}
else
{
if (key == T.data)//找到关键字等于key的数据元素
{
return Delete(ref T);
}
else if (key < T.data)
{
return DeleteBST(ref T.lChild, key);
}
else
{
return DeleteBST(ref T.rChild, key);
}
}
}
/// <summary>
/// 从二叉排序树中删除结点p,并且重接它的左或右子树
/// </summary>
/// <param name="p">要删除的结点</param>
/// <returns></returns>
public static bool Delete(ref BiTNode p)
{
BiTNode q = null, s = null;
if (p.rChild == null)//右子树为空,只需要重接它的左子树
{
q = p;
p = p.lChild;
}
else if (p.lChild == null)//左子树为空,重接右子树
{
q = p;
p = p.rChild;
}
else//左右子树均不为空
{
q = p;
s = p.lChild; //转左,然后向右到尽头(找到待删除结点的前驱)
while (s.rChild != null)
{
q = s;
s = s.rChild;
}
p.data = s.data; //s指向被删除节点的直接前驱结点
if (q != p)
q.rChild = s.rChild;//重接右子树
else
q.lChild = s.lChild; //重接左子树
}
return true;
}
平衡二叉树(AVL树)(Self-Balancing Binary Tree/Height-Balanced Binary Search Tree)
平衡二叉树:是一种二叉排序树,其中每一个结点的左子树和右子树的高度差至多等于1.
平衡因子Balance Factor:将二叉树上结点的左子树深度减去右子树深度的值称为平衡因子; 平衡二叉树上的所有结点的平衡因子只能是-1,0,1。只要二叉树上有一个结点的平衡因子的绝对值大于1,该二叉树就是不平衡的。
最小不平衡子树:距离插入结点最近的。且平衡因子的绝对值大于1的结点为根的子树,我们称为最小不平衡子树。
平衡二叉树的实现原理
基本思想:在构建二叉排序树的过程中,每当插入一个结点时,先检查是否因插入而破坏了树的平衡性,若是,则找出最小不平衡子树。在保持二叉排序树特性的前提下,调整最小不平衡子树中个结点之间的链接关系,进行相应的旋转,使之成为新的平衡二叉树。
平衡二叉树实现算法
结点
public class AVLNode
{
public int key; //结点的值
public int height; //结点的高度
public AVLNode left; //左孩子
public AVLNode right; //右孩子
public AVLNode() { }
public AVLNode(int key, AVLNode left, AVLNode right)
{
this.key = key;
this.left = left;
this.right = right;
}
}
接口
interface IAVLTree
{
AVLNode Root { get; set; }
AVLNode GetRoot();//返回根结点
void PreOrder(AVLNode root);//先序遍历
void InOrder(AVLNode root);//中序遍历
void PostOrder(AVLNode root);//后序遍历
AVLNode Search(AVLNode root, int key);//查找
int Height(AVLNode root);//返回结点的高度
AVLNode LeftLeftRotate(AVLNode root);//左左旋转
AVLNode RightRightRotate(AVLNode root);//右右旋转
AVLNode LeftRightRotate(AVLNode root);//左右旋转
AVLNode RightLeftRotate(AVLNode root);//右左旋转
AVLNode Insert(AVLNode root, int key);//插入
AVLNode DeleteNode(AVLNode root, AVLNode node);//删除结点node
void Destroy(AVLNode root);//销毁树
AVLNode MinNode(AVLNode root);//求树的最大值
AVLNode MaxNode(AVLNode root);//球数的最小值
}
实现接口
class AVLClass : IAVLTree
{
private AVLNode root;
public AVLNode Root { get => root; set => root = value; }
/// <summary>
/// 将node结点删除,并返回根结点
/// </summary>
/// <param name="root">删除前AVL树的根结点</param>
/// <param name="node">要删除的结点</param>
/// <returns>删除结点node后AVL树的根结点</returns>
public AVLNode DeleteNode(AVLNode root, AVLNode node)
{
if (root == null)
{
return null;
}
if (node.key < root.key)//要删除的结点在左子树
{
root.left = DeleteNode(root.left, node);
if (Height(root.right) - Height(root.left) == 2)//删除后二叉树失衡
{
AVLNode rightNode = root.right;
if (Height(rightNode.left) > Height(rightNode.right))
root = RightLeftRotate(root);
else root = RightRightRotate(root);
}
}
else if (node.key > root.key) //要删除的结点在左子树
{
root.right = DeleteNode(root.right, node);
if (Height(root.left) - Height(root.right) == 2)//删除导致二叉树失衡
{
AVLNode leftNode = root.left;
if (Height(leftNode.left) > Height(leftNode.right))
root = LeftLeftRotate(root);
else root = LeftRightRotate(root);
}
}
else
{
if (root.left != null && root.right != null)//结点左右子树均不为空
{
if (Height(root.left) > Height(root.right))
{
AVLNode maxNode = MaxNode(root.left);
root.key = maxNode.key;
root.left = DeleteNode(root.left, maxNode);
}
else
{
AVLNode minNode = MinNode(root.right);
root.key = minNode.key;
root.right = DeleteNode(root.right, minNode);
}
}
else
{
// AVLNode tmp = root;
root = root.left != null ? root.left : root.right;
// dele
}
}
return root;
}
/// <summary>
/// 销毁树
/// </summary>
/// <param name="root"></param>
public void Destroy(AVLNode root)
{
root = null;
}
public AVLNode GetRoot()
{
return root;
}
/// <summary>
/// 求以该结点为根的树的高度
/// </summary>
/// <param name="root"></param>
/// <returns></returns>
public int Height(AVLNode root)
{
if (root == null)
return 0;
else
{
return Math.Max(Height(root.left), Height(root.right)) + 1;
}
}
/// <summary>
/// 中序遍历
/// </summary>
/// <param name="root"></param>
public void InOrder(AVLNode root)
{
if (root == null)
{
return;
}
InOrder(root.left);
Console.WriteLine(root.key);
InOrder(root.right);
}
/// <summary>
/// 将结点插入到AVL树中,并返回根结点
/// </summary>
/// <param name="root">root插入新结点前AVL树的根结点</param>
/// <param name="key">插入的结点的键值</param>
/// <returns>插入结点后AVL树的根结点</returns>
public AVLNode Insert(AVLNode root, int key)
{
if (root == null)
root = new AVLNode(key, null, null);
else if (key < root.key)//插入到左子树
{
root.left = Insert(root.left, key);
if (Height(root.left) - Height(root.right) == 2)//二叉树失衡
{
if (key < root.left.key)
root = LeftLeftRotate(root);
else
root = LeftRightRotate(root);
}
}
else if (key > root.key)//插入到右子树
{
root.right = Insert(root.right, key);
if (Height(root.right) - Height(root.left) == 2)//二叉树失衡
{
if (key > root.right.key)
root = RightRightRotate(root);
else
root = RightLeftRotate(root);
}
}
root.height = Math.Max(Height(root.left), Height(root.right)) + 1;
return root;
}
/// <summary>
/// LL旋转
/// </summary>
/// <param name="root">失衡AVL树根结点</param>
/// <returns>调整后的AVL树根结点</returns>
public AVLNode LeftLeftRotate(AVLNode root)
{
AVLNode lchild = root.left;
root.left = lchild.right;
lchild.right = root;
lchild.height = Math.Max(Height(lchild.left), Height(root)) + 1;
root.height = Math.Max(Height(root.left), Height(root.right)) + 1;
return lchild;
}
/// <summary>
/// LR旋转
/// </summary>
/// <param name="root">失衡AVL树根结点</param>
/// <returns>调整后的AVL树根结点</returns>
public AVLNode LeftRightRotate(AVLNode root)
{
root.left = RightRightRotate(root.left);//先对左子树右右旋转
return LeftLeftRotate(root);//再对根结点左左旋转
}
/// <summary>
/// 求值最大的结点
/// </summary>
/// <param name="root"></param>
/// <returns></returns>
public AVLNode MaxNode(AVLNode root)
{
AVLNode current = root;
while (!(current.right == null))
{
current = current.right;
}
return current;
}
/// <summary>
/// 找到值最小的结点
/// </summary>
/// <param name="root"></param>
/// <returns></returns>
public AVLNode MinNode(AVLNode root)
{
AVLNode current = root;
while (!(current.left == null))
{
current = current.left;
}
return current;
}
/// <summary>
/// 后序遍历
/// </summary>
/// <param name="root"></param>
public void PostOrder(AVLNode root)
{
if (root == null)
{
return;
}
PostOrder(root.left);
PostOrder(root.right);
Console.WriteLine(root.key);
}
/// <summary>
/// 先序遍历
/// </summary>
/// <param name="root"></param>
public void PreOrder(AVLNode root)
{
if (root == null)
{
return;
}
Console.WriteLine(root.key);
PreOrder(root.left);
PreOrder(root.right);
}
/// <summary>
/// RL旋转
/// </summary>
/// <param name="root">失衡AVL树根结点</param>
/// <returns>调整后的AVL树根结点</returns>
public AVLNode RightLeftRotate(AVLNode root)
{
root.right = LeftLeftRotate(root.right);
return RightRightRotate(root);
}
/// <summary>
/// RR旋转
/// </summary>
/// <param name="root">失衡的AVL树根结点</param>
/// <returns>返回调整后的AVL树根结点</returns>
public AVLNode RightRightRotate(AVLNode root)
{
AVLNode rChild = root.right;
root.right = rChild.left;
rChild.left = root;
rChild.height = Math.Max(Height(root), Height(rChild.right)) + 1;
root.height = Math.Max(Height(root.left), Height(root.right)) + 1;
return rChild;
}
/// <summary>
/// 查找
/// </summary>
/// <param name="root">根结点</param>
/// <param name="key">要查找的键值</param>
/// <returns>如果找到,返回找到的结点,否则返回null</returns>
public AVLNode Search(AVLNode root, int key)
{
if (root == null)//查找不成功
return null;
else if (key == root.key)//查找成功
return root;
else if (key < root.key)
return Search(root.left, key);//在左子树查找
else
return Search(root.right, key);//在右子树查找
}
}
多路查找树(Mutil-way Search Tree)、B树
多路查找树
其每一个结点的孩子数可以多于两个,且每一个结点处可以存储多个元素。
2-3树
2-3树是这样的一棵多路查找树:其中每一个结点都具有两个孩子(称为2结点)或者三个孩子(称为3结点)
2结点包含一个元素两个孩子(或没有孩子)
3结点包含两个元素三个孩子(或没有孩子)
2-3-4树
增加了4结点,4结点包含三个元素,四个孩子(或没有孩子)
B树
B树(B-tree)是一种平衡的多路查找树;结点最大的孩子数目称为B树的阶(order)。
一个m阶的B树具有以下性质:
- 如果根结点不是叶结点,则其至少有两棵子树。
- 每一个非根的分支结点都有k-1个元素和k个孩子,其中m/2(向上取整)<=k<=m。每一个叶子结点n都有k-1个元素,其中m/2(向上取整)<=k<=m。
- 所有叶子结点都位于同一层次
- 所有分支结点包含下列数据(n,a0,k1,a1,k2,a2……,kn,an),其中ki(i=1,2……,n)为关键字,且ki<ki+1(i=1,2……,n);ai(i =0,2,……,n)为指向子树根结点的指针,且指针ai-1所指向的所有结点的关键字均小于ki(i = 1,2,……n),an所指子树中所有结点的关键字均大于kn,n * (m/2向上取整)-1 < = n<=m-1)为关键字的个数(或n+1为子树的个数)。
散列表查找(哈希表)
定义
散列技术:是在记录的存储位置和它的关键字之间建立一个人确定的对应关系f,使得每个关键字key对应一个存储位置f(key)。这种对应关系f称为散列函数,又称为哈希(Hash)函数。 采用散列技术将记录存储在一块连续的存储空间中,这块连续的存储空间称为散列表或哈希表(Hash Table)。
构造方法
原则:
1、计算简单
2、散列地址分布均匀
一、直接定址法
f(key) = a x key + b (a、b为常数)
二、数字分析法
三、平方取中法
四、折叠法
五、除留余数法
f(key) = key mod p (p<=m)
mod是取模(求余数)的意思
六、随机数法
处理散列冲突的方法
1、开放定址法
一旦发生冲突,就去寻找下一个空的地址,只有散列表足够大,空的散列地址总能找到,并将记录存入。
2、再散列函数法
3、链地址法
4、公共溢出区法