2 つの形式のバイナリ検索 (C++ 実装)

今、解決しなければならない問題があります

トピックの要件: n 個の要素 (昇順) を持つ整数配列 nums とターゲット値 target を指定して、nums でターゲットを検索する関数を作成し、ターゲット値が存在する場合は添え字を返し、存在しない場合は -1 を返します。

入力: nums= [-1,0,3,5,9,12] 
target= 9出力: 4
説明: 9 は添え字 4 とともに nums に表示されます。
入力: nums= [-1,0,3,5,9,12]、target= 2
出力: -1
説明: nums に 2 が存在しないため、-1 を返します。

前回二分探索を書いた後、ネット上の他の情報を調べてみると、実は二分探索にはよく使われる書き方が2つあることが分かりました、この記事(二分探索)では1つの形式しか書いていないので、あまり詳しく紹介されていないかもしれませ。そこで、覚えやすいように、二分探索の 2 つの形式をここにまとめます。

二分探索の 2 つの形式の主な違いは、区間の定義にあります。①左閉と右閉 [左、右]、②左閉と右開 [左、右)

具体的には、次の 2 つの音程の表現方法は、表現は若干異なりますが、実際には同じ音程の範囲を表現しており、同じ目的を持っています。中学数学) 

 

区間の定義が若干異なるため、プログラム内の変数midの値もそれに合わせて変更されています(変更しないとおかしくなります)ので、以下にこの2つの形式を紹介します。

①左閉右閉[左、右]

ターゲットを左閉右閉 [左、右] 区間内にあるものとして定義するため、次の 2 つの点に注意する必要があります。

  • while ループ条件; while(left<=right)。この時点では left==right が意味があるため、 while のループ条件で<=を使用します。
  • if (nums[mid]>target) の直後をどう処理するか? 現在の nums[mid] がターゲットであってはいけないため、right にはmid-1を割り当てる必要があります。その場合、次に検索される左区間の右境界はmid-1になります。

どうやって書くのですか?

class Solution {
public:
    int search(vector<int>& nums, int target) {
        int left=0,right=nums.size()-1;
        while(left<=right)
        {
            int mid=(right-left)/2+left;
            if(nums[mid]==target)
            {
                return mid;
            }
            else if(nums[mid]>target)
            {
                right=mid-1;
            }
            else
            {
                left=mid+1;
            }
        }
        return -1;
    }
};

上記のプログラムに簡単なコメントを書いてください

int left=0,right=nums.size()-1;

間隔を定義します。左側の境界は left=0、右側の境界は right=nums.size()-1 です。実際、これは閉じた間隔が定義されていることを意味し、配列全体のすべての要素が含まれる必要があります。この範囲内でターゲットを定義します。

コード right=nums.size()-1 に注目してください。なぜ 1 を減算するのでしょうか? nums.size() は nums に含まれる要素の数を返し、配列の添字は 0 から始まることがわかっているため、配列の最後の要素の添字は配列の要素数から 1 を引いたものでなければなりません。

while(left<=right)

ここで <= 記号として書かれているのはなぜですか? 前にも述べましたが、left==right の場合でも意味があるからです。

int mid=(right-left)/2+left;

なぜここでmidをこのように書く必要があるのでしょうか?実際、それと

int mid = (left+rirht)/2

同じことですが、なぜ以下のように書かずにそのように書くのか、主な理由はメモリオーバーフローを恐れているためです。

int 型の整数で表現できる最大数は 2147483647 です。演算中に左と右の数値が両方とも 2147483647 に近づくまで左が右に近づき続けると、加算した結果がオーバーフローして負の数になります。したがって、代わりにmid=(right-left)/2+leftを使用してください

上記の小さなテストを通じて、なぜmid=(right-left)/2+leftと書かなければならないのかを理解できます。

ここでは、別の形式の書き方に出会うかもしれません

int mid=((right-left)>>1)+left

