1. 程式人生 > >前端學習總結(二十二)——常見資料結構與演算法javascript實現

前端學習總結(二十二)——常見資料結構與演算法javascript實現

寫在前面

作為前端開發者而言,可能不會像後端開發那樣遇到很多的演算法和資料結構問題,但是不論是做前端、 服務端還是客戶端, 任何一個程式設計師都會開始面對更加複雜的問題, 這個時候演算法和資料結構知識就變得不可或缺,它是程式設計能力中很重要的一部分。

如今的前端技術發展飛快,再也不像以前那樣只負責檢視層了,更多的互動和資料邏輯需要前端來做,這個時候對於演算法和資料結構就有著更高的要求。此外,前端還可以去做node端,做資料視覺化,做WebGL,三維建模,VR/AR等,這些新興的領域對演算法和資料有著更高的要求,所以想要成為一名高水平的前端工程師,就需要在資料和演算法方面做更多的積累。

最近讀完了一本書,是O’Reilly出本社出版的《資料結構與演算法JavaScript描述》,這本書使用javascript描述了常見的各種資料結構與演算法,可謂是js界的《演算法導論》,前端工程師如果想要提升演算法資料結構方面的基礎,個人覺得這本書是非常好的選擇,下面是我學習過程中的一些收穫和總結。

PS:這裡我把學習的一些總結都寫在了程式碼的註釋裡,這也是我個人學東西的一個習慣,一邊學一邊跑程式碼,把重要的一些總結寫在註釋裡。這樣很有助於技術學習的沉澱,之後查起來也很方便,所以請留意註釋中的內容。

一 開發環境

這裡使用的是火狐官方的一個工具js shell,安裝之後可以使用js命令來在命令列中執行js指令碼,執行的環境是Google V8引擎。

將命令安裝到全域性就能使用,我用的是mac,安裝的時候把二進位制程式和依賴的包拷到/usr/local/bin,就可以在命令列中使用js命令了。

可以使用readline()從命令列中獲取輸入,使用print()輸出結果。

比如,一個簡答的a+b的程式 a+b.js:

var line;
while(line = readline()){
    line = line.split(' ');
    print(parseInt(line[0]) + parseInt(line[1]));
}

命令列中執行(檔案路徑注意寫對,是絕對路徑)

js /myproject/algorithms/a+b.js

就可以在命令列中輸入引數,回車得到結果了,是不是很像用 C++ 命令列解演算法題,沒錯,就是這麼方便。

當然也可以使用 Node.js 環境來執行,具體參考Node.js官方文件即可。

二 物件和麵向物件程式設計

js中5種資料型別,並沒有定義更多的資料型別,但是運用js中一切皆物件的思想,可以自己很方便的去構造出各種複雜的資料型別物件。

這裡討論到的資料結構都被實現為物件。 JavaScript 提供了多種方式來建立和使用物件。 這裡通過如下方式建立:

定義包含屬性和方法宣告的建構函式, 並在建構函式後緊跟方法
的定義。

下面是一個檢查銀行賬戶物件的建構函式:

//該資料物件的建構函式,相當於是一個類
function Checking(amount) {
this.balance = amount; // 屬性
this.deposit = deposit; // 方法
this.withdraw = withdraw; // 方法
this.toString = toString; // 方法
}

this 關鍵字用來將方法和屬性繫結到一個物件的例項上。 下面我們看看對於前面宣告過的方法是如何定義的:

function deposit(amount) {
this.balance += amount;
} 

function withdraw(amount) {
if (amount <= this.balance) {
this.balance -= amount;
} 
if (amount > this.balance) {
print("Insufficient funds");
}
} 

function toString() {
return "Balance: " + this.balance;
}

使用時這樣:

//注意這裡通過: new 建構函式(傳入的引數)生成了一個物件例項
var account = new Checking(500);
account.deposit(1000);
print(account.toString()); //Balance: 1500
account.withdraw(750);
print(account.toString()); // 餘額: 750
account.withdraw(800); // 顯示 " 餘額不足 "
print(account.toString()); // 餘額: 750

這就是一個定義資料物件的方法,下面構建各種資料結構也是使用這樣的方式(除過陣列,因為js本身定義了陣列型別;其次,其他的資料結構的定義有很多事使用資料作為基礎資料結構進行資料儲存的)。

三 陣列

1.字串分割為陣列split與陣列元素拼接轉字串join

