数据结构----栈和队列阅读笔记

认识数据结构

什么是数据结构?下面是维基百科的解释

数据结构是计算机存储、组织数据的方式
数据结构意味着接口或封装:一个数据结构可被视为两个函数之间的接口,或者是由数据类型联合组成的存储内容的访问方法封装

数组是最简单的内存数据结构,下面是常见的数据结构:

数组(Array)
栈(Stack)
队列(Queue)
链表(Linked List)
树(Tree)
图(Graph)
堆(Heap)
散列表(Hash)
下面来学习学习栈和队列…

1.栈数据结构

栈是一种线性数据结构,一个栈可以对数据按照顺序进行组织和管理。

栈是一种遵循后进先出(LIFO)原则的有序集合。新添加的或待删除的元素都保存在栈的同一端,称作栈顶,另一端就叫栈底。在栈里,新元素都接近栈顶,旧元素都接近栈底。
在这里插入图片描述

2.堆栈的应用场景

关于文本编辑器的“撤消”操作:
每次将文本添加到文本编辑器事,该文本被压入栈中。其中第一次添加的文本代表栈的底部(栈底);最后一次的修改表示栈的顶部(栈顶)。如果用户希望撤销最后一次修改,则删除处于栈的顶部的那段文本,这个过程可以不断重复,一直到栈中没有更多内容,这时我们会得到一个空白文件。

3.栈的操作

3.1push(data) 添加数据

3.2pop() 删除最后添加的数据

4.栈的实现

4.1栈的属性
为了实现栈结构,我们将会创建一个名为 Stack 的构造函数。栈的每个实例都有两个属性:_size_storage

function Stack() {
    this._size = 0;
    this._storage = {};
}

this._storage 属性使栈的每一个实例都具有自己的用来存储数据的容器; this._size 属性反映了当前栈中数据的个数。如果创建了一个新的栈的实例,并且有一个数据被存入栈中,那么 this._size 的值将被增加到1。如果又有数据入栈,this._size 的值将增加到2。如果一个数据从栈中被取出,this._size 的值将会减少为1。
4.2栈的方法(操作)
普通的栈常用的有以下几个方法:

push 添加一个(或几个)新元素到栈顶
pop 溢出栈顶元素,同时返回被移除的元素
peek 返回栈顶元素,不对栈做修改
isEmpty 栈内无元素返回true,否则返回false
size 返回栈内元素个数
clear 清空栈
方法1/2: push(data)
(每一个栈的实例都具有这个方法,所以我们把它添加到栈结构的原型中)
我们对这个方法有两个要求:
每当添加数据时, 我们希望能够增加栈的大小。
每当添加数据时,我们希望能够保留它的添加顺序。

Stack.prototype.push = function(data) {
    // increases the size of our storage
    **var size = this._size++;**
 
    // assigns size as a key of storage
    // assigns data as the value of this key
    
    this._storage[size] = data;
};

我们实现push(data)方法时要包含以下逻辑:声明一个变量 size 并赋值为 this._size++。指定 size 为 this._storage 的键;并将数据赋给相应键的值。
如果我们调用push(data)方法5次,那么栈的大小将是5。第一次入栈时,将会把数据存入this._storage 中键名为1对应的空间,当第5次入栈时,将会把数据存入this._storage 中键名为5对应的空间。现在我们的数据有了顺序!
方法2/2: pop()
我们已经实现了把数据送入栈中,下一步我们要从栈中弹出(删除)数据。从栈中弹出数据并不是简单的删除数据,它只删除最后一次添加的数据。

以下是这个方法的要点:

  1. 使用栈当前的大小获得最后一次添加的数据。
  2. 删除最后一次添加的数据。
  3. 使 _this._size 计数减一。
  4. 返回刚刚删除的数据。
Stack.prototype.pop = function() {
    var size = this._size,
        deletedData;
 
    deletedData = this._storage[size];
 
    delete this._storage[size];
    this.size--;
 
    return deletedData;
};

pop()方法满足以上四个要点。首先,我们声明了两个变量:size 用来初始化栈的大小;deletedData 用来保存栈中最后一次添加的数据。第二,我们删除了最后一次添加的数据的键值对。第三,我们把栈的大小减少了1.第四,返回从栈中删除的数据。

如果我们测试当前实现的pop()方法,会发现它适用下面的案例:如果向栈内push数据,栈的大小会增加1,如果从栈中pop()数据,栈的大小会减少1!

为了处理这个用例,我们将向pop()中添加if语句。

Stack.prototype.pop = function() {
    var size = this._size,
        deletedData;
 
    if (size) {
        deletedData = this._storage[size];
 
        delete this._storage[size];
        this._size--;
 
        return deletedData;
    }
};

通过添加if语句,可以使代码在存储中有数据时才被执行。
peek() :查看栈顶元素,可以用数组长度来实现。
// 返回栈顶元素

 peek() {
    return this._items[this._items.length - 1];
  }

isEmpty()
// 判断栈是否为空,若栈为空则返回true,不为空则返回false

 isEmpty() {
    return !this._items.length;
  }

