【算法与数据结构】查找算法语义约定及二分查找

对于有序数组,通常用二分查找(包括改进型的 Fibonacci 查找和插值查找)。

查找算法语义约定

一般简单的查找算法,可以在查找失败时直接返回 -1。但为了让函数更具有通用性(例如对于插入操作,需要定位到精确的位置),通常约定的语义为(假设数组 A[lo,hi),要查找元素 e):返回不大于 e 的最大的下标,几种特殊情况:

  • 如果数组中有多个等于 e 的元素,返回最大下标
  • 如果 e 比所有元素都小,返回 lo - 1
  • 如果 e 比所有元素都大,返回 hi - 1

例如,对于数组 [3, 6, 8, 19],分析如下:

  • 查找 1 时:返回 -1
  • 查找 3 时:返回 0
  • 查找 4 时:返回 0
  • 查找 19 时:返回 3
  • 查找 20 时:返回 3

例如,对于数组 [3, 3, 8, 8],分析如下:

  • 查找 1 时:返回 -1
  • 查找 3 时:返回 1
  • 查找 8 时:返回 3

二分查找基本版(失败返回 -1)

二分查找的思想很简单:

  • 判断当前区间长度,如果 lo == hi 则退出循环
  • 每次查找之前,求区间的中间位置:mi = (lo + hi) / 2
  • 如果 e < arr[mi],转到左侧子区间,继续第一步
  • 如果 arr[mi] < e,转到右侧子区间,继续第一步
  • 如果 e == arr[mi],返回 mi
  • 如果循环结束了,返回 -1
#include <stdio.h>
#include <assert.h>

int binSearchA(int arr[], int lo, int hi, int e) {
    int mi;
    while (lo < hi) {
        mi = (lo + hi) >> 1;
        if (e < arr[mi]) {
            hi = mi;
        } else if (arr[mi] < e) {
            lo = mi + 1;
        } else {
            return mi;
        }
    }
    return -1;
}

void searchTest () {
    assert(binSearchA((int[]){}, 0, 0, 1) == -1);
    assert(binSearchA((int[]){1}, 0, 1, 1) == 0);
    assert(binSearchA((int[]){1}, 0, 1, 0) == -1);
    assert(binSearchA((int[]){1}, 0, 1, 2) == -1);
    assert(binSearchA((int[]){1, 3}, 0, 2, 1) == 0);
    assert(binSearchA((int[]){1, 3}, 0, 2, 3) == 1);
    printf("pass test");
}

int main(void) {
    searchTest();

    return 0;
}

Fibonacci 查找

对于第一版的二分查找,转向左子区间只需要做一次判断,而转向右子区间则需要两次。所以,如果查找时能更多的左转,查找效率会提升。

二分查找是每次取中点,而 Fib 查找和插值查找都优化了取中点的策略。

Fibonacci 数列:1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89。。。(从第三项开始,每一项都是前两项之和)。数列越靠后,前后两项的比值越接近 0.618 黄金分隔点。

Fibonacci 函数(递归版)

递归版效率很低,按照现在计算机的性能,大概计算到 50 左右就能明显感受到延时了。

#include <stdio.h>

int fib(int n) {
    if (n < 2) {
        return n;
    }
    return fib(n - 1) + fib(n - 2);
}

int main(void) {
    printf("%d\n", fib(6));

    return 0;
}

Fibonacci 函数(迭代版)

迭代可以分为两种情况:

  • 只用两个变量 i j,每次迭代时,j 赋值为 i+j,i 赋值为 之前的 j
  • 开辟数组存储之前计算出来的所有值

爬楼梯:

#include <stdio.h>

int fib(int n) {
    int i = 0, j = 1;
    int tmp, count = 0;
    while (count < n) {
        tmp = j;
        j = j + i;
        i = tmp;
        count++;
    }
    return i;
}

int main(void) {
    printf("%d\n", fib(6));

    return 0;
}

更简化版的代码:

