數據結構和算法系列的課程分爲上下兩篇文章,上篇文章主要是講解數據結構,能夠戳導師計劃--數據結構和算法系列(上)進行了解。本篇文章主要講解的是基本算法,輔助的語言依舊是JavaScript
。POST的本篇文章主要是擴展下咱們在開發中的方式,發散下思惟~javascript
排序介紹:html
排序算法又分爲簡單排序
和高級排序
。其中簡單排序包括冒泡排序、選擇排序和插入排序
。高級排序包括希爾排序、歸併排序和快速排序
。【⚠️這裏僅介紹了六種排序算法】html5
下面咱們逐個排序算法來說解下:java
之因此叫冒泡排序,是由於使用這種排序算法時,數據值就會像氣泡那樣從數組的一端漂浮到另外一端。假設正在將一組數字按照升序排列,較大的值會浮動在數組的右側,而較小的值則會浮動到數組的左側。產生這種冒泡的現象是由於算法會屢次在數組中移動過,比較相鄰的數據,當左側值大於右側值的時候將它們互換。git
⚠️ 後面講到的排序算法如無說明,則默認爲升序github
好比下面的簡單列表的例子。web
E A D B H
算法
通過第一次的排序後,列表會變成:shell
A E D B H
數組
前面兩個元素進行了交互。接下來再次排序:
A D E B H
第二個元素和第三個元素進行了交互。繼續進行排序:
A D B E H
第三個元素和第四個元素進行了交換。這一輪最後進行排序:
A D B E H
由於第四個元素比最後一個元素小,因此比較後保持所在位置。反覆對第一個元素執行上面的操做(已經固定的值不參與排序,如第一輪固定的H
不參與第二輪的比較了),獲得以下的最終結果:
A B D E H
相關的動效圖以下:
關鍵代碼以下:
bubbleSort(){
let numElements = this.arr.length;
for(let outer = numElements-1; outer >= 2; --outer){
for(let inner = 0; inner <= outer-1; ++inner){
if(this.arr[inner] > this.arr[inner+1]){
this.swap(inner, inner+1); // 交換數組兩個元素
}
}
}
}
複製代碼
選擇排序從數組的開頭開始,將第一個元素和其它元素進行比較。檢查完全部的元素以後,最小的元素會被放在數組的第一個位置,而後算法會從第二個位置繼續。這個過程進行到數組的倒數第二個位置時,全部的數據便完成了排序。
原理:
選擇排序用到雙層嵌套循環。外循環從數組的第一個元素移動到倒數第二個元素;內循環從當前外循環所指元素的第二個元素
開始移動到最後一個元素,查找比當前外循環所指元素小
的元素。每次內循環迭代後,數組中最小的值都會被賦值到合適的位置。
下面是對五個元素的列表進行選擇排序的簡單例子。初始列表爲:
E A D H B
第一次排序會找到最小值,並將它和列表的第一個元素進行交換:
A E D H B
接下查找第一個元素後面的最小值(第一個元素此時已經就位),並對它們進行交換:
A B D H E
D已經就位,所以下一步會對E H進行互換,列表已按順序排列好以下:
A B D E H
經過gif圖可能容易理解:
關鍵代碼以下:
selectionSort(){
let min,
numElements = this.arr.length;
for(let outer = 0; outer <= numElements-2; outer++){
min = outer;
for(let inner = outer+1; inner <= numElements-1; inner++){
if(this.arr[inner] < this.arr[min]){
min = inner;
}
}
this.swap(outer, min);
}
}
複製代碼
插入排序相似咱們按照數字或字母的順序對數據進行降序或升序排序整理~
原理:
插入排序也用了雙層的嵌套循環。外循環將數組挨個移動,而內循環則對外循環中選中的元素以及內循環數組後面的那個元素進行比較。若是外循環中選中的元素比內循環中選中的元素要小,那麼內循環的數組元素會向右移動,騰出一個位置給外循環選定的元素。
上面表達的晦澀難懂。簡單來講,插入排序就是未排序的元素對已經排序好的序列數據進行合適位置的插入。
若是仍是不懂,結合下面的排序示例來理解下:
下面對五個元素進行插入排序。初始列表以下:
E B A H D
第一次插入排序,第二個元素挪動到第一位:
B E A H D
第二次插入排序是對A進行操做:
B A E H D
A B E H D
第三次是對H進行操做,由於它比以前的元素都大,因此保持位置。最後一次是對D元素進行插入排序了,過程和最後結果以下:
A B E D H
A B D E H
相關的gif圖瞭解一下:
相關代碼以下:
insertionSort(){
let temp,
inner,
numElements = this.arr.length;
for(let outer = 1; outer <= numElements-1; outer++){
temp = this.arr[outer];
inner = outer;
while(inner > 0 && this.arr[inner-1] >= temp){
this.arr[inner] = this.arr[inner-1];
inner--;
}
this.arr[inner] = temp;
}
}
複製代碼
希爾排序是插入排序的優化版,可是,其核心理念與插入排序不一樣,希爾排序會首先比較距離較遠的元素,而非相鄰的元素。
原理:
希爾排序經過定義一個間隔序列來表示數據在排序過程當中進行比較的元素之間有多遠的間隔。咱們能夠動態定義間隔序列,不過對於大部分的實際應用場景,算法用到的間隔序列能夠提早定義好
。
以下演示希爾排序中,間隔序列是如何運行的:
經過下面的gif圖你也許會更好理解:
實現的代碼:
shellSort(){
let temp,
j,
numElements = this.arr.length;
for(let g = 0; g < this.gaps.length; ++g){
for(let i = this.gaps[g]; i < numElements; ++i){
temp = this.arr[i];
for(j = i; j >= this.gaps[g] && this.arr[j - this.gaps[g]] > temp; j -= this.gaps[g]){ // 以前的已經拍好序的了
this.arr[j] = this.arr[j - this.gaps[g]];
}
this.arr[j] = temp; // 這裏和上面的for循環是互換兩個數據位置
}
}
}
複製代碼
🤔思考:[6, 0, 2, 9, 3, 5, 8, 0, 5, 4] 間隔爲3的排序結果是什麼呢?
原理:
把一系列的排好序的子序列合併成一個大的有序序列。從理論上講,這個算法很容易實現。咱們須要兩個排好序的子數組,而後經過比較數據的大小,先從最小的數據開始插入,最後合併獲得第三個數組。然而,實際上操做的至關大的數據的時候,使用歸併排序是很耗內存的,這裏咱們瞭解一下就行。
實現歸併排序通常有兩種方法,一種是自頂向下和自底向上的方法。
上面的gif圖是自頂向下的方法,那麼何爲自頂向下呢?
自頂向下的歸併排序算法就是把數組元素不斷的二分
,直到子數組的元素個數爲一個,由於這個時候子數組一定是有序的,而後再將兩個有序的序列合併成一個新的有序序列,連個有序序列又能夠合併成另外一個新的有序序列,以此類推,直到合併一個有序的數組。以下圖:
自底向上的歸併排序算法的思想是將數組先一個一個歸併成兩兩有序的序列,兩兩有序的序列歸併成四個四個有序的序列,以此類推,直到歸併的長度大於
整個數組的長度,此時整個數組有序。
⚠️注意:數組按照歸併長度劃分,最後一個子數組可能不知足長度要求,這種狀況就要特殊處理了。
快速排序是處理大數據集最快的排序算法之一,時間複雜度 最好的狀況也也是和歸併排序同樣,爲O(nlogn)。
原理:
快速排序
是一種**分而治之(分治)**的算法,經過遞歸的方式將數據依次分解爲包含較小元素和較大元素的不一樣子序列,而後不斷重複這個步驟,直到全部的數據都是有序的。
能夠更清晰的表達快速排序算法
步驟以下:
1 和 2
。咱們來用代碼實現下:
// 快速排序
quickSort(){
this.arr = this.quickAux(this.arr);
}
// aux函數 - 快排的輔助函數
quickAux(arr){
let numElements = arr.length;
if(numElements == 0){
return [];
}
let left = [],
right = [],
pivot = arr[0]; // 取數組的第一個元素做爲基準值
for(let i = 1; i < numElements; i++){
if(arr[i] < pivot){
left.push(arr[i]);
}else{
right.push(arr[i]);
}
}
return this.quickAux(left).concat(pivot, this.quickAux(right));
}
複製代碼
以上介紹了六種排序的算法,固然還有不少其它的排序算法,你能夠到視頻 | 手撕九大經典排序算法,看我就夠了!文章中查看。
在列表中查找數據又兩種方式:順序查找和二分查找。順序查找適用於元素隨機排列的列表;而二分查找適用於元素已排序的列表。二分查找效率更高,可是咱們必須在進行查找以前花費額外的時間將列表中的元素進行排序。
對於查找數據來講,最簡單的就是從列表中的第一個元素開始對列表元素逐個進行判斷,直到找到了想要的元素,或者直到列表結尾也沒有找到。這種方法稱爲順序查找
或者線性查找
。
這種查找的代碼實現方式很簡單,以下:
/* * @param { Array } arr 目標數組 * @param { Number } data 要查找的數組 * @return { Boolean } 是否查找成功 **/
function seqSearch(arr, data){
for(let i = 0; i < arr.length; i++){
if(arr[i] === data){
return true;
}
}
return false;
}
複製代碼
固然,看到上面的代碼,你也許會簡化成下面的這樣的代碼:
function seqSearch(arr, data){
return arr.indexOf(data) >= 0 ? true : false;
}
複製代碼
實現的方式有多種,可是原理都是同樣的,要從第一個元素開始遍歷,有可能會遍歷到最後一個元素都找不到要查找的元素。因此,這是一種暴力查找技巧的一種。
那麼,有什麼更加高效的查找方法嘛?這就是咱們接下來要講的了。
在開始以前,咱們來玩一個猜數字遊戲:
這個遊戲很簡單,若是咱們使用二分查找的策略進行的話,咱們只須要通過短短的幾回就肯定咱們要查找的數據了。
那麼二分查找的原理是什麼呢?
二分查找又稱爲折半查找,對有序的列表
每次進行對半查找。就是這麼簡單@~@!
代碼實現走一波:
/* * @param { Array } arr 有序的數組 ⚠️注意:是有序的有序的有序的 * @param { Number } data 要查找的數據 * @return { Number } 返回查找到的位置,未查找到放回-1值 **/
function binSearch(arr, data){
let upperBound = arr.length -1,
lowerBound = 0;
while(lowerBound <= upperBound){
let mid = Math.floor((upperBound + lowerBound) / 2);
if(arr[mid] < data){
lowerBound = mid + 1;
}else if(arr[mid] > data){
upperBound = mid + 1;
}else{
return mid;
}
}
return -1; // 你朋友選要猜的數據在1-100範圍以外
}
複製代碼
至此,導師計劃--數據結構和算法已經完結。後期的話會在另外一篇文章中補充一下各個算法的時間複雜度的比較(不做爲課程講解,要動筆算算的,並且也就是總結一個表而已~),固然你能夠查看文章算法的時間複雜度並結合實際編寫的代碼來自行理解,並去總結。
文章中的一些案例來自coderwhy的數據結構和算法系列文章,感謝其受權
課程代碼能夠戳相關算法來獲取
部分圖片來自網絡,侵刪
《數據結構與算法JavaScript描述》