数据结构之顺序表和有序表查找其实现

顺序表和有序表查找

顺序查找定义:从线性表中的第一个(或最后一个)数据元素开始,逐个进行数据元素关键字和给定值的比较,若某个数据元素的关键字和给定值相等则查找成功;如果直到最后一个(或第一个)数据元素,其关键字和给定值都不等时,则查找失败。

上一节我们就介绍了简单的顺序查找的方法,代码如下:

// 静态查找算法
int static_search(int a[], int len, int key)
{
    int ret = -1;
    int i = 0;
    // 依次查找
    for(i=0; i<len; i++)
    {
        if( a[i] == key )
        {
            ret = i;
            break;
        }
    }
    
    return ret;
}

我们在分析查找操作的性能时,通常以“其关键字和给定值进行过比较的记录个数的平均值”作为衡量查找算法好坏的依据。这里就引出了平均查找长度的概念。

平均查找长度:为确定记录在查找表中的位置,需和给定值进行比较的关键字个数的期望值称为查找算法在查找成功时的平均查找长度。

通过分析上面的代码,我们发现没进行一次循环时都需要进行两次比较,这就增加的比较次数,也就影响了查找算法的效率。我们可以通过减少比较次数来提高查找效率。实现代码如下:

int another_search(int a[], int len, int key)
{
    int ret = len;
    
    a[0] = key;
    
    while( a[ret] != key )
    {
        ret--;
    }
    
    return ret;
}

这里的算法循环时只进行一次比较,整体比上面的算法比较次数少了一倍,这在查找表中数据较少时没有多大差异,但当查找表中数据很多时优势就会显示出来了。除了上述的简单粗暴的查找方法,有没有其他的效率更高的查找算法呢?当然有了,下面我们就介绍两种高效率的查找算法:折半查找插值查找

折半查找:先确定待查记录所在的范围(区间),然后逐步缩小范围直到找到或找不到记录为止。

基本思想:

1.    首先将查找表进行排序;

2. 取中间数据元素进行比较;

    2.1.  当给定值与中间数据元素的关键字相等时,查找成功;

    2.2.  当给定值小于中间元素时,在中间元素左区间进行二分查找;

    2.3.  当给定值大于中间元素时,在中间元素右区间进行二分查找;

3. 当任意区间均无记录时,查找失败。

上述折半查找过程如下图描述所示:

 

通过对算法思想的理解,我们首先想到了递归的方法实现查找,实现代码如下:

// 二分查找、折半查找(递归版)
int binary_search(int a[], int low, int high, int key) // O(logn)
{
    int ret = -1;
    // 查找区间合法性检查,任意区间均无记录时,查找失败
    if( low <= high )
    {
    	// 计算中间位置
        int mid = (low + high) / 2;
        // 给定值与中间数据元素的关键字相等时,查找成功
        if( a[mid] == key )
        {
            ret = mid;
        }
        // 给定值小于中间元素时,在中间元素左区间进行二分查找
        else if( key < a[mid] )
        {
            ret = binary_search(a, low, mid-1, key);
        }
        // 给定值大于中间元素时,在中间元素右区间进行二分查找
        else if( key > a[mid] )
        {
            ret = binary_search(a, mid+1, high, key);
        }
    }
    
    return ret;
}

这里,有人就要问了,不是还需要排序吗?为啥这里没有排序?我们这里讲的是查找,关于排序算法参考:简单的排序算法

该算法除了用递归的方式还有没有其他的实现方式呢?我们分析上面的代码发现,递归的实质就是缩小比较的区间范围,那我们是不是在需要递归时只改变区间范围,让其无限逼近mid不就找到需要的元素了吗?实现代码如下:

// 二分查找增强版
int binary_search_ex(int a[], int low, int high, int key) // O(logn)
{
    int ret = -1;    
    // 查找区间合法性检查,任意区间均无记录时,查找失败
    while( low <= high )
    {
    	// 计算中间位置
        int mid = (low + high) / 2;        
        // 给定值与中间数据元素的关键字相等时,查找成功
        if( a[mid] == key )
        {
            ret = mid;
            break;
        }
        // 给定值小于中间元素时,更新区间上限
        else if( key < a[mid] )
        {
            high = mid - 1;
        }
        // 给定值大于中间元素时,更新区间下限
        else if( key > a[mid] )
        {
            low = mid + 1;
        }
    }
    
    return ret;
}

折半查找的效率确实不错,但是为什么一定要折半呢?是否有合适的折扣能够使折半查找的效率更高呢?下面我们介绍一下另一种查找方法:插入查找

我们进一步分析折半查找,发现其精髓就是:mid = (low + high) / 2
等量代换:mid = low + (high -low) / 2;
扩展:设f(x) = ½, 则:mid =low + f(x)(high -low);
现在的问题是如何选择f(x)呢f(x)的选择可以不只是常数,但必须满足:0 <= f(x) <= 1
由于二分查找基于有序的线性表,因此对于给定的查找值key,我们可以事先估计它的位置。
如:
key = 4
list = {1, 3, 4, 5, 6, 8, 10, 11, 13, 14, 15, 16}

插值查找:根据要查找的关键字key与查找表中最大最小记录的关键字比较后的查找方法,其核心为:mid = low +f(key)(high -low),f(x) = (x a[low]) / (a[high]a[low]),不同f(x)的选取可以得到不同的查找算法!

下面看一下实现代码:

// 插值查找
int interpolation_search(int a[], int low, int high, int key)
{
    int ret = -1;
    // 参数合法性检查ok
    while( (low <= high) && (a[low] <= key) && (key <= a[high]) )
    {
    	// 计算中间位置mid
    	// 计算查找的折分数0<=fx<=1
        float fx = 1.0f * (key - a[low]) / (a[high] - a[low]);
        int mid = low + fx * (high - low);        
        // 给定值与中间数据元素的关键字相等时,查找成功
        if( a[mid] == key )
        {
            ret = mid;
            break;
        }
        // 给定值小于中间元素时,更新区间上限
        else if( key < a[mid] )
        {
            high = mid - 1;
        }
        // 给定值大于中间元素时,更新区间下限
        else if( key > a[mid] )
        {
            low = mid + 1;
        }
    }
    
    return ret;
}

顺序查找比较土,但却是其他查找算法的基础,二分查找基于有序的线性表,时间复杂度提高到O(logn),二分查找可以根据需要进一步演变为插值查找 。

测试代码如下:

// 测试代码
int main(int argc, char *argv[]) 
{
    // 创建大小为SIZE的数组
    int a[SIZE] = {0};
    // 定义其他变量
    int i = 0;
    int key = 0;       // 查找关键字
    int index = 0;
    
    srand((unsigned int)time(NULL));            // 以系统时间作为种子生成随机数
    // 初始化数组,创建查找表    
    for(i=1; i<=SIZE; i++)
    {
        a[i] = rand() % 100;
    }
    // 确定关键字为50
    key = 50;
    // 打印提示信息
    printf("interpolation Search Demo\n");
    printf("Key: %d\n", key);
    printf("Array: \n");
    // 数组排序,变成有序数组
    SelectionSort(a, SIZE);
    // 打印排序后的数组
    println(a, SIZE);
    // 插入查找
    index = interpolation_search(a, 0, SIZE-1, key);
    // 打印排序结果
    if( index > 0 )
    {
        printf("Success: a[%d] = %d\n", index, a[index]);
    }
    else
    {
        printf("Failed!\n");
    }
    
	return 0;
}

输出结果如下图:

失败

成功





评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值