点击文件名下载附件
n8 E/ e; `5 f- N3 X0 w
一队列的顾客队列的使用场景:后台任务打印/任务处理和栈一样,有不止一种实现队列的方式。但是最简单的是在数组中使用 push 和 shift 方法。如果我们仅使用 push 和 shift 方法来添加和删除元素,我们就在数组中遵循了 FIFO 模式,将数组按照队列来操作。另一个实现办法是列表,如下:// 为队列每一个节点的类 ! ?7 V" F9 N& e9 ]7 L' C* |, \class Node {2 J4 ]5 }- x. Z
//每一个节点包含两个属性,其值以及一个指向下一个节点的指针5 L2 b' \$ p, u. D+ _1 x
constructor(value){& S, r1 w% u. S1 t
this.value = value `9 O5 V4 J# Z+ W+ ~
this.next = null( L. E" i% j. h0 J+ d
}/ i l8 u: s( E/ `2 G
}- J) }9 h: p) R: `# E' Y. [
: D/ o8 D* g7 C: S
// 为队列创建类" M- q* c7 B. E
class Queue {% G( x' D9 S; o+ J! ]) c& E
// 队列包含三个属性:第一个节点、最后一个节点、队列的大小 / u+ u/ n7 i* g% e# h constructor(){ ; K* [' `4 ~3 }+ C this.first = null 8 V7 t: a& d: v! A) V this.last = null . F O8 f0 h" |1 t9 p this.size = 0 - A8 G) e! i3 b8 d- @: I: y } 9 f+ Z2 Y6 R: ~) F! j2 r1 y7 x7 M // enqueue 方法接受一个值并将其添加到队列的末端! m" s8 e2 t1 L; n, \
enqueue(val){5 x5 D6 y5 p" G4 A K9 P$ t
var newNode = new Node(val) # c1 `; E# c# n3 A2 b) ?. p if(!this.first){ , @4 y7 ?0 e! x9 v& D" M0 | this.first = newNode$ A' I# O9 x3 `" m+ I+ q
this.last = newNode- E( R4 ~2 q7 ~, v
} else { * J4 `# h' C ~; t+ e) B this.last.next = newNode+ ^6 z! S$ T* l; ~
this.last = newNode % I/ j; Y- f) ] } 7 K+ W4 b3 m8 m. k _+ c4 s) s( a# W return ++this.size4 m7 A6 K, \9 {: T i
} 0 H% W& s1 Q: d, ^$ ? // dequeue 方法删除队列“最前端”的元素,并返回 8 ^% v6 ^9 C- a# C dequeue(){ & E6 D* I, c: u2 e1 o if(!this.first) return null: u5 j0 p; {& p
2 ^5 R d W- R
var temp = this.first' l5 n4 \0 u4 |* `7 H4 }# `
if(this.first === this.last) {+ Y2 A, ~8 B2 L8 E
this.last = null6 X$ w% Y6 [5 Q: \# f1 R0 V+ b1 U
}8 k2 l# ^, V7 K7 [; s* C
this.first = this.first.next x1 s7 {0 M- Y( E. T, R this.size-- 0 G7 }) m/ E. S, h3 L return temp.value 6 u) W& v2 W6 d+ i2 N5 | } 5 w4 m9 W. X/ E7 s" G9 F! n} / Z1 _9 z% ^6 T$ y& R2 N ! Q( R4 A% F6 H1 I8 V& P! Kconst quickQueue = new Queue 0 n8 c9 R3 m- X9 T4 D" v ' F' ]/ w3 D* QquickQueue.enqueue("value1")$ n! G2 m: ~; R/ n' ?+ f! I( R
quickQueue.enqueue("value2") $ M. c4 N3 F* W- s0 d* n$ i' _, qquickQueue.enqueue("value3") & o I! I* V5 p. Q , U# c- Z% T' j$ g8 S% b; ^* iconsole.log(quickQueue.first) /* " L4 _. c" G( C3 W! B6 k! S
Node { 4 G- ]* ~( j3 F: @ value: 'value1'," K( \3 O; l; \ Q' Q2 v$ g
next: Node { value: 'value2', next: Node { value: 'value3', next: null } }+ p0 D0 B+ k: I# ?1 j+ t6 T
} / K) |8 j1 |8 K | */ 3 t% r; z- Q, U( H" X/ [9 dconsole.log(quickQueue.last) // Node { value: 'value3, next: null } * g) }4 A: N# M2 ?; d7 |$ cconsole.log(quickQueue.size) // 3 9 r- d5 [5 S6 ?4 T2 k. G 6 I+ V: b& E6 u" V! m* K( Z$ WquickQueue.enqueue("value4")- \, X! m+ C+ ^; P7 p# S+ z
console.log(quickQueue.dequeue()) // value1, t& }- k2 W: H
队列方法的大 O 表示法:插入 - O(1)删除 - O(1)查询 - O(n)访问 - O(n)链表链表是一种以列表存储值的数据结构,在列表中每一个值都被当作为一个节点,每一个节点都通过指针与列表的下一个值关联(若该节点是列表最后一个元素则下一个值为 null)。有两种链表:单链表和双链表。两种链表的运作方式类似,但是在单链表中每一个节点有单指针指向下一个节点,在双链表中,每一个节点有双指针,一个指向下一个节点,一个指向上一个节点。, _6 h5 @% U5 f0 @2 H, q (, 下载次数: 82)
上传
点击文件名下载附件
" E. y% v3 X7 F
在单链表中每一个节点有单指针 4 @8 V% d4 | c8 Q5 T(, 下载次数: 130)
上传
点击文件名下载附件
8 b. a5 u8 R6 K' K2 y M
在双链表中每一个节点有双指针列表的第一个元素被当作头,列表的最后一个元素被当作尾。和数组一样,列表的长度由列表中的元素个数决定。列表和数组主要不同包括:列表没有索引,列表中的每一个值仅“知道”其通过指针连接到的值。因为列表没有索引,所以我们不能随机访问列表中的元素。当我们想要访问一个值,必须通过从头到尾遍历整个列表的方法。没有索引的好处是添加或删除列表中任意部分比在数组中更高效。我们只需要重新分配指针指向的“相邻”值,但是在数组中,我们需要重新分配余下所有值的索引。和其他所有数据结构一样,可以采用不同的方法来操作以链表存储的数据。通常会使用:push(在尾部添加)、pop(在尾部删除)、unshift(在头部添加)、shift(在头部删除)、get(获取)、set(设置)、remove(删除)和 reverse(反转)。我们先来看看如何实现单链表,再来看看如何实现双链表。单链表完全实现单链表的代码如下:// 为列表中的每一个节点创建一个类- W9 V# l5 Z2 V, B8 W$ a
class Node{ ! W) w; X& M# d6 q1 l+ U" ~- R5 | // 每一个节点有两个属性:其值和指向下一个值的指针$ S/ O/ `. p9 G/ l8 }+ b0 |
constructor(val){ , @( {) O1 d W' k% p7 V, i, g this.val = val, H' y9 x3 ?1 t
this.next = null 0 i+ X# h! i/ z* u! Z6 t } - G1 F+ _4 @' j9 d/ Z N}+ K0 _/ q _4 c5 W- P$ y
( L$ d# Q. I1 _& b//为列表创建一个类 $ [' L+ r! z$ Q* V/ o! B) nclass SinglyLinkedList{# p" W3 C4 w- O
// 列表有三个属性,头、尾和列表大小4 D) G1 u( V: l1 Z! P4 p' s# H
constructor(){% M/ U/ M5 t& a; w
this.head = null : q" w2 i2 j6 x0 p this.tail = null3 V% [( {# @! |
this.length = 0# A7 Q/ ]5 Q5 V" t: M
}2 F! x1 h+ I$ b' B
// 向 push 方法传入一个值作为参数,并将其赋值给队列的尾6 F" k8 N, {( O) @" [
push(val) { + t4 d7 t4 C- u8 Z- e5 Z) B const newNode = new Node(val)5 i, \) J3 I3 z" K, P Z
if (!this.head){ + j8 _7 ~. l. e j this.head = newNode 9 ~! B: {6 e/ n" Y& {0 c2 E this.tail = this.head( J" O+ g* _) @. y& t
} else { * d; J( w4 K1 m this.tail.next = newNode 7 A; l) \1 d8 V this.tail = newNode & w2 p7 g+ {$ h) A8 U. I }5 m0 f9 j$ S/ T$ O
this.length++% N/ U. b% j. o4 e6 m) b, }
return this3 s' a8 R4 v2 j P0 b$ V1 b
} * Q# `! ~ w R+ l# W // pop 方法删除队列尾1 p$ F, A4 A* y4 e2 {0 R$ _
pop() { 4 ]9 |2 [. h* t6 S3 A& d( d; x8 x9 @) H% A if (!this.head) return undefined: V+ R4 U3 p- M) b. p4 ]
const current = this.head+ }( e* Q4 t+ {. N4 \
const newTail = current 7 c+ I! l5 `8 x3 r% y+ j while (current.next) { 4 X) | k+ r' a% v0 w# M$ t& Z newTail = current 9 q, |! p) l/ q4 f current = current.next' o- f: A; G1 k9 \0 f
} 4 c3 j3 X$ D* k this.tail = newTail 8 e5 r, y# X2 r" l% ? this.tail.next = null+ |7 b6 M6 \2 c y1 j5 ?" A8 _, D
this.length--6 H9 }% g( I1 c o, {
if (this.length === 0) {3 R/ W% Z5 g F* ?6 _
this.head = null5 a+ \( Q/ {, z" Y, H9 a3 g# ~) X
this.tail = null% f! H! s* D& B7 {: k, v3 {
} 2 n3 ?4 T* L8 k7 Y return current 7 f+ I0 K2 Z! ?1 D } / h6 _- @0 n% p! i/ P // shift 方法删除队列头# r0 v" |6 l; e' @, m# k
shift() { z# a, G2 w9 r8 R) r. S if (!this.head) return undefined ' e3 U j( a& ~% n' @ var currentHead = this.head# n* |# R+ S6 `# W
this.head = currentHead.next 6 p2 [5 q, i" a/ l4 F2 |& l this.length--( S( E; C. ?! I: y' T9 z
if (this.length === 0) { ' ]3 Q7 h2 P/ `1 M3 [; ] this.tail = null( N. m1 |8 Q- |# q+ V( T q
}* f/ e, l5 X: L. g M
return currentHead# N1 W+ i( \* z6 A( T$ U3 E; N
}7 D# h. j! q7 B7 ?
// unshift 方法将一个值作为参数并赋值给队列的头. S7 c9 r9 q7 m5 t
unshift(val) {$ n7 [0 {/ V+ i: [
const newNode = new Node(val)% x$ e- h" H* e# d7 F4 e2 R" I$ a
if (!this.head) {* T: k# V" g; l3 C8 A, K* q8 _
this.head = newNode8 {3 z# L# S; b7 s, d
this.tail = this.head- r& o0 o7 J7 J/ }
} ( {6 c0 l: O- _) } p newNode.next = this.head0 d( o8 F% j* F- L
this.head = newNode z* ^+ g3 m2 k# W9 A/ s, L2 r this.length++ 1 }5 H& t2 W$ K' p" c- g) v. b return this) D$ K, X9 l( r+ j3 y
} 1 j1 m% R' Z4 s0 c( F( f0 r# x' I- n // get 方法将一个索引作为参数,并返回此索引所在节点的值$ S; h' B. y1 D9 }4 N9 L
get(index) { & q+ K! S% b2 m; F if(index < 0 || index >= this.length) return null 2 ?. Z) v" @- w+ l const counter = 0 # k' O. [" [/ ?, V- }) X const current = this.head 3 J4 B9 p W' u( n8 d, H8 R while(counter !== index) { 6 Z P) Z/ d. r# U) N5 Z current = current.next) y; s* G2 x; K2 Z
counter++$ K4 M9 O9 y V, o% P
}! L4 H( h( X5 i6 s
return current; `% y8 ^0 s+ Z% x. i6 h0 ^* A+ g7 Q
} , T2 [, b" Z8 _ // set 方法将索引和值作为参数,修改队列中索引所在的节点值为传入的参数值 9 {+ s( D; o1 E+ j9 L2 G& D set(index, val) { ' P0 T2 H" p. W5 n1 |( k const foundNode = this.get(index) ! s; v6 k4 y& S/ a% ` if (foundNode) { , ?8 e! z+ Z- W3 m foundNode.val = val# @% i% N4 g9 }$ R8 V
return true- |, {& {+ f4 C% K, d+ P! V( L9 T
} : q) u" W2 ]. x' T d- C return false, Q! V9 B( N. D3 g1 w
} q$ V9 d+ S# d3 q' Y/ p, s
// insert 方法将索引和值作为参数,在队列索引位置插入传入的值 6 ?" P( ]' A2 L# s( X6 H insert(index, val) {) [1 i' |' o' A5 P+ P$ i& Z$ u: `
if (index < 0 || index > this.length) return false 0 @0 K) m8 H. \, C x if (index === this.length) return !!this.push(val)9 F0 `5 b- o- z2 K; I8 K
if (index === 0) return !!this.unshift(val)! L! {4 [/ d+ d' t2 Z. ^3 g
. @0 e7 |- o6 b const newNode = new Node(val)1 R5 m$ b5 m. |% N2 M. |
const prev = this.get(index - 1)1 A ~# n5 t7 B, C% | [
const temp = prev.next / V0 Y# F1 q- i! x: p prev.next = newNode {6 q4 Q0 W3 W* O8 u
newNode.next = temp ; n7 ]8 M* a* ~2 @6 ^9 T this.length++ / m% j7 X1 M. @3 H5 O return true 5 n4 l+ h; W) j% w% v& Q } % ~/ m1 P$ J! s y- Z& D/ V // remove 方法将索引作为参数,在队列中删除索引所在的值" K3 Q; z/ w# {/ ?. u
remove(index) {$ E+ y; X; [5 n4 [- _. [
if(index < 0 || index >= this.length) return undefined : G, v: h" `* D( C if(index === 0) return this.shift()8 C2 }, v5 d/ X& c' U1 u6 ~& U
if(index === this.length - 1) return this.pop(); P) ]9 J0 z% c9 G
const previousNode = this.get(index - 1)2 _5 z# b/ l" {
const removed = previousNode.next / g- U0 H0 B0 m, `$ |+ a previousNode.next = removed.next: L, A" L, k4 u, b- X8 v' t
this.length-- # I/ l1 C- e9 {4 t return removed 8 Z( p: C* O) {7 _ } # J8 z' J+ K( R // reverse 方法反转队列和所有指针,让队列的头尾对调0 c2 [. }; V4 h; d
reverse(){' ~ H& ?& ?) P4 R
const node = this.head- g7 N9 V0 r2 [2 y j S
this.head = this.tail 7 t6 U1 O5 ]" O0 ]4 w5 b this.tail = node7 t' @ a: H% b: S
let next! ]7 u. L) s6 Z; S" K5 [" n
const prev = null) S3 c( S0 m) T+ U1 X7 m2 {1 ?# I, l1 w
for(let i = 0; i < this.length; i++) { H' ~7 O9 u; e7 g next = node.next - _" M- u1 O8 K node.next = prev& D4 ?% @; n, p. M( L! f. h
prev = node 2 |7 H* M+ k8 x1 ]0 | node = next" f& z. z( p& u& F {# w2 C7 r+ S
} ' [$ Z' Z8 [6 g% @/ {' t return this ! ?' C. B. E& l3 C: W } : {$ k5 {1 T# Q}1 r$ e4 `" t+ Z, T% U; H# e! u
单链表的复杂度为:插入 - O(1)删除 - O(n)查找 - O(n)访问 - O(n)双链表如上文所述,双链表和单链表的区别在于双链表的前后两个节点之间由双指针相互连接,而单链表只有一个指向下一个值的指针。双指针使得在特定场景下双链表比单链表的表现更好,但是也增加了存储空间的成本(存储双指针比单指针更占位置)。完全实现双链表的代码类似于:// 创建列表节点的类; _+ N% o3 w4 _' s
class Node{ 9 e& d# E/ o) v, } // 每一个节点包含三个属性,其值,一个指向上一个节点的指针,一个指向下一个节点的指针 h0 E0 g; \1 D( N: L6 p4 l$ m6 R constructor(val){2 ^& m1 @/ g7 i
this.val = val;" m: ?+ r5 O* J- @9 E! h1 i5 L
this.next = null;$ H$ }/ I, ]' y5 R2 @
this.prev = null;+ S% B3 V& p$ f. N
}5 F8 h. a* l# t, y& `6 G( a
}* p7 J8 s1 `4 m1 k" \
p- X8 S% z( \5 v5 ?* S// 创建一个列表的类 ' \9 o/ N# l) C6 E' h" Vclass DoublyLinkedList { ' f. A6 E% [: b2 b4 i0 G // 列表有三个属性,头,尾和列表的大小! u5 y+ A3 @& f
constructor(){) ~: T, j3 {7 B# q9 e: `! e' P
this.head = null a& }8 n& W, L% A this.tail = null8 H7 H) S4 G6 @: `3 {5 v" `
this.length = 05 Q- E# z% @; c% Z( K, y7 X9 }: U
}; }: ~# M) K9 |6 H7 `0 Y1 s3 r
// push 方法将值作为参数并赋值给队列尾. E! K5 M( O4 P( p2 s& t) g# E
push(val){ " D2 x6 q3 ^$ X const newNode = new Node(val) ' x7 }8 L6 Q9 n if(this.length === 0){+ d& E/ B1 R' Y& ~+ Y- C. c- _
this.head = newNode ! ], A, m2 g$ ~ this.tail = newNode 5 X9 g9 Q" e9 F. x/ H } else {8 ^: K9 ~. w6 {7 B
this.tail.next = newNode9 A- ]5 p3 n; X a, |) U, I9 l
newNode.prev = this.tail ! t; B' I5 j! s this.tail = newNode , q y" V( ?. a$ D) G! O. Y) L } - ]1 i) P3 y2 s( L, j }' O1 e. M6 o this.length++ ! T0 d6 a( h+ A, T: x% o. c* T } return this' u9 A _& b2 H' k7 Y
} + i& m# c& e" d8 v9 v; q! C // pop 方法删除队列尾 + ?+ n% I7 o+ l5 d pop(){% _: c% d0 [) ?
if(!this.head) return undefined ( }# X. P! F7 L) t( K: E const poppedNode = this.tail1 Q( o2 g: @! P F# f2 j
if(this.length === 1){ 7 c e: o+ w1 l this.head = null9 c$ f/ |, I6 b6 S3 b7 [
this.tail = null ) a% `" E: r0 R6 U% I6 R } else {4 L0 E! i" K3 O* S9 Z. e# d5 R! J
this.tail = poppedNode.prev + I( u( R- f) b: F4 S this.tail.next = null 7 |: o- k/ I* T& R4 t' t poppedNode.prev = null7 D$ a+ G3 |6 v" S% Y& d
}7 ]- j& x$ n; O" b- ~5 Q
this.length--) K$ m% b# E4 j4 L: W# U [
return poppedNode9 A! u4 r6 E' x, N3 Q
}8 r( n. W' I& @- p! ^+ v
// shift 方法删除队列头2 y9 E, a% p$ @2 P+ Q7 s$ Q
shift(){6 a7 `# O5 \ F
if(this.length === 0) return undefined8 S8 j: ?. t. d8 B
const oldHead = this.head; X! u' \% Z: Z" T9 I! z5 K. b
if(this.length === 1){ ( g3 h# f9 c6 C8 U4 c this.head = null% w; U/ s( d k& R4 [+ r
this.tail = null ' f$ \: N0 I. [0 i } else{, i, u6 t+ m- X5 {. H/ P3 Z
this.head = oldHead.next 2 j/ Q2 u2 u d& M5 t+ M this.head.prev = null M- g1 u2 d% k# A6 [$ F+ S
oldHead.next = null7 M' a; X- v4 p
} . Y, ^, F% g. n- F+ n. [. _ this.length--3 M% ?0 w7 H& P; ? |4 X
return oldHead , j* B* W: a1 f: ~1 e% I& o }( U; W1 x& X" Z( \2 u+ |& f v
// unshift 方法将值作为参数并赋值给队列头 # A1 i3 n$ M/ S unshift(val){* s( j6 ~- u- Z; K4 I
const newNode = new Node(val)% S" p, B9 f! O2 t9 z
if(this.length === 0) {6 s% I* ^$ b9 d; @. C
this.head = newNode - K& v+ D' F8 u this.tail = newNode ( t7 O0 j8 L! y! V$ N, k/ M } else {. f& b) p% b( [9 c
this.head.prev = newNode; P# S& {1 d" V1 Y( l4 f- a
newNode.next = this.head& d' O4 {$ {7 T P% E, d
this.head = newNode$ ^$ e/ w0 J, H9 O; v- X5 q
}. v) \' s v; s& j
this.length++' U" S' f( }0 a8 t4 v/ E6 N/ i
return this 2 i% Z0 J' u" z. P2 O/ L } : s" H. h8 c- d+ p; l // get 方法将索引作为参数并返回队列对应索引的值 D) g/ ~* j! Y& K' s' |4 h
get(index){ " j& R/ [" S9 a( E4 T1 ~ if(index < 0 || index >= this.length) return null* v+ v0 p7 T4 o/ t p2 E' ?
let count, current * j; U# ^: e- t+ g) O$ b1 v" K) @ if(index <= this.length/2){ : H' X" B* _0 B$ h3 M& G) B0 c count = 0 }) [: {5 r/ M6 k7 \4 _
current = this.head & W1 D. R3 [/ u' J! e9 J while(count !== index){. ?, s$ R3 C+ B+ c" d
current = current.next& a' R7 D5 c$ L# `4 f
count++ 3 Q+ b( [! V2 U t# f! { }. `# z' d& g* Y2 N
} else {8 M! p4 J: U6 H% J3 w
count = this.length - 1 5 _4 A/ k3 ?( J8 V: A _ current = this.tail 1 e6 }% C" W# _; H; r4 U$ q! r while(count !== index){" R- w6 ~7 y+ T; C1 y8 o" F
current = current.prev 2 t2 _8 [+ ?( D count--" r. a5 b* f% ]& v% U
} " D" G7 p7 |# m4 n0 M' Q' F } 4 ?) P' W! A; f. z return current2 g7 n+ C, n0 W" C
} * {1 X& t- e% z h) v( P // set 方法将索引和值作为参数,修改队列中索引所在的节点值为传入的参数值5 t, R5 ^; z& K" y
set(index, val){- e% n: p2 E- y& e: J/ x
var foundNode = this.get(index)" c4 l' T6 \1 H3 `, |% d4 N
if(foundNode != null){& h. p" j. k+ w# @6 e" M
foundNode.val = val / Q% I; ]' |9 {+ G6 E return true $ q* W0 b' J7 m2 C }& I6 J- W9 W0 X( L% K) h
return false" ^+ U2 t+ |1 ?
} + k; A& }8 \- @- \. j // insert 方法将索引和值作为参数,将值插入队列响应索引位置% _6 E2 r t9 [& b! E! d- o+ m
insert(index, val){ ( t2 \+ `, P) H6 Y5 e if(index < 0 || index > this.length) return false! Z, [6 U+ H+ b" A
if(index === 0) return !!this.unshift(val) ) f+ s* [0 D8 o# u% n9 { if(index === this.length) return !!this.push(val)% C9 n# h& x, _' `
1 H2 w' l' x+ d4 ] var newNode = new Node(val)7 @+ v% ?+ I) [ ^) C S+ l( p
var beforeNode = this.get(index-1) 1 `# i$ e: V" q% z( _; U var afterNode = beforeNode.next3 K4 k5 e, x. O; M* l9 X