js數據結構和算法學習(一)

本系列內容來源《學習Javascipt數據結構與算法》,源文件使用es5代碼編寫,在這里我使用ES6來編寫相關實例

下面內容我是按書上順序寫的,不過并不是完全一致,加入了我自己的理解

環境安裝與配置

// 項目目錄如下
-dist
-src
    -main.js
index.html
  • 目錄下運行npm init -yes建立package.json文件

  • 安裝http-server

// 只是學習,沒必要安裝到全局
npm i --save-dev http-server
...
// 啟動在當前目錄下的服務器
./node_modules/http-server/bin/http-server
  • 安裝babel
npm i --save-dev babel-cli babel-preset-es2015
  • 創建.babelrc文件,為babel轉換設置相關規則
{
  "presets": ["es2015"],
  "plugins": []
}

數組相關方法

// 下面數組方法,都基于下面3個數組
let arr1 = [1, 2, 3];
let arr2 = [4, 5, 6];
let arr3 = [7, 8, 9];
let arr4 = [1, 2, 3, 4, 5, 6, 7, 8, 9];
let arr5 = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15];
let arr6 = [1, 2, 3, 2, 1, 3];
let isEven = function (x) {
    return (x % 2 === 0);
}

let isEven2 = function (x) {
    console.log(x % 2 === 0);
}

下面方法[0]表示不改變原始數組,[1]表示改變原始數組

  • concat:對數組進行連接,返回一個新數組 [0]
// 下面輸出[1, 2, 3, 4, 5, 6, 7, 8, 9]
// arr1,arr2,arr3不變
arr1.concat(arr2, arr3); 
  • join: 使用指定符號連接數組數據,返回一個字符串,默認為‘,’ [0]
arr1.join(); // '1,2,3'
arr1.join('-'); // '1-2-3';
  • pop: 刪除并返回數組的最后一個元素 [1]
arr1.pop(); // 3,此時arr1變為[1, 2]
  • push: 向數組的末尾添加一個或更多元素,并返回新的長度。 [1]
arr1.push(4); // 3, arr1為[1, 2, 4]
  • shift: 與pop功能相似,這個方法時刪除并返回第一個元素 [1]
arr1.shift(); // 1, 此時arr1為[2, 4]
  • unshift: 與push功能相似,向數組開頭添加一個或更多元素,并返回新的長度 [1]
arr1.unshift(1); // 返回3,此時arr1為[1, 2, 4]
  • reverse: 顛倒數組中元素的順序。 [1]
arr1.reverse(); // [4, 2, 1]
  • sort: 對數組進行排序,可以接受函數,設置排序的規則 [1]
arr4.reverse(); // 先故意倒序,[9, 8, 7...]
arr4.sort(); // 對數組進行排序, [1, 2, 3...]
  • slice: 返回指定范圍的數組元素(可以接受兩個參數,開始位置start和結束位置end,包含開始位置的元素,不包含結束位置的元素,下標從0開始算) [0]

slice(start, end);

arr4.slice(0, 4); // [1, 2, 3, 4]
  • splice: 向/從數組中添加/刪除/替換項目,然后返回被刪除的項目 [1]

splice(index,howmany,item);

// howmany設為0就不會刪除元素,該方法變成向指定位置添加元素
// 此時arr4為[1, "a", 2, 3, 4, 5, 6, 7, 8, 9]
arr4.splice(1, 0, 'a'); // 沒有元素刪除,此時返回為[]
...
// howmany設為1就會刪除元素,不過此時方法看起來是替換元素
// 此時arr4為[1, "b", 2, 3, 4, 5, 6, 7, 8, 9]
arr4.splice(1, 1, 'b'); // 返回["a"],這是被刪除的元素
...
// howmany設為2以上的數字,就能體現出刪除元素的功能了
// 此時arr4為[1, "b", 3, 4, 5, 6, 7, 8, 9]
arr4.splice(1,2,'b'); // ["b", 2]
  • map: 對數組的進行迭代,然后把每一次的執行結果組成一個新數組返回 [0]
// isEven參數x接收了arr5每個元素,并進行判斷,并把判斷結果返回成一個數組
// 返回新數組[false, true, false, true, false, true, false, true, false, true, false, true, false, true, false]
arr5.map(isEven); 
  • forEach: 與map方法類似,對數組進行迭代,區別在于不會返回新的數組 [0]
arr6.forEach(isEven); // 沒有任何返回
arr6.forEach(isEven2); // 在控制臺會輸出每一個元素判斷后的值
  • filter: 對數組進行迭代,把符號條件的元素,組合成一個新數組 [0]
arr6.filter(isEven); // [2, 4, 6, 8, 10, 12, 14]
  • some: 對數組進行迭代,只要某個數組元素符合條件,就會返回true [0]
arr6.some(isEven); // true,數組中存在偶數
  • every: 對數組進行迭代,要求每個數組元素都要符合條件,才會返回true [0]