var sentence = "the quick brown fox jumped over the lazy dog";
/**1.
 * 字串.split(分隔符) 將字串生成為陣列
 * */
var words = sentence.split(" ");
for (var i = 0; i < words.length; ++i) {
    print("word " + i + ": " + words[i]);
}
/**2.
 * 陣列轉字串
 * .join(分隔符)
 * 陣列各元素間放分隔符並連線成一個字串
 * join("") 就是 直接將陣列個元素拼接起來生字串
 * .toString()連線成字串後 預設中間會用,隔開
 * */

2.indexOf-查詢陣列是否存在某元素及下標

var names = ["David","Cynthia","Raymond","Clayton","Jennifer"];
putstr("Enter a name to search for: ");
var name = readline();
/**1.
 * 陣列.indexOf(引數值) 引數值是否存在於陣列,
 * 存,返第一個出現該元素的下標;
 * 不存,返-1;
 *
 * 陣列.lastIndexOf(引數值)
 * 反序找第一個的下標(如果出現,否則返-1)
 *
 * */
var position = names.indexOf(name);
if (position >= 0) {
    print("Found " + name + " at position " + position);
}
else {
    print(name + " not found in array.");
}

3.陣列中間新增和刪除修改元素splice

直接上程式碼,總結見註釋:

/**
 * 1.splice() 將現有陣列進行擷取,返回所擷取生成出來的陣列,且現有陣列改變,是擷取後的陣列
 * 可用於為一個數組增加或移除或修改元素
 * 引數一:擷取(刪除)的起始索引(0是第一個元素)
 * 引數二:擷取(刪除)的元素的個數
 * 引數三:刪除擷取後要新增進陣列的元素(可以是個陣列)
 * */

/**2.
 * 陣列中間插入元素(放在數組裡插入)
 * */
var nums = [1,2,3,7,8,9];
var newElements = [4,5,6];
nums.splice(3,0,newElements);
print(nums); // 1,2,3,4,5,6,7,8,9

/**3.
 * 要插入陣列的元素不必組織成一個數組, 它可以是任意的元素序列
 * */
var nums = [1,2,3,7,8,9];
nums.splice(3,0,4,5,6);
print(nums);

/**4.
 * 從陣列中刪除元素
 * */
var nums = [1,2,3,100,200,300,400,4,5];
nums.splice(3,4);
print(nums); // 1,2,3,4,5

4.不生成新陣列的迭代器方法-forEach每個元素都操作-every所有都滿足-some有一個滿足-reduce累計操作

直接上程式碼,總結見註釋:

/**
 * Created by chenhaoact on 16/8/13.
 */
/**
 * 1. 陣列.forEach(func) 對陣列每個元素執行某操作
 * 它接受一個函式作為引數,對陣列中的每個元素使用該函式
 * */
function squareFunc(num) {
    print(num, num * num); //列印多個字元的時候自動中間會加空格
}

var nums = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
nums.forEach(squareFunc);

/**
 * 2. 陣列.every(func), 檢查陣列中每個元素是否滿足某條件
 * 它接受一個返回值為布林型別的函式, 對陣列中的每個元素使用該函式。
 * 如果對於所有的元素,該函式均返回 true, 則該方法返回 true
 *
 * 陣列.some(func)
 * 是否存在一個元素滿足
 * 也接受一個返回值為布林型別的函式, 只要有一個元素使得該函式返回 true,
 * 該方法就返回 true
 * */
function isEven(num) {
    return num % 2 == 0;
}
var nums = [2, 4, 6, 8, 10];
var even = nums.every(isEven);
if (even) {
    print("all numbers are even");
} else {
    print("not all numbers are even");
}

/**
 * 3.
 * reduce() 陣列中的各個元素累計進行操作
 * 它接受一個函式, 返回一個值。 該方法會從一個累加值開始, 不斷對累加值和
 * 陣列中的後續元素呼叫該函式, 直到陣列中的最後一個元素, 最後返回得到的累加值。
 * */

//使用 reduce() 方法為陣列中的元素求和:
function add(runningTotal, currentValue) {
    return runningTotal + currentValue;
}
var nums = [1,2,3,4,5,6,7,8,9,10];
var sum = nums.reduce(add); //
print(sum); // 顯示 55

//reduce() 方法也可以用來將陣列中的元素連線成一個長的字串
function concat(accumulatedString, item) {
    return accumulatedString + item;
}
var words = ["the ", "quick ","brown ", "fox "];
var sentence = words.reduce(concat);
print(sentence); // 顯示 "the quick brown fox"


