JavaScript-常見排序算法實現(xiàn)方法匯總

常見比較排序
1.冒泡排序
2.選擇排序:簡單選擇排序和堆排序
3.插入排序:直接插入排序和希爾排序
4.快速排序
5.歸并排序
常見非比較排序
1.計數(shù)排序
2.基數(shù)排序
3.桶排序
常見算法的穩(wěn)定性:
堆排序蕉陋、快速排序、希爾排序键耕、直接選擇排序是不穩(wěn)定的排序算法,
基數(shù)排序柑营、冒泡排序屈雄、直接插入排序、歸并排序官套、折半插入排序是穩(wěn)定的排序算法酒奶。

復雜度和穩(wěn)定性(如圖)

排序.png

注釋:
n 數(shù)據(jù)規(guī)模
k 桶的個數(shù)
In-place 占用常數(shù)內存,不占用額外內存
Out-place 占用額外內存

一奶赔、冒泡排序(Bubble Sort)--惩锖浚考

冒泡排序是一種簡單的排序算法。它重復地走訪要排序的數(shù)列站刑,一次比較兩個元素另伍,如果他們的順序錯誤就把他們交換過來,走訪數(shù)列的工作是重復地進行直到?jīng)]有要交換的為止,也就是說該數(shù)列已排序完成摆尝。這個算法的名字由來是因為越小的元素會經(jīng)過不斷交換慢慢浮到數(shù)列的頂端温艇。

1.1 算法描述
  • 步驟1:比較相鄰的元素,如果第一個比第二個大堕汞,就交換它們兩個勺爱,大的靠后
  • 步驟2:對每一對相鄰元素做同樣的工作,從開始第一對到結尾的最后一對讯检,這樣在最后的元素應該是最大的
  • 步驟3:針對所有的元素重復以上的步驟琐鲁,除了最后一個
  • 步驟4:重復步驟1~3,直到排序完成人灼。
1.2 動圖展示
bubble.gif
1.3 代碼演示
function bubbleSort (arr) {
  for (let i = 0; i < arr.length - 1; i++) {
    for (let j = 0; j < arr.length - 1 - i; j++) {
      if (arr[j] > arr[j+1]) {
        let temp = arr[j+1]
        arr[j+1] = arr[j]
        arr[j] = temp
        // 或者用 [arr[j], arr[j+1]] = [arr[j+1], arr[j]]
      }
    }
  }
  return arr
}
1.4 算法分析
  • 最佳情況:T(n) = O(n)
  • 最差情況:T(n) = O(n^2)
  • 平均情況:T(n) = O(n^2)

二围段、選擇排序(Selection Sort)

選擇排序是表現(xiàn)最穩(wěn)定的排序算法之一,因為無論什么數(shù)據(jù)進去都是O(n^2)的時間復雜度挡毅,所以用到它的時候蒜撮,數(shù)據(jù)規(guī)模越小越好。唯一的好處可能就是不占用額外的內存空間了跪呈。
選擇排序的工作原理是:首先在未排序的序列中找到最卸文ァ(大)元素,存放到排序序列的起始位置耗绿,然后苹支,再從剩余未排序元素中繼續(xù)尋找最小(大)元素误阻,然后放到已排序序列的末尾债蜜,以此類推,直到排序完成究反。

2.1 算法描述

n個記錄的直接選擇排序結果可經(jīng)過n-1趟直接選擇排序得到有序結果寻定。

  • 步驟1:取出未排序序列的第一個元素,設為初始最小值精耐,遍歷該元素之后的部分并比較大小狼速。
  • 步驟2:如果有更小的元素,與該元素交換位置卦停,設為最小的向胡。
  • 步驟3:每次遍歷找出剩余元素中的最小值并放在已排序序列的最后,如此惊完,n-1趟結束僵芹,數(shù)組有序化了。
2.2 動圖展示
select.gif
2.3 代碼演示
function  selectionSort (arr) {
  for (let i = 0; i < arr.length; i++) {
    let min_index = i;
    for (let j = i+1; j < arr.length; j++) {
      if (arr[j] < arr[min_index]) min_index = j
    }
    // 交換兩個元素
    let temp = arr[min_index]
    arr[min_index] = arr[i]
    arr[i] = temp
    // 或者[arr[min_index], arr[i]] = [arr[i], arr[min_index]]
  }
  return arr
}
2.4 算法分析
  • 最佳情況:T(n) = O(n^2)
  • 最差情況:T(n) = O(n^2)
  • 平均情況:T(n) = O(n^2)

三小槐、堆排序(Heap Sort)--衬磁桑考

