栈:
栈是一个线性结构,在计算机中时一个相当常见的数据结构
栈的特点是只能在某一端添加或删除,遵循先进先出的原则
每种数据结构都可以用很多种方式来实现,其实可以把栈看成是数组的一个子集,所以这里使用数组来实现
class Stack{
constructor(){
this.stack=[];
}
push(item){
this,stack.push(item);_
}
pop(){
this.stack.pop();
}
peek(){
return this,stack[this.getCount()-1];
}
getCount(){
return this.stack.length;
}
isEmpty(){
return this.getCount===0;
}
}
应用:
由于只包含字符的字符串’(’,’)’,’{’,’}’,’[‘和’]’,确定输入字符串是有效的。
如果输入字符串有效:
1.必须使用相同类型的括号关闭左括号。
2.必须以正确的顺序关闭打开括号。
请注意,空字符串也被视为有效。
let isValid=function(s){
let map={
'(':-1,
')':1,
'[':-2,
']':2,
'{':-3,
'}':3
}
let stack=[];
for(let i=0;i<s.length;i++){
if(map[s[i]]<0){
stack.push(s[i]);
}else{
let last=stack.pop();
if(map[last]+map[s[i]]!=0) return false
}
}
if(stack.length>0) return false;
return true
};
队列:
队列是一个线性结构,特点是在某一段添加数据,在另一端删除数据,遵循先先进先出的原则。
实现:
单链队列:
class Queue{
constructor(){
this.queue=[];
}
enQueue(item){
this.queue.push(item);
}
deQueue(){
return this.queue.shift();
}
getHeader(){
return this.queue[0];
}
getLength(){
return this.queue.length;
}
isEmpty(){
return this.getLength()===0
}
}
因为单链队列在出栈操作的时候需要O(n)的时间复杂度,所以引入了循环队列。循环队列的出对操作平均是O(1)的时间复杂度。
循环队列:
class SqQueue{
constructor(length){
this.queue=new Array(length+1);
//队头
this.first=0;
//队尾
this.last=0;
//当前队列的大小
this.size=0;
}
enQueue(item){
//判断队尾+1是否为队头
//如果是就代表需要扩容数据
//%this.quque.length是为了防止数组越界
if(this.first===(this.last+1)%this.queue.length){
this.resize(this.getLength()*2+1);
}
this.queue[this.last]=item;
this.size++:
this.last=(this.last+1)%this.queue.length;
}
dequeue(){
if(this.isEmpty()){
throw Error('Queue is empty');
}
let r=this.queue[this.first];
this.queue[this.first]=null;
this.first=(this.first+1)%this.queue.length;
this.size--;
//判断当前队列的大小是否过小
//为了保证不浪费空间,在队列空间等于总长度四分之一时,
//且不为2时缩小总长度为当前的一半
if(this.size===this.getLength()/4&&this.getLength()/2!==0){
this.resize(this.getLength()/2);
}
return r;
}
getHeader(){
if(this.isEmpty()){
throw Error('Queue is empty');
}
return this.queue[this.first];
}
getLength(){
return this.queue.length-1;
}
isEmpty(){
return this.first===this.last;
}
resize(length){
let q=new Array(length);
for(let i=0;i<length;i++){
q[i]=this.queue[(i+this.first)%this.queue.length]
}
this.queue=q;
this.first=0;
this.last=this.size;
}
}
链表:
链表是一个线性结构,同时也是一个天然的递归结构。链表结构可以充分利用计算机内存空间,实现灵活的内存动态管理。但是链表失去了数组随机读取的优点,同时链表由于增加了节点的指针域,空间开销比较大。
实现:
单向链表:
class Node{
constructor(v,next){
this.value=v;
this.next=next;
}
}
class LinkList{
constructor(){
//链表长度
this.size=0;
this.dummyNode=newNode(null,null);
}
find(header,index,currentIndex){
if(inedx===currentIndex) return header;
return this.find(header.next,index,currentIndex+1);
}
addNode(v,index){
this.checkIndex(index);
let prev=this.find(this.dummyNode,index,0);
//当往链表末尾插入时,prev.next为空
//其他情况时,因为要插入节点,
//所以插入的节点的next应该是prev.next
//然后设置prev.next为插入的节点
prev.next=next Node(v,prev.next);
this.size++;
return prev.next;
}
insertNode(v,index){
return this.addNode(v,index);
}
addToFirst(v){
return this.addNode(v,0);
}
addToLast(v){
return this.addNode(v,this.size);
}
removeNode(index,isLast){
this.checkIndex(index);
index=isLast?last-1:index;
let prev=this.find(this.dummyNode,index,0);
let node=prev.next;
prev.next=node.next;
node.next=null;
this.size--;
return node;
}
removeFirstNode(){
return this.removeNode(0);
}
removelastNode(){
return this.removeNode(this.size,0);
}
checkIndex(index){
if(index<0||index>this.size) throw Error('Index error');
}
getNode(index){
this.checkIndex(index);
if(this.isEmpty()) return;
return this.find(this.dummyNode,index,0).index
}
isEmpty(){
return this.size===0;
}
getSize(){
return this.size;
}
}
树
二叉树:
树拥有很多结构,二叉树是树种最常用的结构,同时也是一个天然的递归结构。
二叉树拥有一个根节点,每个节点至多拥有两个子节点,分别为:左节点和右节点。树的最底部节点称之为叶节点,当一颗树的叶数量为满时,该树可以称为满二叉树。
二分搜索树:
二分搜索树也是二叉树,拥有二叉树的特征。但是区别在于二叉搜索树每个节点的值都比他的左子树的值大,比他右子树上的值小。
这种存储方式很适合数据搜索。如下图所示,当需要查找6的时候,因为需要查找的值比根节点的值大,所以只需要在根节点的走字数上寻找,大大提高了搜索效率。
实现:
class Node{
constructor(value){
this.value=value;
this.left=null;
this.right=null;
}
}
class BST{
constructor(){
this.root=null;
this.size=0;
}
getSize(){
return this.size;
}
isEmpty(){
return this.size===0;
}
addNode(v){
this.root=this._addChild(this.root,v);
}
//添加节点时需要比较添加的节点值和当前
//节点值的大小
_addChild(node,v){
if(!node){
this.size++;
return new Node(v);
}
if(node.value>v){
node.left=this._addChild(node.left,v);
}else{
node.right=this._addChild(node.right,v);
}
return node;
}
}
以上是最基本的二叉搜索树的实现,接下来实现树的遍历
对于树的遍历来说,有三种遍历方式,分别为先序遍历、中序遍历、后序遍历。三种遍历的区别在于何时访问节点。在遍历过程中,每个节点都会遍历三次,分别遍历到自己,遍历左子树和遍历右子树。如果实现先序遍历,那么只需要第一次遍历到节点时进行操作即可。
//先序遍历可用于打印树的结构
//先序遍历先访问根节点,然后访问左节点,最后访问右节点
preTraversal(){
this._pre(this.root);
}
_pre(node){
if(node){
console.log(node.left);
this._pre(node.left);
this._pre(node.right);
}
}
//中序遍历可用于排序
//对于BST来说中序遍历可以实现一次遍历就得到有序的值
//中序遍历表示先访问左节点,然后访问根节点,最后访问右节点
midTraversal(){
this._mid(this.root);
}
_mid(node){
if(node){
this._mid(node.left);
console.log(value);
this._mid(node.right);
}
}
//后序遍历可用于先操作子节点再操作符节点的场景
//后序遍历表示先访问左节点,然后访问右节点,最后访问根节点。
backTraversal(){
this._back(this.root);
}
_back(node){
if(node){
this._back(node.left);
this._back(node.right);
console.log(node.value);
}
}
以上的这几种遍历都可以称之为深度遍历,对应的还有种遍历叫广度遍历,也就是一层层的遍历。对于广度遍历来说,我们需要利用之前讲过的队列结构来完成。
breadthTraversal(){
if(!this.root) return null;
let q=new Queue();
q.enQueue(this.root);
while(!q.isEmpty()){
let n=q.deQueue();
console.log(n.value);
if(n.left) q.enQueue(n.left);
if(n.right) q.enQueue(n.right);
}
}
接下来先介绍如何在树中寻找最小值或最大值,因为二分搜索树的特性,所以最小值一定在根的最左边,最大值相反
getMin(){
return this._getMin(this.root).value;
}
_getMin(node){
if(!node.left) return node;
return this._getMin(node.left);
}
getMax(){
return this._getMax(this.root).value;
}
_getMax(node){
if(!node.right) return node;
return this._getMax(node.right);
}
转载连接:https://juejin.im/book/5bdc715fe51d454e755f75ef/section/5bdc723a6fb9a049c43d1843