1. 排序演算法
排序演算法就是講一組雜亂無章的資料,按照從小到大
或從大到小
的方式,變成一組有序的資料。首先,我們編寫一個陣列函式,用於產生陣列和列印陣列。
function ArrayList() {
let arr = [];
// 插入資料
this.insert = (item) => {
arr.push(item);
};
// 列印資料
this.toString = () => {
return arr.join(',');
};
// 交換兩個元素的位置
let swap = (index1, index2) => {
let aux = arr[index1];
arr[index1] = arr[index2];
arr[index2] = aux;
}
}
複製程式碼
2. 氣泡排序
冒泡是演算法中最簡單的,但是從時間上來看,是最慢
的。
什麼是氣泡排序?
- 氣泡排序就是 取出陣列中的
第n個元素
a,將該元素與 其後面所有的元素
(b)依次比較,如果a > b
,交換它們的位置 - 但在實際過程中,當
a
元素被放到最後,它就不應該再參與比較
this.bubbleSort = () => {
let length = arr.length;
for (let j = 0; j < length; j++) {
// 因為 i 要與 i + 1 比較,因此 i <length -1; 不然會越界
for (let i = 0; i < length - 1 - j; i++) {
if (arr[i] > arr[i+1]) {
swap(i, i+1);
}
}
}
};
複製程式碼
3. 選擇排序
選擇排序:先找到資料結構中最小的值,並將其放在第一位,接著找到資料中第二小的值,放在第二位,依次類推。
this.selectSort = () => {
let length = arr.length, min;
for (let i = 0; i < length; i++) {
// 將當前位置設定為min
min = i;
for (let j = i; j < length; j++) {
if (arr[min] > arr[j]) {
min = j;
}
}
if ( i !== min) {
swap(min, i);
}
}
}
複製程式碼
選擇排序同樣也是一個複雜度為O(n2)的演算法。和氣泡排序一樣,它包含有巢狀的兩個迴圈,這導致了二次方的複雜度。
4. 插入排序
插入排序是每次排一個陣列項。
- 排第一個值,就保留在第一個位置
- 排第二個值,看它放在第一個位置,還是第二個位置
- 排第三個值,看它是放在第一個位置,還是第二個位置,還是第三個位置
- ...
總結一句話:當計算a
的正確位置,與比a
的索引小的值
比較,將比a
大的值全部往後移動,騰出位置,將a
插入
// for 迴圈做法
this.insertSort = () => {
let length = arr.length;
for (let i = 0; i < length; i++) {
let insertIndex = i;
for(let j = i-1; j >=0; j--) {
if (arr[j] > arr[insertIndex]) {
swap(j, insertIndex);
insertIndex = j;
} else {
break;
}
}
}
}
// while迴圈
this.insertSort = () => {
let length = arr.length,
j;
for (let i = 0; i < length; i++) {
j = i, // 會改變,用於儲存當前需要對比的索引
temp = arr[i]; // 儲存需要移動的value
while(j > 0 && arr[j-1] > temp) {
// 將大於temp的值往後移動,給temp騰出位置
arr[j] = arr[j-1];
j--;
}
// 將temp 賦值給騰出來的位置
arr[j] = temp;
}
}
複製程式碼
排序小型陣列時,此演算法比選擇排序和氣泡排序效能要好。
5. 歸併排序
歸併排序是一種分治演算法
。將元是陣列切分成較小的陣列,直到每個小陣列只有一個位置,接著將小陣列歸併成為較大的陣列,直到最後,只有一個排序完畢的大陣列。
由於分治法:歸併排序也是遞迴
let mergeSortRec = (array) => {
let length = array.length;
if(length === 1) { //{1}
return array; //{2}
}
// 將陣列分為左右兩組
let mid = Math.floor(length / 2),
left = array.slice(0, mid),
right = array.slice(mid, length);
// 遞迴呼叫,將陣列拆分為只有一個長度的陣列,通過merge(排序)
return merge(mergeSortRec(left), mergeSortRec(right));
}
let merge = (left, right) => {
// 記錄 left的索引,right的索引
let iL = 0,
iR = 0,
lLength = left.length,
rLength = right.length;
let result = [];
// 如果 left或right其中某個被迴圈處理完畢,跳出迴圈
while(iL < lLength && iR < rLength) {
if (left[iL] < right[iR]) {
result.push(left.shift());
iL++;
} else {
result.push(right.shift());
iR++;
}
}
// 將剩下的沒被處理的資料,追加到結果集中
if (iL < left.length) {
result.push(...left);
}
if (iR < right.length) {
result.push(...right);
}
return result;
}
複製程式碼
歸併排序的效能比前三個好,是O(nlog^n)。
6. 快速排序
快速排序也是分治方法,將原始陣列分為較小的組(但並沒有像歸併排序那樣將它們分開)。
- 找到陣列中間一項 作為
主元
- 建立
兩個
指標,指向陣列第一項
和最後一項
- 向右移動
左指標
,直到找到元素比主元
大的值a,停止 - 向左移動
右指標
,直到找到元素比主元
小的值b,停止 - 交換a,b兩個值,並將指標繼續移動
- 當左指標 > 右指標,結束比較。將
左指標
作為下一次主元
的索引,。
this.quickSort = () => {
quick(arr, 0, arr.length - 1);
};
// 排序arr, 左指標, 右指標
let quick = (sortArr, left, right) => {
if (sortArr.length <= 0) {
return;
}
// 排序: 最終生成一個陣列,按照步驟,以主元為基準,移動左右指標處理一遍後的資料
let index = partition(sortArr, left, right);
// 以主元為基準,左邊的再一次排序
if (left < index - 1) {
quick(sortArr, left, index - 1);
}
// 以主元為基準,右邊的再一次排序
if (index < right) {
quick(sortArr, index, right);
}
};
let partition = (sortArr, left, right) => {
// 本輪迴圈的 主元
let pivot = sortArr[Math.floor((left + right)/2)];
// 左索引,右索引
let lI = left, lR = right;
// 當左索引 > 右索引時,結束本輪排序
while( lI <= lR) {
// 找到 主元 左邊 大於它的值
while (sortArr[lI] < pivot) {
lI++;
}
while(sortArr[lR] > pivot) {
lR--;
}
if (lI <= lR) {
swap(lI, lR);
lI++;
lR--;
}
}
// 返回左索引,作為下一次分組的 `主元`
return lI;
}
複製程式碼
快速排序步驟:
- 在資料集之中,選擇一個元素作為"基準"(pivot)。
- 所有小於"基準"的元素,都移到"基準"的左邊;所有大於"基準"的元素,都移到"基準"的右邊。
- 對"基準"左邊和右邊的兩個子集,不斷重複第一步和第二步,直到所有子集只剩下一個元素為止。
快速排序的雖然也是O(nlog^n),但是效能比其他O(nlog^n)的好。
7 順序查詢
在一個陣列中查詢某一個具體值位置的實現
this.sequentialSearch = (item) => {
for ( let i = 0; i < arr.length; i++) {
if (arr[i] === item) {
return i;
}
}
return -1;
}
複製程式碼
8. 使用二分搜尋查詢方式
該演算法要求:被搜尋的資料結構是已排序結構。
- 選擇陣列的中間值
- 如果選中值是待搜尋值,返回結果
- 如果搜尋值比選中值小,則到第一步:並從選中值的左邊的子陣列中查詢
- 如果搜尋值比選中值大,則到第一步:並從選中值的右邊的子陣列中查詢
this.binarySearch = (item) {
// 先排序
this.quickSort();
// 左邊開始索引
let low = 0,
// 右邊開始索引
high = arr.length -1,
// 中間索引
mid,
// 當前選中被比較的值
element;
while (low < high) {
mid = Math.floor((mid + high) / 2);
element = arr[mid];
// 代表被查詢值在mid的左邊
if (element > item) {
high = mid;
// 代表被查詢的值在mid的右邊
} else if (element < item) {
low = high;
} else {
return mid;
}
}
return -1;
}
複製程式碼