/**
 * 4.reduceRight() 方法,從右到左執行。
 * 下面的程式使用 reduceRight() 方法將陣列中的元素進行翻轉:
 * */
function concat(accumulatedString, item) {
    return accumulatedString + item;
}
var words = ["the ", "quick ","brown ", "fox "];
var sentence = words.reduceRight(concat);
print(sentence); // 顯示 "fox brown quick the"

5.生成新陣列的迭代器方法-map每個元素都執行某操作結果組成的陣列-filter陣列中滿足某條件的元素組成的陣列

直接上程式碼,總結見註釋:

/**
 * Created by chenhaoact on 16/8/13.
 */
/**
 * 1. 陣列.map(func)
 * map() 和 forEach() 有點兒像,
 * 對陣列中的每個元素使用某個函式。 兩者的區別是
 * map() 返回一個新的陣列, 該陣列的元素是對原有元素應用某個函式得到的結果
 * */
function curve(grade) {
    return grade += 5;
}
var grades = [77, 65, 81, 92, 83];
var newgrades = grades.map(curve);
print(newgrades); // 82, 70, 86, 97, 88

/**
 * 2.下面是對一個字串陣列使用 map() 方法的例子:
 * 陣列 acronym 儲存了陣列 words 中每個元素的第一個字母。
 * 然而, 如果想將陣列顯示為真正的縮略形式, 必須想辦法除掉連線每個陣列元素的逗號,
 * 如果直接呼叫 toString() 方法, 就會顯示出這個逗號。
 * 使用 join() 方法, 為其傳入一個空字串作為引數, 則可以幫助我們解決這個問題
 * */
function first(word) {
    return word[0];
}
var words = ["for", "your", "information"];
var acronym = words.map(first);
print(acronym)
print(acronym.join("")); // 顯示 "fyi"

/**
 * 3.filter() 傳入一個返回值為布林型別的函式。
 * 和 every() 方法不同的是,
 * 當對陣列中的所有元素應用該函式,該方法並不返回 true,
 * 而是返回一個新陣列, 該陣列包含應用該函式後結果為 true 的元素。
 * */
//下列程式篩選陣列中的奇數和偶數元素
function isEven(num) {
    return num % 2 == 0;
}

function isOdd(num) {
    return num % 2 != 0;
}
var nums = [];
for (var i = 0; i < 20; ++i) {
    nums[i] = i + 1;
}
var evens = nums.filter(isEven);
print("Even numbers: ");
print(evens);
var odds = nums.filter(isOdd);
print("Odd numbers: ");
print(odds);
//執行結果如下:Even numbers:2,4,6,8,10,12,14,16,18,20 Odd numbers:1,3,5,7,9,11,13,15,17,19

//下面使用 filter() 篩選所有成績及格的分數:
function passing(num) {
    return num >= 60;
}
var grades = [];
for (var i = 0; i < 20; ++i) {
    grades[i] = Math.floor(Math.random() * 101);
}
var passGrades = grades.filter(passing);
print("All grades:");
print(grades);
print("Passing grades: ");
print(passGrades);

//還可以使用 filter() 方法過濾字串陣列,下面這個例子過濾掉了那些不包含“ cie” 的單詞:
function afterc(str) {
    if (str.indexOf("cie") > -1) {
        return true;
    }
    return false;
}
var words = ["recieve","deceive","percieve","deceit","concieve"];
var misspelled = words.filter(afterc);
print(misspelled); // 顯示 recieve,percieve,concieve

6.二維陣列和多維陣列

直接上程式碼,總結見註釋:

/**
 * Created by chenhaoact on 16/8/13.
 */
/**
 * 1.建立二維陣列
 * 比較好的方式是遵照 JavaScript: TheGood Parts( O’Reilly) 一書第 64 頁的例子,
 * 通過擴充套件 JavaScript 陣列物件, 為其增加了一個新方法,
 * 該方法根據傳入的引數, 設定了陣列的行數、 列數和初始值
 * */
Array.matrix = function(numrows, numcols, initial) {
    var arr = [];
    for (var i = 0; i < numrows; ++i) {
        var columns = [];
        for (var j = 0; j < numcols; ++j) {
            columns[j] = initial;
        }
        arr[i] = columns;
    }
    return arr;
}