ここでの「>>」演算子は右シフト演算子です。

例えば

 

  

 したがって、 int mid=((right-left)>>1)+left と int mid=(right-left)/2+left がわかります。これら 2 つの形式は同等ですが、ビット演算子 >> の方が ' よりも優れています。 /「もっと早くしてよ。ビット演算子はメモリデータを直接演算するため、10進数に変換する必要がなく、処理速度が高速です。

if(nums[mid]==target)
{
    return mid;
}

このコードは理解しやすいと思います。nums[mid]==target の場合、つまり、mid 位置の値が探しているターゲットと正確に等しい場合は、mid を直接返すだけで十分です。以降の範囲を絞って検索する必要はありませんが、配列は昇順で要素が重複していないので、値がターゲットと一致する位置は 1 つだけであることになります。

 else if(nums[mid]>target)
     {
         right=mid-1;
     }

この文は、ターゲットが元の間隔の左半分にあるため、新しい間隔の範囲は [left, middle-1] であることを意味します。

else
    {
         left=mid+1;
    }

 この文は、ターゲットが元の間隔の右半分にあるため、新しい間隔の範囲は [mid+1,right] であることを意味します。

while ループの外側とは、配列内にターゲットが見つからない場合は -1 を返すことを意味します。 

②左閉、右開[左、右)

ターゲットが左-閉じた-右-開いた[左,右)の区間で定義されている場合、境界処理の問題が変わります。これに注意してください。そうでない場合は間違っています。

  • while(left<right)、区間 [left, right) では left==right は無意味であるため、ここでは < が使用されていることに注意してください。
  • if(nums[mid]>target), right=mid、現在の nums[mid] が target と等しくないため、左の区間に移動して検索を続けますが、この時点では、区間は左が閉じ、右が開いているため、 、右は中央に更新され、

コアコードは次のとおりです

int search(vector<int>& nums, int target) {
    int left = 0, right = nums.size();
    while (left < right)
    {
        int mid = (right - left) / 2 + left;
        if (nums[mid] == target)
        {
            return mid;
        }
        else if (nums[mid] > target)
        {
            right = mid;
        }
        else
        {
            left = mid + 1;
        }
    }
    return -1;

}

ここでは一文ごとの説明はしませんが、唯一注意する必要があるのは、

else if (nums[mid] > target)
  {
      right = mid;
  }

これには適切な間隔の処理が含まれるため、注意してください。 

次に、2 つの形式のコードを vs に入れてテストします。

#include<iostream>
#include<vector>
using namespace std;

①[left,right]
//int search(vector<int>& nums, int target) {
//    int left = 0, right = nums.size() - 1;
//        while (left <= right)
//        {
//            int mid = (right - left) / 2 + left;
//            if (nums[mid] == target)
//            {
//                return mid;
//            }
//            else if (nums[mid] > target)
//            {
//                right = mid - 1;
//            }
//            else
//            {
//                left = mid + 1;
//            }
//        }
//        return -1;
// }

//②[left,right)
int search(vector<int>& nums, int target) {
    int left = 0, right = nums.size();
    while (left < right)
    {
        int mid = (right - left) / 2 + left;
        if (nums[mid] == target)
        {
            return mid;
        }
        else if (nums[mid] > target)
        {
            right = mid;
        }
        else
        {
            left = mid + 1;
        }
    }
    return -1;

}

int main()
{
    vector<int> vec{ 1,4,5,7,11,14,18 };
    cout << "vec: ";
    for (auto c : vec)
    {
        cout << c << " ";
    }
    cout << endl;
    int tar = 5;
   /* int tar = 2;*/
    int i=search(vec, tar);
    if (i >= 0)
        cout << tar<<" index is " << i << endl;
    else
        cout << tar<<" not found" << endl;
}

プログラムを実行した結果は次のようになります。 

 

おすすめ

転載: blog.csdn.net/yangSHU21/article/details/130571823