C語言程序設計100例之(21):折半查找

例21  折半查找

問題描述php

順序查找是一種最簡單和最基本的檢索方法。其基本思想是:從檢索表的一端(如表中第一個記錄或最後一個記錄)開始,逐個進行記錄的關鍵字和給定值的比較。若某個記錄的關鍵字和給定值比較相等,則查找成功;不然,若直至檢索表的另外一端(如最後一個記錄或第一個記錄),其關鍵字和給定值比較都不等,則代表表中沒有待查記錄,查找不成功。算法

順序查找能夠寫成一個簡單的一重循環,循環中依次將檢索表(不妨設爲數組a)中的元素與給定值比較,若相等,用break退出循環。算法描述爲:編程

                for (i=0; i< n;i++)數組

                     if (a[i]==x) break;函數

這樣,循環結束後,若循環控制變量i小於數組元素個數n,則查找成功;不然,查找失敗。測試

順序查找實現簡單,但效率不高。當待查找序列有序時,即各檢索表中元素的次序是按其記錄的關鍵字值的大小順序存儲的。此時採用折半查找會大幅提升查找效率。spa

折半查找的基本思想是先肯定待查數據的範圍(區間),而後逐步縮小範圍直到找到或找不到該記錄爲止。具體作法是:先取數組中間位置的數據元素與給定值比較。若相等,則查找成功;不然,若給定值比該數據元素的值小(或大),則給定值必在數組的前半部分(或後半部分),而後在新的查找範圍內進行一樣的查找。如此反覆進行,直到找到數組元素值與給定值相等的元素或肯定數組中沒有待查找的數據爲止。所以,折半查找每查找一次,或成功,或使查找數組中元素的個數減小一半,當查找數組中再也不有數據元素時,查找失敗。orm

輸入一個整數,在給定的有序數組中查找該整數是否存在,若存在,給出其數組的下標;若不存在,輸出查找不成功信息。blog

輸入格式排序

第一行是一個正整數N (1 ≤ N ≤ 100000),表明數組中元素的個數。

第二行有N個整數,這N個整數從小到大排列好了。

第三行是一個整數M,表明待查找元素的個數。

接下來的M行,每行有一個整數x,表示每一個待查找的元素。

輸出格式

輸出有M行,每行一個整數。若待查找元素x在數組中存在,輸出其數組元素的下標;若不存在,輸出-1。

輸入樣例

20

1 6 9 14 15 17 18 23 24 28 34 39 48 56 67 72 89 92 98 100

3

1

25

72

輸出樣例

0

-1

15

        (1)編程思路。

         設有一數組a[n],數組中的元素按值從小到大排列有序。用變量low、high和mid分別指示待查元素所在區間的下界、上界和中間位置。初始時,low=0,high=n-1。

        1)令 mid = (low+ high) /2 。

        2)比較給定值x與a[mid]值的大小

        若a[mid] == x ,則查找成功,結束查找;

        若a[mid]> x ,則代表給定值x只可能在區間low ~ mid-1內,修改檢索範圍。令high=mid-1,low值保持不變;

        若a[mid]< x ,則代表給定值x只可能在區間mid+1~high內,修改檢索範圍。令low=mid+1,high值保持不變。

        3)比較當前變量low和high的值,若low≤high,重複執行第1)、2)兩步,若low>high,代表數組中不存在待查找的元素,查找失敗。

        例如,設一有序的數組中有11個數據元素,它們的值依次爲{3,8,15,21,35,54,63,79,82,92,97},用折半查找在該數組中查找值爲82和87的元素的過程如圖1所示。

  圖1 折半查找的查找過程

         圖1(a)所示爲查找成功的狀況,僅需比較2次。若用順序查找,則需比較9次。圖1(b)所示爲查找不成功的狀況,此時由於low>high,說明數組中沒有元素值等於87的元素。獲得查找失敗信息,也只需比較4次。若用順序查找,則必須比較12次。

        折半查找過程一般可用一個二叉斷定樹表示。對於上例給定長度的數組,折半查找過程可用圖2所示的二叉斷定樹來描述,樹中結點的值爲相應元素在數組中的位置。查找成功時剛好走了一條從根結點到該元素相應結點的路徑,所用的比較次數是該路徑長度加1或結點在二叉斷定樹上的層次數。因此,折半查找在查找成功時所用的比較次數最多不超過相應的二叉斷定樹的深度[log2n]+ 1。同理,查找不成功時,剛好走了一條從根結點到某一終端結點的路徑。所以,所用的比較次數最多也不超過[log2n] + 1。

 

