一、初识数据结构与算法
“数据结构是数据对象,以及存在于该对象的实例和组成实例的数据元素之间的各种联系。这些联系可以通过定义相关的函数来给出。”——Sartaj Sahni,《数据结构、算法与应用》
“数据结构是ADT(抽象数据类型 Abstract DataType)的物理实现。”—— Clifford A.Shaffer,《数据结构与算法分析》
“数据结构(data structure)是计算机中存储、组织数据的方式。通常情况下,精心选择的数据结构可以带来最优效率的算法。” ——中文维基百科
1. 常见的数据结构
-
数组(Aarray)
-
栈(Stack)
-
链表(Linked List)
-
图(Graph)
-
散列表(Hash)
-
队列(Queue)
-
堆(Heap)
-
树(Tree)
2. 算法
算法(Algorithm)是指解题方案的准确而完整的描述,是一系列解决问题的清晰指令,算法代表着用系统的方法描述解决问题的策略机制。也就是说,能够对一定规范的输入,在有限时间内获得所要求的输出。如果一个算法有缺陷,或不适合于某个问题,执行这个算法将不会解决这个问题。不同的算法可能用不同的时间,空间或效率来完成同样的任务。一个算法的优劣可以用空间复杂度与时间复杂度来衡量。
二、数组结构
数组:数据集合
数据类型分类
-
number
/string
/boolean
/undefined
/null
/object
/function
/array
/ ... -
数组也是数据类型中的一种
-
我们简单的把所有数据类型分为两个大类 基本数据类型 和 复杂数据类型
-
基本数据类型:
number
/string
/boolean
/undefined
/null
-
复杂数据类型:
object
/function
/array
/ ...
数组创建
// 创建一个空数组
let arr = new Array()
console.log(arr) // []
// 创建一个长度为3的数组
let arr1 = new Array(3)
console.log(arr1) // [ <3 empty items> ]
可以发现即使是空数组也是有长度的,开了内存空间但没有数据。
字面量创建数组
// 创建一个空数组
var arr1 = []
// 创建一个有内容的数组
var arr2 = [1, 2, 3]
内置构造函数创建数组
// 创建一个空数组
var arr1 = new Array()
// 创建一个长度为 10 的数组
var arr2 = new Array(10)
// 创建一个有内容的数组
var arr3 = new Array(1, 2, 3)
数组长度
数组中数据元素个数。
callback这里也可以使用解构的方法获得数组长度。
let arrLen = [1,2,3]
console.log(arrLen) // [1,2,3]
console.log(arrLen.length) // 3
const {length} = arrLen
console.log(length) // 3
数组的索引
-
索引,也叫做下标,是指一个数据在数组里面排在第几个的位置
-
注意: 在所有的语言里面,索引都是从 0 开始的
// 数组索引
var IndexArr = [1,2,3,4]
console.log(IndexArr[0]) // 1
console.log(IndexArr[2]) // 3
console.log(IndexArr[3]) // 4
数组的常用方法
push()
push
是用来在数组的末尾追加一个元素
pop()
pop
是用来删除数组末尾的一个元素(传入参数也无效)
shift()
shift
是删除数组最前面的一个元素
unshift()
unshift
是在数组的最前面添加一个元素
splice()
-
splice
是截取数组中的某些内容,按照数组的索引来截取 -
语法:
splice(从哪一个索引位置开始,截取多少个,替换的新元素)
(第三个参数可以不写) -
let arr = [1,2,3] arr.push(4) console.log([...arr])// [1,2,3,4] arr.pop() console.log([...arr])// [1,2,3] arr.shift() console.log([...arr])// [2,3] arr.unshift(6) console.log([...arr])// [6,2,3] arr.unshift([1,2,3,4]) console.log([...arr])// [Array(4),6,2,3] arr.splice(1,1) console.log([...arr])// [Array(4),3] arr.splice(1,1,[1,2,2,2]) console.log([...arr])// [Array(4),Array(4),3]
reverse()
reverse
是用来反转数组使用的
sort()
数组常用方法之 sort
concat()
-
concat
是把多个数组进行拼接 -
和之前的方法有一些不一样的地方,就是
concat
不会改变原始数组,而是返回一个新的数组
join()
-
join
是把数组里面的每一项内容链接起来,变成一个字符串 -
可以自己定义每一项之间链接的内容
join(要以什么内容链接)
-
不会改变原始数组,而是把链接好的字符串返回
indexof()
-
indexOf
用来找到数组中某一项的索引,没找到则返回-1 -
语法:
indexOf(你要找的数组中的项)
foreach()
-
和
for
循环一个作用,就是用来遍历数组的 -
语法:
arr.forEach(function (item, index, arr) {})
filter()
-
和
map
的使用方式类似,按照我们的条件来筛选数组 -
把原始数组中满足条件的筛选出来,组成一个新的数组返回
map()
和 forEach
类似,只不过可以对数组中的每一项进行操作,返回一个新的数组
let arr = [1,2,3]
arr.reverse()
console.log([...arr])// [3,2,1]
let arrUnsorted = [3,4,1,2]
arrUnsorted.sort()
console.log([...arrUnsorted])// [1,2,3,4]
arrUnsorted.sort((a,b) => a - b) // [1,2,3,4]
arrUnsorted.sort((a,b) => b - a) // [4,3,2,1]
const newArr = arr.concat(arrUnsorted)
console.log([...newArr])// [3,2,1,4,3,2,1]
let str = arr.join('-')
console.log(str)// 3-2-1
console.log(arr.indexOf(2))// 1
console.log(arr.indexOf(5))// -1
for(let i = 0; i < arr.length; i++){
console.log(arr[i])
}
arr.forEach((item,index) => {
console.log(item,index)
})
arr.map((item,index) => {
console.log(item,index)
})
arr.filter((item,index) => {
console.log(item,index)
})
var arr1 = [1, 2, 3]
// 使用 map 遍历数组
var newArr1 = arr.map(function (item, index, arr) {
// item 就是数组中的每一项
// index 就是数组的索引
// arr 就是原始数组
return item + 10
})
console.log(newArr) // [11, 12, 13]
// 使用 filter 过滤数组
var newArr2 = arr.filter(function (item, index, arr) {
// item 就是数组中的每一项
// index 就是数组的索引
// arr 就是原始数组
return item > 1
})
console.log(newArr2) // [2, 3]
三. 栈结构
1.认识栈结构
栈(stack)又名堆栈,它是一种运算受限的线性表。限定仅在表尾进行插入和删除操作的线性表。这一端被称为栈顶,相对地,把另一端称为栈底。向一个栈插入新元素又称作进栈、入栈或压栈,它是把新元素放到栈顶元素的上面,使之成为新的栈顶元素;从一个栈删除元素又称作出栈或退栈,它是把栈顶元素删除掉,使其相邻的元素成为新的栈顶元素。
特点:后进先出即Last in First Out(LIFO)。
class Stack{
#items= [] // 私有属性
push(data){
this.#items.push(data)
}
pop(){
return this.#items.pop() // 删除并返回数组的最后一个元素
}
peek(){
// return this.#items[this.#items.length - 1]
return this.#items.at(-1) // 返回数组的最后一个元素
}
isEmpty(){
return this.#items.length === 0
}
size(){
return this.#items.length
}
clear(){
this.#items = []
}
toString(){
return this.#items.join('')
}
}
十进制转二进制
function convert(decNumber){
let remStack = new Stack()
let number = decNumber
let rem
let string = ""
while(number>0){
rem = number%2
remStack.push(rem)
number = Math.floor(number/2)
}
while(!remStack.isEmpty()){
string+=remStack.pop()
}
return string
}
进制转换法
function convert(decNumber,base){
let remStack = new Stack()
let number = decNumber
let rem
let string = ""
let baseStr = "0123456789ABCDEF"
while(number>0){
rem = number%base
remStack.push(rem)
number = Math.floor(number/base)
}
while(!remStack.isEmpty()){
string+= baseStr[remStack.pop()]
}
return string
}
四. 队列
队列是一种特殊的线性表,特殊之处在于它只允许在表的前端(front)进行删除操作,而在表的后端(rear)进行插入操作,和栈一样,队列是一种操作受限制的线性表。进行插入操作的端称为队尾,进行删除操作的端称为队头。队列中没有元素时,称为空队列。
队列的数据元素又称为队列元素。在队列中插入一个队列元素称为入队,从队列中删除一个队列元素称为出队。因为队列只允许在一端插入,在另一端删除,所以只有最早进入队列的元素才能最先从队列中删除,故队列又称为先进先出(FIFO—first in first out)线性表。
队列的封装
class Queue {
#items = {}
#count = 0
#lowCount = 0
dequeue() {
if(this.isEmpty()){
return undefined
}
let res = this.#items[this.#lowCount]
delete this.#items[this.#lowCount]
this.#lowCount++
return res
}
enqueue(data) {
this.#items[this.#count] = data
this.#count++
}
front() {
return this.#items[this.#lowCount]
}
isEmpty() {
return this.size() === 0
}
size() {
return this.#count-this.#lowCount
}
clear() {
this.#items = {}
this.#count = 0;
this.#lowCount = 0
}
toString() {
let str = ""
for(let i =this.#lowCount;i<this.#count;i++){
str += `${this.#items[i]} `
}
return str
}
}
队列的应用-击鼓传花(约瑟夫环)
function game(list,num){
let queue = new Queue()
for(let i=0;i<list.length;i++){
queue.enqueue(list[i])
}
while(queue.size()>1){
for(let i=0;i<num;i++){
queue.enqueue(queue.dequeue())
}
console.log(queue.dequeue(),"淘汰了")
}
return {
winner:queue.dequeue()
}
}
game(["kerwin","tiechui","xiaoming","gangdaner","guludunzi"],7)
双端队列
class DeQueue {
#items = {}
#lowCount = 0
#count = 0
removeFront() {
if (this.isEmpty()) {
return undefined
}
let res = this.#items[this.#lowCount]
delete this.#items[this.#lowCount]
this.#lowCount++
return res
}
addBack(data) {
this.#items[this.#count] = data
this.#count++
}
addFront(data) {
if(this.isEmpty()){
this.addBack(data)
}else if(this.#lowCount>0){
this.#lowCount--;
this.#items[this.#lowCount] = data
}else{
for(let i = this.#count;i>0;i--){
this.#items[i] = this.#items[i-1]
}
this.#count++;
this.#lowCount = 0;
this.#items[0] = data
}
}
removeBack() {
if (this.isEmpty()) {
return undefined
}
this.#count--;
const result = this.#items[this.#count]
delete this.#items[this.#count]
return result
}
peekFront() {
return this.#items[this.#lowCount]
}
peekBack() {
if (this.isEmpty()) {
return undefined
}
return this.#items[this.#count - 1]
}
isEmpty() {
return this.size() === 0
}
size() {
return this.#count - this.#lowCount
}
clear() {
this.#items = {}
this.#count = 0
this.#lowCount = 0
}
toString() {
let str = ""
for (let i = this.#lowCount; i < this.#count; i++) {
str += `${this.#items[i]} `
}
return str
}
}