KT学算法(二)——循环有序数组查找指定元素

问题描述

一个循环有序的数组是形如:“12,16,18,20,41,100,1,4,6,9” 这样的数组。

问题分析

对于循环有序数组,一种简单的定义是:

循环有序数组是将一个有序数组切成两段,并交换位置得到引用块内容

比如现将1,4,6,9,12,16,18,20,41,100在9和12处切分,得到两段:1,4,6,9和12,16,18,20,41,100,再交换这两段的位置就得到了一开始的循环有序数组。

另一种比较严格的定义是:

对于一个循环有序数组{A1,A2,……An},存在一个i,满足1 < i < n,使得{A1,A2,……Ai}和{Ai,Ai+1,……An}同为单调不减,或单调不增数组。且{A1,A2,……Ai}中的任意一个元素恒大与等于或恒小于等于{Ai,Ai+1,……An}中的任意一个元素。

算法设计

对于这样一个具有一定单调性的数组,最容易想到的O(n)算法显然不是最佳结果。考虑到有序数列的查找算法,通过二分法,可以实现O(log n)的时间复杂度,因此,我们尝试修改二分法,来实现O(log n)的循环有序数组查找指定元素算法。

理论基础

二分法的本质在于,每次只需要搜索一半的数列,而且很容易判定,是哪一半数列需要被搜素。

本例中,我们依然可以每次只搜索一半的数列,但是,对于选择哪一半数列需要继续被搜素,就不像传统的二分法那样显然了。

但是循环有序数组具有两个非常优良的性质:

1.将一个循环有序数组一分为二,一定得到一个有序数组和另一个循环有序数组

2.长度不超过2的循环有序数组其实就是有序数组。

关于以上两点,我也没有严格的数学证明,读者可以以最初的循环有序数组为例,或重新举例,自行验证。

这两点性质为循环有序数组的二分法查找提供了理论基础:对于一个给定的待搜索元素,现将原数组一分为二,因为循环有序数组比较难以通过代码判定,我们只需要判定,哪一个数组是有序数组,且是否需要在这个有序数组中进行搜索即可。

解题思路

首先我们要判定这个数组,是增加型的还是减少型的。也即是说,如果用之前的定义一来判定,我们要先弄清楚这个循环有序数组的原数组是单调不减的还是单调不增的。

这一点并不难,只要比较数组的第一个元素和最后一个元素即可。也就是说,如果A1 > An,那么A一定是增加型的循环有序数组,如果A1 < An,那么A一定是增加型的循环有序数组。A1 = An是非常麻烦的情况,可以通过比较A2 和 An-1得出结果,这里我就没有考虑。

一旦确定了循环有序数组是增加型的还是减少型的,就要开始判断左边一半和右边一半哪一个是有序的。这里以增加型的举例,减少型的同理。

如果A[middle] >= A[begin],那么左边一定是有序的。因为如果左边是循环有序的,那么最大值点一定出现在左侧,且最大值点左侧的数恒大于最大值点右侧的数。这与A[middle] > A[begin]矛盾。反之同理。

确定了有序的一侧后,就要判断是不是在这一侧立面搜索了。这个判断非常简单,只要确定待搜索的数的值是否在有序数列的两个端点值之间即可。

最后通过循环,就可以类似二分法,找到待搜索的数的位置。

代码实现

int main(int argc, const char * argv[]) {
//    int a[] = {12,16,18,20,41,100,1,4,6,9};
    int a[] = {9,6,4,1,100,41,20,18,16,12};
    int target = 20;
    int arrayLength = sizeof(a)/sizeof(a[0]) - 1;

    int index = getIndex(a,target,arrayLength);
    index == -1 ? printf("not found") : printf("index = %d",index);

    return 0;
}

int getIndex(int a[],int target,int arrayLength){
    int beginPos = 0;
    int endPos = arrayLength;
    if (a[beginPos] >= a[endPos]) {
        while (beginPos <= endPos) {
            int middlePos = beginPos + (endPos - beginPos)/2;
            int middleValue = a[middlePos];

            //说明这是一个在增加的循环有序数组
            if (middleValue >= a[beginPos]) {
                //左侧单调递增
                if (target == a[middlePos]) {
                    return middlePos;
                }
                else if (target < a[middlePos] && target >= a[beginPos]){
                    //一定是在左侧查找
                    endPos = middlePos - 1;
                }
                else{
                    //在右侧查找
                    beginPos = middlePos + 1;
                }
            }
            else{
                //右侧单调递增,同理
                if (target == a[middlePos]) {
                    return middlePos;
                }
                else if (target > a[middlePos] && target <= a[endPos]){
                    //一定是在右侧查找
                    beginPos = middlePos + 1;
                }
                else{
                    //在左侧查找
                    endPos = middlePos - 1;
                }
            }
        }
        //没找到元素
        return -1;
    }
    else{
        while (beginPos <= endPos) {
            int middlePos = beginPos + (endPos - beginPos)/2;
            int middleValue = a[middlePos];

            //说明这是一个在减少的循环有序数组
            if (middleValue >= a[beginPos]) {
                //右侧单调递减
                if (target == a[middlePos]) {
                    return middlePos;
                }
                else if (target < a[middlePos] && target >= a[endPos]){
                    //一定是在右侧查找
                    beginPos = middlePos + 1;
                }
                else{
                    //在右侧查找
                    endPos = middlePos - 1;
                }
            }
            else{
                //左侧单调递减,同理
                if (target == a[middlePos]) {
                    return middlePos;
                }
                else if (target <= a[beginPos] && target > a[middlePos]){
                    //一定是在左侧查找
                    endPos = middlePos - 1;
                }
                else{
                    //在左侧查找
                    beginPos = middlePos + 1;
                }
            }
        }
        //没找到元素
        return -1;
    }
}