圖2  描述折半查找過程的二叉斷定樹

        (2)源程序。

#include <stdio.h>

#define N 100001

int main()

{

    int a[N],n;

    scanf("%d",&n);

    for (int i=0;i<n;i++)

        scanf("%d",&a[i]);

    int m;

    scanf("%d",&m);

    while (m--)

    {

        int x;

        scanf("%d",&x);

        int low =0, high =n-1,mid;   // 置區間初值

        while (low<=high)

        {

            mid = (low+high)/2 ;

            if (x == a[mid])   break;           // 找到待查記錄

            else if (x<a[mid])  high=mid-1;   // 繼續在前半區間進行檢索

            else  low=mid+1;                      // 繼續在後半區間進行檢索

        }

        if (low<=high)       // 找到待查記錄

            printf("%d\n",mid);

        else

            printf("-1\n");

    }

    return 0;

}

習題21

21-1  Can you find it?

        本題選自杭州電子科技大學OJ題庫(http://acm.hdu.edu.cn/showproblem.php?pid=2141)

Problem Description

Give you three sequences of numbers A, B, C, then we give you a number X. Now you need to calculate if you can find the three numbers Ai, Bj, Ck, which satisfy the formula Ai+Bj+Ck = X.

Input

There are many cases. Every data case is described as followed: In the first line there are three integers L, N, M, in the second line there are L integers represent the sequence A, in the third line there are N integers represent the sequences B, in the forth line there are M integers represent the sequence C. In the fifth line there is an integer S represents there are S integers X to be calculated. 1<=L, N, M<=500, 1<=S<=1000. all the integers are 32-integers.

Output

For each case, firstly you have to print the case number as the form "Case d:", then for the S queries, you calculate if the formula can be satisfied or not. If satisfied, you print "YES", otherwise print "NO".

Sample Input

3 3 3

1 2 3

1 2 3

1 2 3

3

1

4

10

Sample Output

Case 1:

NO

YES

NO

        (1)編程思路。

        本題的題意是:有A、B、C三個數組,每一個數組中有若干個整數元素,要求判斷對於輸入的整數x,是否能夠在A、B、C三個數組中各取一個元素,使得三個元素之和等於x,若能夠取得,輸出「YES」,不然輸出「NO」。

        預先求出數組A和B中任意各取一個數相加所獲得的和值,存儲到q數組中,而後對q數組進行排序,排序後去掉數組q中重複的和值。

        對於輸入的X,枚舉數組C中的每一個元素C[i],而後在q數組中採用折半查找是否存在X-C[i],若存在,則輸出「YES」,不然輸出「NO」。

      (2)源程序。

#include <stdio.h>

#include<algorithm>

using namespace std;

#define N 501

int binsearch(int a[],int n,int key);

int main()

{

    int a[N],b[N],c[N];

    int q[N*N];

    int cnt=0,l,m,n;

    while(scanf("%d%d%d",&l,&m,&n)!=EOF)

    {

                int i,j,k;

        for (i=0;i<l;i++) scanf("%d",&a[i]);

        for (i=0;i<m;i++) scanf("%d",&b[i]);

        for (i=0;i<n;i++) scanf("%d",&c[i]);

        k=0;

        for (i=0;i<l;i++)

          for (j=0;j<m;j++)

             q[k++]=a[i]+b[j];

        sort(q,q+k);

        for (i=j=1;i<k;i++)

            if (q[i]!=q[j]) q[j++]=q[i];      // 去重

        k=j;

        printf("Case %d:\n",++cnt);

        int s,x;

        scanf("%d",&s);

        for(i=1;i<=s;i++)

        {

                scanf("%d",&x);

                for(j=0;j<n;j++)

                     if (binsearch(q,k,x-c[j])!=-1) break;

                printf("%s\n",(j<n)?"YES":"NO");

        }

   }

    return 0;

}

int binsearch(int a[],int n,int key)

{

    int low =0,high =n-1;

    while (low<=high)

    {

        int mid = (low+high)/2 ;

        if (key==a[mid])   return mid;               // 找到待查記錄

         else if (key<a[mid])  high=mid-1;       // 繼續在前半區間進行檢索

          else  low=mid+1;                     // 繼續在後半區間進行檢索

    }

    return -1;                               // 找不到待查記錄

}

21-2  木材加工

        本題選自洛谷題庫 (https://www.luogu.org/problem/P2440)

題目描述

木材廠有一些原木,如今想把這些木頭切割成一些長度相同的小段木頭(木頭有可能有剩餘),須要獲得的小段的數目是給定的。固然,咱們但願獲得的小段木頭越長越好,你的任務是計算可以獲得的小段木頭的最大長度。木頭長度的單位是cm。原木的長度都是正整數,咱們要求切割獲得的小段木頭的長度也是正整數。

例若有兩根原木長度分別爲11和21,要求切割成到等長的6段,很明顯能切割出來的小段木頭長度最長爲5.

輸入格式

第一行是兩個正整數N和K(1 ≤ N ≤ 100000,1 ≤ K ≤ 100000000),N是原木的數目,K是須要獲得的小段的數目。

接下來的N行,每行有一個1到100000000之間的正整數,表示一根原木的長度。

輸出格式

可以切割獲得的小段的最大長度。若是連1cm長的小段都切不出來,輸出」0」。

輸入樣例

3 7

232

124

456

輸出樣例

114

        (1)編程思路。

        這個問題能夠採用相似於折半查找的方法進行解決。

        設left是切割的小段木頭的最短長度,right是最大長度,初始時,left爲1,right爲最長的原木長度。

        每次取left和right的中間值mid(mid = (left + right) / 2)進行嘗試,測試採用當前長度mid進行加工,可否切割出須要的段數K,測試算法描述爲:

        num = 0;

        for (i = 0; i < n; i++)

         {

               if (num >= k) break;

              num = num + len[i] / mid ;

         }

        若是當前mid值能夠加工出所需段數(即num >= k),就增大mid值繼續試(經過讓left = mid的方法來增大mid),不符合要求就減少mid值繼續試(經過讓right = mid的方法來減少mid)。直到left + 1 >= right結束嘗試,所得的left值就是能夠加工出的小段木頭的最大長度。

       (2)源程序。

#include <stdio.h>

int main()

{

         int n, k, len[100001], i, left, right, mid,num;

         scanf("%d%d",&n,&k);

         right = 0;

         int sum=0;

          for (i = 0; i < n; i++)

         {

               scanf("%d",&len[i]);

               sum+=len[i];

              if (right < len[i]) right = len[i];

         }

        if (sum<k)

        {

              printf("0\n");

              return 0;

        }

        left =1 ;

        while ( left + 1  < right)

         {

               mid = (left + right) / 2;

               num = 0;

               for (i = 0; i < n; i++)

               {

                     if (num >= k) break;

                     num = num + len[i] / mid ;

               }

              if ( num >= k )

                      left = mid;

             else

                     right = mid;

         }

         printf("%d\n",left);

         return 0;

}

21-3  xx的位數

題目描述

使得 xx 達到或超過 n 位數字的最小正整數 x 是多少?

輸入格式

一個正整數 n(n<=2000000000)。

輸出格式

使得 xx達到 n 位數字的最小正整數 x。

輸入樣例

11

輸出樣例

10

        (1)編程思路。

        正整數m的位數爲[log10(m)]+1  ([ ]表示向下取整)。

        本題求x^x的位數,也就是求[log10(x^x)]+1,利用對數的運算法則,log10(x^x)=x* log10(x)。因爲對數函數知足單調遞增,所以可採用二分的思想求x* log10(x)≥n-1的最小值。

        初始時,設left爲1,right爲最大整數2e9。

        每次取left和right的中間值mid(mid = (left + right) / 2)進行嘗試,看當前數mid^mid的位數是否達到或超過n。

        若是當前mid^mid的位數小於n(即mid*log10(mid)<n),說明mid小了,就增大mid值繼續試(經過讓left = mid+1的方法來增大mid),若當前mid^mid的位數不小於n(即mid*log10(mid)>=n),說明mid值符合要求,但爲了找到其最小值,就減少mid值繼續試(經過讓right = mid的方法來減少mid)。直到left >= right結束嘗試,所得的left值就是可使得 xx 達到或超過 n 位數字的最小正整數 x。

        (2)源程序。

#include <stdio.h>

#include <math.h>

int main()

{

    int n;

    scanf("%d",&n);

    n--;

    int left=1,right=2e9;

    while (left<right)

    {

        int mid=(left+right)/2;

        if (mid*log10(mid)<n) left=mid+1;

        else right=mid;

    }

    printf("%d\n",left);

    return 0;

}

相關文章
相關標籤/搜索