arr6.every(isEven); // false,數組中有奇數,不符合每個元素都是偶數的條件
  • reduce: 對數組元素進行迭代,數組元素按順序進行兩兩操作,并把結果繼續傳遞當作下次計算的第一個元素,直至遍歷到最后一個數組元素,并返回最后計算結果 [0]

reduce(累積變量, 當前變量, 當前位置, 原數組);
累積變量默認是數組第一個元素

arr2.reducce(function(a, b) {
    console.log(a, b);
    return a * b;
});
...
// 這時控制臺輸出為,20就是數組元素4,5的計算結果,最后返回的是20和6的計算結果
// 計算規則可以隨意定位,并不局限于四則運算
4 5
20 6
< 120
...
arr2.reduce(function (a, b) {
  return a + '-' + b;
});
// 這是控制臺輸出'4-5-6'
  • reduceRight: 規則和reduce一致,區別在于reduce是按數組順序進行計算,reduceRight是按數組逆序進行計算 [0]

累積變量默認是數組最后一個元素

arr2.reduceRight(function (a, b) {
  return a + '-' + b;
});
// '6-5-4'
  • indexOf: 數組迭代,查找數組中是否有指定元素,有的話返回出現的位置(數組下標,從0開始),同時終止迭代,否則遍歷完整個數組,如果整個數組都沒有指定數組,該值返回-1 [0]
arr2.indexOf(6); // 2
arr2.indexOf(60); // -1
...
arr2.indexOf(5, 2); // -1,接受第二個參數表示從什么位置開始搜索
arr2.indexOf(5, 1); // 1
  • lastIndexOf: 和indexOf方法類似,區別在于這個方法是查詢元素最后出現的位置 [0]
arr6.indexOf(1); // 0
arr6.lastIndexOf(1); // 4

棧是一種遵從后進先出(LIFO)原則的有序集合,新添加的或者待刪除的元素都保存在棧的末尾,稱為棧頂,另一端叫棧底

創建棧

// 原書是使用es5的寫法,我這里換為es6
class Stack {
    constructor () {
        this.items = [];
    }
    push (element) {
        this.items.push(element);
    }
    pop () {
        return this.items.pop();
    }
    peek () {
        return this.items[this.items.length - 1];
    }
    isEmpty () {
        return this.items.length === 0;
    }
    size () {
        return this.items.length;
    }
    clear () {
        this.items = [];
    }
    print () {
        console.log(this.items.toString());
    }
}
let stack = new Stack();
console.log(stack.isEmpty());

在終端運行下面命令

babel ./src/main.js -o ./dist/main.js

如果想簡單一些,可以把上面命令寫到package.json的script中,通過npm run運行

"scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "build": " babel ./src/main.js -o ./dist/main.js"
},

編譯完成后我們在index.html文件引入編譯后的main.js,在之前啟動的服務器刷新下,就能看到控制臺輸出true(也就是stack.isEmpty()的值),在控制臺我們可以嘗試使用定義好的方法來驗證相關功能,一個基本的棧就建立完成,總結下我們這個棧一共有如下幾個功能:

  • push: 向棧頂添加元素
  • pop: 移除棧頂元素
  • peek: 返回棧頂元素
  • isEmpty: 判斷棧中是否有元素
  • clear: 清空棧中所有元素
  • size: 返回棧中有多少元素

把十進制數字轉為二進制

js本身有方法使用toString(2)就能得到一個二進制,不過這并不是底層算法,下面用算法,把一個十進制數字轉為二進制

// 以10為例,下面的計算并不是嚴格意義上的計算公式,只是一種計算思路
10 / 2 = 5; // 余數為0
5 / 2 = 2;  // 余數為1
2 / 2 = 1;  // 余樹為0
1 / 2 = 0;  // 余數為1,所以10的二進制為1010
// 以11為例
11 / 2 = 5; // 余數為1
5 / 2 = 2;  // 余數為1
2 / 2 = 1;  // 余數為0
1 / 2 = 0;  // 余數為1 

從上面的過程我們分析整個計算過程應該是先算出要轉換的數字和2相除的整數為多少,如果這個整數值為0就終止整個計算過程,如果不是就繼續與2相除,直至結果為0,期間產生的余數就是對應2進制的值

// 10進制轉2進制算法代碼
// 方法是使用上面棧定義的方法
let divideBy2 = function (decNumber) {
    let remStack = new Stack();
    let rem = 0;
    let binaryString = '';
    while (decNumber > 0) {
        rem = decNumber % 2 // 記錄當前余樹是多少
        remStack.push(rem);  // 存入棧中
        decNumber = parseInt(decNumber / 2); // 與2除取整
    }
    while (!remStack.isEmpty()) {
        binaryString += remStack.pop().toString();
    }
    return binaryString;
};

漢諾塔

