這篇文章主要深刻數據結構與算法在解決實際問題怎麼運用和分析的,對於 IP 對屬地查找自己有 API 接口,那這篇文章主要對原理內部查詢過程實現作詳細解析,體會怎麼將數據結構和算法解決實際的問題。前端
今天主要模擬一下怎麼在 20 萬數據中定位一個 IP 地址的歸屬地,不知道你們有沒有用過百度搜索過 IP 地址的歸屬地。當咱們在百度輸入 IP 地址時,就會出現這個 IP 地址的歸屬地。
java
或者有一些 IP 歸屬地的查詢工具也能夠迅速的查找到 IP 歸屬地。
算法
IP 地址數據那麼龐大,它是怎麼在短短不到一秒時間查找出 IP 地址的歸屬地呢?隨後我帶着疑問模擬了在 20 萬條數據中快速查找一個 IP 地址的歸屬地。數組
問題分析瀏覽器
咱們知道每一個 IP 由兩部分組成的,分別是網絡地址和主機地址。並且每一個 IP 地址是隨機動態分配的,因此說,每一個地區的 IP 地址的前多少位表明哪一個地區,後多少位表明地區中的局域網。每一個因此劃定了 IP 範圍,每一個表明不一樣的歸屬地。網絡
[112.222.133.0, 112.222.133.255] 山東濰坊市 [112.222.135.0, 112.222.136.255] 山東煙臺市 [112.222.156.34, 112.222.157.255] 山東青島市 [112.222.48.0, 112.222.48.255] 北京朝陽區 [112.222.49.15, 112.222.51.251] 福建省福州 [112.222.56.0, 112.222.56.255] 廣東省深圳市
咱們逐漸的將問題轉化爲了數據分析問題,也就是說,咱們怎麼查找一個 IP 地址所屬的範圍從而得出 IP 歸屬地呢?咱們可能會想到用快速增刪改查的數據結構和算法,平衡樹、散列表、跳錶、基於數組的二分查找等。數據結構
IP 地址的區間是連續的,可能先考慮到用一下二分查找,可是二分查找是有前提條件的:dom
一、二分查找是基於順序數組的,運用的數組在時間複雜度爲 (1) 的時間內隨機快速訪問數據的特性。數據結構和算法
二、二分查找它必須是有序數據,並且不能頻繁的進行動態插入和刪除數據,適合一次排序,屢次查找的狀況回到咱們問題符合要求。函數
經過兩個二分查找的條件繼續進行問題的分析,那麼問題又來了,二分查找是快速的查找一個數據是否存在一組數據中,並且效率極高,1000億查找一個數據只需 36 次查找。可是咱們的要解決的問題是在區間查找。
二分查找的擴展
彆着急,二分查找還可能有重複的數據,怎麼解決?因此二分查找會延伸到查找重複數據的第一個數據或最後一個數據,均可以經過二分查找的算法進行改進的。
若是咱們想要查找的 IP 地址在某一區間內,咱們能不能轉化爲查找最後一個小於等於某一個區間的起始值。舉個簡單例子:有一下區間[1,5]、[6,10]、[11,15]、[1六、20],好比 IP 爲 9 ,每一個區間的起始值分別爲 一、六、十一、16,也就是說 9 在這組區間起始值中,最後一個小於等於 9 的值,也就是 6 ,而後咱們拿 9 去區間[ 6,10] 去查找是否存在該 IP ,若是存在,咱們就輸出該區間對應的 IP 歸屬地。
解決方案
問題已經分析完成了,下一步開始將問題轉換爲數據結構與算法的形式來解決。若是你真認爲問題分析完成只剩下寫代碼了,你會接連的遇到棘手的問題。爲了可以讓你們更能體會到實際問題的複雜性,我會採用分步式遞進最終的解決方法。
問題一:當下手開始寫代碼時,你會發現 IP 地址並非像上述咱們用到的整數,那咱們怎麼辦呢?
※ 解決:你會想能不能將 IP 轉化爲整數來計算,這裏我用 js 來轉化。
1 //將 IP 地址轉化爲整數 2 const ipInt = (ip) =>{ 3 //IP轉成整型 4 var num = 0; 5 ip = ip.split("."); 6 num = Number(ip[0]) * 256 * 256 * 256 + Number(ip[1]) * 256 * 256 + Number(ip[2]) * 256 + Number(ip[3]); 7 num = num >>> 0; 8 return num; 9 }
問題二:IP 地址其實是動態生成的,怎麼來進行模擬那麼多隨機的 IP 地址呢?
※ 解決:最大的 IP 是 255.255.255.255 轉化成整數爲 4294967295。也就是 40 億,那咱們用隨機函數在 40 億的範圍內隨機生成 20 萬個的 IP 地址。
1 let i = 0; 2 const arrIp = []; 3 //隨機生成 200000 條 IP 數據 4 while(i < 10000){ 5 const number = Math.floor(Math.random()*10000000); 6 arrIp.push(number); 7 i++; 8 }
問題三:隨機生成的 IP 地址是無序的,咱們要進行排序,那麼排序的方式有不少,冒泡、歸併、快排、堆排序等,選擇哪種呢?
※ 解決:對於在 20 萬的 IP 查詢一個 IP 的歸屬地,我用 js 在瀏覽器中實現的,想到存儲空間有限,因此排序空間複雜度不能過高,查詢效率又不能太慢。快排的能夠實現空間複雜度爲 O(1) 排序,並且排序效率複雜度爲 O(nlog2n)
1 //對 20 萬條數據進行快速排序 2 // 參數一(arrIP):要排序的數組IP 參數二(start):指向起始指針 參數三(end):指向末尾指針 3 const quickSort = (arr,startIndex,endIndex) =>{ 4 //遞歸終止條件 5 if(startIndex < endIndex){ 6 //通常選擇最後一個元素爲區分點(下標索引) 7 let pivot = endIndex; 8 //獲取一組數據區分後的大於 pivot 點最後元素的索引 9 let partitionIndex = partition(arr,pivot,startIndex,endIndex); 10 //進行遞歸 11 quickSort(arr,startIndex,partitionIndex-1); 12 quickSort(arr,partitionIndex+1,endIndex); 13 } 14 } 15 16 // 獲取排好序的區分點 Index 17 const partition = (arr,pivot,startIndex,endIndex) =>{ 18 //獲取到該區分點的值 19 let pivotVal = arr[pivot]; 20 //永遠指向第一個大於 pivot 的值 21 let swapIndex = startIndex; 22 //進行篩選 23 // i 爲遍歷數據指針 24 for(let i = startIndex; i < endIndex; i++){ 25 if(arr[i] < pivotVal){ 26 swap(arr,i,swapIndex); 27 swapIndex++; 28 } 29 } 30 //將大於 pivot 的值和小於 pivot 的值中間點和 pivot 的值交換 31 swap(arr,swapIndex,pivot) 32 //返回區分點的索引 33 return swapIndex; 34 } 35 36 //交換 37 const swap = (arr,i,j) =>{ 38 let temp = arr[i]; 39 arr[i] = arr[j]; 40 arr[j] = temp; 41 }
問題四: 由於咱們要作的是查詢某 IP 在哪一區間,而不是查找該 IP 地址,因此要對二分查找代碼進行改進,讓其轉化爲小於等於某區間的起始位置。
1 //對 20 萬數據匹配IP對屬地(二分查找) 2 const findIpAddress = (arr,value) =>{ 3 //聲明兩個指針 4 let low = 0; 5 let high = arr.length - 1; 6 7 while(low <= high){ 8 //取中間值 9 let mid = Math.floor((low + (high - low))/2); 10 //判斷中間值 11 if(arr[mid] <= value){ 12 //進一步判斷是不是小於 IP 區間的終點值[改進] 13 if(mid == arr.length - 1 || arr[mid + 1] > value){ 14 return mid; 15 }else{ 16 low = mid + 1; 17 } 18 }else{ 19 high = mid - 1; 20 } 21 } 22 return false; 23 }
IP 區間歸屬地咱們本身設置幾個簡單的區間模擬一下,可是實際中不少的 IP 地址歸屬地劃分的很精細的,因此咱們在這很少陳述。
代碼咱們都作好了,我在這用前端作了一的簡單的交互頁面,咱們來模擬一下,你會發現,當咱們劃分區間後,數據並無 20 萬,由於咱們只記錄區間的起始值查找就能夠了,20 萬數據實際大約也就是十幾萬甚至小於這個值。
咱們能夠設想一下若是把全球的數據存儲到瀏覽器中會發生什麼,因此小鹿隨機生成了 50 億的數據,來進行排序二分查找,你猜發生了什麼狀況?
瀏覽器只在呼呼的轉圈,並不顯示什麼,好吧,做爲一個前端開發者,存儲那麼多的數據來進行操做內存溢出了。若是你是一名後臺開發者,能夠嘗試着用後臺語言實現一下,看看能不能數據量大時,能不能再進行查找了?
經過上邊的測試,小鹿從中又得出兩個二分查找的適用條件:
一、數據量不能太大,數組在內存中須要連續的內存空間,像 java 語言,在內存空間緊張的狀況下,二分查找就不適用了。可是 js 中的數組並非連續的,而是以哈希映射的方式存在的。
二、數據量不能過小,若是數據量過小,咱們直接遍歷就能夠了,無序寫複雜的二分查找來進行查詢。
二分查找的三點重點:
一、循環退出條件
注意是 low <= height,而不是 low < heigh。若是是後者,會形成循環指向一個數據。
二、mid 的取值
由於若是 low 比和 height 大的話,二者之和可能會溢出。應寫成 low+(high-low)/2 ,若是優化到極致的話,改進爲位運算符 low+((high-low)>>1)。
三、low 和 high 的更新
若是不進行 +1 和 -1 ,就有可能會發生死循環。
總結
自從學習數據結構與算法以來,發現它確實能解決不少咱們身邊實際的問題,而不只僅停留到刷各類各樣的算法題上。咱們刷算法題的主要目的呢,是提升邏輯思惟能力分析能力。還有一種能力也是須要提升的就是一個實際問題怎麼才能轉化爲數據結構和算法問題,再考慮用什麼樣的數據結構和算法去解決?怎麼找到一個最優的解決方案?
它對咱們的理解、分析、轉化實際問題到數據結構與算法提出了一個更高的要求,從以前寫了兩篇用數據結構與算法解決實際問題總結來看,我我的以爲不只僅須要分析問題的能力,還考驗一我的對全部數據結構與算法的靈活運用、優化、以及思想有很大的挑戰性,由於不侷限於一個算法題,還要考慮到實際的不少考慮不到的因素。