int fib(int n) {
    int i = 0, j = 1;
    int count = 0;
    while (count++ < n) {
        j = j + i;
        i = j - i;
    }
    return i;
}

数组保存之前的所有值:

#include <stdio.h>
#include <stdlib.h>

int fib(int n) {
    if (n < 2) {
        return n;
    }
    int *arr = (int *)malloc(sizeof(int) * (n + 1));
    int count = 1;
    int tmp;
    arr[0] = 0;
    arr[1] = 1;
    while (count < n) {
        arr[count + 1] = arr[count - 1] + arr[count];
        count++;
    }
    tmp = arr[n];
    free(arr);
    return tmp;
}

int main(void) {
    printf("%d\n", fib(6));

    return 0;
}

Fib 查找

每次确定 Fibonacci 查找的中点时,需要先创建 Fibonacci 数组。例如要在区间 A[0, 10) 中查找时,因为 fib(6) = 8,fib(7) = 13,所以数组中有 7 个元素,分别放 Fibonacci 数列前 7 个值。

#include <stdlib.h>
#include <assert.h>

int fib(int n) {
    int i = 0, j = 1;
    int count = 0;
    while (count++ < n) {
        j = j + i;
        i = j - i;
    }
    return i;
}

int fibSearch(int arr[], int lo, int hi, int e) {
    int mi;
    
    int k = 0;
    int fib_arr_len = 10;
    int *fib_arr = (int *)malloc(sizeof(int) * fib_arr_len);
    while (hi - lo > fib(k) - 1) {
        fib_arr[k] = fib(k);
        k++;
        if (k == fib_arr_len) {
            fib_arr_len *= 2;
            fib_arr = (int *)realloc(fib_arr, sizeof(int) * fib_arr_len);
        }
    }
    
    while (lo < hi) {
        k = 0;
        while (hi - lo < fib_arr[k++]);
        mi = lo + fib_arr[k] - 1;
        if (e < arr[mi]) {
            hi = mi;
        } else if (arr[mi] < e) {
            lo = mi + 1;
        } else {
            return mi;
        }
    }
    return -1;
}

void searchTest () {
    assert(fibSearch((int[]){}, 0, 0, 1) == -1);
    assert(fibSearch((int[]){1}, 0, 1, 1) == 0);
    assert(fibSearch((int[]){1}, 0, 1, 0) == -1);
    assert(fibSearch((int[]){1}, 0, 1, 2) == -1);
    assert(fibSearch((int[]){1, 3}, 0, 2, 1) == 0);
    assert(fibSearch((int[]){1, 3}, 0, 2, 3) == 1);
    assert(fibSearch((int[]){1, 3, 4, 5, 8}, 0, 5, 3) == 1);
    printf("pass test");
}

int main(void) {
    searchTest();
    return 0;
}

二分查找(完整语义版)

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>

int binSearch(int arr[], int lo, int hi, int e) {
    int mi;
    while (lo < hi) {
        mi = (lo + hi) >> 1;
        if (e < arr[mi]) {
            hi = mi;
        } else {
            lo = mi + 1;
        }
    }
    return --lo;
}

void searchTest () {
    assert(binSearch((int[]){}, 0, 0, 1) == -1);
    assert(binSearch((int[]){1}, 0, 1, 1) == 0);
    assert(binSearch((int[]){1}, 0, 1, 0) == -1);
    assert(binSearch((int[]){1}, 0, 1, 2) == 0);
    assert(binSearch((int[]){1, 3}, 0, 2, 1) == 0);
    assert(binSearch((int[]){1, 3}, 0, 2, 3) == 1);
    assert(binSearch((int[]){1, 3, 3, 5, 8}, 0, 5, 3) == 2);
    printf("pass test");
}

int main(void) {
    searchTest();
    return 0;
}
发布了295 篇原创文章 · 获赞 158 · 访问量 101万+

猜你喜欢

转载自blog.csdn.net/kikajack/article/details/102308509