冒泡排序(Bubble Sort)
通過相鄰元素的比較和交換,使得每一趟循環都能找到未排數組的最大值或最小值,把它放到後面;
普通冒泡排序
let arr = [2,44,3,13,11,35,8]
function sort(arr){
for(let i=0,len=arr.length; i<len-1;i++){
// flag用來標誌每一輪是否有交換數據;如果沒有,則說明數組已有序
let flag = true
for(let j=0;j<len-i-1;j++){
if(arr[j]>arr[j+1]){
[arr[j],arr[j+1]] = [arr[j+1],arr[j]];
flag = false;
}
}
if(flag) return;
}
}
sort(arr)
console.log(arr) // [ 2, 3, 8, 11, 13, 35, 44 ]
雙向冒泡排序
let arr = [2,44,3,13,11,35,8]
function sort(arr){
let low = 0;
let high = arr.length -1;
while(low < high){
let flag = true;
// 找到最大值放在右邊
for(let i=low;i<high;i++){
if(arr[i]>arr[i+1]){
[arr[i],arr[i+1]] = [arr[i+1],arr[i]];
flag = false;
}
}
high--;
// 找到最小值放在左邊
for(let j=high;j>low;j--){
if(arr[j]<arr[j-1]){
[arr[j],arr[j-1]] = [arr[j-1],arr[j]];
flag = false;
}
}
low++;
if(flag)return;
}
}
sort(arr)
console.log(arr) // [ 2, 3, 8, 11, 13, 35, 44 ]
快速排序
- 從數組中取出一個數作爲基準。
- 在原數組中進行移動,將大於基準的數放到基準右邊,小於基準的數放到基準左邊,在基準左右形成兩個子數組。
- 在左右子數組中反覆執行步驟1、2,直到所有子數組只剩下一個數。
let arr = [2,44,3,13,11,35,8]
function sort(arr){
if(arr.length <=1) {
return arr;
}
// 獲取基準的索引
let pivotIndex = Math.floor(arr.length/2);
// 找到基準,把基準從原數組中刪除
let pivot = arr.splice(pivotIndex,1)[0];
// 定義左右數組
let left = [];
let right = [];
// 比基準小的放在left,比基準大的放在right
arr.forEach(item => {
if(item < pivot){
left.push(item);
}else {
right.push(item);
}
});
return sort(left).concat([pivot],sort(right));
}
console.log(sort(arr)) // [ 2, 3, 8, 11, 13, 35, 44 ]
直接插入排序
基本思想:
在已排序的數據序列從後向前掃描,找到對應的位置插入未排序的數據。
插入排序通常採用佔位的形式,空間複雜度爲O(1)。
因此,從後向前掃描的過程中,需要反覆的把已排序的元素逐步向後挪位,爲新插入元素提供插入的位置。
- 從第一個元素開始,該元素可以認爲已經被排序
- 取出下一個元素,在已經排序的元素序列中從後向前掃描
- 如果該元素(已排序)大於新元素,將該元素移到下一位置
- 重複步驟3,直到找到已排序的元素小於或者等於新元素的位置
- 將新元素插入到下一位置中
- 重複步驟2
let arr = [2,44,3,13,11,35,8]
function sort(arr){
for(let i=1,len=arr.length;i<len;i++){
let temp = arr[i]; // 需要排序的元素
let j=i-1; // 默認已排序元素的最後一位的索引
while(j>=0 && arr[j]>temp){ // 在已排序好的隊列中從後向前掃描
arr[j+1]=arr[j]; // 已排序的元素大於新元素,將該元素移到下一個位置
j--;
}
arr[j+1]=temp; // 當已排序的元素<=新元素或到頭了,即可將新元素加到該元素後面或首位
}
return arr
}
console.log(sort(arr)) // [ 2, 3, 8, 11, 13, 35, 44 ]
希爾排序
希爾排序也是一種插入排序,它是簡單插入排序經過改進之後的一個更高的版本,也稱爲縮小增量排序,同時該算法是衝破O(n^2)的第一批算法之一。它與插入排序的不同之處在於,它會優先比較距離較遠的元素。
基本思路:
根據增量來分割數組,對每一組進行直接插入排序
let arr = [2,44,3,13,11,35,8]
function sort(arr){
let len = arr.length;
// gap 即爲增量
for (let gap = Math.floor(len / 2); gap > 0; gap = Math.floor(gap / 2)) {
for (let i = gap; i < len; i++) {
let j = i;
let current = arr[i];
while(j - gap >= 0 && current < arr[j - gap]) {
arr[j] = arr[j - gap];
j = j - gap;
}
arr[j] = current;
}
}
return arr;
}
console.log(sort(arr)) // [ 2, 3, 8, 11, 13, 35, 44 ]
簡單選擇排序
選擇排序是一種簡單直觀的排序算法
基本思路:
首先在未排序序列中找到最小/最大 元素,存放在排序序列的起始位置;然後,再從剩餘未排序元素中繼續尋找最小/最大元素,然後放到已排序序列的末尾。以此類推,直到所有元素均排序完畢。
let arr = [2,44,3,13,11,35,8]
function sort(arr){
let len = arr.length;
let minIndex,temp;
for(let i=0;i<len-1;i++){
minIndex=i;
for(let j=i+1;j<len;j++){ // 循環尋找最小的數
if(arr[j]<arr[minIndex]){
minIndex=j; // 保存最小數的索引
}
}
[arr[i],arr[minIndex]] = [arr[minIndex],arr[i]];
}
return arr;
}
console.log(sort(arr)) // [ 2, 3, 8, 11, 13, 35, 44 ]
堆排序
參考網站
堆排序可以說是一種利用堆的概念來排序的選擇排序,分爲兩種方法:
- 大頂堆:每個節點的值都大於或等於其子節點的值,在堆排序算法中用於升序排列
- 小頂堆:每個節點的值都小於或等於其子節點的值,在堆排序算法中用於降序排列
我們用簡單的公式來描述一下堆的定義就是:
大頂堆:arr[i] >= arr[2i+1] && arr[i] >= arr[2i+2]
小頂堆:arr[i] <= arr[2i+1] && arr[i] <= arr[2i+2]
// 交換兩個節點
function swap(A, i, j) {
let temp = A[i];
A[i] = A[j];
A[j] = temp;
}
// 將 i 結點以下的堆整理爲大頂堆,注意這一步實現的基礎實際上是:
// 假設 結點 i 以下的子堆已經是一個大頂堆,shiftDown函數實現的
// 功能是實際上是:找到 結點 i 在包括結點 i 的堆中的正確位置。後面
// 將寫一個 for 循環,從第一個非葉子結點開始,對每一個非葉子結點
// 都執行 shiftDown操作,所以就滿足了結點 i 以下的子堆已經是一大
//頂堆
function shiftDown(A, i, length) {
let temp = A[i]; // 當前父節點
// j<length 的目的是對結點 i 以下的結點全部做順序調整
for(let j = 2*i+1; j<length; j = 2*j+1) {
temp = A[i]; // 將 A[i] 取出,整個過程相當於找到 A[i] 應處於的位置
if(j+1 < length && A[j] < A[j+1]) {
j++; // 找到兩個孩子中較大的一個,再與父節點比較
}
if(temp < A[j]) {
swap(A, i, j) // 如果父節點小於子節點:交換;否則跳出
i = j; // 交換後,temp 的下標變爲 j
} else {
break;
}
}
}
// 堆排序
function heapSort(A) {
// 初始化大頂堆,從第一個非葉子結點開始
for(let i = Math.floor(A.length/2-1); i>=0; i--) {
shiftDown(A, i, A.length);
}
// 排序,每一次for循環找出一個當前最大值,數組長度減一
for(let i = Math.floor(A.length-1); i>0; i--) {
swap(A, 0, i); // 根節點與最後一個節點交換
shiftDown(A, 0, i); // 從根節點開始調整,並且最後一個結點已經爲當
// 前最大值,不需要再參與比較,所以第三個參數
// 爲 i,即比較到最後一個結點前一個即可
}
}
let arr = [50,45,40,20,25,35,30,10,15]
heapSort(arr);
console.log(arr) // [ 10, 15, 20, 25, 30, 35, 40, 45, 50 ]
二路歸併排序
參考鏈接
歸併排序採用的是分治的思想,首先是“分”,將一個數組反覆二分爲兩個小數組,直到每個數組只有一個元素;其次是“治”,從最小數組開始,兩兩按大小順序合併,直到併爲原始數組大小,下面是圖解:
// 融合兩個有序數組,這裏實際上是將數組 arr 分爲兩個數組
function mergeArray(arr, first, mid, last, temp) {
let i = first;
let m = mid;
let j = mid+1;
let n = last;
let k = 0;
while(i<=m && j<=n) {
if(arr[i] < arr[j]) {
temp[k++] = arr[i++];
} else {
temp[k++] = arr[j++];
}
}
while(i<=m) {
temp[k++] = arr[i++];
}
while(j<=n) {
temp[k++] = arr[j++];
}
for(let l=0; l<k; l++) {
arr[first+l] = temp[l];
}
return arr;
}
// 遞歸實現歸併排序
function mergeSort(arr, first, last, temp) {
if(first<last) {
let mid = Math.floor((first+last)/2);
mergeSort(arr, first, mid, temp); // 左子數組有序
mergeSort(arr, mid+1, last, temp); // 右子數組有序
arr = mergeArray(arr, first, mid, last, temp);
}
return arr;
}
// example
let arr = [2,44,3,13,11,35,8]
let temp = new Array();
console.log(mergeSort(arr, 0 ,arr.length-1, temp)); // [ 2, 3, 8, 11, 13, 35, 44 ]
持續更新中。。。