size()
// 栈元素个数

  size() {
    return this._items.length;
  }

clear()
// 清空栈

  clear() {
    this._items = [];
  }

print ()
以字符串显示栈中所有内容
*/

print () {
  console.log(items.toString());
};

4.3 栈的完整实现
我们已经实现了完整的栈结构。不管以怎样的顺序调用任何一个方法,代码都可以工作!下面使代码的最终版本:

function Stack() {
    this._size = 0;
    this._storage = {};
}
 
Stack.prototype.push = function(data) {
    var size = ++this._size;
    this._storage[size] = data;
};
 
Stack.prototype.pop = function() {
    var size = this._size,
        deletedData;
 
    if (size) {
        deletedData = this._storage[size];
 
        delete this._storage[size];
        this._size--;
 
        return deletedData;
    }
};

一下实现和上面代码上稍微有区别,但原理相同
4.4十进制转二进制的函数
原理就是输入要转换的数字,不断的除以二并取整。并且最后运用while循环,将栈中所有数字拼接成字符串输出。

function divideBy2(decNumber) {
 function Stack() {

// 用数组来模拟栈
var items = [];
this.push = function(element) {
  items.push(element);
};
this.pop = function() {
  return items.pop();
};
this.isAmpty = function() {
  return items.length === 0
};
}
var remStack = new Stack(),
  rem,
  binaryString = '';

while (decNumber > 0) {
  rem = Math.floor(decNumber % 2);
  console.log(rem);
  remStack.push(rem);
  console.log(remStack);
  decNumber = Math.floor(decNumber / 2);
  console.log(decNumber);
}

while (!remStack.isAmpty()) {
  binaryString += remStack.pop().toString();
}

return binaryString;
};
console.log(divideBy2(8));

在这里插入图片描述

5、从栈到队列

当我们想要按顺序添加数据或删除数据时,可以使用栈结构。根据它的定义,栈可以只删除最近添加的数据。如果想要删除最早的数据该怎么办呢?这时我们希望使用名为queue的数据结构。

6、队列

与栈类似,队列也是一个线性数据结构。与栈不同的是,队列只删除最先添加的数据。

队列是遵循先进先出(FIFO,也称为先来先服务)原则的一组有序的项。队列在尾部添加新元素,并从顶部移除元素。最新添加的元素必须排在队列的末尾.
在这里插入图片描述

队列在web中的一个更实际的例子是Web浏览器的事件循环。当触发不同事件时,例如单击某个按钮,点击事件将被添加到事件循环队列中,并按照它们进入队列的顺序进行处理。

现在我们具有了队列的概念,接下来就要定义它的操作,队列的操作和栈非常相似。区别就在被删除的数据在什么地方。

enqueue(data) 将数据添加到队列中。

dequeue 删除最早加入队列的数据。

7、队列的方法

现在我们将创建队列会用到的三个方法:size(), enqueue(data), 和 dequeue(data)。
方法1/3:size( )
这个方法有两个作用:

  1. 返回当前队列的长度。
  2. 保持队列中键的正确范围。
    Queue.prototype.size = function() {
    return this._newestIndex - this._oldestIndex;
    };

实现 size() 可能显得微不足道,但你会很快发现并不是这样的。为了理解其原因,我们必须快速重新审视 size() 在栈结构中的实现。

回想一下栈的概念模型,假设我们把5个盘子添加到一个栈上。栈的大小是5,每个盘子都有一个数字,从1(第一个添加的盘子)到5(最后一个添加的盘子)。如果我们取走三个盘子,就只剩下两个盘子。我们可以简单地用5减去3,得到正确的大小,也就是2。这是关于栈大小最重要的一点:当前大小相当于从栈顶部的盘子(2)到栈中其他盘子(1)的计数。换句话说,键的范围总是从当前大小到1之间。

现在,让我们将栈大小的实现应用到队列中。假设有五个顾客从我们的售票系统中取到了票。第一个顾客有一张显示数字1的票,第五个客户有一张显示数字5的票。现在有了一个队列,拿着第一张票的第一位顾客。

假设第一个客户接受了服务,这张票会从队列中被移除。与栈类似,我们可以通过从5减去1来获得队列的正确大小。那么服务队列中还有4张票。现在出现了一个问题:队列的大小不能对应正确的票号。如果我们从五减去一个,得到大小是4,但是不能使用4来确定当前队列中剩余票的编号范围。我们并不能确定队列中票号的顺序到底是1到4还是2到5。

这就是 oldestIndex 和 newestIndex 这两个属性 在队列中的用途。当oldestIndex 和 newestIndex相同时,队列是空的。

属性 _newestindex可以告诉我们被分配在队列中的最大键,
属性 _oldestindex 可以告诉我们最先进入队列中的键。

方法2/3:enqueue(data):向队列尾部添加几个项
对于 enqueue 方法,有两个功能:
1.使用_newestIndex 的值作为 this._storage 的键,并使用要添加的数据作为该键的值。
2._newestIndex 的值增加1。

基于这两个功能,我们将编写 enqueue(data) 方法的代码:

