JavaScript数据结构与算法基础笔记
Posted 十九万里
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JavaScript数据结构与算法基础笔记相关的知识,希望对你有一定的参考价值。
1、基础数据结构
1.1数据结构种类
数组
栈
队列和双端队列
链表
集合
字典和散列表
递归
树
二叉堆和堆排序
在写每个知识点的时候 我自己总结的方式是按照定义>javascript实现方式>对应的方法>算法实现的结构去写的 后面有想法在继续补充
1.2 数组
1.2.1 数组定义
js数组其实就是API的调用 是一种最简单的内存数据结构 数组存储一系列同一种数据类型的值
注:javascript中数组可以保存不同类型的值 但是一般不推荐哈
1.2.2 数组创建
在javascript中有两种创建数组的方式
1.使用Array构造函数
let shuzu=new Array();
注意:括号里面参数可以有参数,若为一个数字,表示该数组的长度,如果为多个数字或者一个(多个)非数字表示的是传递数组中应该包含的值。
2.使用数组字面量
let shuzu=[];
1.2.3 数组方法
添加元素 (首尾)
删除元素(首尾)
1、在数组末尾添加元素
使用push方法
numbers.push(11);
原生的方法:
numbers[numbers.length] = 10;
在Javascript中,数组是可以修改的对象,如果要添加元素,会动态生长,所以直接赋值给数组最后一个空位即可
2、在数组开头插入元素
使用unshift方法
numbers.unshift(10);
3从数组末尾删除元素
使用pop方法;
numbers.pop();
4、数组开头删除元素
使用shif方法
number.shif();
5、任意位置添加或者删除元素
使用splice方法:
splice接受多个参数
number.splice(4.,0,6,4)
第一个参数标识要删除或者插入的元素索引值
第二个参数是删除元素的个数,这个例子我们要添加元素所以第二个删除元素的个数为0.
第三个参数往后,就是我们 要添加到数组里面的值
1.2.4 数组的扩展
二维数组本质上是以数组作为数组元素的数组,即“数组的数组”,
类型说明符 数组名[常量表达式][常量表达式]。二维数组又称为矩阵,
行列数相等的矩阵称为方阵。对称矩阵a[i][j] = a[j][i],
对角矩阵:n阶方阵主对角线外都是零元素
var arr = [[1,2],[‘a’,‘b’]];
console.log(arr[1][0]); //a 第2列第1行所在的元素
1.2.5 javascript常用的数组方法
1、concat(),连接两个或者更多的数组,并返回一个新的数组**
2、ES6:copyWithin(),从数组的指定位置复制元素到数组的指定位置。语法:array.copyWithin(target, start, end)
3、ES6:entries(),返回数组的迭代对象
4、every(),检测数组所有元素是否都符合指定条件,接收一个函数作为参数,用于检测数组中的元素
5、ES6:fill(),将一个固定的值替换数组里面的元素。语法:array.fill(value, start, end)
6、filter(),返回一个数组,里面包含符合条件的元素
7、find(),返回数组中符合条件的第一个元素
8、findIndex(),返回数组中符合条件的第一个元素的所在位置
9、forEach(),数组每个元素都执行一次回调函数
10、ES6:from(),将伪数组转换为真正的数组
11、ES6:includes(),判断数组中是否函数指定的值,如有返回true,否则返回false
12、indexOf(),返回数组中指定元素的位置,如果数组中没有指定的元素则返回-1
13、isArray(),判断一个对象是否为数组,是 返回true,不是 返回false
15、ES6:keys(),从数组创建一个包含数组键的可迭代对象
16、lastIndexOf(),返回指定的元素在数组中最后出现的位置,在数组的后面开始搜索
17、map(),返回一个新数组,数组中的元素是原始数组的元素调用函数之后处理的值
18、pop(),删除数组的最后一个元素,并返回删除的元素
19、push(),在数组后面添加新元素,并返回数组新的长度
20、reduce(),将元素的值计算为一个值,从左到右
21、reduceRight(),将元素的值计算为一个值,从右到左
22、reverse(),反转数组元素的排列顺序
23、shift(),删除并返回数组第一个元素
24、unshift(),向数组的最前面添加新元素,并返回新的数组长度
25、slice(),返回指定的数组元素,第一个参数是开始的位置,第二个是结束位置(不包含结束位置的元素)
26、splice(),添加和删除数组中的元素,第一个参数是要删除的元素的开始位置,第二个参数是要删除的元素的个数。第三个以及以后的参数都是添加到数组中的新元素
27、some(),检测数组中是否含有指定的元素,有的话就返回true,没有就返回false
28、sort(),对数组进行排序,可以接收一个比较函数
29、toString(),将数组转为字符串,并返回结果
30、valueOf(),返回数组对象的原始值
常用的我已经加粗了!!!
1.2.6 类型数组
由于javascript与c和Java等语言不同,javascript数组不是强类型。可以存储任意类型数据
那么如何让javascript的数组也存储单一的数据类型呢?
这个就用到类型数组。
具体语法:
let myArray = new TypedArray(length)
//在实际使用中 把TypedArray换成下面列表中需要的类型
1.2.7TypeScript中的数组
Typescript中最简单的方法是使用「类型 + 方括号」来表示数组
let fibonacci: number[] = [1, 1, 2, 3, 5];
数组的项中不允许出现其他的类型:
在使用数组方法时,对应增删改查的元素类型必须一致,否则会报错。
详解文章:
TypeScript 数组Array操作
1.3 栈
1.3.1 概述
栈是一种遵从先进后出(LIFO)原则的有序集合。
新添加或待删除的元素都保存在栈的末尾,称作栈顶,另一端就叫栈底。
在栈里,新元素都靠近栈顶,旧元素都接近栈底
用日常生活中的例子:
栈就像我们的衣服口袋,先放进去的东西后取出来,后放进去的东西能先取出来
栈被用在编程语言的编译器和内存中用于保存变量,方法调用等等,也被用于浏览器历史记录(浏览器的返回按钮)
1.3.2 基于javascript数组创建栈
在javascript中用数组创建一个栈一共7个步骤:
1、先声明一个stack类,用于存放栈
2、向栈中使用push添加元素(只能从栈顶添加)
3、从栈中使用pop方法移除元素(从栈顶移除)
4.查看栈顶元素(peek方法)
5.检查栈是否为空 方法为isEmpty 如果栈为空返回true 否则返回false
6.使用clear方法清空栈元素
7.可以开始使用stack类
经过上面的六步,stack已近具有了栈的特点。
代码实现:
//创建一个基于数组单位栈
class stack{
constructor{
this.items = [];
}
}
//向栈中使用push方法添加元素
push () {
this.items.push();
}
// 使用pop方法从栈顶移除元素
pop(){
reyurn this.items.pop();
}
//经过上面这两个步骤,基本实现了栈的先进后出的原则
//查看栈顶元素 (length -1)
peek{
return this.items{this.items.length - 1};
}
//使用isEmpty检查栈是否为空
isEmpty(){
return this.items.length === 0;
}
//完成上面的步骤后,使用clear方法清除栈里面的所有元素
clear(){
return this.items.length;
}
//至此。栈已经完成了
就可以使用栈的方法来对栈进行操作了
1.3.3 基于javascript对象创建栈
前面我们已经学会使用数组来创建栈了,但在实际的应用中,处理大量数据的时候,需要评估如何使用数据是最高效的,使用数组的时间复杂度为0(n),n代表数组的长度,因为数组是一个有序集合,为了保证元素排序,会占用更多的内存我们需要迭代整个数组直到寻找到目标元素。
由此引出了对象创建栈
对象能直接获取元素,占用较少的内存空间,而且能够按照我们的要求排列数据。
操作步骤一共六步:
1、声明一个stack类
2、向栈中插入元素
3、验证栈是否为空和它的大小
4、从栈中弹出元素
5、查看栈值并将栈清空
6、创建tostring方法
代码实现:
//首先声明一个stack类
class stack {
constructor () {
this.count = 0 ; //使用count来记录栈的大小,也能帮助我们删除和添加元素
this.items = {};
}
}
//向栈中加入元素
push(element) {
this.items[this.count ] = element;
this.count ++ ;
}
//在js中,对象是键值对的形式表现出来,我们使用count作为items对象的键名,插入的元素则是它的值,插入元素后,我们把count++,以便于下一个变量值插入
//count属性也表示栈的大小,我们可以返回count的size来计算栈的大小
size(){
return this.count;
}
//验证栈是否为空
isEmpty(){
return this.count === 0;
}
//查看栈顶的值并将栈清空
peek{
if (this.isEmpty()){
return unerfined;
}
return this.items{this.items.length - 1};
}
//清空栈把值变为构造函数的初始值就ok
clear (){
this.items = {};
this.count = 0;
}
//也可以使用另外一种方法:
while (!this.isEmpty()){
this.pop();
}
//创建tostring方法
在前面的数组中没有提string方法,是因为数组内置就有tostring属性,使用对象的时候,就需要我们手动去穿件了
toString () {
if (this.isEmpty()){
return '';
}
let objStru=ing = '${this.items[0]}';
for (let i = 1 ; i < this.count; i++ ){
objString = '${objstring},${this.items[i]}';
}
return objstring ;
}
//如果栈是空的,返回空值就ok了
//如果不是空的。底部第一个元素作为字符串的初始值,然后迭代整个栈
至此,一个基于对象创建的栈就完成了
1.3.4 保护数据结构内部元素
在我们创建一个栈后,别的同事也可能要使用,我们想保护内部的元素,只允许改变我们暴露的元素,这个时候就提及到了保护数据结构内部元素:
主要有三种方法:
1、下划线命名约定
下划线命名定义
一部分开发者喜欢在javascript中使用下划线命名约定来标记一个属性为私有属性
下划线命名约定实在属性名称之间加一个下划线-,不过这种方法只是一种约定,不能保护数据,看不懂的程序员就gg了
代码实现:
class stack {
constructor () {
this._count = 0;
this._items = {};
}
}
2、symbol实现类
使用symbol实现类
在es6中规定,symbol是不可变的,可以用作对象的属性
代码实现:
cosnt_items = symbol('stacItems');
class stack {
constoructor () {
this[_items] = [];
}
//栈的方法
}
3、使用weakMap实现类
使用weakMap实现类
weakMap就能确保属性私有,weakMap是键值对的存在,键是对象,值是属性。
1.3.5 javascript中用栈解决实际问题
使用栈实现阶乘的递归
使用栈来模拟5!的过程,首先将数字5到1压入栈,然后使用一个循环将数字挨个弹出并连乘
代码实现:
1 function fact(num) {
2 var stack=new Stack;
3 while(num>0){
4 stack.push(num--);
5 }
6 var sum=1;
7 while(stack.length>0){
8 sum*=stack.pop;
9 }
10 return sum;
11 }
12
13 console.log(fact(5)) //120
1.3.6 合法括号
下面的字符串中包含小括号,请编写一个函数判断字符串中的括号是否合法,所谓合法,就是括号成对出现
sdf(ds(ew(we)re)rwqw)qwrwq 合法
(sd(qwqe)sd(sd)) 合法
()()sd()(sd()dw))( 不合法
思路分析
括号存在嵌套关系,也存在并列关系,如果使用数组来存储这些括号,然后再想办法一对一的抵消掉,似乎可行。但是我们无法判断一个左括号对应的是哪一个右括号。在数组的角度思考这个问题,就有些困难。
现在,我们使用栈来解决这个问题
遇到左括号,就把做括号压入栈中
遇到右括号,判断栈是否为空,如果为空则说明没有左括号与之相对应,字符串括号不合法。如果栈不为空,则把栈顶元素移除,这对括号就抵消了。
当for循环结束,如果栈是空的,说明所有的左右括号都抵消了,如果栈力还有元素,则说明缺少右括号,字符串括号不合法。
代码实现:
function is_leagl_brackets(string){
var stack = new Stack();
for (var i = 0;i<string.length;i++) {
var item = string[i];
// 遇到做括号入栈
if(item == '('){
stack.push(item)
}else if (item == ')'){
// 遇到右括号,判断栈是否为空
if(stack.isEmpty()){
return false
}else {
stack.pop() // 弹出左括号
}
}
}
// 如果栈为空,说明字符串括号合法
return stack.isEmpty()
}
console.log(is_leagl_brackets('sdf(ds(ew(we)re)rwqw)qwrwq')) // true
console.log(is_leagl_brackets('(sd(qwqe)sd(sd))')) // true
console.log(is_leagl_brackets('()()sd()(sd()dw))(')) // false
实现一个有min方法的栈
供一个min方法,返回栈里的最小的元素,且时间复杂度为O(1)
function MinStack() {
var data_stack = new Stack();
var min_stack = new Stack();
// 用min_stack 记录每次 push 进来的元素之后,栈中的最小值
this.push = function (item) {
data_stack.push(item);
if(min_stack.isEmpty() || item < min_stack.top()){
min_stack.push(item)
}else {
min_stack.push(min_stack.top())
}
};
// 这样,每次pop之后,min_stack 也会将上次栈中的最小值弹出
this.pop() = function () {
data_stack.pop()
min_stack.pop()
}
this.min = function () {
return min_stack.top()
}
}
实际应用中还有许多,这里我就不挨个列举 了。
1.4 队列和双端队列
1.4.1队列数据结构
1、队列的概念:
队列遵循的是先进先出原则的一组有序的列。队列在尾部添加元素,顶部移除元素。
联系到日常生活中排队付款,买菜,后面加人排队,前面的人付款先走。
2、队列的创建:
1、使用一个类来创建队列
class Queue { constructor() { this.count = 0; this.lowestCount = 0;//追踪队列的第一个元素 this.items = {}; }
2.使用enqueue方法向队列中添加元素(添加在队列末尾)
enqueue(element) { this.items[this.count] = element; this.count++; }
3、使用dequeue方法从队列中删除元素(从队列顶部移除)
size() { return this.count - this.lowestCount; };isEmpty() { return this.size() === 0; };
4、查看队列最前面的项(使用peek方法)
peek() { if (this.isEmpty()) { return undefined; } return this.items[this.lowestCount]; }
5、使用isEmpty方法检查队列是否为空和获取他的长度
6.清空队列
clear() { this.items = {}; this.count = 0; this.lowestCount = 0; }
7.创建tostring方法
toString() { if (this.isEmpty()) { return ‘’; } let objstring =${this.items[this.lowestCount]}
; for (let i = this.lowestCount + 1; i < this.count; i++) { objstring =${objString},${this.items[i]}
; } return objString; }
创建方法根据队列的原理和前面讲到得栈相似,这里我就不重复了
1.4.2 双端队列数据结构(deque)
1、双端队列的概念:
双端队列是一种允许我们同时从队列的前端和后端添加和移除元素的特殊队列
在计算机中双端队列常见的应用是储存一系列的撤销操作,用户在撤销后,该操作会被存储在一个双端队列中,可以点击撤销和烦撤销,
由于双端队列同时遵守了先进先出和后进先出的原则,可以说他是把队列和栈结合的一种数据结构。
2、双端队列的创建:
1、 创建双端队列
class Deque { constructor() { this.count = 0; this.lowestCount = 0; this.items = {}; }
2、队首添加元素
addFront(element) { if (this.isEmpty()) {//空队列 this.addBack(element); } else if (this.lowestCount > 0) {//之前被删除,重新添加 this.lowestCount–; this.items[this.lowestCount] = element; } else { for (let i = this.count; i > 0; i–) { this.items[i] = this.items[i - 1]; } this.count++; this.items[0] = element; } }
3、 队尾添加元素
addBack(element) { this.items[this.count] = element; this.count++; }
4、队首删除元素
removeFront() { if (this.isEmpty()) { return undefined; } const result = this.items[this.lowestCount]; delete this.items[this.lowestCount]; this.lowestCount++; return result; }
5、队尾删除元素
removeBack() { if (this.isEmpty()) { return undefined; } this.count–; const result = this.items[this.count]; delete this.items[this.count]; return result; }
6、返回队首元素
peekFront() { if (this.isEmpty()) { return undefined; } return this.items[this.lowestCount]; }
7、返回队尾元素
peekBack() { if (this.isEmpty()) { return undefined; } return this.items[this.count - 1]; }
1.4.3队列和双端队里的应用
模拟击鼓传花游戏
情景:孩子们围城一圈,把花传递给身边的人,某一时刻停止,花在谁手上,谁就推出。重复这个操作,剩下的最后一个人就是胜利者。
代码实现:
function hotPotato(elementsList, num) { const queue = new Queue(); const elimitatedList = [];
for (let i = 0; i < elementsList.length; i++) { queue.enqueue(elementsList[i]); }
while (queue.size() > 1) { for (let i = 0; i < num; i++) { queue.enqueue(queue.dequeue()); } elimitatedList.push(queue.dequeue()); }
return { eliminated: elimitatedList, winner: queue.dequeue() };}
回文检查器
检查一个词组挥着字符串是否为回文。
代码实现:
function palindromeChecker(aString) { if ( aString === undefined || aString === null || (aString !== null && aString.length === 0) ) { return false; } const deque = new Deque(); const lowerString = aString.toLocaleLowerCase().split(' ').join(''); let firstChar; let lastChar;
for (let i = 0; i < lowerString.length; i++) { deque.addBack(lowerString.charAt(i)); }
while (deque.size() > 1) { firstChar = deque.removeFront(); lastChar = deque.removeBack(); if (firstChar !== lastChar) { return false; } }
return true;};
1.5 链表
1.5.1 链表概述(LinkedList)
链表存储有序的元素集合,但不同于数组,链表中的元素在内存中并不是连续放置的。每个元素由一个存储元素本身的节点和一个指向下一个元素的引用(也称为指针或链接)组成。下图讲解:
相对于传统的数组,链表的一个好处在于,添加或移除元素的时候不需要移动其他元素。然而,链表需要使用指针,因此实现链表的时候需要注意。数组的另一个细节是可以直接访问任何位置元素,而要想访问链表中间的一个元素,需要从起点(表头)开始送达列表直到找到所需要元素。
现实实例就是火车,火车有多节车厢相互衔接,通过接轨来链接火车。车厢就是链表的元素,而接轨就是指针。
1.5.2 创建链表
function LinkedList() {
var Node = function (val) { //新元素构造
this.val = val;
this.next = null;
};
var length = 0;
var head = null;
this.append = function (val) {
var node = new Node(val); //构造新的元素节点
var current;
if (head === null) { //头节点为空时 当前结点作为头节点
head = node;
} else {
current = head;
while (current.next) { //遍历,直到节点的next为null时停止循环,当前节点为尾节点
current = current.next;
}
current.next = node; //将尾节点指向新的元素,新元素作为尾节点
}
length++; //更新链表长度
};
this.removeAt = function (position) {
if (position > -1 && position < length) {
var current = head;
var index = 0;
var previous;
if (position == 0) {
head = current.next;
} else {
while (index++ < position) {
previous = current;
current = current.next;
}
previous.next = current.next;
}
length--;
return current.val;
} else {
return null;
}
};
this.insert = function (position, val) {
if (position > 数据结构与算法(刺猬书)读书笔记----目录
5分钟快速入门JavaScript数据结构与算法的基础①JavaScript数据结构与算法系列
5分钟快速入门JavaScript数据结构与算法的基础①JavaScript数据结构与算法系列