//測試該生成二維陣列方法的一些測試程式碼
var nums = Array.matrix(5,5,0);
print(nums[1][1]); // 顯示 0
var names = Array.matrix(3,3,"");
names[1][2] = "Joe";
print(names[1][2]); // display"Joe"

/**
 * 還可以僅用一行程式碼就建立並且使用一組初始值來初始化一個二維陣列:
 * 對於小規模的資料, 這是建立二維陣列最簡單的方式。
 * */
var grades = [[89, 77, 78],[76, 82, 81],[91, 94, 89]];
print(grades[2][2]); // 顯示 89


/**
 * 2.處理二維陣列的元素
 * 兩種最基本的方式: 按行訪問和按列訪問
 * */

/**
 * 按行訪問:
 * 外層迴圈對應行,內層迴圈對應列,每次對每一行的元素進行一些操作
 *
 * 以陣列 grades 為例, 每一行對應一個學生的成績記錄。
 * 可以將該學生的所有成績相加, 然後除以科目數得到該學生的平均成績。
 * */
var grades = [[89, 77, 78],[76, 82, 81],[91, 94, 89]];
var total = 0;
var average = 0.0;
for (var row = 0; row < grades.length; ++row) {
    for (var col = 0; col < grades[row].length; ++col) {
        total += grades[row][col];
    }

    average = total / grades[row].length;
    print("Student " + parseInt(row+1) + " average: " +
        average.toFixed(2));
    total = 0;
    average = 0.0;
}

/**
 * 按列訪問:
 * 外層迴圈對應列,內層迴圈...,每次對每一列的元素進行一些操作
 *
 * 下面的程式計算了一個學生各科的平均成績,即:每一列的資料想加取平均值:
 * */
var grades = [[89, 77, 78],[76, 82, 81],[91, 94, 89]];
var total = 0;
var average = 0.0;
for (var col = 0; col < grades.length; ++col) {
    for (var row = 0; row < grades[col].length; ++row) {
        total += grades[row][col];
    } a
    verage = total / grades[col].length;
    print("Test " + parseInt(col+1) + " average: " +
        average.toFixed(2));
    total = 0;
    average = 0.0;
}

/**
 * 3.參差不齊的陣列
 * 參差不齊的陣列是指陣列中每行的元素個數彼此不同。 有一行可能包含三個元素, 另一行
 * 可能包含五個元素, 有些行甚至只包含一個元素。 很多程式語言在處理這種參差不齊的數
 * 組時表現都不是很好, 但是 JavaScript 卻表現良好, 因為每一行的長度是可以通過計算得到的
 * */
//假設陣列 grades 中, 每個學生成績記錄的個數是不一樣的, 不用修改程式碼, 依然可以正確計算出正確的平均分:
var grades = [[89, 77],[76, 82, 81],[91, 94, 89, 99]];
var total = 0;
var average = 0.0;
for (var row = 0; row < grades.length; ++row) {
    for (var col = 0; col < grades[row].length; ++col) {
        total += grades[row][col];
    }
    average = total / grades[row].length;
    print("Student " + parseInt(row+1) + " average: " + average.toFixed(2));
    total = 0;
    average = 0.0;
}

/**
 * 4.物件陣列
 * 物件組成的陣列,陣列的方法和屬性對物件依然適用。
 * */

/**
 * 注意 這裡通過一個函式生成了一個物件
 * 生成物件的函式裡傳入引數,然後設定
 * this.屬性 = ...
 * this.方法 = function...
 * 這樣的函式即建構函式
 * */
function Point(x,y) {
    this.x = x;
    this.y = y;
}
function displayPts(arr) {
    for (var i = 0; i < arr.length; ++i) {
        print(arr[i].x + ", " + arr[i].y);
    }
}

/**
 * 注意 這裡通過 var ... = new 建構函式(實際引數)
 * 生成了該物件的一個例項物件
 * */
var p1 = new Point(1,2);
var p2 = new Point(3,5);
var p3 = new Point(2,8);
var p4 = new Point(4,4);
//物件組成的陣列
var points = [p1,p2,p3,p4];
for (var i = 0; i < points.length; ++i) {
    print("Point " + parseInt(i+1) + ": " + points[i].x + ", " + points[i].y);
}
var p5 = new Point(12,-3);
//使用 push() 方法將點 (12, -3) 新增進陣列, 使用 shift() 方法將點 (1, 2) 從陣列中移除。
points.push(p5);
print("After push: ");
displayPts(points);
points.shift();
print("After shift: ");
displayPts(points);