堆排序是利用堆這種數(shù)據(jù)結構所設計的一種選擇排序算法。堆是一種近似完全二叉樹的數(shù)據(jù)結構,并同時滿足堆積的性質:即子節(jié)點的鍵值或索引總是小于(或者大于)它的父節(jié)點攀痊。
1桐腌、堆是樹的一種,當堆的父節(jié)點都大于或者都小于子節(jié)點時苟径,分別稱為最大堆或最小堆案站。
2、可以用數(shù)組來表示樹(堆)棘街。從0開始蟆盐,以數(shù)組的第index個元素為堆的父節(jié)點,其左右子節(jié)點分別為數(shù)組的第2index+1和2index+2個元素遭殉。
3石挂、已排序的元素將放在數(shù)組尾部。

3.1 算法描述
  • 步驟1:把數(shù)組整理為最大堆的順序险污,那么堆的根節(jié)點或者說數(shù)組的第一個元素痹愚,就是最大的值
  • 步驟2:排序:把最大值與未排序部分的最后一個元素交換,剩余的部分繼續(xù)調整為最大值蛔糯,每次建堆都能找到剩余元素中最大的一個拯腮。
    注意:
  • 第一次建堆時只需要遍歷數(shù)組左側一半元素就夠了,并且要從中點向左側倒序遍歷蚁飒,這樣才能保證把最大的元素移動到數(shù)組頭部
  • 排序時动壤,當然就要遍歷數(shù)組里所有元素了
3.2 動圖展示
heap.gif
3.3 代碼演示
function heapSort (arr) {
  let len = arr.length
  if (len <= 1) return arr
  // 1. 建最大堆
  // 遍歷一半元素即可,必須從中點開始向左遍歷
  for (let middle = Math.floor(len/2); middle >= 0; middle--) {
    maxHeapify(arr, middle, len)
  }
  // 2.排序淮逻,遍歷所有元素
  for (let j = len; j >= 1; j--) {
    // 2.1 將最大的根元素arr[0]與最后一個元素arr[j-1]交換
    swap(arr, 0, j-1)
    // 2.2 剩余的元素繼續(xù)建最大堆
    maxHeapify(arr, 0, j-2)
  }
  return arr
}
// 建最大堆
function maxHeapify (arr, middle_index, length) {
  // 1. 假設父節(jié)點位置的值最大
  let largest_index = middle_index
  // 2. 計算左右節(jié)點位置
  let left_index = 2*middle_index + 1,
    right_index = 2*middle_index + 2
  // 3. 判斷父節(jié)點是否最大
  // 如果沒有超出數(shù)組長度琼懊,并且子節(jié)點比父節(jié)點大,那么修改最大節(jié)點的索引
  // 左邊更大
  if (left_index <= length && arr[left_index] > arr[largest_index])
    largest_index = left_index
  // 右邊更大
  if (right_index <= length && arr[right_index] > arr[largest_index])
    largest_index = right_index
  // 4. 如果largest_index發(fā)生了更新爬早,那么交換父子位置哼丈,遞歸計算
  if (largest_index !== middle_index) {
    swap(arr, middle_index, largest_index)
    // 因為這時一個較大的元素提到了前面,一個較小的元素移到了后面
    // 小元素的新位置之后可能還有比它更大的筛严,需要遞歸
    maxHeapify(arr, largest_index, length)
  }
}
function swap (arr, a, b) {
  let temp = arr[a]
  arr[a] = arr[b]
  arr[b] = temp
}
3.4 算法分析
  • 最佳情況:T(n) = O(nlogn)
  • 最差情況:T(n) = O(nlogn)
  • 平均情況:T(n) = O(nlogn)

四醉旦、插入排序(Insertion Sort)

插入排序是一種簡單直觀的排序算法。其工作原理是通過構建有序序列脑漫,對于未排序數(shù)據(jù)髓抑,在已排序序列中從后向前掃描咙崎,找到相應位置并插入优幸。插入排序在實現(xiàn)上,通常采用In-place排序(即只需要用到O(1)的額外空間排序)褪猛,因此從后向前掃描過程中网杆,需要反復把已排序元素逐步向后挪位,為最新元素提供插入空間。
注:插入排序將已排序的數(shù)組放在數(shù)組前面碳却。

4.1 算法描述
  • 步驟1:從第一個元素開始队秩,該元素可以認定為已排序
  • 步驟2:取出下一個元素,在已經(jīng)排序的元素序列中從后向前掃描
  • 步驟3:如果該元素(已排序)大于新元素昼浦,將該元素向后挪一位置
  • 步驟4: 重復步驟3馍资,直到找到已排序的元素小于或者等于新元素的位置
  • 步驟5:將新元素插入到該位置后。
  • 步驟6:重復步驟2-5
