C/C++的二分查找


假設有一種溫度傳感器,已經測得它的電壓和溫度的對應關系,將電壓值以ADC轉換后的數字量的值表示,形成溫度-AD值的對照表,如下。

 

 

大致成一條反比關系的曲線。

 

ADC的底層驅動已經寫好,對外有一個接口可以實時讀取該傳感器的AD值。現在要求,讀出AD值以后,通過二分查找,查找它對應的溫度值,然后存到另一個結構體中,供后續其它模塊使用。

 

寫二分查找函數,可以這么構思。

 

把AD值看作X軸,溫度值看作Y軸。函數有一個輸入,一個輸出。輸入就是從底層接口獲取的ad值,輸出就是查到的對應的溫度值。所以首先可以有兩個參數,前者作為形參,接收輸入;后者設定為一個指針,指向外面定義的一個存儲輸出的變量。其它參數有:兩張表(AD和溫度),還有限制查找范圍的表格的大小。

 

因此函數頭可以寫成下面這個樣子。前兩個是const SW類型的指針,指向兩張表,然后是表格的大小和輸入輸出。(所有基礎類型定義在其它頭文件里,此處SW表示signed word,是signed short類型)

B look_up_table_sw(const SW * p_XTable_SW, const SW * p_YTable_SW, UW TableSize_UW, SW ValueX_SW, SW *ValueY_SW)

 

函數返回的類型是“B”布爾類型,查到或者沒查到。

 

接下來考慮函數的實現。

 

最開始應該做個安全檢查,確保指針不是空的,表格是有的。

if (p_XTable_SW != NULL && p_YTable_SW != NULL && TableSize_UW > 0)
{
    //......
}

 

保證這一步起碼沒問題后,再開始工作。

 

首先,看看AD值是不是超出AD表格的上下限,超出了也算找到,查到的溫度值就取作溫度相應的最高和最低值。

if (ValueX_SW <= p_XTable_SW[0])
{
    *ValueY_SW = p_YTable_SW[0];
    ret = TRUE;
}
else if (ValueX_SW >= p_XTable_SW[TableSize_UW - 1])
{
    *ValueY_SW = p_YTable_SW[TableSize_UW - 1];
    ret = TRUE;
}
else
{
    //......
}

 

在else{}中繼續補充代碼。此時,說明AD值是在查找范圍之內的,下面才是二分查找的重點。

 

二分查找的思想,其實可以用一種猜數字的小游戲舉例。

 

魔術師對女孩說:“你在1-100隨便想一個數字,你只要每次告訴我猜大了還是猜小了,我一定能在10次以內猜到它。”女孩想好了一個數字(假如是88),魔術師每次都猜中間的數,不是整數就向下取整。第一次猜50,女孩說小了,那么魔術師瞬間就排除掉了一半的數,前1~49都不用想了。第二次猜75,女孩說小了。第三次猜87,小了。第四次猜93,大了。第五次猜90,大了。第六次就猜到了88。

 

二分查找實際上是個效率很高的查找算法,但是要求表格是張有序表,適用於不會怎么變但又查找頻繁的表格,就像本次要求中的,AD值和溫度值有明確的一一對應的、關系簡單的表格。

 

所以首先定義出表示猜大還是猜小的變量,和表示猜的中間數的變量。在一個循環中不斷查找,每次循環開始,刷新猜的中間數。結構如下。

UW nLow = 0x00;

UW nHigh = TableSize_UW - 1;

UW nMid = (nHigh + nLow) / 2;