写在最后

对于出现A0 = An的情况,我们继续判断A1和An-1的大小。因此可以通过循环来实现,因此,在最坏情况下,这个算法依旧可能变为线性的算法。当然,如果已经规定了循环有序数组中不会出现相同的元素,那么就简单多了。

由于仅仅测试了几个数据,不保证代码100%正确,欢迎热心的读者测试以上代码,并告诉我可能存在的bug。谢谢。

时间: 2024-10-11 06:36:44

KT学算法(二)——循环有序数组查找指定元素的相关文章

leetcode——Search a 2D Matrix 二维有序数组查找(AC)

Write an efficient algorithm that searches for a value in an m x n matrix. This matrix has the following properties: Integers in each row are sorted from left to right. The first integer of each row is greater than the last integer of the previous ro

1142: 零起点学算法49——找出数组中最大元素的位置(下标值)

1142: 零起点学算法49--找出数组中最大元素的位置(下标值) Time Limit: 1 Sec  Memory Limit: 64 MB   64bit IO Format: %lldSubmitted: 1937  Accepted: 977[Submit][Status][Web Board] Description 找出数组中最大的元素的下标. Input 多组测试,每组先输入一个不大于10的整数n 然后是n个整数 Output 输出这n个整数中最大的元素及下标值 Sample I

[算法] 循环有序数组查找

有序数组二分查找的变形,代码如下: #include<stdio.h> #include<stdlib.h> int main() { int *array = (int *)malloc(sizeof(int)*16); int i; for(i = 0; i < 16; i++) { *(array + i) = (i+5) % 16; } int ret = search(array, 16, 10); printf("%d", ret); retu

[算法] 循环有序数组查找非递归实现

非递归实现 #include<stdio.h>#include<stdlib.h> int isOrdered(int *array, int begin, int end) { return *(array+end) > *(array+begin) ? 1 : 0; } int contains(int *array, int begin, int end, int theNum) { return theNum >= *(array + begin) &&

二维“有序”数组查找问题的解决

题目:在一个二维数组中,每一行都按照从左到右递增的顺序排序,诶一列都按照从上到下递增的顺序排序,请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否包含了该整数. 例如下面的二维数组就是每行.没列都递增排序.如果在这个数组中查找数字7,则返回true(找得到):如果查找数字5,由于数组不含该数字,则返回false. 1 2 8 9 2 4 9 12 4 7 10 13 6 8 11 15 如下图所示,会出现三种情况. 数组中选取的数字(图中全黑的位置)刚好是要查找的数字(相等),查找

Problem B: 零起点学算法81——找出数组中最大元素的位置(下标值

#include<stdio.h> int main(void) { int n,a[10],i,max; while(scanf("%d",&n)!=EOF) { for(i=0;i<n;i++) scanf("%d",&a[i]); max=0; for(i=1;i<n;i++) { if(a[max]<a[i]) max=i; } printf("%d %d",a[max],max); prin

26、删除有序数组中的元素,数组仍然有序

删除有序数组中的元素,数组仍然有序 方法一: 删除一个有序数组的一个元素,采用两个数组实现 代码实现: /* 2017年6月19日16:16:31 功能:删除数组中一个数据之后数组依然有序 */ #include"stdio.h" #define M 9 int main() { int a[M] = {1,2,3,4,5,6,7,8,9}; int b[M-1]; int i, j, num; bool flag; printf("请输入将要删除的数据的数值:")

循环有序数组,查找值

一.从一个循环有序数组总查找给定值 1.思路:先通过中间值和最后一个或者第一个元素比较,找出局部有序范围,再通过二分查找局部有序段 private static int sortArrFindOne(int arr[], int low, int high, int target) { int mid = (high - low) / 2 + low; if (arr[mid] == target) return mid; if (arr[mid] < arr[high]) { if (arr[

在二维有序数组中搜索某个数(存在否、出现次数)

在二维有序数组中搜索某个数(存在否.出现次数) 问题描述 写出一个高效的算法来搜索m×n矩阵中的值,返回这个值出现的次数. 这个矩阵具有以下特性: 每行中的整数从左到右是排序的. 每一列的整数从上到下是排序的. 在每一行或每一列中没有重复的整数. 样例 考虑下列矩阵: [ [1, 3, 5, 7], [2, 4, 7, 8], [3, 5, 9, 10] ] 给出target = 3,返回 2 实现思路 由于数组每行从左到右是有序的,每列从上到下是有序的,因此可以考虑从二维数组的右上角开始搜索.