4.2 動圖展示
insert.gif
4.3 代碼演示
// 第一種一般寫法
function insertionSort (arr) {
  for (let index = 1; index < arr.length; index++) {
    // 取出一個未排序元素
    let current_ele = arr[index]
    // 已排序元素的最后一個的位置
    let ordered_index = index - 1
    while (arr[ordered_index] >= current_ele && ordered_index >= 0) {
      // 使用前面的值覆蓋當前的值
      arr[ordered_index + 1] = arr[ordered_index]
      // 向前移動一個位置
      ordered_index--
    }
    // 遍歷完成关噪,前面的元素都比當前元素小鸟蟹,把未排序元素賦值進去
    arr[ordered_index + 1] = current_ele
  }
  return arr
}
// 第二種結合冒泡排序的寫法
function insertionSort (arr) {
  for (let i = 0; i < arr.length; i++) {
    // 對前面已排序的數(shù)組和新選出來的元素執(zhí)行一趟冒泡排序
    for (let j = i + 1; j >= 0; j--) {
      if (arr[j] < arr[j-1]) {
        let temp = arr[j]
        arr[j] = arr[j-1]
        arr[j-1] = temp
      }
    }
  }
  return arr
}
4.4 算法分析
  • 最佳情況:T(n) = O(n)
  • 最差情況:T(n) = O(n^2)
  • 平均情況:T(n) = O(n^2)

五、希爾排序(Shell Sort)

希爾排序也是一種插入排序使兔,它是簡單插入排序經(jīng)過改進之后的一個更高效的版本建钥,也稱為縮小增量排序,它與插入排序的不同之處在于虐沥,它會優(yōu)先比較距離比較遠的元素熊经。首先指定一個增量gap,對數(shù)組分組欲险,使得每相距gap-1的元素為一組镐依,共分成gap組,對每組執(zhí)行插入排序盯荤,逐步縮小gap的大小并繼續(xù)執(zhí)行插入排序馋吗,直到為1,也就是整個數(shù)組為一組秋秤,對整個數(shù)組進行插入排序宏粤。gap對排序結果沒有影響,只是影響了算法效率灼卢。

5.1 算法描述

設置增量gap=length/2绍哎,縮小增量繼續(xù)以gap=gap/2的方式,

  • 步驟1:共三層循環(huán)鞋真,外層循環(huán)用來逐步減少gap的值
  • 步驟2:中層與內層兩層循環(huán)基本上就是插入排序崇堰,細節(jié)上的不同直接看代碼就好。
5.2 動圖展示
shell.png
5.3 代碼演示
// 第一種寫法
function shellSort (arr) {
  // 外層循環(huán)逐步縮小增量gap的值
  for (let gap = 5; gap > 0; gap = Math.floor(gap/2)) {
    // 中層和內層是插入排序
    // 普通插入排序從第1個元素開始涩咖,這里分組了海诲,要看每個組的第1個元素
    // 共分成了gap組,第一組的第1個元素索引為gap
    // 第一組元素索引為0檩互,0+gap特幔,0+2+gap,...闸昨,第二組元素索引為1蚯斯,1+gap薄风,2+2+gap,...
    for (let i = gap; i < arr.length; i++) {
      let current_ele = arr[i]
      // i每次減少gap拍嵌,只會與當前元素相隔n*(gap-1)的元素比較遭赂,也就是只會與同組的元素比較
      let ordered_index = i - gap
      // 對分組后的數(shù)組進行插入排序
      while (ordered_index >= 0 && arr[ordered_index] > current_ele) {
        arr[ordered_index + gap] = arr[ordered_index]
        ordered_index -= gap
      }
      arr[ordered_index + gap] = current_ele
    }
  }
  return arr
}

// 第二種寫法,口訣:3 for + 1 if
function shellSort (arr) {
  let length = arr.length
  for (let gap = Math.floor(length/2); gap > 0; gap = Math.floor(gap/2)) {
    for (let i = gap; i < length; i++) {
      for (let j = i - gap; j >= 0; j -= gap) {
        if (arr[j] > arr[j + gap]) {
          [arr[j], arr[j + gap]] = [arr[j + gap], arr[j]] // 交換兩個元素
        }
      }
    }
  }
  return arr
}
5.4 算法分析
  • 最佳情況:T(n) = O(nlog2n)
  • 最壞情況:T(n) = O(nlog2n)
  • 平均情況:T(n) = O(nlog2n)