/**
 * 5.物件中的陣列
 * 在物件中, 可以使用陣列儲存複雜的資料。
 * 實際演算法應用與解決方案中,
 * 很多資料都被實現成一個物件,
 * 物件內部使用陣列儲存資料。
 *
 * 下例中, 建立了一個物件, 用於儲存觀測到的周最高氣溫。
 * 該物件有兩個方法, 一個方法用來增加一條新的氣溫記錄,
 * 另外一個方法用來計算儲存在物件中的平均氣溫
 *
 * 很實用和常用的技巧!!!
 * */

//物件建構函式
function weekTemps() {
    this.dataStore = []; //物件建構函式裡 設定某些屬性為一個數組儲存比較複雜的資料
    this.add = add; //設定物件的方法
    this.average = average;
}

//定義物件方法的操作,裡面使用this.屬性名 代表物件的某屬性
function add(temp) {
    this.dataStore.push(temp);  //對物件的陣列型資料進行陣列式操作
}
function average() {
    var total = 0;
    for (var i = 0; i < this.dataStore.length; ++i) {
        total += this.dataStore[i];
    }
    return total / this.dataStore.length;
}
var thisWeek = new weekTemps();
thisWeek.add(52);
thisWeek.add(55);
thisWeek.add(61);
thisWeek.add(65);
thisWeek.add(55);
thisWeek.add(50);
thisWeek.add(52);
thisWeek.add(49);
print(thisWeek.average()); // 顯示 54.875

四 列表-js實現

直接上程式碼,總結見註釋:

/**
 * Created by chenhaoact on 16/8/14.
 */
/**
 * 1.列表抽象資料型別
 * 列表是一組有序的資料。 每個列表中的資料項稱為元素。
 * 在 JavaScript 中, 列表中的元素可以是任意資料型別。 列表中可以儲存多少元素並沒有事先限定,實際中受記憶體限制
 *
 * 適用於:
 * 列表中儲存的元素不是太多時。 當不需要在一個很長的序列中查詢元素, 或者對其進行排序時, 列表顯得尤為有用。
 * 反之, 如果資料結構非常複雜, 列表的作用就沒有那麼大了。
 *
 * 如果儲存的順序不重要(順序重要的話可以考慮如堆疊等), 也不必對資料進行查詢, 那麼列表就是一種再好不過的
 * 資料結構。 對於其他一些應用, 列表就顯得太過簡陋了
 *
 *
 * 屬性與方法:
 * listSize( 屬性) 列表的元素個數,最後一個元素的位置下標是 listSize - 1
 * pos( 屬性) 列表的當前位置
 * length( 屬性) 返回列表中元素的個數
 * clear( 方法) 清空列表中的所有元素
 * listToString( 方法) 返回列表的字串形式,這裡不適用toString() 不去覆蓋原生的toString()
 * getElement( 方法) 返回當前位置的元素
 * insert( 方法) 在現有元素後插入新元素
 * append( 方法) 在列表的末尾新增新元素
 * remove( 方法) 從列表中刪除元素
 * front( 方法) 將列表的當前位置設移動到第一個元素
 * end( 方法) 將列表的當前位置移動到最後一個元素
 * prev( 方法) 將當前位置後移一位
 * next( 方法) 將當前位置前移一位
 * currPos( 方法) 返回列表的當前位置
 * moveTo( 方法) 將當前位置移動到指定位置
 * */

/**
 * 2.實現列表類,定義建構函式
 * 注意這裡定義的刪除查詢等方法都是傳入一整個元素的值,列表由一系列元素組成,元素即最小的那個單元
 * */
function List() {
    this.listSize = 0; //listSize是屬性
    this.pos = 0;
    this.dataStore = []; // 初始化一個空陣列來儲存列表元素,即底層資料結構是陣列
    this.clear = clear;
    this.find = find;
    this.listToString = listToString;
    this.insert = insert;
    this.append = append;
    this.remove = remove;
    this.front = front;
    this.end = end;
    this.prev = prev;
    this.next = next;
    this.length = length; //length是方法
    this.currPos = currPos;
    this.moveTo = moveTo;
    this.getElement = getElement;
    this.length = length;
    this.contains = contains;
}

/**
 * 3. append()
 * 該方法給列表的下一個位置增加一個新的元素,
 * 這個位置剛好等於變數 listSize 的值,新元素就位後, 變數 listSize 加 1,
 * []訪問的時候元素個數直接就++了
 * */
function append(element) {
    this.dataStore[this.listSize++] = element;
}

/**
 * 4. find()
 * find() 方法通過對陣列物件 dataStore 進行迭代,查詢給定的元素。
 * 如果找到,就返回該元素在列表中的位置,否則返回 -1,這是在陣列中找不到指定元素時返回的標準值
 * */
function find(element) {
    for (var i = 0; i < this.dataStore.length; ++i) {
        if (this.dataStore[i] == element) {
            return i;
        }
    }
    return -1;
}

/**
 * 5. remove()
 * 需要在列表中找到該元素, 然後刪除它, 並且調整底層的陣列物件以填補刪除該元素後留下的空白。
 * js中可以使用 splice() 方法簡化這一過程。
 *
 * remove() 方法使用 find() 方法返回的位置對陣列 dataStore 進行擷取。 陣列改變後, 將變
 * 量 listSize 的值減 1, 以反映列表的最新長度。 如果元素刪除成功, 該方法返回 true,
 * 否則返回 false。
 * */
function remove(element) {
    var foundAt = this.find(element);
    if (foundAt > -1) {
        this.dataStore.splice(foundAt, 1);
        --this.listSize;
        return true;
    }
    return false;
}

/**
 * 6. length()
 * 返回列表中元素的個數
 * */
function length() {
    return this.listSize;
}

/**
 * 7. listToString() 顯示列表中的元素,
 * 該方法返回的是一個數組, 而不是一個字串, 但它的目的是為了顯示列表的
 * 當前狀態, 因此返回一個數組就足夠了。
 * */
function listToString() {
    return this.dataStore;
}

/**
 * 8. insert() 向列表中插入一個元素
 * insert() 方法需要知道將元素插入到什麼位置,
 * 假設插入是指插入到列表中某個元素之後.
 *
 * insert() 方法用到了 find() 方法, find() 方法會尋找傳入的 after 引數在列
 * 表中的位置, 找到該位置後, 使用 splice() 方法將新元素插入該位置之後, 然後將變數
 * listSize 加 1 並返回 true, 表明插入成功。
 * */
function insert(element, after) {
    var insertPos = this.find(after);
    if (insertPos > -1) {
        this.dataStore.splice(insertPos + 1, 0, element);
        ++this.listSize;
        return true;
    }
    return false;
}

/**
 * 9. clear() 清空列表中所有的元素
 * clear() 方法使用 delete 操作符刪除陣列 dataStore, 接著在下一行建立一個空陣列。 最
 * 後一行將 listSize 和 pos 的值設為 1, 表明這是一個新的空列表
 * */
function clear() {
    delete this.dataStore;
    this.dataStore = [];
    this.listSize = this.pos = 0;
}

/**
 * 10. contains() 判斷給定值是否在列表中
 * */
function contains(element) {
    for (var i = 0; i < this.dataStore.length; ++i) {
        if (this.dataStore[i] == element) {
            return true;
        }
    }
    return false;
}

/**
 * 11. 下面的方法都是通過控制當前位置 pos 和 listSize 來實現的
 * */

//當前位置移動到首元素
function front() {
    this.pos = 0;
}

//當前位置移動到尾元素
function end() {
    this.pos = this.listSize - 1;
}

//當前位置向前移動一位
function prev() {
    if (this.pos > 0) {
        --this.pos;
    }
}

//當前位置向後移動一位
function next() {
    if (this.pos < this.listSize - 1) {
        ++this.pos;
    }
}

//獲得當前位置
function currPos() {
    return this.pos;
}

//當前位置移動移動到某個位置(傳入的是位置數字,從零開始)
function moveTo(position) {
    this.pos = position;
}

/**
 * 12. getElement() 返回列表的當前元素
 * */
function getElement() {
    return this.dataStore[this.pos];
}

/**
 * 13. 應用1: 建立一個由姓名組成的列表,來展示怎麼使用這些方法
 * */
//建立一個列表例項物件
var names = new List();
names.append("Clayton");
names.append("Raymond");
names.append("Cynthia");
names.append("Jennifer");
names.append("Bryan");
names.append("Danny");

//現在移動到列表中的第一個元素並且顯示它:
names.front();
print(names.getElement()); // 顯示 Clayton

