点击文件名下载附件
1 @8 C& x) Q/ l' N
在单链表中每一个节点有单指针 ; B! w( E8 C$ @% n) K(, 下载次数: 26)
上传
点击文件名下载附件
+ I+ x6 x% C* z& ~ 在双链表中每一个节点有双指针列表的第一个元素被当作头,列表的最后一个元素被当作尾。和数组一样,列表的长度由列表中的元素个数决定。列表和数组主要不同包括:列表没有索引,列表中的每一个值仅“知道”其通过指针连接到的值。因为列表没有索引,所以我们不能随机访问列表中的元素。当我们想要访问一个值,必须通过从头到尾遍历整个列表的方法。没有索引的好处是添加或删除列表中任意部分比在数组中更高效。我们只需要重新分配指针指向的“相邻”值,但是在数组中,我们需要重新分配余下所有值的索引。和其他所有数据结构一样,可以采用不同的方法来操作以链表存储的数据。通常会使用:push(在尾部添加)、pop(在尾部删除)、unshift(在头部添加)、shift(在头部删除)、get(获取)、set(设置)、remove(删除)和 reverse(反转)。我们先来看看如何实现单链表,再来看看如何实现双链表。单链表完全实现单链表的代码如下:// 为列表中的每一个节点创建一个类 9 x; T8 V m4 U. e' E. pclass Node{9 a1 \' j$ ?/ g
// 每一个节点有两个属性:其值和指向下一个值的指针" J6 y, I. V5 W0 f2 T
constructor(val){3 ?. P3 r B" {
this.val = val ! ^. a% ?; M" O& G7 V8 u' b$ K this.next = null" R- m/ q+ Q/ g6 Z9 B* n
} 5 r; F* v1 M& x} " J6 B9 D* n* R6 e" x$ k& G9 e4 ^: H! L6 y& j0 w# n
//为列表创建一个类 6 x8 J" \. w+ J2 I4 V+ xclass SinglyLinkedList{$ I; o, c2 F2 g, F z! j! h w% c
// 列表有三个属性,头、尾和列表大小3 z% y( B& e" y
constructor(){ - `" S, t/ Y# N. n, ^ this.head = null8 U" d* K4 H3 H/ `$ o- P
this.tail = null+ V- A( Q" o+ K
this.length = 0 ! a5 ^+ x/ X: }/ G9 |/ F } 0 e) B y3 ~- B4 r // 向 push 方法传入一个值作为参数,并将其赋值给队列的尾7 [1 A" V) T/ Q
push(val) {* @: ^* |( d4 H) W, P3 B/ I$ _
const newNode = new Node(val) $ u$ D9 |* ?) _ b3 ?; ~ if (!this.head){ 8 h2 |& [8 X* b this.head = newNode : Y$ a& T$ i$ g4 g% c3 F9 I& T this.tail = this.head , n5 |( {4 ^. b } else { * _, t. q w$ d- x! t$ \9 O this.tail.next = newNode 2 `& e5 m0 a: k+ p5 j0 | this.tail = newNode- S/ l7 ^# I& P: z6 S
}0 O8 c! k0 p) q' M5 j# m+ K" E5 ~: U
this.length++8 P2 W2 ]$ l& ^$ V% r, H+ @- P
return this3 [# ?! q6 M/ O3 M7 o: x" O
} ) z2 [4 g9 v8 E! b" w // pop 方法删除队列尾 : e, H$ p$ ^% r4 w) Q1 s% }; T& i- g pop() { ) E4 m/ A! K& E- g. h7 S6 k4 c if (!this.head) return undefined2 ]2 r* `: P8 I( Y* g4 Q* g! e
const current = this.head7 q/ G4 m; Q2 e( o
const newTail = current4 n1 w9 | k' J1 J% a& {/ H% I/ G
while (current.next) {3 }1 {+ R) c. U* F
newTail = current 1 l1 q9 y6 L0 _4 G% y: K current = current.next 1 c% {9 P6 D7 d0 C# C# }4 h3 } }( |+ |. B# h, j: G: B
this.tail = newTail6 p7 H( A6 R! m2 c1 j
this.tail.next = null ! a) }4 {' v3 ~) E- r3 r this.length--; A5 f- }0 V7 `9 b* I! d
if (this.length === 0) {! E" Q+ K# Y" a* h3 X
this.head = null ( ]# r! e6 y: p/ t this.tail = null' d' Y2 g; J# ]3 S- ]' v* E; ~& }( Q
}, [: U$ k' k* @' h- g
return current . _% T5 W* A2 Y$ k& i } & C; _) @% [7 U! \" m) z // shift 方法删除队列头 $ o1 H7 U2 t6 j shift() { , W5 u/ n8 j7 ? if (!this.head) return undefined / U. o Z' {, X( ^$ ]1 [8 U3 ^ var currentHead = this.head& Y# p; m4 ^9 \1 M: z$ `2 Q" X
this.head = currentHead.next$ H( z( H' i* ?1 G! z5 b6 p1 e
this.length--6 i. J9 `) v& D& c5 U
if (this.length === 0) { , j3 T0 n0 g- H8 W+ K this.tail = null 3 a; \$ v. @, d- [8 l) T. M5 i4 u# w( Q } 4 P3 C/ y4 l: B% u5 Z. `& Z' @3 L4 j return currentHead ) O& l; T: y2 r! P8 } } 6 v, h, p9 Z3 f3 b2 T // unshift 方法将一个值作为参数并赋值给队列的头 0 t/ M, L& h2 {; X) t+ [ unshift(val) { 4 Y0 m* w/ C) c$ v, A6 i8 j const newNode = new Node(val) % q9 n6 `4 p2 E* o if (!this.head) { % i7 _! m7 ~. G, h5 V. B! z- B this.head = newNode3 Y/ A, I4 M$ ^) A8 y/ |# l' X
this.tail = this.head ; l& E3 |! f8 J8 r } 2 |& R9 R' N: _% k2 }# j0 L newNode.next = this.head ]. _: ` Q. d+ W5 D* e9 c* G this.head = newNode3 h' J0 C, Y4 D H
this.length++6 R$ I* ?) M" [. m- w( m- _- g: k( R
return this4 E$ u0 S+ F2 B5 o! l4 A
} ; q7 P, i! x5 p // get 方法将一个索引作为参数,并返回此索引所在节点的值% i: z/ W" s+ l3 w; V3 ` h0 o0 }
get(index) { C$ s& O8 I4 N if(index < 0 || index >= this.length) return null6 \% l. J0 \& Y; p4 I, R' K b5 e
const counter = 0 + |6 s# c1 `* L3 ?* e% k' O4 c const current = this.head 5 u# F7 d4 A, S" l- q/ _ while(counter !== index) {# ?$ @# d% J0 E5 v' m$ w. x
current = current.next 0 O' C) z1 }$ G1 l$ Y! a' W counter++, E* q K. Q$ v& A
} 1 o2 m% e! F4 `( t3 @7 b8 S return current G0 d7 C/ |& p }7 e7 Z0 L0 w, {8 R$ N
// set 方法将索引和值作为参数,修改队列中索引所在的节点值为传入的参数值 , o8 x# V- V' S8 }* D: _( } set(index, val) {: S. A' E9 _1 o8 [! _, |
const foundNode = this.get(index) 9 p! X M( s; L) I( D7 t( b if (foundNode) {3 F! v+ j$ T4 m& S' x
foundNode.val = val - B& Z3 i3 b. G6 @# U8 L return true; ]. D7 U: Y1 a# ^ l
}8 z6 f! W' d3 y7 m5 w
return false 6 e: Q4 |+ x" q5 |. m }5 D( I% Z, s: }
// insert 方法将索引和值作为参数,在队列索引位置插入传入的值) b6 P( g) Q1 l* } v I+ h7 S
insert(index, val) { 7 V1 b: j" N' A! }) {1 w4 T if (index < 0 || index > this.length) return false 9 [( y: z& x0 N O1 d; Z if (index === this.length) return !!this.push(val) 9 o$ ]0 J1 ~9 C @7 K- S if (index === 0) return !!this.unshift(val): r& I, Z4 P& o6 p9 S/ l
) V' }2 {( F% N* G
const newNode = new Node(val) ) B$ a. P$ ~# D. R1 z! _! } const prev = this.get(index - 1). I H$ n0 W R$ d1 j' f! D# I
const temp = prev.next ( A2 T* S; g' g" g/ W1 n) r$ H9 Q prev.next = newNode * W3 R- N* F' F. t newNode.next = temp7 @" P/ c5 u$ M. O7 ~1 F
this.length++ b' ?0 D0 G! X! U& ?+ V
return true8 W: a: Z* F" ]( [/ I- @- K2 U
}# G. [# A" d- } f5 ]! A
// remove 方法将索引作为参数,在队列中删除索引所在的值0 U' r4 z! c9 }& r) W& x) F0 `4 `
remove(index) {0 ~ P; m! G# r& g! x2 @
if(index < 0 || index >= this.length) return undefined : m( P/ E' t: d if(index === 0) return this.shift()* j" Z$ K/ D) D* d4 N
if(index === this.length - 1) return this.pop()# y6 w. f) R6 @. w; e, J, o
const previousNode = this.get(index - 1)' m$ ^" M5 w; i @6 m: R. ]3 ^
const removed = previousNode.next + _# }2 P3 J+ ^# E D previousNode.next = removed.next2 P$ h: Z: }+ E
this.length-- $ B, y( k) V9 z return removed 7 ]+ E$ `$ a; L& L. F1 S, D } * r- a: i. S% a7 O7 u$ n // reverse 方法反转队列和所有指针,让队列的头尾对调 % ~3 |% G7 @: Z r1 L4 t reverse(){ 7 B6 P5 S% l/ {4 ~. q9 O, y! ] const node = this.head 8 T5 [+ U1 P6 f' ~2 j& V' k this.head = this.tail K% e, C# y9 D! M this.tail = node* B4 K# m& @$ }% }
let next 7 `! [5 V# F% ` const prev = null) i! e% _6 @1 w
for(let i = 0; i < this.length; i++) {7 S9 ?; W; d8 u- {$ e7 `+ m
next = node.next$ I, h" S# m& J; c; `
node.next = prev5 S) t6 b' N$ N' ?' }
prev = node7 C+ q( w0 k2 I0 v; W
node = next ' u- J8 }& H( A/ C4 P/ a }3 H) a" s$ u9 k
return this ) n3 q% k$ [1 U5 I1 i7 m }1 J' h5 b: n* y1 p0 M
} & b% N9 g4 Q& h$ [9 O$ F单链表的复杂度为:插入 - O(1)删除 - O(n)查找 - O(n)访问 - O(n)双链表如上文所述,双链表和单链表的区别在于双链表的前后两个节点之间由双指针相互连接,而单链表只有一个指向下一个值的指针。双指针使得在特定场景下双链表比单链表的表现更好,但是也增加了存储空间的成本(存储双指针比单指针更占位置)。完全实现双链表的代码类似于:// 创建列表节点的类 ! I# P' Y6 I3 j' r2 M5 O- Oclass Node{ ! o2 o( Z( m6 l0 \2 P | // 每一个节点包含三个属性,其值,一个指向上一个节点的指针,一个指向下一个节点的指针 . \! S. W3 }5 R% P6 R6 K8 u+ j constructor(val){8 T- |/ p- n; ^# u0 ?+ M. U- `. @
this.val = val;! w9 _: b' j) c. S, F3 {
this.next = null; 6 W$ c( I G0 r! F6 i# p this.prev = null; 1 h7 @ n. n3 H( u' N3 \ } w j7 m5 J* v2 n7 ]1 ]} , D8 f/ G) P7 G, ]& N' r9 |" E) M8 X5 Q( {6 j- q
// 创建一个列表的类 ) B' Y9 K1 r* G6 z. mclass DoublyLinkedList {, S A; O7 h+ d: N1 Q3 P! L
// 列表有三个属性,头,尾和列表的大小; j7 v- V& _8 ~/ a. X2 ~
constructor(){7 u! o; q$ X: A" v" j+ Z
this.head = null " `9 P" Z% H' V this.tail = null ! u3 @# @6 O( q$ ]) l. R# Y0 D9 P1 B this.length = 0 # ]+ v4 u9 R# p' K, p! Q* T }) x- f" f C0 [' h5 t& M6 v- q1 u0 i
// push 方法将值作为参数并赋值给队列尾 5 E& w" l$ ^8 z0 S# e, k+ P push(val){7 L- a. K% ~1 C% [1 Q5 R1 k
const newNode = new Node(val)0 k: B3 K& i5 e# |4 V
if(this.length === 0){' l$ q2 X' y& T3 q& {) B8 v, c5 h/ ]
this.head = newNode 6 a5 x6 b2 `2 } this.tail = newNode# p. z) T- R7 [$ o' ~! J
} else {; V0 `4 i" ^5 ]/ Y
this.tail.next = newNode . u9 ]' H& L z* z* ^4 l8 q newNode.prev = this.tail 0 S4 u( c8 O1 ~" u$ M) t this.tail = newNode" S; _' D$ c' R* ]% P
} % `) V# R8 s: k4 z L# v# Q this.length++' d1 Z' k5 d* T0 D. w/ L" i
return this 5 \6 u/ q. l# }) U9 Z6 M } 1 y' c' u f/ [! P // pop 方法删除队列尾+ r, ]6 c/ i& q
pop(){ 9 J& x3 M$ \" x4 n2 \/ Q# b4 U if(!this.head) return undefined 2 }" D; i, x; s a E) r7 s const poppedNode = this.tail 4 z" w/ @& E. |# m; I& h% b* r if(this.length === 1){ J$ v& n9 p8 { V" ?
this.head = null . J0 Z; G5 H4 d$ I this.tail = null 9 |' a+ d ~4 z# R- k6 n' B! O3 O } else { 2 }: Z& _" h( m# q: Y( f( I this.tail = poppedNode.prev 2 J5 L) j# R6 g. E" s this.tail.next = null * S5 |5 m s5 m poppedNode.prev = null" b. b5 v, c. ]3 U8 |- w
} ; @' Y. d9 T# ^: x$ N this.length--* |& }# I9 N+ N8 m& R9 {
return poppedNode ! U$ M2 x) x9 t5 n }7 q/ X# z3 ~# f$ Y6 }- Q5 Q
// shift 方法删除队列头+ O0 q" j" h' @% z- {7 o
shift(){ ; \+ Z* F% k7 \, O. J if(this.length === 0) return undefined% d# A0 j- r- o: W; @
const oldHead = this.head- o* p, ?! U+ G2 a
if(this.length === 1){ , x; z: Q5 C# N this.head = null 3 d" {1 {; w9 O5 Z8 ` this.tail = null % D" c; `& }* g } else{) k( t* B0 u' x8 t2 M E: `
this.head = oldHead.next5 F/ |9 M2 s0 |& c- ?; A; Z5 ^5 R
this.head.prev = null 6 B; {, Y& \9 V% D* ? oldHead.next = null8 W) H; d4 v( a* }$ f
} - ?* v6 z* P; R+ l( s this.length-- 9 z; g5 \+ B% a+ b5 u return oldHead4 Q# c) t! ^0 B
}) Y$ y& m$ ]2 h: O7 Z4 j; O5 ~9 ?0 ^
// unshift 方法将值作为参数并赋值给队列头& b4 R3 N' e! c$ y
unshift(val){9 X2 z2 K Q5 i! \
const newNode = new Node(val)) f6 L* m- J/ p' U$ t `. K; {
if(this.length === 0) {: Y _) v6 Z @; r4 y' ^
this.head = newNode 7 W3 l; a1 e/ a& T this.tail = newNode) c' U$ e2 d- }' o& f' z
} else {% m( f% v: }1 n
this.head.prev = newNode - _8 h# w d7 n ?1 e2 ? newNode.next = this.head & v' p! o' E1 Q" H1 { x this.head = newNode* Z3 f' n' L+ M' P# \& n) z9 y
}' K( g3 k" c& K# `6 s
this.length++ 4 U" {) @ V0 ]" X return this- W j1 j! ?5 X ^1 k3 Y1 O- _
} 2 }3 F3 Z2 v; n" l // get 方法将索引作为参数并返回队列对应索引的值% V9 M1 F& s7 v: Q6 L
get(index){$ d% i- N3 R$ \/ w6 N% k3 |6 L
if(index < 0 || index >= this.length) return null* a9 r8 y6 }4 R
let count, current2 ^* f- v) o' z) g
if(index <= this.length/2){ w/ r, P0 [6 s0 m count = 0% b( H+ d9 x0 O
current = this.head- E, O; B& O' F* w8 o m
while(count !== index){: Q$ m" a ?1 | t- }& F: X i
current = current.next' K6 y/ Z/ z: j; I, i! {2 q) J
count++ \' Z+ T" [) s# d }; R# u: g* Z5 O2 q1 n( p2 X
} else { ( d/ }; Q% f. [1 F* A0 n' j) Z count = this.length - 10 f. p! z7 N! z: ^# ~) ?7 Z8 r, G
current = this.tail - N% ~. M) Z! [3 a3 H while(count !== index){- O) M. A8 D6 L4 b$ g' ?: Q! \
current = current.prev+ B) g: N3 U1 L e) P
count--) \, r# @$ Y9 | @5 T) f, Q
} # g# q# U- Q& ?% P) u' q }0 q+ z: X! c( U; h# n
return current. Z; w' x! Y( R1 ]3 F0 N5 x
} V( B' O: F" D- }
// set 方法将索引和值作为参数,修改队列中索引所在的节点值为传入的参数值 , f; ^& U! w; H' z9 m set(index, val){ / q; q1 H/ T% J7 _ var foundNode = this.get(index) ! M8 S3 ]' K% F/ D- ^ if(foundNode != null){ , e' n9 P1 @* S& ` foundNode.val = val 2 [/ O( u0 C# A& ^2 S7 T return true 8 J/ B; b& z+ c/ g0 v } 1 J! _' G8 m( `( Q return false Y+ B0 m0 Z2 r- B. t) m3 M
}3 f" Q) d: s( L! D- n
// insert 方法将索引和值作为参数,将值插入队列响应索引位置/ ~6 v: H0 } W5 h; b
insert(index, val){ 7 z6 A8 Y- @. \# T& ^4 e if(index < 0 || index > this.length) return false " @9 g# [2 ?! o1 b: h if(index === 0) return !!this.unshift(val) & [& F7 z# ]1 q5 ] if(index === this.length) return !!this.push(val)4 h$ O2 C) J' m" |. @+ {3 j. W' N
0 H% T* |9 H% g$ ]/ X' h |5 I
var newNode = new Node(val)- N; e5 T- b! |. _/ M. x' Y
var beforeNode = this.get(index-1) % C$ q7 [1 s% N var afterNode = beforeNode.next* b% N- [8 w- ] [! L9 j! E
/ r8 Q" r; w- k6 a
beforeNode.next = newNode, newNode.prev = beforeNode! ~( s1 e( U5 t% b0 i |6 q: o
newNode.next = afterNode, afterNode.prev = newNode: p: ]6 p: Q3 D
this.length++ # h4 ^! J* a: o: x return true1 `5 v% c& u: c* Y; d8 c+ ]4 |* _
}7 Z. E2 B: B3 U
} : f5 T/ I% x8 `$ n0 j- S双链表的大 O 表示法为:插入 - O(1)删除 - O(1)搜索 - O(n)访问 - O(n)树树是一种以父子关系相连的节点之间的数据结构,也就是说节点之间相互依赖。" G, Z& z) ~5 Z# b (, 下载次数: 31)
上传
点击文件名下载附件
( S' d' {, N* p
树结构树由根节点(树的第一个节点)开始,其他所有由根发展出来的节点被称作子节点。树结构最底部的节点没有“后代”,被称为叶节点。树的高度由父子节点相连的层数决定。和链表及数组不同的地方是,树是非线性的,程序可以在数据结构内选择不同的方向遍历数据,从而得出不同的值。而在链表或者数组中,程序由一个端点开始遍历到另一端点,每一次都重复同样的路径。构成树结构一个重要的要素是仅从父到子连接的节点是合法的。“亲属”之间或者由子向父节点是我连接都不被允许(这样的连接会形成图表,是另一种数据结构),另一个重要的要素是树只能有一个根节点。程序中使用树的场景有:DOM 模型人工智能中的情景分析操作系统中的文件夹有不同类型的树,每一种类型的树的值都遵从不同的模式而组织起来,这样也就适用于不同的解决问题的场景。最常见的两种树是二叉树和堆。二叉树二叉树是每个节点最多只有两个节点的树结构。: z. [ Q3 w4 P# ` (, 下载次数: 26)
点击文件名下载附件
- _8 W" ^ G* m 加权图想要了解加权图,可以想象你需要向用户展现一个标注了不同地点的地图,你需要告诉用户从一个地方到另一个地方需要花多长时间。加权图就可以用来表现这个场景,你可以使用节点来存储地点的信息,节点之间的连接就是两个地点之间的道路,连接的权重代表从一个地点到另一个地点的物理距离。9 Z. z% u z" ~8 A/ P; t (, 下载次数: 34)
上传
点击文件名下载附件
; l- I% Q/ Z2 I9 Z) P 加权图被大量应用在定位系统你应该已经猜到了,无加权图即节点之间的连接没有被分配权重,所以节点间的连接没有额外的信息,只表达节点间的关系。如何表达图在编码图的时候,主要可以使用两种方法:邻接矩阵和邻接列表。让我们分别看看这两种方法的优缺点。邻接矩阵是一个二维结构代表图的节点和节点之间的连接。如果我们使用这个的例子: t. b I" S: R: h& o (, 下载次数: 25)