標籤:javascript 排序演算法
前言
計劃趕不上變化,本來想深入學習python的我,無奈要轉到js開發,在js基本0基礎的情況下,最近也狂補js知識了。
本著好記性不如爛筆頭的信念,我決定總結一下js中數組的使用。
建立數組
js中數組的聲明可以有如下幾種方式:
var arr = []; // 簡寫入模式var arr = new Array(); // new一個array對象var arr = new Array(arrayLength); // new一個確定長度的array對象
要說明的是:
雖然第三種方法聲明了數組的長度,但是實際上數組長度是可變的。也就是說,即使指定了長度為5,仍然可以將元素儲存在規定長度之外,這時數組的長度也會隨之改變。
此外,還需要明確的一點:
js是弱類型語言,也就是數組中的元素類型不需要一樣。
舉個數組中元素類型不一致的例子:
var arr = [1, 2, 3, 4, ‘wangzhengyi‘, ‘bululu‘];for (var i = 0; i < arr.length; i ++) { console.log(arr[i]);}
數組元素訪問
JavaScript數組的索引值也是從0開始的,我們可以直接通過數組名+下標的方式對數組元素進行訪問。
範例程式碼如下:
var arr = [1, 2, 3];console.log(arr[0]);console.log(arr[1]);
此外,數組的遍曆推薦使用連續for迴圈的模式,不推薦for-in,具體原因參考:Loop through array in JavaScript
遍曆數組範例程式碼如下:
var arr = [1, 2, 3, 4, ‘wangzhengyi‘, ‘bululu‘];for (var i = 0, len = arr.length; i < len; i ++) { console.log(arr[i]);}
注意:
上述代碼中,一個小最佳化在於提前擷取數組的大小,這樣不需要每次遍曆都去查詢數組大小。對於超大數組來說,能提高一定的效率。
添加數組元素
有三種方法可以往一個數組中添加新的元素,分別是:push、unshift、splice。下面我分別來介紹一下這三種方法。
push
push方法,在數組末尾添加元素。範例程式碼如下:
var arr = [];arr.push(1);arr.push(2);arr.push(3);for (var i = 0, len = arr.length; i < len; i ++) { console.log(arr[i]);}
執行結果為:
123
unshift
unshift方法,是在數組頭部添加元素。範例程式碼如下:
var arr = [];arr.unshift(1);arr.unshift(2);arr.unshift(3);for (var i = 0, len = arr.length; i < len; i ++) { console.log(arr[i]);}
執行結果如下:
321
splice
splice方法是在數組的指定位置插入新元素,之前的元素則是自動順序後移。注意splice的函數原型為:
array.splice(index, howMany, element...)
howMany表示要刪除的元素個數,如果只是添加元素,此時howMany需要置為0。
範例程式碼如下:
var arr = [1, 2, 3, 4];arr.splice(1, 0, 7, 8, 9);for (var i = 0, len = arr.length; i < len; i ++) { console.log(arr[i]);}
執行結果如下:
1789234
刪除數組元素
與增加數組元素一樣,刪除數組中的元素也有三個方法,分別是:pop、shift和splice。接下來,分別講解一下這三個函數的用法。
pop
pop方法是移除數組中的最後一個元素。push和pop的組合可以將數組實作類別似於棧(先入後出)的功能。範例程式碼如下:
var arr = [];arr.push(1);arr.push(2);arr.push(3);while (arr.length != 0) { var ele = arr.pop(); console.log(ele);}
shift
shift方法是移除第一個元素,數組中的元素自動前移。(這種方法肯定對應著效率問題,時間複雜度是O(n))。
var arr = [];arr.push(1);arr.push(2);arr.push(3);function traverseArray(arr) { for (var i = 0, len = arr.length; i < len; i ++) { console.log(arr[i]); }}while (arr.length != 0) { var ele = arr.shift(); traverseArray(arr);}
大家可以自己考慮運行結果。
splice
在增加數組元素的時候,我們就講過splice,這個函數原型中有一個howMany參數,代表從index開始刪除之後的多少個元素。
範例程式碼如下:
var arr = [1, 2, 3, 4, 5, 6, 7];function traverseArray(arr) { for (var i = 0, len = arr.length; i < len; i ++) { console.log(arr[i]); }}arr.splice(1, 3);traverseArray(arr);
執行結果為:
157
數組的拷貝和截取
舉個例子,代碼如下:
var arr1 = [1, 2, 3, 4];var arr2 = arr1;
這個時候,arr2隻是儲存arr1數組在堆記憶體的地址,並沒有在堆記憶體重新申請記憶體搞一個數組出來。所以對arr2的修改會同時影響到arr1。因此,如果我們需要拷貝一份數組該怎麼做呢?這就引出了需要學習的slice和concat函數。
slice
這裡的slice和python文法的slice是一樣的,都是返回數組的切片。slice函數原型為:
array.slice(begin, end)
返回從begin到end的所有元素,注意包含begin,但是不包含end。
預設begin,預設從0開始。預設end,預設到數組末尾。
因此,拷貝數組我們可以通過如下代碼實現:
var arr1 = [1, 2, 3, 4];var arr2 = arr1.slice();arr2[2] = 10000function traverseArray(arr) { for (var i = 0, len = arr.length; i < len; i ++) { console.log(arr[i]); }}traverseArray(arr1);traverseArray(arr2);
執行結果如下:
123412100004
concat
concat方法將建立一個新數組,然後將調用它的對象(this 指向的對象)中的元素以及所有參數中的數群組類型的參數中的元素以及非數群組類型的參數本身按照順序放入這個新數組,並返回該數組.
範例程式碼如下:
var alpha = ["a", "b", "c"];var number = [1, 2, 3]// 新數組為["a", "b", "c", 1, 2, 3]var complex = alpha.concat(number);
排序演算法的實現
我的JS水平就是渣渣,所以我就用類似於JAVA和C的方式來寫JavaScript的排序演算法了。
而且這裡我不講演算法原理,僅僅只是代碼實現,可能會有Bug,歡迎大家部落格評論指導。
插入排序
插入排序(Insertion-Sort)的演算法描述是一種簡單直觀的排序演算法。它的工作原理是通過構建有序序列,對於未排序資料,在已排序序列中從後向前掃描,找到相應位置並插入。插入排序在實現上,通常採用in-place排序(即只需用到O(1)的額外空間的排序),因而在從後向前掃描過程中,需要反覆把已排序元素逐步向後挪位,為最新元素提供插入空間。
實現代碼如下:
function insertSort(arr) { if (!arr) return; var len = arr.length; if (len == 0 || len == 1) return; for (var i = 1, len = arr.length; i < len; i ++) { var stand = arr[i]; for (var j = i - 1; j >= 0; j --) { if (arr[j] > stand) { arr[j + 1] = arr[j]; } else { arr[j + 1] = stand; break; } } } return arr;}
時間複雜度為:O(n^2)
當然,該演算法是有最佳化餘地的,例如將搜尋替換的位置演算法改為二分尋找。
冒泡排序
經典的排序演算法,提到冒泡排序我就心痛。本科時候的必須論文的冒泡排序演算法的改進,結果寫完論文之後都不能完整的實現冒泡排序演算法,好尷尬。
if (!arr) return; var len = arr.length; if (len == 0 || len == 1) return; for (var i = 0; i < len; i ++) { for (var j = 0; j < len - i - 1; j ++) { if (arr[j] > arr[j + 1]) { var tmp = arr[j + 1]; arr[j + 1] = arr[j]; arr[j] = tmp; } } } return arr;}
時間複雜度為:O(n^2)
快速排序
非常經典的排序演算法,排序過程主要i分為三步:
- 從數列中挑出一個元素,稱為 “基準”(pivot);
- 重新排序數列,所有元素比基準值小的擺放在基準前面,所有元素比基準值大的擺在基準的後面(相同的數可以到任一邊)。在這個分區退出之後,該基準就處於數列的中間位置。這個稱為分區(partition)操作;
- 遞迴地(recursive)把小於基準值元素的子數列和大於基準值元素的子數列排序。
實現代碼如下:
function quickSort(arr, bt, ed) { if (bt < ed) { var pivot = findPartition(arr, bt, ed); quickSort(arr, bt, pivot - 1); quickSort(arr, pivot + 1, ed); }}function findPartition(arr, bt, ed) { var stand = arr[bt]; while (bt < ed) { while (bt < ed && arr[ed] >= stand) { ed --; } if (bt < ed) { arr[bt ++] = arr[ed]; } while (bt < ed && arr[bt] <= stand) { bt ++; } if (bt < ed) { arr[ed --] = arr[bt]; } } arr[bt] = stand; return bt;}
時間複雜度為:O(nlogn)。
歸併排序
也是非常經典的排序演算法,我就是藉著學習js的機會複習經典的排序演算法了。歸併排序的思想可以參考我的這篇部落格:歸併排序。我這裡唯寫js實現。
function mergeSort(arr, bt, ed) { if (bt < ed) { var mid = bt + parseInt((ed - bt) / 2); mergeSort(arr, bt, mid); mergeSort(arr, mid + 1, ed); mergeArray(arr, bt, mid, ed); }}function mergeArray(arr, bt, mid, ed) { var mArr = []; var i = bt, j = mid + 1; while (i <= mid && j <= ed) { if (arr[i] <= arr[j]) { mArr.push(arr[i++]); } else { mArr.push(arr[j ++]); } } if (i <= mid) { mArr = mArr.concat(arr.slice(i, mid + 1)); } if (j <= ed) { mArr = mArr.concat(arr.slice(j, ed + 1)); } for (var h = 0; h < mArr.length; h ++) { arr[bt + h] = mArr[h]; }}
寫歸併排序的時候還有一個小插曲:就是js不能自動取整,後來用了parseInt方法,感覺萌萌大。
JavaScript-數組詳解