从一个骗局谈生活中的基础算法
曾經有一個著名的騙局:
小明是一個賭馬愛好者,最近他連續幾次提前收到了預測賭馬結果的郵件,從一開始由于不屑而錯失良機,到漸漸深信不疑,直到最后給郵件發送方匯了巨款才發現上當。
看過這個的人應該知道,騙子收集到一份郵件信息后,分組發送不同預測結果的郵件,賭馬結果公布后,再將篩選出來的那部分人分組,繼續發送下一輪預測郵件。幾輪過后,肯定能保證一部分人收到的預測結果是完全正確的。這也是最關鍵的部分。
那么騙子是如何從幾萬或幾十萬用戶中尋找這些“幸運兒”的呢?這是一種二分法的思想。
假如要順序在100萬人中尋找一個人,最多需要100萬次,而二分法只需要18次。
下面講講一些能夠解決生活中一些具體問題的常用算法。
二分查找
對于一個長度為N的數組,簡單查找最多需要N步;二分查找最多只需要logN步(約定底數為2)。
二分查找相較于簡單查找,極大地提高了效率,但是二分查找的前提是列表是有序的,這也導致了諸多限制。
快速排序
D&C
D&C(divide and conquer)分而治之是一種重要的解決問題思路。當面對問題束手無策時,我們應該考慮一下:分而治之可以解決嗎?
現在有一個問題,假如一塊土地(1680*640)需要均勻地分為正方形,而且正方形的邊長要盡量的大。該怎么分?
這個問題本質就是求兩條邊長的最大公因數。可以使用歐幾里得算法(輾轉相除)
快速排序
快速排序是一種常用的排序算法,比選擇排序快得多(O(n^2)),快速排序也使用了D&C。
選擇基準值
將數組分成兩個子數組:基準值左邊的數組和基準值右邊的數組
對這兩個數組進行快速排序
快速排序的最糟情況是O(n^2),O(n^2)已經很慢了,為什么還要叫它快速排序呢?
快速排序的平均運行時間為O(nlogn),而合并排序的時間總是O(nlogn),合并排序似乎更有優勢,那為什么不用合并排序呢?
因為大O表示法中的n是一個常量,當兩種算法的時間復雜度不一樣時,即使n在數值上不同,對總時間的影響很小,所以通常不考慮。
但有些時候,常量的影響很大,對快速排序和合并排序就是這樣,快速排序的常量小得多,所以當這兩種算法的時間復雜度都為O(nlogn)時,快速排序要快得多。而相較于最糟的情況,快速排序遇上平均情況的可能性更大,所以可以稍稍忽視這個問題。(快速排序最糟的情況下調用棧為O(n),在最佳情況下,調用棧長O(logn))
散列表
使用散列函數和數組可以構建散列表,散列表是包含額外邏輯的數據結構。
但是要編寫出完美的散列函數幾乎不可能,假如給兩個鍵分配的空間相同的話就會出現沖突。如何處理沖突呢?最簡單的辦法是:假如在某一空間上產生沖突,就在這一空間后再加上一個鏈表。但是假如這個鏈表很長,會很影響查找的速度(鏈表只能順序查找,查找時間為O(n))
所以一個能盡量避免沖突的散列函數是多么重要,那么怎么編寫一個性能較高的散列表呢?
較低的填裝因子(一旦填裝因子大于0.7,就需要調整長度)
良好的散列函數(讓數組中的值呈均勻分布,可以了解下SHA函數)
廣度優先搜索
廣度優先搜索能夠解決兩個問題:
兩個節點之間是否存在相連的路徑
最短的距離是多少?這個“最短距離”的含義有很多種。
想象這么一個問題:你想在你的微信好友和好友的好友中尋找是否有人是一名消防員,該如何查找?并且盡可能這人和你的關系更近些。
迪克斯特拉算法
在圖中,搜索最小的“段”數可以用廣度優先算法,這就相當于默認每條邊的權重是相同的,如果每條邊的權重不同呢?那就需要用到迪克斯特拉算法。
概括來說,迪克斯特拉算法就是從起點開始,首先尋找最廉價的節點,更新其開銷并標記為已處理,然然后在未處理的節點中尋找開銷最小的節點,然后以此往復下去。
針對書中的這樣一個問題,我把題干提取出來:目標是用樂譜換鋼琴。現在樂譜可以免費換海報;海報加30元換吉他;海報加35元換架子鼓;樂譜加5元可以換唱片;唱片加15元換吉他;唱片加20元換架子鼓;吉他加20元換鋼琴;架子鼓加10元換鋼琴。
現在我用圖把這個關系表示出來:
可以看出這是一個加權圖,現在我們要使用迪克斯特拉算法尋找最短路徑。
最后的最低開銷表為:
節點
開銷
海報 | 0 |
唱片 | 5 |
吉他 | 20 |
鼓 | 25 |
鋼琴 | 35 |
父子節點表為:
父節點
子節點
樂譜 | 唱片 |
樂譜 | 海報 |
唱片 | 吉他 |
唱片 | 鼓 |
鼓 | 鋼琴 |
可以看出,最優的交換的路徑為:piano-drum-record-music
最低開銷為:35元
貝爾曼-福德算法
在迪克特拉斯算法的基礎上,我們考慮這樣一種情況,假如邊的權重存在負值。
在迪克特拉斯算法中,我們首先尋找最廉價的節點,更新其開銷,再尋找未處理節點中最廉價的節點,以此往復。
可能出現這樣一個情況:
在將海報標記為已處理后,開始處理唱片,但是唱片到海報的路徑使得海報的開銷更小,又將更新海報的開銷,但是海報已經標記為已處理。那么就會出現一些問題。假如繼續使用迪克特拉斯算法,最后的結果肯定是錯的,大家可以更改參數試一下。為了正確解決問題,這時需要使用貝爾曼-福德算法。
貪心算法
對于一些比較復雜的問題,使用一些算法不能簡單有效地解決,這時候往往會使用貪心算法:每步操作都選擇局部最優解,最終得到的往往就是全局最優解。這似乎是想當然的做法,但是很多情況下真的行之有效。當然,貪心算法不適用于所有場景,但是他簡單高效。因為很多情況并不需要追求完美,只要能找到大致解決問題的辦法就行了。
假如我們面對這么一個問題:假設我開了一家網店,在全國各省都有生意,現在面臨發快遞的問題,假設現在的基礎物流不是很完善,每家快運公司只能覆蓋很少幾個省,那么我該如何在覆蓋全國34個省級行政區的情況下,選擇最少的快運公司?
這個問題看似不難,其實很復雜。
現在假設有n家快運公司,那么全部的組合有2^n種可能。
N
2^N
10 | 1024 |
20 | 1048576 |
50 | 1125899906842624 |
可以看到,假如有50家快遞公司,我將要考慮1125千億種可能。可以看到,沒有算法能很快的計算出這個問題,那么我們可以使用貪心算法,求局部最優解,然后將最終得到的視為全局最優解。
那么在這個問題下如何使用貪心算法?核心在于什么是局部最優條件?可以這樣:
選擇一家覆蓋了最多未覆蓋省的公司。
重復第一步。
來源:果核里的圖靈
版權歸原作者所有,轉載僅供學習使用,不用于任何商業用途,如有侵權請留言聯系刪除,感謝合作。
數據與算法之美
用數據解決不可能
長按掃碼關注
總結
以上是生活随笔為你收集整理的从一个骗局谈生活中的基础算法的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 用python挖一挖知乎上宅男们最喜欢的
- 下一篇: 荐书 | 攻克世纪难题,拒绝领取菲尔兹奖