在開發的過程當中, 常常會遇到集合排序, 那麼通常狀況下, 咱們都是使用list.OrderBy()的方式來排序, 也無需關注到裏面算法的實現是個什麼樣子. 正好這幾天準備回顧一下數據結構與算法. html
首先來了解一下, 排序大體能夠分爲哪幾種:算法
交換排序: 包括冒泡排序,快速排序。數組
選擇排序: 包括直接選擇排序,堆排序。數據結構
插入排序: 包括直接插入排序,希爾排序。dom
合併排序: 合併排序。post
List.OrderBy()採起的就是快速排序方式. 冒泡排序既然和它是同一種排序方式, 那麼咱們將他們比較一下看看. 看看哪種排序更好一點.ui
1、示例(先看結果, 再講思想)url
static void Test() { //五次比較 for (int i = 1; i <= 5; i++) { List<int> list = new List<int>(); //插入2k個隨機數到數組中 for (int j = 0; j < 2000; j++) { Thread.Sleep(3); list.Add(new Random((int)DateTime.Now.Ticks).Next(0, 100000)); } Console.WriteLine("\n第" + i + "次比較:{0}...", string.Join(",", list.Take(10))); Stopwatch watch = new Stopwatch(); watch.Start(); var result = list.OrderBy(single => single).ToList(); watch.Stop(); Console.WriteLine("\n快速排序耗費時間:" + watch.ElapsedMilliseconds); Console.WriteLine("輸出前是十個數:" + string.Join(",", result.Take(10).ToList())); watch.Start(); result = BubbleSort(list); watch.Stop(); Console.WriteLine("\n冒泡排序耗費時間:" + watch.ElapsedMilliseconds); Console.WriteLine("輸出前是十個數:" + string.Join(",", result.Take(10).ToList())); } } //冒泡排序算法 static List<int> BubbleSort(List<int> list) { int temp; int count = list.Count; //第一層循環: 代表要比較的次數,好比list.count個數,確定要比較count-1次 for (int i = 0; i < count - 1; i++) { //list.count-1:取數據最後一個數下標, //j>i: 從後往前的的下標必定大於從前日後的下標,不然就超越了。 for (int j = count - 1; j > i; j--) { //若是前面一個數大於後面一個數則交換 if (list[j - 1] > list[j]) { temp = list[j - 1]; list[j - 1] = list[j]; list[j] = temp; } } } return list; }
這段代碼是從參考連接裏面拿的, 我的比較懶啊, 並且冒泡排序算法仍是很簡單的, 就不本身寫了spa
從上面的結果來看, 快速排序比冒泡排序快的不是一星半點啊. pwa
看到了神奇之處, 接下來就是深刻了解一下, 算法的思想和實現.
2、思想及實現
1. 冒泡排序
首先來解釋一下冒泡吧, 在水裏面, 呼出一口氣, 造成一個泡泡, 這個泡泡會在上升的過程當中, 逐漸變大(水壓愈來愈小致使的). 最後露出說面破掉了.
聯繫着這種思想, 能夠想到, 冒泡排序, 應該就是讓大的數, 逐漸往上升, 一直升到比它大的數前面, 破掉了.
根據這種思想, 就大體有一個過程在腦海中造成了, 來看一下代碼: (下面的圖還蠻形象的, 就偷過來了)
//冒泡排序算法 static List<int> BubbleSort1(List<int> list) { int temp; int count = list.Count; for (int i = 0; i < count - 1; i++) { for (int j = 1; j < count; j++) { //若是前面一個數大於後面一個數則交換 if (list[j - 1] > list[j]) { temp = list[j - 1]; list[j - 1] = list[j]; list[j] = temp; } } } return list; }
首先, 每一次循環, 我都能肯定一個數的位置, 那麼須要循環多少次呢? 最後一個數應該不須要再循環比較了吧, 也沒數跟他比較了. 因此循環n-1次就好了.
接着, 這裏面的每一次循環, 其實就是一個冒泡的過程了. 把開始的數與後面一位數進行比較, 若是大於後面的數, 就向後移一位. (其實想一想, 這個也挺麻煩的, 爲啥比較一次就要移動一次呢? 我不能找到他的位置, 才互換麼? 起碼減小了換的操做了)
我這裏的代碼與上面示例的稍有不一樣, 稍微注意一下.
來看一下這裏的時間複雜度, 雖然外面只循環了n-1次, 裏面也只循環了n-3次, 看似複雜度爲(n-1)*(n-3), 可是若是n夠大的話, -1或者-3甚至-100, 對最後的結果影響都是很小的.
按照最壞的狀況算的話, 這裏的冒泡排序的時間複雜度, 極限狀況是O(n2), 那麼他有沒有理想狀況呢? 好像沒有啊, 就算這個數組已經排好序了, 好像程序仍是要這樣從頭走到尾啊, 一點都沒有少什麼. 因此這裏的平均複雜度, 也是 O(n2). 這麼看來, 冒泡排序並非一種理想的排序方式.
若是不能提供更好的排序方式的話, 仍是老老實實的使用List.OrderBy的方式去排序吧.
2. 快速排序
快速排序算法其實也叫分治法, 其步驟大體能夠分爲這麼幾步:
1. 先從數列中取出一個數做爲基準數Num(取得好的話, 是能夠減小步驟的)
2. 分區, 將大於Num的數放在它的右邊, 小於或等於它的數放在它的左邊
3. 再對左右區間重複前兩操做, 直到各個區間只有一個數爲止.
從上面的文字可能仍是不太好理解這個過程, 那麼我用一張圖片來描繪一下這個過程
通過一輪比較以後, 總感受這裏要遞歸啊. 牽涉到遞歸, 那麼他的空間複雜度可能會比冒泡排序高一點.
既然一輪的過程已經清楚了, 那麼就先寫出一輪的代碼好了
static int Division(List<int> list, int left, int right) { int baseNum = list[left]; while (left < right) { while (left < right && list[right] > baseNum) { right -= 1; } list[left] = list[right];
while (left < right && list[left] <= baseNum) { left += 1; } list[right] = list[left]; } list[left] = baseNum; return left; }
這裏的Left+=1和Right-=1都是有前提條件的, 前提條件爲:Left < Right
接下來就比較簡單了, 一個遞歸調用, 這個遞歸的思想是很簡單的:
static void QuickSort(List<int> list, int left, int right) { if (left < right) { int i = Division(list, left, right); QuickSort(list, left, i - 1); QuickSort(list, i + 1, right); } }
快速排序的複雜度:
最糟糕的狀況下, 複雜度爲: O(n2)
最優的狀況下, 複雜度爲: O(nlog2n)
其複雜度的計算和證實, 額, 我是給不出來了, 可是參考裏面是有的.
最差狀況下, 快速排序和冒泡排序的時間複雜度是同樣的, 可是最優狀況下, 冒泡排序是 n * n, 而快速排序的是 n * log2n,
若是n=16,
則冒泡是 16 * 16
快速排序是 16 * 4
可見, 只要你不是背到家, 都是比冒泡來的快的.
參考: