JavaScript中的算法之美——棧、隊列、表

html

最近花了比較多的時間來學習前端的知識,在這個期間也看到了不少的優秀的文章,其中Aaron可能在這個算法方面算是個人啓蒙,在此衷心感謝Aaron的付出和奉獻,同時本身也會堅決的走前人這種無私奉獻的分享精神,爲編程愛好者提供一些優秀的文章前端

JavaScript中的棧實現算法

要說到棧,這裏咱們先將一下什麼是棧,棧就是一個在計算機中特殊的數據列表,棧的特色是先進的數據最後纔會被彈出來編程

在JavaScript中提供了可操做的方法, 入棧push,出棧pop,最早進入要最後纔會彈出數組

棧的實現原理圖大體以下,咱們能夠將棧理解爲一個抽象的模型數據結構

 接下來咱們就來說解一下JavaScript的代碼實現函數

一、首先咱們要建立一個棧的類學習

二、通常對於數據結構咱們是要實現增、刪、改、查的功能。可是對於棧來講,改這個功能是沒必要要實現的,由於棧因爲是連續的且後進先出等因素,因此棧是無法修改的,也就是要實現增、刪、查這幾個功能,還要實現清空、獲取棧的長度這兩個功能,同時還要引入棧頂這個參數來做爲棧的變化的參考標準優化

空棧的實現this

第一種方法是直接將直接將一個function嵌套到另一個function中,也就是第一個function至關於類,第二個function至關於方法,再結合深刻學習JavaScript(二)中的知識,咱們能夠構建一個有public,private概念的棧

function Stack(){
    this.dataStore = []
    this.top    = 0;
    this.push   = push;
    this.pop    = pop;
    this.peek   = peek;
    this.length = length;
    return{
        top:top,
        push:push,
        pop:pop,
        peek:peek,
        length:length
    }
}

function push(element){
    this.dataStore[this.top++] = element;
}

function peek(element){
    return this.dataStore[this.top-1];
}

function pop(){
    return this.dataStore[--this.top];
}

function clear(){
    this.top = 0
}

function length(){
    return this.top
}

要注意在這裏面爲了保證主函數的簡潔,因此將其餘的一些方法的實現封裝在函數的外部而後再去調用

第二種方法是經過繼承的方式來實現的

function Stack(){
    this.dataStore = []
    this.top    = 0;

}

Stack.prototype.push=function(element){
    this.dataStore[this.top++] = element;
}

Stack.prototype.peek=function (element){
    return this.dataStore[this.top-1];
}

Stack.prototype.pop=function (){
    return this.dataStore[--this.top];
}

Stack.prototype.clear=function (){
    this.top = 0
}

Stack.prototype.length=function (){
    return this.top
}

這種方法無法實現像第一種方法同樣能夠保證方法的封閉性

因爲棧的特性是先進後出,因此利用這個特性咱們能夠對數組來進行倒序相關的操做,比較典型的是迴文

迴文

迴文指的是不管是從後往前仍是從前日後獲得的結構都是相同的

下面咱們就來經過棧實現判斷字符串是否爲迴文

完整的代碼以下:

function Stack(){
    this.dataStore = []
    this.top    = 0;
    this.push   = push
    this.pop    = pop
    this.peek   = peek
    this.length = length;
}

function push(element){
    this.dataStore[this.top++] = element;
}

function peek(element){
    return this.dataStore[this.top-1];
}

function pop(){
    return this.dataStore[--this.top];
}

function clear(){
    this.top = 0
}

function length(){
    return this.top
}
function isPalindrome(word){
    var s=new Stack();
    for(var i=0,len=word.length;i<len;i++){
        s.push(word[i]);
    }
    var rstring="";
    while(s.length()>0){
        rstring+=s.pop();
    }
    if(rstring===word){
        return true;
    }else{
        return false;
    }
}
isPalindrome("123");  //false
isPalindrome("12321");  //true

JavaScript中的隊列實現

 隊列是隻容許在一端進行插入操做,另外一個進行刪除操做的線性表,隊列是一種先進先出(First-In-First-Out,FIFO)的數據結構

 隊列的實現思路跟棧的實現思路基本上是同樣的,因此咱們在這裏就直接貼出代碼就好了

function Queue() {
    this.dataStore = [];
    this.enqueue   = enqueue;
    this.dequeue   = dequeue;
    this.first     = first;
    this.end       = end;
    this.toString  = toString;
    this.empty     = empty;
}

///////////////////////////
// enqueue()方法向隊尾添加一個元素: //
///////////////////////////
function enqueue(element) {
    this.dataStore.push(element);
}

/////////////////////////
// dequeue()方法刪除隊首的元素: //
/////////////////////////
function dequeue() {
    return this.dataStore.shift();
}

/////////////////////////
// 可使用以下方法讀取隊首和隊尾的元素: //
/////////////////////////
function first() {
    return this.dataStore[0];
}

function end() {
    return this.dataStore[this.dataStore.length - 1];
}

/////////////////////////////
// toString()方法顯示隊列內的全部元素 //
/////////////////////////////
function toString() {
    var retStr = "";
    for (var i = 0; i < this.dataStore.length; ++i) {
        retStr += this.dataStore[i] + "\n";
    }
    return retStr;
}

////////////////////////
// 須要一個方法判斷隊列是否爲空 //
////////////////////////
function empty() {
    if (this.dataStore.length == 0) {
        return true;
    } else {
        return false;
    }
}

var q = new Queue();
q.enqueue("Aaron1");
q.enqueue("Aaron2");
q.enqueue("Aaron3");

console.log("隊列頭: " + q.first());   //("Aaron1");
console.log("隊列尾: " + q.end());  //("Aaron3");

JavaScript中的表結構實現

 雖然在JavaScript中的棧和隊列都是基於數組來實現的,因此在刪除元素的時候,都會涉及到對其餘元素的影響,可是不管是什麼語言,隊列和棧都有一個十分使人討厭的特色,不能在中間的某個位置上添加元素,這個時候咱們就須要用到表結構來解決問題了

 

鏈表通常有,單鏈表、靜態鏈表、循環鏈表、雙向鏈表

單鏈表:就是很單一的向下傳遞,每個節點只記錄下一個節點的信息,就跟無間道中的梁朝偉同樣作臥底都是經過中間人上線與下線聯繫,一旦中間人斷了,那麼就沒法證實本身的身份了,因此片尾有一句話:"我是好人,誰知道呢?」

靜態鏈表:就是用數組描述的鏈表。也就是數組中每個下表都是一個「節」包含了數據與指向

循環鏈表:因爲單鏈表的只會日後方傳遞,因此到達尾部的時候,要回溯到首部會很是麻煩,因此把尾部節的鏈與頭鏈接起來造成循環

雙向鏈表:針對單鏈表的優化,讓每個節都能知道先後是誰,因此除了後指針域還會存在一個前指針域,這樣提升了查找的效率,不過帶來了一些在設計上的複雜度,整體來講就是空間換時間了

 單鏈表,單鏈表的實現,咱們能夠當作是一個對象(包括數據+地址),而後把這一個對象指向另一個對象(也就是把上一個對象傳遞給下一個對象),這樣重複下去,也就實現了咱們所說的單鏈表,因爲地址的定義是指向下一個數據的地址,可是在未添加數據的時候,咱們是不知道下一個數據地址的, 因此爲了克服這個問題咱們能夠換個思路,雖然是這樣定義的,可是若是咱們從後往上看,一級一級的指向上一個地址,也就是把當前鏈賦予下級。好了,咱們來按照這個思路來實現單鏈表

 

function LinkList(){
    var data={},
        prev=null;
    return{
        add:function(val){
        prev={
            data:val,
            previous:prev||null
        }
        }
    }
}
var link=LinkList();
link.add("a1");
link.add("a2");
link.add("a3");

 

插入節點

上面說了鏈表的結構對於插入數據比較方便,因此咱們就來介紹一下節點的插入,節點的插入思路是:先建立一個孤立的節點,而後是遍歷鏈表中是否存在咱們所須要的data,若是沒有就在最後面插入,若是有的話就在查找到的節點後面插入,在這裏咱們應該關注的是鏈表的結構,這裏咱們生成的鏈表的結構在思想上有點像遞歸思想

//建立節
function createNode(data) {
    this.data = data;
    this.next = null;
}
//初始化頭部節
//從headNode開始造成一條鏈條
//經過next銜接
var headNode = new createNode("head");

//在鏈表中找到對應的節
var findNode = function createFindNode(currNode) {
    return function(key){
        //循環找到執行的節,若是沒有返回自己
        while (currNode.data != key) {
            currNode = currNode.next;
        }
        return currNode;                
    }
}(headNode);

//插入一個新節
this.insert = function(data, key) {
    //建立一個新節
    var newNode = new createNode(data);
    //在鏈條中找到對應的數據節
    //而後把新加入的掛進去
    var current = findNode(key);
    //插入新的接,更改引用關係
    //1:a-b-c-d
    //2:a-b-n-c-d
    newNode.next = current.next;
    current.next = newNode;
};

 

其中最爲關鍵的代碼以下所示,這一段代碼是我看過的最爲精闢的代碼,下面咱們就來分析一下

//在鏈表中找到對應的節
var findNode = function createFindNode(currNode) {
    return function(key){
        //循環找到執行的節,若是沒有返回自己
        while (currNode.data != key) {
            currNode = currNode.next;
        }
        return currNode;                
    }
}(headNode);

 

其中咱們爲了肯定鏈表的開頭,咱們先定義了一個headNode的節點,而後是將一個key傳進來,注意的是傳進來的Key會被初始化爲節點,由於方法中是有自執行的,且已經傳入了headNode節點,因此傳入的格式也被肯定了,這個時候currNode會等於headNode+currNode

如圖所示:

爲何爲這樣?由於headNode是一個全局變量,能夠用來儲存每次添加的節點,然而因爲currNode也是一個全局變量而且經過currNode=currNode.next;因此會獲取上一個節點的的位置,因此不論插入第幾個對象都只循環兩次,一次是上一個對象,另外一次是這個對象,這個調試一下就清楚了

 文章在這裏特別感謝:Aaron

相關文章
相關標籤/搜索