Queue.prototype.enqueue = function(data) {
this._storage[this._newestIndex] = data;
this._newestIndex++;
};
该方法的主体只有两行代码。 在第一行,用 this._newestIndex 为this._storage 创建一个新的键,并为其分配数据。 this._newestIndex 始终从1开始。在第二行代码中,我们将 this._newestIndex 的值增加1,将其更新为2。
方法2/3:dequeue( )
以下是此方法的两个功能点:

1.移除队列的第一项(也就是排在最前面的项)
2.属性 _oldestIndex 加1。

Queue.prototype.dequeue = function() {
var oldestIndex = this._oldestIndex,
deletedData = this._storage[oldestIndex];

delete this._storage[oldestIndex];
this._oldestIndex++;

return deletedData;

};
在 dequeue( )的代码中,我们声明两个变量。 第一个变量 oldestIndex 给 this._oldestIndex 赋值。第二个变量 deletedData 被赋予 this._storage[oldestIndex] 的值。

下一步,删除队列中最早的索引。之后将 this._oldestIndex 的值加1。最后返回刚刚被删除的数据。

与栈的 pop() 方法第一次实现中出现的问题类似,dequeue() 在队列中没有数据的情况下不应该被执行。我们需要一些代码来处理这种情况。

Queue.prototype.dequeue = function() {
    var oldestIndex = this._oldestIndex,
        newestIndex = this._newestIndex,
        deletedData;
 
    if (oldestIndex !== newestIndex) {
        deletedData = this._storage[oldestIndex];
        delete this._storage[oldestIndex];
        this._oldestIndex++;
 
        return deletedData;
    }
};

每当 oldestIndex 和 newestIndex 的值不相等时,我们就执行前面的逻辑。
队列的完整实现代码
到此为止,我们实现了一个完整的队列结构的逻辑。下面是全部代码。

function Queue() {
    this._oldestIndex = 1;
    this._newestIndex = 1;
    this._storage = {};
}
 
Queue.prototype.size = function() {
    return this._newestIndex - this._oldestIndex;
};
 
Queue.prototype.enqueue = function(data) {
    this._storage[this._newestIndex] = data;
    this._newestIndex++;
};
 
Queue.prototype.dequeue = function() {
    var oldestIndex = this._oldestIndex,
        newestIndex = this._newestIndex,
        deletedData;
 
    if (oldestIndex !== newestIndex) {
        deletedData = this._storage[oldestIndex];
        delete this._storage[oldestIndex];
        this._oldestIndex++;
 
        return deletedData;
    }
};

普通的队列常用的有以下几个方法:

enqueue 向队列尾部添加一个(或多个)新的项
dequeue 移除队列的第一(即排在队列最前面的)项,并返回被移除的元素
head 返回队列第一个元素,队列不做任何变动
tail 返回队列最后一个元素,队列不做任何变动
isEmpty 队列内无元素返回true,否则返回false
size 返回队列内元素个数
clear 清空队列

class Queue {
  constructor() {
    this._items = [];
  }

  enqueue(item) {
    this._items.push(item);
  }

  dequeue() {
    return this._items.shift();
  }

  head() {
    return this._items[0];
  }

  tail() {
    return this._items[this._items.length - 1];
  }

  isEmpty() {
    return !this._items.length;
  }

  size() {
    return this._items.length;
  }

  clear() {
    this._items = [];
  }
}

实际应用:
击鼓传花的小游戏。
原理就是循环到相应位置时,队列弹出那个元素。最后留下的就是赢家。

/**
 * 击鼓传花的小游戏
 * @param  {Array}  nameList 参与人员列表
 * @param  {Number} num      在循环中要被弹出的位置
 * @return {String}          返回赢家(也就是最后活下来的那个)
 */
 function hotPotato(nameList, num) {
  function Queue() {
  var items = [];
  this.enqueue = function(ele) {
  items.push(ele);
};
this.dequeue = function() {
  return items.shift()
};
// this.tail()=function(){
//     return items[items.length - 1];
//   }
this.size=function() {
    return items.length;
  };
  this.print =function() {
  console.log(items.toString());
};

}
  var queue = new Queue();

  for (var i = 0; i < nameList.length; i++) {
    queue.enqueue(nameList[i]);
  }
  // console.log(1,queue);
  queue.print();
  var eliminated = '';

  while (queue.size() > 1) {
    for (var i = 0; i < num; i++) {
      queue.enqueue(queue.dequeue());
    }
    queue.print();
    // console.log(2,queue);
    eliminated = queue.dequeue();
    queue.print();
    console.log(eliminated + " Get out!")
  }

  return queue.dequeue()
}
console.log('jieguo',hotPotato(['美','丽','媛','你','好'],3));

与栈类比,栈仅能操作其头部,队列则首尾均能操作,但仅能在头部出尾部进。当然,也印证了上面的话:栈和队列并不关心其内部元素细节,也无法直接操作非首尾元素。
总结:
栈和队列:栈和队列都是按照顺序存储数据,栈只能对栈顶进行操作,队列只能在尾部添加在头部弹出;且它们不关心内部的元素状态。
参考:
参考文献1
参考文献2
参考文献3

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值