歡迎來到查找的世界,在學習完各種數據結構之後,總算走到了這一步,不知道大家有什麼感想呢?反正我是邊學邊忘,現在讓我去說說圖的那幾個算法還是在蒙圈的狀態中。不過學習嘛,就是一步一步的來,暫時搞不懂的東西其實也是可以放一放的。打破砂鍋和堅持不懈當然是好的品德,但有些東西可能真的是需要時間去消化的,甚至可能是需要真實的項目經歷才能徹底搞明白。在我們編程行業來說就是典型的這種實踐的學習形式效果會更好,很多人在上大學的時候對於數據結構以及其它專業課都是以死記硬背爲主,包括上了多少年班的同學可能都沒有在業務代碼中真正的使用過什麼算法,所以理解它們確實是非常困難的。這時,我們可以暫時休息一下,轉換一下思路,學習最主要的就是預習和複習,在這次學習完之後,將來再進行多次的複習,研究各種不同的資料,遲早有一天大家都能搞明白的。
今天的內容其實就非常簡單了,可以說是除了線性表之外最簡單的內容。我們只研究兩個非常初級的查找,那就是順序查找和折半查找。相信不少同學可能早就會了,一般培訓機構講數據結構和算法時,查找必講二分,排序必講冒泡,更不用說正規大學對口專業出身的同學了。當然,這兩個也是非常簡單的,不管你有沒有基礎,咱們一起來看看吧。
不管你是什麼算法題,還是在實際的業務開發中,查找都是非常重要的,甚至可能比排序還要重要。想想你整天面向數據庫編程是在幹嘛?不就是 CRUD 嘛,其中大部的業務還都是以搜索查找居多,我們在優化數據庫時,也主要是優化各種查詢語句。當然,要說到數據庫的查找那就太高深了,以後我們學習 MySQL 相關的知識時再詳細講解,特別是索引中的 B+ 樹,就是數據結構和算法的核心思想的體現。好吧,不吹牛了,也不敢在這裏多說了,因爲自己也沒研究透呢。
線性查找(順序查找)
顧名思義,不管是叫線性還是叫順序,很明顯,就是一條數據一條數據的對比下去就好啦。
function SearchSeq($sk, $arr)
{
for ($i = 0; $i < count($arr); $i++) {
if ($sk == $arr[$i]) {
echo $i, PHP_EOL;
break;
}
}
echo "線性查找次數:" . $i, PHP_EOL;
}
嗯,真的是連解釋都不想解釋了,這段代碼要是看不懂的話就先去複習下基本的循環和條件判斷語句吧!很明顯,一次線性查找的時間複雜度就是 O(N) 。
二分查找(折半查找)
既然都這麼簡單,那麼我們再直接給出折半查找的代碼。
function SearchBin($sk, $arr){
$left = 0;
$right = count($arr) - 1;
$i = 0;
while ($left <= $right) {
$i++;
$mid = (int) (($left + $right) / 2);
if ($arr[$mid] > $sk) {
$right = $mid - 1;
} else if ($arr[$mid] < $sk) {
$left = $mid + 1;
} else {
echo $mid, PHP_EOL;
break;
}
}
echo "折半查找次數:" . $i, PHP_EOL;
}
折半查找的前提是數據必須是有序的,這樣我們就可以根據數據問題的長度來獲取中間的數,然後跟要對比的數進行比較,如果小於這個數,就在前一半數據中查找,如果大於這個數,就在後一半部分中進行查找。一會看例子再詳細說明。
對比
兩個算法其實都很簡單,我們直接看看他們的運行情況和效率區別。
$arr = [5, 6, 19, 25, 4, 33, 56, 77, 82, 81, 64, 37, 91, 23];
// 輸入 56
fscanf(STDIN, "%d", $searchKey);
SearchSeq($searchKey, $arr);
// 6
// 線性查找次數:6
sort($arr);
print_r($arr);
SearchBin($searchKey, $arr);
// 8
// 折半查找次數:3
首先我們定義了一個數組,其實就是隨便給了一些數據。然後輸入一個數據,查找它在數組中的位置。比如我們在測試代碼中輸入了 56 ,線性查找是循環進行了 6 次,找到 56 所在的位置爲下標 6 的位置。
對於折半查找來說,我們需要先給數組排序,這時 56 會排在下標爲 8 的位置,而在折半查找的循環中,我們只循環了 3 次就找到了這個位置。是不是感覺快了很多,一下就快了一倍。這可不是它的真正實力哦,折半查找的真實實力是 對數 級別的效率,也就是它的時間複雜度爲 O(logN) 。我們先來結合上面的代碼看下它這三次循環都幹了什麼。
第一次進入,mid 爲 6 (0+13=13,除2),下標爲 arr[6] 的值爲 3 ,比 56 小,所以 left = 6+1 = 7
第二輪循環,mid 爲 10(7+13=20,除2),下標爲 arr[10] 的值爲 77 ,比 56 大,所以 right = 10-1 = 9
第三輪循環,mid 爲 9(7+9=16,除2),下標爲 arr[8] 的值爲 56,結束
其實很多猜數字的遊戲也都是這麼玩的,比如給你一個範圍,0-100的數,猜他寫下的是哪個數,最快最簡單的方法也就是這種折半查找的方式,我們只需要最多 7 次就可以猜出 100 以內的數。很明顯,這就是對數的威力。下面我們再來看一個更直觀的,十萬個有序的數,我們就找最後那一個數,看看順序查找和折半查找能有多大差距。
$arr = range(1, 100000);
$searchKey = 100000;
SearchSeq($searchKey, $arr);
// 99999
// 線性查找次數:99999
SearchBin($searchKey, $arr);
// 99999
// 折半查找次數:17
嗨不嗨,這就是對數的威力!!我們需要 2 的 7 次方纔能覆蓋 100 以內的數,但我們只需要 2 的 17 次方,就能覆蓋十萬以內的數,這個效率差距還是隨着 N 的越來越大而越來越明顯的。
總結
今天的內容是不是很簡單,雖說內容簡單,但是我們卻見識到了不同算法效率之間的巨大差異。當然,折半查找也有其本身的侷限,那就是數據必須是的序的,當然,在合適的情況下我們也可以選用一個 O(logN) 的排序算法,這樣總體的時間複雜度就還能保持在對數級別了。總之,先掌握好這些簡單的內容,千萬別在面試的時候連這一關都過不了哦!
測試代碼:
參考文檔:
《數據結構》第二版,嚴蔚敏
《數據結構》第二版,陳越
《數據結構高分筆記》2020版,天勤考研