六横辆、快速排序(Quick Sort)--称菜考

快速排序的工作原理是:通過一趟排序將待排記錄分隔成獨立的兩部分,其中一部分記錄的關鍵字均比另一部分的關鍵字小狈蚤,則可分別對這兩部分記錄繼續(xù)進行排序逆粹,以達到整個序列有序§懦停快速排序是對冒泡排序的一種改進僻弹,第一趟排序時將數(shù)據(jù)分成兩部分,一部分比另一部分的所有數(shù)據(jù)都要小他嚷。然后遞歸調用蹋绽,在兩邊都實行快速排序。

6.1 算法描述

快速排序使用分治法將一個串分為兩個子串筋蓖,具體步驟如下:

  • 步驟1:從數(shù)列里挑一個元素卸耘,稱為基準(pivot)。比如第一個元素
  • 步驟2:重新排序數(shù)列粘咖,所有比基準小的元素擺放在基準前面蚣抗,建一個數(shù)組;所有比基準大的元素擺放在基準后面瓮下,也建一個數(shù)組翰铡;相同的數(shù)可以放在任一邊。在這個分區(qū)退出之后,該基準就處于數(shù)列的中間位置,這個稱為分區(qū)操作坏挠。
  • 步驟3:遞歸地把小于基準元素的子數(shù)列和大于基準元素的子數(shù)列排序。繼續(xù)執(zhí)行步驟1白群,直到數(shù)組只剩1個元素;遞歸的同時把這三部分連接起來

普通快速排序沒有考慮與 pivot 相等的情況,只建了更小和更大的兩個數(shù)組。
像上面考慮與 pivot 相等的情況時漠秋,又叫做[三路快排]。

6.2 動圖展示
quick.gif
6.3 代碼演示
function quickSort (arr) {
  // 只剩1個元素的話抵屿,不能再分割了
  if (arr.length <= 1) return arr
  // 取第1個元素為基準值
  let pivot = arr[0]
  // 分割為左小右大兩個數(shù)組庆锦,以及包含元素本身的中間數(shù)組
  let left = [], middle = [pivot], right = []
  for (let i = 1; i < arr.length; i++) {
    if (arr[i] === pivot) middle.push(arr[i])
    else if (arr[i] < pivot) left.push(arr[i])
    else right.push(arr[i])
  }
  // 遞歸并連接
  return quickSort(left).concat(middle, quickSort(right))
}
6.4 算法分析
  • 最佳情況:T(n) = O(nlogn)
  • 最差情況:T(n) = O(n^2)
  • 平均情況:T(n) = O(nlogn)

七、歸并排序(Merge Sort)--成胃茫考

歸并排序始終都是O(nlogn)的時間復雜度肥荔,代價是需要額外的內存空間。歸并排序是建立在歸并操作上的一種有效的排序算法朝群。該算法是采用分治法的典型應用燕耿,它是一種穩(wěn)定的排序算法。通過將已有序的子序列合并姜胖,得到完全有序的序列誉帅,即先使每個子序列有序,再使子序列段間有序右莱,若將兩個有序表合并成一個有序表蚜锨,稱為2路歸并。

7.1 算法描述
  • 步驟1:把長度為n的序列分為兩個長度為n/2的子序列
  • 步驟2:對這兩個子序列分別采用歸并排序
  • 步驟3:將兩個排序好的子序列合并成一個最終的排序序列慢蜓。
7.2 動圖展示
merge.gif
7.3 代碼演示
// 分割
function mergeSort (arr) {
  // 如果只剩1個元素亚再,分割結束
  if (arr.length <= 1) return arr
  // 否則繼續(xù)分成2部分
  let middle_index = Math.floor(arr.length / 2),
    left = arr.slice(0, middle_index),
    right = arr.slice(middle_index)
  return merge(mergeSort(left), mergeSort(right))
}
function merge (left, right) {
  let result = []
  // 當左右兩個數(shù)組都還沒有取完的時候,比較大小然后合并
  while (left.length && right.length) {
    if (left[0] < right[0]) result.push(left.shift()) // 將左邊數(shù)組中的第一個元素取出存入result
    else result.push(right.shift())
  }
  // 其中一個數(shù)組空了晨抡,另一個還剩下一些元素
  // 因為是已經(jīng)排序過的氛悬,所以直接concat就好了
  // 注意 concat 不改變原數(shù)組
  if (left.length) result = result.concat(left)
  if (right.length) result = result.concat(right)
  return result
}

// 合并
7.4 算法分析
  • 最佳情況:T(n) = O(nlogn)
  • 最差情況:T(n) = O(n^2)
  • 平均情況:T(n) = O(nlogn)