//接下來向後移動一個單位並且顯示它:
names.next();
print(names.getElement()); // 顯示 Raymond

//先向前移動兩次, 然後向後移動一次, 顯示出當前元素, 看看 prev() 方法的應用
names.next();
names.next();
names.prev();
print(names.getElement()); // 顯示 Cynthia

/**
 * !遍歷!
 * 由前向後遍歷列表:
 * 在 for 迴圈的一開始, 將列表的當前位置設定為第一個元素。 只要 currPos 的值小於列表
 * 的長度-1 (因為pos是從0開始的,比較完之後才會移動next() ), 就一直迴圈, 每一次迴圈都呼叫 next() 方法將當前位置向前移動一位。
 * */
//這裡用names.pos++比較好,因為next() pos永遠到不了names.length(),會一直迴圈
for (names.front(); names.currPos() < names.length(); names.pos++) {
    print(names.getElement());
    //print(names.currPos());
}
//但注意經過上面的遍歷操作,pos指向的是最後一位+1,所以要 -1一次
names.pos -= 1;

/**
 * 從後向前遍歷列表
 * 迴圈從列表的最後一個元素開始, 噹噹前位置大於或等於 0 時, 呼叫 prev() 方法後移一位。
 *
 * 迭代器只是用來在列表上隨意移動, 而不應該和任何為列表增加或刪除元素的方法一起使用
 * */
//這裡用names.pos--比較好,因為pre() pos永遠到0就不會降了,會一直迴圈
for(names.end(); names.currPos() >= 0; names.pos--) {
    print(names.getElement());
}
//但注意經過上面的遍歷操作,pos指向的是-1,所以要 +1一次
names.pos += 1;

五 棧-js實現

入棧push-出棧pop-訪問棧頂元素peek-清空棧clear

直接上程式碼,總結見註釋:

/**
 * Created by chenhaoact on 16/8/14.
 */
/**
 * 1.棧是一種高效的資料結構, 因為資料只能在棧頂新增或刪除, 所以這樣的操作很快
 * 棧是一種特殊的列表, 棧內的元素只能通過列表的一端訪問, 這一端稱為棧頂。
 * 一摞盤子是現實世界中常見的棧的例子.
 * 棧具有後入先出的特點, 所以任何不在棧頂的元素都無法訪問。
 * 為了得到棧底的元素, 必須先拿掉上面的元素。
 *
 * 屬性:
 * top  棧陣列的第一個空位置 = 棧頂元素的位置+1 ,top處是空的,它處於棧頂元素之上  (top從0開始,0代表在棧底,及棧是空的),同時也為了標記哪裡可以加入新元素,當向棧內壓入元素時, 該變數增大
 * 即:第 棧頂元素的 陣列下標是 top - 1
 * empty 棧內是否含有元素,用 length 屬性也可以達到同樣的目的
 *
 * 方法:
 * push() 元素入棧,
 * pop() 元素出棧,(也可以訪問棧頂的元素,但是呼叫該方法後,棧頂元素被刪除)
 * peek() 預覽棧頂元素,只返回棧頂元素,不刪除它。
 * length() 返回棧內元素的個數(top應該是等於陣列的length的,所以用top屬性也可)
 * clear() 清除棧內所有元素
 * */
//棧類的建構函式
function Stack() {
    this.dataStore = []; //底層資料結構是陣列
    this.top = 0; //top應該是等於陣列的length的
    this.push = push;
    this.pop = pop;
    this.peek = peek;
    this.length = length;
    this.clear = clear;
}

/**
 * 2. push()
 * 向棧中壓入一個新元素, 需要將其儲存在陣列中變數 top 所對
 * 應的位置, 然後將 top 值加 1, 讓top指向陣列中下一個空位置
 * 特別注意 ++ 操作符的位置, 它放在 this.top 的後面, 這樣新入棧的元素就被放在
 * top 的當前值對應的位置, 然後再將變數 top 的值加 1, 指向下一個位置
 * */
function push(element) {
    this.dataStore[this.top++] = element;
}

/**
 * 3. pop()
 * pop() 方法恰好與 push() 方法相反——它返回棧頂元素, 同時將變數 top 的值減 1
 * 也可以改造一下,只--this.top,不返回棧頂元素
 * */
function pop() {
    return this.dataStore[--this.top];
}

/**
 * 4. peek()
 * peek() 方法返回陣列的第 top-1 個位置的元素, 即棧頂元素
 * */
function peek() {
    return this.dataStore[this.top-1];
}

function length(){
    return this.top;
}

function clear() {
    this.top = 0;
}

/**
 * 5.測試 Stack 類的實現
 * */
var s = new Stack();
s.push("David");
s.push("Raymond");
s.push("Bryan");
print("length: " + s.length());
print(s.peek());
var popped = s.pop();
print("The popped element is: " + popped);
print(s.peek());
s.push("Cynthia");
print(s.peek());
s.clear();
print("length: " + s.length());
print(s.peek());
s.push("Clayton");
print(s.peek());

/**
 * 6.棧的應用一:數字進位制間的相互轉換
 *
 * 利用棧將一個數字從一種數制轉換成另一種數制。
 * 假設想將數字 n 轉換為以 b 為基數
 * 的數字, 實現轉換的演算法如下:
 * (1) 最高位為 n % b, 將此位壓入棧。
 * (2) 使用 n/b 代替 n。
 * (3) 重複步驟 1 和 2, 直到 n 等於 0, 且沒有餘數。
 * (4) 持續將棧內元素彈出, 直到棧為空, 依次將這些元素排列, 就得到轉換後數字的字元
 * 串形式。
 *
 * 下面就是該函式的定義, 可以將十進位制的數字轉化為二至九進位制的數字:
 * */
function mulBase(num, base) {
    var s = new Stack();
    do {
        s.push(num % base);
        num = Math.floor(num /= base);
    } while (num > 0);
    var converted = "";
    while (s.length() > 0) {
        converted += s.pop();
    }
    return converted;
}

//將數字轉換為二進位制和八進位制
function mulBase(num, base) {
    var s = new Stack();
    do {
        s.push(num % base);
        num = Math.floor(num /= base);
    } while (num > 0);
    var converted = "";
    while (s.length() > 0) {
        converted += s.pop();
    }
    return converted;
}
var num = 32;
var base = 2;
var newNum = mulBase(num, base);
print(num + " converted to base " + base + " is " + newNum);
num = 125;
base = 8;
var newNum = mulBase(num, base);
print(num + " converted to base " + base + " is " + newNum);
//輸出: 32 converted to base 2 is 100000  125 converted to base 8 is 175

// ToDo: 而二進位制轉10進位制和16進位制怎麼轉?

/**
 * 7.棧的應用二: 迴文
 * 使用棧,可以輕鬆判斷一個字串是否是迴文。 將拿到的字串的每個字元按從左至
 * 右的順序壓入棧。 當字串中的字元都入棧後, 棧內就儲存了一個反轉後的字串, 最後
 * 的字元在棧頂, 第一個字元在棧底.
 *
 * 字串完整壓入棧內後, 通過持續彈出棧中的每個字母就可以得到一個新字串, 該字元
 * 串剛好與原來的字串順序相反。 我們只需要比較這兩個字串即可, 如果它們相等, 就
 * 是一個迴文。
 * */
//下例是一個利用前面定義的 Stack 類, 判斷給定字串是否是迴文的程式。
function isPalindrome(word) {
    var s = new Stack();
    for (var i = 0; i < word.length; ++i) {
        s.push(word[i]);
    }
    var rword = "";
    while (s.length() > 0) {
        rword += s.pop();
    }
    if (word == rword) {
        return true;
    }
    else {
        return false;
    }
}
var word = "hello";
if (isPalindrome(word)) {
    print(word + " is a palindrome.");
}
else {
    print(word + " is not a palindrome.");
}
word = "racecar"
if (isPalindrome(word)) {
    print(word + " is a palindrome.");
}
else {
    print(word + " is not a palindrome.");
}

/**
 * 8.遞迴與使用棧模擬遞迴過程
 * 棧常常被用來實現程式語言, 使用棧實現遞迴即為一例
 * */
//斐波那契遞迴
function factorial(n) {
    if (n === 0) {
        return 1;
    }
    else {
        return n * factorial(n-1);
    }
}

print(factorial(5));

//使用棧模擬上面的遞迴
function fact(n) {
    var s = new Stack();
    while (n > 1) {
        s.push(n--);
    }
    var product = 1;
    while (s.length() > 0) {
        product *= s.pop();
    }
    return product;
}
print(fact(5));

六 佇列-js實現

enqueue向隊尾新增一個元素-dequeue刪除隊首元素-front讀取隊首元素-back讀取隊尾元素queueToString顯示佇列內的所有元素-e