漢諾塔(又稱河內塔)問題是源于印度一個古老傳說的益智玩具。大梵天創造世界的時候做了三根金剛石柱子,在一根柱子上從下往上按照大小順序摞著64片黃金圓盤。大梵天命令婆羅門把圓盤從下面開始按大小順序重新擺放在另一根柱子上。并且規定,在小圓盤上不能放大圓盤,在三根柱子之間一次只能移動一個圓盤(來源百度百科)。

漢諾塔的解法主要是對分治遞歸的理解,這個理解可以參考盜夢空間的多層夢境,從第一層夢境到第四層夢境,再從夢境中醒過來的過程。

書上和網上查到都提到使用棧解決漢諾塔,不過實現后發現其實使用棧也不過是記錄數據的方式,并沒有什么特別的地方(簡單的東西說的那么高大上,琢磨了半天...)

遞歸解法
// 這里用了class的寫法
// 使用時new hanoi(4);表示要移動圓盤數,別太大,會卡死
class hanoi {
    constructor (disc) {
        this.disc = disc; // 設定要移動多少圓盤
        this.src = 'A'; // 設定源支柱名詞
        this.aux = 'B'; // 輔助支柱名詞
        this.dst = 'C'; // 設定目標支柱名詞
    }
    descMove ({disc = this.disc, src = this.src, aux = this.aux, dst = this.dst} = {}) {
        // 這個函數的意義是表示,某個圓盤,要從src(源支柱)移動到dst(目標支柱),aux是輔助支柱,
        if (disc) {
            // 至少有一個圓盤才會進行下面的邏輯
            // 下面函數進行的意義是指,如果至少是兩個圓盤(因為是一個圓盤時,回調當前函數時disc為0,不會有任何輸出)
            // 整個算法體現了分治遞歸的思路,我們是假設有三個支柱,A是源支柱,B是輔助支柱,C是目標支柱
            // 最終的目的是把A支柱上的所有圓盤移動到C支柱上,如果正向解這個問題,會很復雜,而且會涉及很多判斷
            // 使用分治的思路,把問題簡化,
            // 整體來看漢諾塔的算法是拆分成如下過程(最終結束的三步)
            // 先把(n-1)的圓盤,移動到輔助支柱
            // 把n圓盤移動到目標支柱
            // 再把(n-1)的圓盤,以輔助支柱為源支柱,源支柱為輔助支柱,進行再一次的遞歸,直至圓盤為1
            // 反過來看就是移動的過程
            this.descMove({disc : disc - 1, src: src, aux : dst, dst : aux});
            console.log(`移動${disc}號圓盤,從${src}移動到${dst}`);
            this.descMove({disc: disc - 1, src : aux, aux : src, dst: dst});
        }
    }
}
結合棧的解法

算法思路還是使用遞歸思路,區別在于使用棧來存儲

// 這里使用了上面定義的棧
// 這里可以先驗證pillarSrc和pillarDst
// 運行hanoi(3, pillarSrc, pillarAux, pillarDst);
// 再次查看兩個棧的值,就能發現模擬的圓盤,從源支柱,移動到目標支柱上
let pillarSrc = new Stack(); // 源支柱
let pillarAux = new Stack(); // 輔助支柱
let pillarDst = new Stack(); // 目標支柱
let n = 3; // 初始時有多少圓盤
while (n--) {
    pillarSrc.push(n); // 在源支柱插入數據
}
let hanoi = (dist, src, aux, dst) => {
    console.log(dist);
    if (dist === 1) {
        // 當只有一個圓盤時,把圓盤從源支柱移動到目標支柱
        moveStack(src, dst);
    } else {
        hanoi(dist - 1, src, dst, aux);
        moveStack(src, dst);
        hanoi(dist - 1, aux, src, dst);
    }
}
let moveStack = (src, dst) => {
    if (!src.isEmpty()) {
        let moveElem = src.pop(); // 把源棧移出棧頂
        dst.push(moveElem); // 把從源棧演出的元素插入到目標支柱
    }
}
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,533評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,055評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,365評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,561評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,346評論 6 404
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,889評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,978評論 3 439
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,118評論 0 286
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,637評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,558評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,739評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,246評論 5 355
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 43,980評論 3 346
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,362評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,619評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,347評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,702評論 2 370

推薦閱讀更多精彩內容

  • 1 序 2016年6月25日夜,帝都,天下著大雨,拖著行李箱和同學在校門口照了最后一張合照,搬離寢室打車去了提前租...
    RichardJieChen閱讀 5,114評論 0 12
  • 最近在慕課網學習廖雪峰老師的Python進階課程,做筆記總結一下重點。 基本變量及其類型 變量 在Python中,...
    victorsungo閱讀 1,713評論 0 5
  • 1,心理學家的惡作劇之假精神病人實驗 在心理學領域有一種現象被稱為“貼標簽效應”也就是暗示效應。所謂貼標簽效應,具...
    晚晴521閱讀 1,269評論 0 1