八、二分查找(Binary Search)

二分查找算法耘柱,也叫折半查找如捅。它的工作原理是:針對一個有序的數(shù)據(jù)集合,每次通過跟區(qū)間的中間元素對比调煎,將待查找的區(qū)間縮小為之前的一半镜遣,直到找到要查找的元素,或者區(qū)間縮小為0士袄。

8.1 算法描述
  • 步驟1:從有序數(shù)組的最中間元素開始查找悲关,如果該元素正好是指定查找的值,則查找過程結束娄柳,否則進行下一步坚洽。
  • 步驟2:如果指定要查找的元素大于或者小于中間元素,則在數(shù)據(jù)大于或小于中間元素的那一半?yún)^(qū)域查找西土,然后重復第一步
  • 步驟3:重復以上過程讶舰,直到找到目標元素的索引,查找成功需了;或者直到子數(shù)組為空跳昼,查找失敗。
8.2 動圖展示
binary.png
8.3 代碼演示
// 循環(huán)查找法
function binarySearch (arr, target) {
  let low = 0, high = arr.length - 1
  while (low <= high) {
    let mid = Math.floor((low + high) / 2)
    if ( target > arr[mid]) {
      low = mid + 1
    } else if (target < arr[mid]) {
      high = mid - 1
    } else { // mid = target
      return mid
    }
  }
  return -1
}
8.4 算法分析

二分查找的時間復雜度為O(logn)肋乍。雖然二分查找的時間復雜度為O(logn)但是比很多O(1)的速度都要快鹅颊,因為O(1)可能標示一個非常大的數(shù)值。

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末墓造,一起剝皮案震驚了整個濱河市堪伍,隨后出現(xiàn)的幾起案子锚烦,更是在濱河造成了極大的恐慌,老刑警劉巖帝雇,帶你破解...
    沈念sama閱讀 217,907評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件涮俄,死亡現(xiàn)場離奇詭異,居然都是意外死亡尸闸,警方通過查閱死者的電腦和手機彻亲,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,987評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來吮廉,“玉大人苞尝,你說我怎么就攤上這事』侣” “怎么了宙址?”我有些...
    開封第一講書人閱讀 164,298評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長调卑。 經(jīng)常有香客問我曼氛,道長,這世上最難降的妖魔是什么令野? 我笑而不...
    開封第一講書人閱讀 58,586評論 1 293
  • 正文 為了忘掉前任舀患,我火速辦了婚禮,結果婚禮上气破,老公的妹妹穿的比我還像新娘聊浅。我一直安慰自己,他們只是感情好现使,可當我...
    茶點故事閱讀 67,633評論 6 392
  • 文/花漫 我一把揭開白布低匙。 她就那樣靜靜地躺著,像睡著了一般碳锈。 火紅的嫁衣襯著肌膚如雪顽冶。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,488評論 1 302
  • 那天售碳,我揣著相機與錄音强重,去河邊找鬼。 笑死贸人,一個胖子當著我的面吹牛间景,可吹牛的內容都是我干的。 我是一名探鬼主播艺智,決...
    沈念sama閱讀 40,275評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼倘要,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了十拣?” 一聲冷哼從身側響起封拧,我...
    開封第一講書人閱讀 39,176評論 0 276
  • 序言:老撾萬榮一對情侶失蹤志鹃,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后泽西,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體曹铃,經(jīng)...
    沈念sama閱讀 45,619評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,819評論 3 336
  • 正文 我和宋清朗相戀三年尝苇,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片埠胖。...
    茶點故事閱讀 39,932評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡糠溜,死狀恐怖,靈堂內的尸體忽然破棺而出直撤,到底是詐尸還是另有隱情非竿,我是刑警寧澤,帶...
    沈念sama閱讀 35,655評論 5 346
  • 正文 年R本政府宣布谋竖,位于F島的核電站红柱,受9級特大地震影響,放射性物質發(fā)生泄漏蓖乘。R本人自食惡果不足惜锤悄,卻給世界環(huán)境...
    茶點故事閱讀 41,265評論 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望嘉抒。 院中可真熱鬧零聚,春花似錦、人聲如沸些侍。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,871評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽岗宣。三九已至蚂会,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間耗式,已是汗流浹背胁住。 一陣腳步聲響...
    開封第一講書人閱讀 32,994評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留刊咳,地道東北人措嵌。 一個月前我還...
    沈念sama閱讀 48,095評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像芦缰,于是被迫代替她去往敵國和親企巢。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,884評論 2 354

推薦閱讀更多精彩內容