本文是学习大话数据结构的简要知识点汇总。
先说三种简单的:
1.顺序表查找
2.有序表查找
3.线性索引查找
查找就是根据给定的某个值,在查找表中确定一个其关键字等于给定值的数据元素(或记录)。
按照操作方式分为:
1.静态查找表:只作查找操作的查找表。(查询某个数据是否存在查找表、查询某个数据的各个属性)
2.动态查找表:在查找过程中同时插入查找表中不存在的数据元素,或者从查找表中删除已经存在的某个数据元素。(查找时插入数据元素、查找时删除数据元素)
为了提高查找的效率,我们需要专门为查找操作设置数据结构,这种面向查找操作的数据结构称为查找结构。
从逻辑上来说,查找所基于的数据结构是集合,集合中的记录之间没有本质关系。可是想要获得较高的查找性能,我们就不能不改变数据元素之间的关系,在存储时可以将查找集合组织成表、树等结构。
例如:对于静态查找表来说,我们不妨应用线性表结构来组织数据,这样可以使用顺序查找算法,如果在对主关键字排序,这可以应用折半查找等技术来进行高效的查找。
如果是需要动态查找,这会复杂一些,可以考虑二叉排序树的查找技术。
另外,还可以用散列表结构来解决一些查找问题。
顺序表查找:
顺序查找:又叫线性查找,是最基本的查找技术,从表中第一个(或最后一个)记录开始,逐个进行记录的关键字和给定值比较,若某个记录的关键字和给定值相等,这查找成功;如果直到最后一个(或第一个)记录,其关键字和给定值比较都不等时,则表中没有所查的记录。
最简单的顺序查找:
//顺序查找
public int? Sequential_Search(int[] arr, int key)
{
for (int i = 0; i < arr.Length; i++)
{
if (arr[i] == key)
return arr[i];
}
return null;
}
但还可以优化:仿照书中的代码,数组第一个位置不存数据,设置成哨兵。
在查找方向的尽头设置哨兵免去了在查找过程中每次比较后都要判断查找位置是否越界的小技巧,在总数据比较多时,效率提高很大,哨兵可以在数组的开头或结尾。
时间复杂度O(1)
可以把容易查找到底记录放到前面,不常用的放后面,效率可以大福提高。
public int? Sequential_Search(int[] arr, int key)
{
int i = arr.Length - 1;
arr[0] = key;
while (arr[i] != key)
{
i--;
}
return i == 0 ? null : arr[i];
}
有序表查找:
一个线性表有序时,对于查找很有帮助。
1.折半查找:又称二分查找。前提是线性表中的记录必须是关键码有序(通常从小到大有序),线性表必须采用顺序存储。
折半查找的基本思想是:在有序表中,取中间记录作为比较对象,若给定值与中间记录的关键字相等,则查找成功;若给定值小于中间记录的关键字,则在中间记录的左半区继续查找;若给定值大于中间记录的关键字,则在中间记录的右半区继续查找。不断重复上述过程,直到查找成功,或所有查找区域无记录,查找失败为止。
//二分查找:返回索引
public int BinarySearch(int[] arr, int key)
{
int low = 0; //最低下标
int high = arr.Length - 1; //最高下标
int mid = 0; //中间下标
while (low <= high)
{
mid = (low + high) / 2; //折半
if (key > arr[mid]) //查找值大于中间值
{
low = mid + 1; //最低下标调整到中间下标高一位
}
else if (key < arr[mid]) //查找值小于中间值
{
high = mid - 1; //最高下标调整到中间下标低一位
}
else
{
return mid; //相等,找到位置
}
}
return -1; //未找到
}
折半查找的时间复杂度为O(log2n)(以2为底)。
由于折半查找的前提条件是需要有序表顺序存储,对于静态查找表,一次排序后不再变化,这样的算法已经比较好呢。但对于需要频繁插入和删除操作的数据集来说,维护有序的排序会带来不小的工作量,就不建议使用。
2.插值查找
为什么一定要折半,而不是四分之一或者折更多呢?
折半查找里的mid
mid = (low + hight)/2 = low + (high - low)/2
改进方案:不是除以2了
mid=low + (key - arr[low]) / (arr[high] - arr[low]) * (high - low)
我们只需要在折半查找算法的代码中更改为如下:
mid=low + (key - arr[low]) / (arr[high] - arr[low]) * (high - low)
就得到了另一种有序表查找算法,插值查找法。
插值查找是根据要查找的关键字key与查找表中最大最小记录的关键字比较后的查找方法,其核心就在于插值的计算公式:
(key - arr[low]) / (arr[high] - arr[low])
从时间复杂度来看,也是O(logn),但对于表长较大,而关键字分布又比较均匀的查找表来说,插值查找算法的平均性能比折半查找好得多。反之数组中如果分布类似{0,1,2,2000,2001,…999999}这种极端不均匀的数据,插值查找未必很合适。
这样看来,折半查找也可以说是插值查找的一种。
3.菲波那契查找
菲波那契查找是利用了黄金分割原理来实现的。
这个查找算法需要一个斐波那契数列的数组:
C#代码:待查找的数组需要排好序,这里用List,便于增加元素
//这个数组长度不是固定的,可以用代码生成
int[] F = { 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89 };
public int FibonacciSearch(List<int> arr, int key)
{
int low, high, mid, i, k;
low = 0; //定义最低下标为记录首位
high = arr.Count - 1; //定义最高下标为记录末位
k = 0;
while (F[k] - 1 < arr.Count) //计算arr.Length位于斐波那契数列的位置
{
k++;
}
int temp = arr[arr.Count - 1];
for (i = arr.Count; i < F[k] - 1; i++) //将不满数值补全
{
arr.Add(temp);
}
while (low <= high)
{
mid = low + F[k - 1] - 1; //计算当前分割的下标
if (key < arr[mid]) //若查找记录小于当前分隔记录
{
high = mid - 1; //最高下标调整到分隔下标mid-1处
k = k - 1; //斐波那契数列下标减一位
}
else if (key > arr[mid]) //若查找记录大于当前分隔记录
{
low = mid + 1; //最低下标调整到分隔下标mid+1处
k = k - 2; //斐波那契数列下标减两位
}
else
{
if (mid <= arr.Count - 1)
{
return mid; //若相等则说明mid即为查找到的位置
}
else
{
return arr.Count - 1; //若mid>n说明是补全数值,返回arr.Length-1
}
}
}
return -1;
}
线性索引查找:
稠密索引:
是指在线性索引中,将数据集中的每个记录对应一个索引项。
对稠密索引这个索引表来说,索引项一定是按照关键码有序的排列。
有序意味着可以用到折半、插值、斐波那契等有序查找算法。
但如果数据集非常大,意味着索引也有同样的数据集长度规模,对内从有限的计算机来说,需要反复去访问磁盘,查找性能反而大大下降。
分块索引:
是把数据集的记录分成了若干块,并且这些快需要满足两个条件:
1.块内无序:每一块内的记录不要求有序。
2.块间有序:只有块间有序,才有可能在查找时带来效率。
对于分块有序的数据集,将每块对应一个索引项,这种索引方法叫做分块索引。
倒排索引:
但如果记录特别多还需要特殊处理提高速度。