while (nLow < nHigh)
{
nMid = (nHigh + nLow) / 2;
//...... }

 

在while{}中繼續補充代碼。考慮哪些情況下退出循環。

 

循環判斷條件寫的是查找下限小於上限,這是最最保底的判斷了,實際上只要能確定到要找的值在哪里,也就是說,我知道它是第幾個,或在第幾個的邊上一點點,就可以了。

 

比如我知道它是第三個,那么它就是Y軸數組里的Y(3),我知道它在第三個和第四個之間,那么它就在Y(3)和Y(4)之間,可以用某種方法(比如插值)算出來,這可以放到后面進行。簡單來說,退出這個循環的情形就是,我知道它在哪里了

 

補充如下。

if (nMid == nLow)
{
    break;
}
else if (((ValueX_SW >= p_XTable_SW[nMid]) && (ValueX_SW <= p_XTable_SW[nMid + 1])) || ((ValueX_SW <= p_XTable_SW[nMid]) && (ValueX_SW >= p_XTable_SW[nMid - 1])))
{
    break;
}
else if (ValueX_SW > p_XTable_SW[nMid])
{
    nLow = nMid + 1;
}
else
{
    nHigh = nMid - 1;
}

 

第一個if依然是個保底的判斷,這里可以自己演算一下,nMid最次的結果就是向下取整,最后和下限是一樣的值。然而一般在第二個else if中就可以跳出循環了,這一步就是確定了AD值的具體位置。然后是兩個沒確定位置的情況,讓下限變成原來的中間數(猜小了,往上猜),或讓上限變成原來的中間數(猜大了,往下猜)。每次循環都會刷新的nMid,就是每次重猜的數。代碼里的nMid表示的數組里的序號。

 

好,到這里,我知道了AD值的具體位置,但還有有三種情況。

①它正好是我猜的數(nMid)。

②它在我猜的數右邊一點。(nMid和nMid+1之間)

③它在我猜的數左邊一點。(nMid和nMid-1之間)

 

第一種好處理,它是X軸的第nMid個,對應的當然也就是Y軸的第nMid個。

if (p_XTable_SW[nMid] == ValueX_SW)
{
    *ValueY_SW = p_YTable_SW[nMid];
    ret = TRUE;
}

 

第二種和第三種怎么處理,畫個圖更好理解。比如說下面的例子,a是從接口獲取的AD值,通過上面的查找,確定了它在第二個和第三個值之間。現在nMid就是2,a落在nMid和nMid+1之間。當然,我想知道的溫度b也落在了Y軸的nMid和nMid+1之間。

(畫直線只是簡單表示反比關系)

 

這里我采取線性插值的方式計算b是多少。所謂線性插值就是假設相鄰點之間是條直線,斜率是一樣的。

 

我們把局部畫一下,就像這樣:

 

可以用斜率或正切相等的方式,也可以用相似三角形,總之原理其實一樣,得到如下的關系式:

 

 

進而:

 

 

第三種情況在左邊,是完全一樣的,只要整體減小一格,把原來的x(mid)替換成x(mid-1),原來的x(mid+1)替換成x(mid),y(mid+1)替換成y(mid),y(mid)替換成y(mid-1)就行了。

 

正比關系的推導過程是一樣一樣的。

 

因此三種情況的代碼如下:

if (p_XTable_SW[nMid] == ValueX_SW)
{
    *ValueY_SW = p_YTable_SW[nMid];
    ret = TRUE;
}
else if (ValueX_SW < p_XTable_SW[nMid])
{
    *ValueY_SW = p_YTable_SW[nMid-1] - (ValueX_SW - p_XTable_SW[nMid-1]) * (p_YTable_SW[nMid-1] - p_YTable_SW[nMid]) / (p_XTable_SW[nMid] - p_XTable_SW[nMid-1]);
    ret = TRUE;
}
else
{
    *ValueY_SW = p_YTable_SW[nMid] - (ValueX_SW- p_XTable_SW[nMid]) * (p_YTable_SW[nMid]-p_YTable_SW[nMid + 1]) / (p_XTable_SW[nMid + 1] - p_XTable_SW[nMid]);
    ret = TRUE;
}

 

這樣,二分查找函數就寫完了,最后:

return ret;

 

整項工作要求的完整的二分查找函數的實現,放在lookup.cpp中,如下。

#include "lookup_pub.h"

B look_up_table_sw(const SW * p_XTable_SW, const SW * p_YTable_SW, UW TableSize_UW, SW ValueX_SW, SW *ValueY_SW)
{
    B ret = FALSE;

    if (p_XTable_SW != NULL && p_YTable_SW != NULL && TableSize_UW > 0)
    {
        if (ValueX_SW <= p_XTable_SW[0])
        {
            *ValueY_SW = p_YTable_SW[0];
            ret = TRUE;
        }
        else if (ValueX_SW >= p_XTable_SW[TableSize_UW - 1])
        {
            *ValueY_SW = p_YTable_SW[TableSize_UW - 1];
            ret = TRUE;
        }
        else
        {
            UW nLow = 0x00;

            UW nHigh = TableSize_UW - 1;

            UW nMid = (nHigh + nLow) / 2;

            while (nLow < nHigh)
            {
                nMid = (nHigh + nLow) / 2;

                if (nMid == nLow)
                {
                    break;
                }
                else if (((ValueX_SW >= p_XTable_SW[nMid]) && (ValueX_SW <= p_XTable_SW[nMid + 1])) || ((ValueX_SW <= p_XTable_SW[nMid]) && (ValueX_SW >= p_XTable_SW[nMid - 1])))
                {
                    break;
                }
                else if (ValueX_SW > p_XTable_SW[nMid])
                {
                    nLow = nMid + 1;
                }
                else
                {
                    nHigh = nMid - 1;
                }
            }

            if (p_XTable_SW[nMid] == ValueX_SW)
            {
                *ValueY_SW = p_YTable_SW[nMid];
                ret = TRUE;
            }
            else if (ValueX_SW < p_XTable_SW[nMid])
            {
                *ValueY_SW = p_YTable_SW[nMid-1] - (ValueX_SW - p_XTable_SW[nMid-1]) * (p_YTable_SW[nMid-1] - p_YTable_SW[nMid]) / (p_XTable_SW[nMid] - p_XTable_SW[nMid-1]);
                ret = TRUE;
            }
            else
            {
                *ValueY_SW = p_YTable_SW[nMid] - (ValueX_SW- p_XTable_SW[nMid]) * (p_YTable_SW[nMid]-p_YTable_SW[nMid + 1]) / (p_XTable_SW[nMid + 1] - p_XTable_SW[nMid]);
                ret = TRUE;
            }
        }
    }

    return ret;
}

 

其它補充的源代碼附在后面。

 

App_Typedefine.h中對基礎類型作宏定義。

#ifndef _APP_TYPEDEFINE_H_
#define _APP_TYPEDEFINE_H_

// ==== basic type ====
typedef unsigned char               UBYTE;
typedef signed char                 SBYTE;
typedef unsigned short              UWORD;
typedef signed short                SWORD;

typedef unsigned char               BOOL;
typedef unsigned char               BOOLEAN;

typedef float                       FLOAT;
typedef double                      DOUBLE;

typedef unsigned char                 APP_RESULT;
#define APP_E_NOT_OK                (0x01U)
#define APP_E_OK                    (0x00U)

typedef UBYTE UB;
typedef SBYTE SB;
typedef UWORD UW;
typedef SWORD SW;

typedef FLOAT F;

typedef BOOL  B;


//typedef void VOID
#define VOID      void

// Definition of local function
#define LOCFUNC   static

// Definition of a function    
#define PUBFUNC

// MACRO used for BOOL type
#ifdef FALSE
#undef FALSE
#endif
#define FALSE ((BOOL)0)

#ifdef TRUE
#undef TRUE
#endif
#define TRUE  (!FALSE)

// MACRO used for inline function, used for optimization
#ifdef  CSPLUS
#define INLINE 
#else
#define INLINE inline
#endif

// MACRO used for const
#ifdef CONST
#undef CONST
#endif
#define CONST    const

// MACRO used for static variable
#define LOCVAR   static

// MACRO used for static functions
#define LOCFUNC  static


// Keep Same as MPU_NULL_PST_GUARD_LowAddress
#define NULL_PST     ((void*)0xFEE00000)

// Definition of NULL-pointer
#ifndef NULL
#define NULL     (0)
#endif

// Definition of an invalid pointer value
#define PTR_INVALID (0xFEE00000)

#define NULL_POINTER(type) ((type *) PTR_INVALID)

// min-max-constants for integer data types
#define UB_MAX          ((UB)255)
#define UB_MIN          ((UB)0)

#define UBYTE_MAX       ((UB)255)
#define UBYTE_MIN       ((UB)0)

#define SB_MAX          ((SB)127)
#define SB_MIN          ((SB)-128)

#define SBYTE_MAX       ((SB)127)
#define SBYTE_MIN       ((SB)-128)

#define UW_MAX          ((UW)65535)
#define UW_MIN          ((UW)0)

#define UWORD_MAX       ((UW)65535)
#define UWORD_MIN       ((UW)0)


#define SW_MAX          ((SW)32767)
#define SW_MIN          ((SW)-32768)

#define SWORD_MAX       ((SW)32767)
#define SWORD_MIN       ((SW)-32768)

#define UL_MIN          ((UL)0)
#define UL_MAX          ((UL)4294967295)

#define ULONG_MIN       ((UL)0)
#define ULONG_MAX       ((UL)4294967295)

#define SL_MAX          ((SL)2147483647)
#define SL_MIN          ((SL)-2147483648)

#define SLONG_MAX       ((SL)2147483647)
#define SLONG_MIN       ((SL)-2147483648)

#endif

 

lookup_pub.h對查找函數作外部聲明,引用了基礎類型的頭文件。

#ifndef LOOKUP_PUB_H_INCLUDED
#define LOOKUP_PUB_H_INCLUDED

#include "App_TypeDefine.h"

extern B look_up_table_sw(const SW * pSrcTable_SW, const SW * pDestTable_SW, UW TableSize_UW, SW ValueSrc_SW, SW *ValueDest_SW);

#endif 

 

HwaAdc_pub.h中定義了用於存值、供其它模塊使用的結構體,以及adc處理函數的外部聲明。

#ifndef _HWA_ADC_PUB_INCLUDED_
#define _HWA_ADC_PUB_INCLUDED_

#include "App_TypeDefine.h"

#define ADC_TABLE_SIZE      27

typedef struct
{
    UB state_UB;
    SW value_SW;
}HWA_TEMP_ADC_INFO_ST;

typedef struct
{
    HWA_TEMP_ADC_INFO_ST    TempInCar_st;
    // form ADC

}IF_HWA_ADC_READ_INFO_ST;

typedef struct
{
    IF_HWA_ADC_READ_INFO_ST Adc_st;

}IF_SRU_HWA_INFO_READ_PACKET_ST;

extern const SW HWA_ADC_AD_Table_SW[ADC_TABLE_SIZE];

extern VOID hwa_adc_process(VOID);

#undef EXTERN
#endif

 

HwaAdc.cpp定義了一個內聯函數,用於把查到的溫度值存到上面定義的結構體內。實現了adc處理函數,調用查找函數然后傳送找到的溫度值。為便於測試,所以寫成下面這樣,ad值自己手動輸入,將傳出去的結果顯示出來。

其中定義的normvalue是起放大作用的,因為查找函數返回SW類型,查找后算的值也會向下取整,但是算出的有可能是個浮點數,那就體現不出小數點后面的精度了,所以先放大,再計算,最后返回的整數就能看到小數點后面幾位

#include "lookup_pub.h"
#include "HwaAdc_pub.h"
#include <iostream>

//展現精度
#define normvalue 100

IF_SRU_HWA_INFO_READ_PACKET_ST                           hwa_t5_write_pst;

const SW HWA_ADC_AD_Table_SW[ADC_TABLE_SIZE] = {
    22,25,29,33,38,
    45,52,61,73,86,
    102,123,147,178,217,
    265,326,403,502,629,
    794,1010,1293,1670,
    2174,2856,3787
};

const SW HWA_ADC_INDOOR_TEMP_Table_SW[ADC_TABLE_SIZE] = {
    90 * normvalue,85 * normvalue,80 * normvalue,75 * normvalue,70 * normvalue,
    65 * normvalue,60 * normvalue,55 * normvalue,50 * normvalue,45 * normvalue,
    40 * normvalue,35 * normvalue,30 * normvalue,25 * normvalue,20 * normvalue,
    15 * normvalue,10 * normvalue,5 * normvalue,0 * normvalue,-5 * normvalue,
    -10 * normvalue,-15 * normvalue,-20 * normvalue,-25 * normvalue,
    -30 * normvalue,-35 * normvalue,-40 * normvalue
};

INLINE VOID VDB_Set_Temp_InCar_V(SW val_sw)
{
    hwa_t5_write_pst.Adc_st.TempInCar_st.value_SW = val_sw;
}

VOID hwa_adc_process(VOID)
{
    UW adc_value_uw;
    SW adc_value_sw;

    //TempInCar_sw
    //adc_value_uw = analog_if_query_channel(HWA_ADC_CH_INDOOR_TEMP);
    std::cout << "Enter ad value input:";
    while (std::cin >> adc_value_uw)
    {
        if (look_up_table_sw(HWA_ADC_AD_Table_SW, HWA_ADC_INDOOR_TEMP_Table_SW, ADC_TABLE_SIZE, (SW)adc_value_uw,
            &adc_value_sw))
        {
            VDB_Set_Temp_InCar_V(adc_value_sw);
        }
        std::cout << "ad value from analogif is " << adc_value_uw << ",";
        std::cout << "TEMP value set out is:" << hwa_t5_write_pst.Adc_st.TempInCar_st.value_SW << "\n";
        std::cout << "Enter ad value input:";
    }

}

 

最后是主函數main.cpp。

#include "HwaAdc_pub.h"
#include <stdlib.h>

int main()
{
    hwa_adc_process();

    system("pause");
}

 

測試結果

 

AD值剛開始輸入21,超出范圍, 所以溫度取到90度。

輸入23,24,可以看到是88.34度,86.67度。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM