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

相关文章
相关标签/搜索