/ Z/ L, g9 p: }/ x6 s/ x( q4 f% x
【Java演示】什么是链表?数据结构3 N7 [9 D% v( N' v; q
一、单向链表
) _( F) t# p! l8 g; P. C
: D& ]! {9 g% X% s+ F6 S* r" w' \
$ k8 O1 x, g8 q. `7 I( U链表(linked list)是一种在物理上非连续、非顺序的数据结构,由若干节点(node)所组成。
% A- ^" D& `; e! j* O6 Z单向 链表的每一个节点又包含两部分,一部分是存放数据的变量data,另一部分是指向下一个节点的指针next。4 V( X6 n: U6 Q& G
链表的第1个节点被称为头节点,最后1个节点被称为尾节点,尾节点的next指针指向空。" ~, H7 R2 G. W8 E- L: O% j
7 m" c6 {) U- D
什么叫随机存储呢?0 @' S& ?+ w8 }8 K1 F9 N9 Y$ N
, f4 F# c: ~ F& Z0 ^如果说数组在内存中的存储方式是顺序存储,那么链表在内存中的存储方式则是随机存储 。: b; {5 b1 X1 }! S5 F% U2 P
上一节我们讲解了数组的内存分配方式,数组在内存中占用了连续完整的存储空间。而链表则采用了见缝插针的方式,链表的每一个节点分布在内存的不同位置,依靠next指针关联起来。这样可以灵活有效地利用零散的碎片空间。
4 Q# A% Z6 `1 L! _% n6 X8 K' ^, y
, x+ [' ]+ e) }+ D1 C5 P$ l9 d
# y" s6 ^/ c6 }0 x, F) {图中的箭头代表链表节点的next指针。 链表的基本操作1. 查找节点在查找元素时,链表不像数组那样可以通过下标快速进行定位,只能从头节点开始向后一个一个节点逐一查找。
9 o, Y4 u+ X: f8 R. {% d' g- s% o6 A ]* N* J& ^
/**
5 A+ G1 W) m8 U& y" V * 链表查找元素( @/ J' D, N: v1 k& R, n3 _
*8 B k7 B, a Q( T! l8 e" G! o4 E+ K
* @param index 查找的位置
~/ G& l' V7 {" R) G4 d2 z: \1 Q * @return index位置的Node对象
8 }, M/ F% @, \ X2 {3 H0 V */
1 B* b4 Q* z" I9 c( C public Node get(int index) {
0 ^3 L. h: B8 `9 D- e if (index < 0 || index > size) {
% n) w6 c! R! ~4 o8 T throw new IndexOutOfBoundsException("超出链表的节点的范围!");. X, [3 b, ~- D6 ?& n2 }
}+ l$ X0 d+ ^) w2 T. M
Node temp = head;; G4 u/ n! r7 h/ u
for (int i = 0; i < index; i++) {
5 o$ Z0 l8 G8 a7 @- W0 r9 G temp = temp.next;2 L, ~: k0 J$ @" X' [9 w& c
}
: p1 {: j* |& |- r" T return temp;+ \+ T" k& q G
}
! ?: @- J$ |4 G/ V/ |6 v# ~2 R
( W6 B- T; J& R链表中的数据只能按顺序进行访问,最坏的时间复杂度是O(n) 2. 更新节点
+ Q/ W: ~/ h+ ^( ~( c8 U" E' L3 ^( \6 D' [+ j2 `
如果不考虑查找节点的过程,链表的更新过程会像数组那样简单,直接把旧数据替换成新数据即可。
* x) q6 n+ y0 U如果不考虑查找元素的过程,只考虑纯粹的更新节点操作,时间复杂度是O(1)
# {6 Z- U0 V2 H/**; C" @/ [! T% L0 o. J
* 更新节点 将列表中指定位置的节点的data替换为指定的data。
, N/ Z( d. u& S u2 T *, C- H+ s1 c. U7 T7 r/ t
* @param index 需要更新的节点的位置& `" H+ Q5 L, Y; ]6 t( }% C
* @param data 新data# V0 n/ i% ^ C9 w. g: ^) c8 m, G
* @return 旧data( @1 T7 ]$ S6 t: z, C+ f; w- j
*/
$ i+ ^ ?8 e2 C# {7 f public int set(int index, int data) {
3 F, j) m3 W9 l8 T* i Node x = get(index);
# j3 M7 V6 E" a- c5 C: u int oldVal = x.data;" {4 m' p& G @" y
x.data = data;9 g) B6 B3 A+ O
return oldVal;
4 J& X& L% ^% a3 |! I8 `3 J/ ]! p }- h) u' r- b7 {
( D0 ~5 g- e. c5 V. L; W" p. x
3. 插入节点只要内存空间允许,能够插入链表的元素是无穷无尽的,不需要像数组那样考虑扩容的问题。 与数组类似,链表插入节点时,同样分为3种情况。 - 尾部插入
- 头部插入
- 中间插入
* T* I/ x5 O) K& s @0 ]- m$ ~/ Z/ n 3.1. 尾部插入尾部插入,是最简单的情况,把最后一个节点的next指针指向新插入的节点即可。
0 L7 q6 M' f+ K+ b
f% f: C+ T, h- e2 ^ F
3.2. 头部插入头部插入,可以分成两个步骤。 - 第1步,把新节点的next指针指向原先的头节点。
- 第2步,把新节点变为链表的头节点。9 P$ |# C: [1 i# C8 k& J* s& [
% n) _9 u' I1 D
! ?2 ^7 Y o8 ~; M
3.3. 中间插入中间插入,同样分为两个步骤。 - 第1步,新节点的next指针,指向插入位置的节点。
- 第2步,插入位置前置节点的next指针,指向新节点。9 X6 p4 n7 ^+ h8 H* m
' N% ]" H [* I+ C: K3 O
# w# w, ]8 }4 _: }+ Q5 G三钟情况的代码合到一起/ {3 u1 L2 t! _% ~/ q4 o+ a
! d% L& Z0 l# T: S: Q+ Y8 A5 |/**
! w2 _' u0 Q/ s6 q9 ^9 q * 链表插入元素
1 P4 S; u7 K* t; T# } *
( |4 W! T4 L( S0 C. s* v! Q3 r * @param index 插入位置
1 ^! l- K2 ?& i- D: ^' O' z" v * @param data 插入元素 被插入的链表节点的数据
+ ~) Q& T& F8 S6 C8 w */
8 B8 W8 T% k3 n" e1 y; \+ T0 v2 M public void insert(int index, int data) {+ }! Z0 d8 `+ H3 q3 g0 P
if (index < 0 || index > size) {" K+ u9 {: ~/ C+ j) y/ }
throw new IndexOutOfBoundsException("超出链表节点范围!");
+ I" D+ k3 c! C% g: r: d- L0 t }+ q* p% x1 ]& Z/ J5 z$ D% y; h
Node insertedNode = new Node(data);
b9 O' b6 l0 }! L% B, o" u if (size == 0) {# j/ B& E4 D7 T8 u$ \1 Y0 v) r4 |
//空链表( Z/ D. x5 W( K. F! ^! X
head = insertedNode;" G& S5 z0 b# p: a5 S% r, F6 J* B% ?7 b; H
last = insertedNode;
* X2 ?) X" q7 V$ h8 r } else if (index == 0) {1 ?2 ]& h; E+ B$ p3 l
//插入头部
6 a0 I' h5 b/ U9 a3 @+ F: c7 e insertedNode.next = head;
; t1 D- R0 U# l2 H7 S0 ^/ X: n0 t2 q5 w head = insertedNode;, Z. h) b1 ~# r
} else if (size == index) {, g" }- C% B6 m) _
//插入尾部 q0 L, i. k1 l4 h$ ?; V! C
last.next = insertedNode;
" o& Q" [4 b: f' {9 o% U last = insertedNode;
% @" A: A/ o n0 F7 e } else {$ o. m5 [* r, r. [4 L, ]
//插入中间* V3 | t1 p( l' C3 ] Z7 X
Node prvNode = get(index - 1);1 o( n; n/ j' M
insertedNode.next = prvNode.next;
4 i8 l5 C: n- S" k4 A3 e8 W& c; ^0 P+ R prvNode.next = insertedNode;* n0 s( F! x# \* L M ]
}
! @2 I7 q6 ^( k: g9 _( V j size++;
' m7 s$ }% ?9 F) t5 K+ C/ ` }7 ^/ e+ `- [( g1 `# \. {5 {; T
& Y+ |* F G6 d1 u
/**
2 _3 d* O* G/ z+ f7 q* Q) E- Q * 链表插入元素
) L+ ^( O+ ^+ `/ x3 v *
. W+ [! f9 Q* i: W4 T& ]- v" p * @param index 插入位置: }) E5 e" Q/ ?$ y& [
* @param data 插入元素 被插入的链表节点的数据
/ i; n( U7 ~3 U1 l* C( t$ I. Z */
7 R# p6 F; B4 ?2 M9 P; | public void insert(int index, int data) {% k1 n0 K5 g8 q- p% W9 |9 | k
if (index < 0 || index > size) {/ j8 ]7 ~% O }( L
throw new IndexOutOfBoundsException("超出链表节点范围!");) n: c8 ^$ r4 h& U/ S/ }$ Q5 k
}
. w7 M$ D p2 A2 D% ^; ~9 L4 T1 Q/ n' d Node insertedNode = new Node(data);2 j' |8 E" J7 k& f- p# Z
if (size == 0) {: J R. M: @6 G& x" k: X1 ?
//空链表# W6 \! E1 l% b$ @5 }4 O
head = insertedNode;
' \+ ~$ G: L/ h* J2 q5 w last = insertedNode;4 K0 I& m: ~) z4 Q1 ^
} else if (index == 0) {) o y4 q) T& U U8 v, ^
//插入头部; b1 d& _* a( ?1 R6 c
insertedNode.next = head;
4 Z) E- ^, ?9 M4 H E head = insertedNode;
9 R* a7 \) E2 p } else if (size == index) {
% E! `5 \1 X/ T1 j6 ^. u/ q //插入尾部 W4 [( u+ @" ?) u
last.next = insertedNode;
0 s& Z3 D6 P/ K5 r" l2 L7 R last = insertedNode;, ~' d, X' z; H& Q! X
} else {7 S" @8 s# w/ N$ T$ {* F
//插入中间/ c: [& r% i- Q, `# |
Node prvNode = get(index - 1);
4 x5 {0 Y8 E) L5 L, F* T- Q insertedNode.next = prvNode.next;
3 i& D* ]- J6 J$ P! [; g prvNode.next = insertedNode;
" v0 x7 i% Q! p }
, V4 ?- \7 H- q4 }+ x/ W# G size++;
3 S6 g+ h. m6 e; b* `) D8 G }2 Y, [6 v+ N0 p
4. 删除元素链表的删除操作同样分为3种情况。 - 尾部删除
- 头部删除
- 中间删除; R: M, l' ~1 I6 J' a
4.1. 尾部删除尾部删除,是最简单的情况,把倒数第2个节点的next指针指向空即; J) S, w1 P2 M% t8 S$ r" W
可。
6 V$ O$ F- [- t+ Y! Q; J& r( u- a( y+ o* P; A Z6 C
4.1. 头部删除头部删除,也很简单,把链表的头节点设为原先头节点的next指针即可。
% J' q) A! W: K8 m, k1 Z
$ p; _8 m2 b, g; ?; N6 B
4.1. 中间删除中间删除,同样很简单,把要删除节点的前置节点的next指针,指向要
6 F6 c, q A7 n, @删除元素的下一个节点即可。
7 P3 ~& l7 q! @$ K
P4 [9 h1 M3 ~ a2 c
0 R Q/ f5 h6 O! U, {4 @! m
这里需要注意的是,许多高级语言,如Java,拥有自动化的垃圾回收机制,所以我们不用刻意去释放被删除的节点,只要没有外部引用指向它们,被删除的节点会被自动回收。
6 S. j9 N$ l2 o8 P2 I; A5 M' J如果不考虑插入、删除操作之前查找元素的过程,只考虑纯粹的插入和删除操作,时间复杂度都是O(1)" o- H: R6 d8 c
/**4 S |% I8 T/ }
* 链表删除元素% v4 \2 s2 G( S5 ^; a
*
6 Q( w4 I8 Q( |% T# I. Z6 [ * @param index 删除的位置+ ^) J a/ a2 _. T' V6 T0 G: Y
* @return 被删除的节点5 k6 ~$ t9 L7 N' h) f3 ?
*/
2 j" f% y i: u! s& V public Node remove(int index) {* _8 y) H0 w0 z5 t
if (index < 0 || index > size) {+ L! o F: u7 I- _0 F; D2 }
throw new IndexOutOfBoundsException("超出链表节点范围");
; K4 `8 y) t& b1 `! E }0 W* l. v; r5 z0 M7 {! ~" W& k
Node removeNode;# ~- ^+ A! \5 } o) a/ D
if (index == 0) {6 k$ m% Z7 q$ @3 X3 D4 X8 V
if (size == 0) {
' V3 p7 }/ a# [ throw new NullPointerException("当前链表为空,不可以进行删除操作");
# }0 |( y* U/ r3 u }( U. t- j. L y' K( S
//删除头节点# i' ]7 Q6 ^. l$ {! k
removeNode = head;
& J( w: J; _1 l head = head.next;8 m, X6 q; i/ s( r4 s
} else if (index == size - 1) {+ T2 t) C ?0 @+ W; F, A
//删除尾节点7 N7 d6 d5 t) @2 e
Node preNode = get(index - 1);$ W' h m7 Y, d6 a2 `
removeNode = preNode.next;9 R6 J( o0 z; }& B \% @
preNode.next = null;
4 c6 j) J% ]0 z( [( X& u5 d7 L last = preNode;5 x I1 A* |% s
} else {, {* H0 b% z4 p% u+ d
//删除中间节点
% t8 F" R+ j% Y4 X7 R# ?! g Node prevNode = get(index - 1);
3 T$ t" k$ E/ f; S removeNode = prevNode.next;
+ O% [. C% H- H; U# R" W' {6 b prevNode.next = prevNode.next.next; M) Q, `0 w0 z4 V& _
}
% U" x$ s- A* {; p- q size--;$ Q3 M( Z: P' i
return removeNode;3 z, w& }% k2 h" @) [* u9 T
}
5 T( N \5 v% o. o2 I7 LJava实现链表的完整代码package chapter2.part2;
3 {" h, F& `3 q- r" T8 _/ u" g1 a. C' f( Z; K* N- ?) W9 `
/**
$ E- q+ L* g& H0 I * Created by IntelliJ IDEA.
- A8 J! F4 l* v, K) e1 o7 ` *6 p) \3 }& A6 q% i, W5 @* V
* @Author: 张志浩 Zhang Zhihao5 f& ?7 I3 m4 z" `$ l7 R
* @Email: 3382885270@qq.com
8 i+ J/ E! B% z * @Date: 2020/5/3
0 J3 I; k# F0 C' t * @Time: 13:391 l' g2 D( X+ i2 @: G$ x
* @Version: 1.06 k/ a9 g: p# O) z
*// W5 r4 I3 V8 Y5 e6 |
public class MyLinkedList2 {0 {1 s2 m% ~+ G8 h3 J/ y; Y
private Node head; //头节点
* x" v9 D5 L, K' x) e$ _ private Node last; //尾节点. ^5 N, d) y) M; g: p" h
private int size; //链表实际长度
/ c6 G# e, Y; f1 d! z+ {: m$ \9 p/ a2 w5 |. i
public static void main(String[] args) {
c3 F' ^; p/ a2 g MyLinkedList2 myLinkedList = new MyLinkedList2();+ Q+ Z9 _: H ?2 ~
// myLinkedList.remove(0); // java.lang.NullPointerException: 当前链表为空,不可以进行删除操作
! W' P' r! N0 L' u+ b# f! ]" i' {// myLinkedList.remove(3); // java.lang.IndexOutOfBoundsException: 超出链表节点范围
- N- u9 \' s; _+ r myLinkedList.insert(0, 3);
" Y6 ?& r, `/ ~2 I# m% Q myLinkedList.insert(1, 7);
/ S# q2 q7 J$ d7 O/ L! e' }! M( e5 R, G myLinkedList.insert(2, 9);! Y& L: |: ~* [" h
myLinkedList.insert(3, 5);& V5 _7 E- g' D# h) ]% K- {! A; Z
myLinkedList.insert(1, 6);* U+ J" p1 N& V; _% B& ~) b9 j" O
myLinkedList.remove(0);/ A3 Z- W7 T; Q5 `6 R
myLinkedList.set(0, 23);! X3 F- f. H- }& h0 B# n# W
myLinkedList.output();
( Q4 C) ]$ F' @( s+ U2 w$ v2 N }
2 F( N9 e, W0 M+ U7 U" p( ]) f4 i* y
/**
) Z3 F& o6 A2 P! l * 链表插入元素
: L. G1 g( s+ K; p- S; j *% h$ O* ~7 e; S1 {+ x6 r, s
* @param index 插入位置9 T* G% P! v9 \1 D
* @param data 插入元素 被插入的链表节点的数据, q q5 v' s) b: r J' e
*/
/ H* o, Y" _' [1 O9 T ^ }1 h& j7 y public void insert(int index, int data) {
# K( i, X/ k) T4 f$ [3 M" b if (index < 0 || index > size) {- ~& z- A, N3 Q" _6 f
throw new IndexOutOfBoundsException("超出链表节点范围!");
9 P0 o5 B, I7 |6 [2 \ }+ Q+ _2 U# N) y
Node insertedNode = new Node(data);6 d- E% I) }5 M( E. \5 [* Y
if (size == 0) {+ l4 y, ^" ^9 I( _2 N) X8 ?6 J
//空链表
4 q- M$ k+ Y) S* R4 j; }8 a head = insertedNode;# \+ P. F/ e% b8 n! d& n
last = insertedNode;7 H! Y/ |0 a* W8 B, j: X
} else if (index == 0) {* a/ G( ?& y' p0 o/ j
//插入头部$ _+ J& q: V. F @
insertedNode.next = head;
# p7 G2 Q* p# _" x head = insertedNode;3 f5 t8 v2 t6 }
} else if (size == index) {# I) P0 |1 T! ]2 }2 t. q- m. e
//插入尾部- O4 G- _% U- n% P# }2 e& {8 _, b
last.next = insertedNode;
6 `! f+ k9 _, ^; X last = insertedNode;
- y! u, `& l3 E) ?9 Z' S } else {* T0 i+ E& v# S
//插入中间
# x: w" `/ F& H( ?3 a" O Node prvNode = get(index - 1);
7 ^ a' P, z. _& Y, p( _; g6 ~ insertedNode.next = prvNode.next;
/ Z: D3 d# C5 _% s) S& ~0 D+ {. k2 } prvNode.next = insertedNode;
, L$ g) D& w, B% b }! F, S. m; k# `5 e1 M( l5 s7 x5 I
size++;9 G. T% Y: H( d0 m! e
} f" d6 {; ]: F$ {- D6 L" w
0 h+ e5 }' C4 l' z* f /**, n. `5 {% n# f# E. t7 c$ d2 {2 {+ x
* 链表删除元素
4 |6 J* @; v( G* r0 B *3 g: _9 r( i; v- V
* @param index 删除的位置. w) \ Q6 d Q6 U
* @return 被删除的节点
$ `1 G6 c% C0 X% `3 z4 V- S */. ?6 A0 H# P2 |
public Node remove(int index) {
/ Q0 c& A$ H' T4 D) ^9 g) T if (index < 0 || index > size) {
4 t* E7 p0 _: ^& @/ `7 O l9 _" l throw new IndexOutOfBoundsException("超出链表节点范围");
- L& B/ @ ^. u+ r- A& L }
: W! |! F- F: N$ }$ v3 j Node removeNode;4 d) }! f/ ~: q, y- S1 ]
if (index == 0) {
* J- B) U" e ^7 s# G9 j if (size == 0) {
0 H3 \/ m r$ b throw new NullPointerException("当前链表为空,不可以进行删除操作");
% U0 g6 v( D" q0 C" `& z+ D6 X- X }
0 j: x6 ^) f/ S6 K, L //删除头节点2 R9 a5 U' U2 }( p- u- Q0 Q
removeNode = head;
. L4 P, R2 \9 W5 | head = head.next;" g, X/ R0 C9 q2 J/ O$ ~' }
} else if (index == size - 1) {
; V) h+ p0 d/ X' ], M0 m& N //删除尾节点
% v/ Q- x' q: A Node preNode = get(index - 1);# A- h3 s% }2 c, {9 p# z
removeNode = preNode.next;
" N% p; E3 {# b& s! J) ]# ? preNode.next = null;
1 s+ J8 j c. j, {; T- I/ k last = preNode;
0 w- w& J" M2 w/ ? } else {2 }# R" D) }' B% e
//删除中间节点- G5 ^* z, B, p; ?
Node prevNode = get(index - 1);- ~* N; q; }: E
removeNode = prevNode.next;
5 R* r0 _8 A3 O( s% u3 i prevNode.next = prevNode.next.next; x# B: F' F6 ?; c6 U( J8 U3 W
}
: I9 O6 E/ Z2 T! V, x# P size--;, I9 {* Z6 I$ K
return removeNode;7 {. N) r* U8 o7 u5 t- D# @
}
# v2 ~& f" s) p. u) r" T
& ]7 f8 @0 G/ ?! y /**
, I: Y1 M0 z. m. \ * 更新节点 将列表中指定位置的节点的data替换为指定的data。; k+ r- \( @! e
* v7 J2 t& K3 P# B* ?! v- Q* q+ @
* @param index 需要更新的节点的位置2 A6 Q& D$ T% J- T8 m; _3 z4 Q8 Z
* @param data 新data
* [: T; K( [9 Q0 B* k( Z * @return 旧data
' J( g, C( E* z4 V5 g3 B) B% u */, g/ ]0 \9 F" Q. b$ {4 [
public int set(int index, int data) {
$ N% b- u' g9 j! i4 { Node x = get(index);0 w- Z' n: @' Y" P6 L4 P
int oldVal = x.data;* D& K y. w/ F
x.data = data;
}2 j9 V% s& g, } return oldVal;3 O. @4 Y. s6 W0 I) k6 G2 y
}2 `7 U2 W7 i: l, C0 x. H9 B% ~0 a
Y! n, s4 n* T% l7 D: A C
/**, B5 M* Z) ~ H, [! _) l
* 链表查找元素& p+ I x1 o/ S) h) n7 z
*3 N$ C( X5 s, q! ^! u% R* `
* @param index 查找的位置
3 `8 N/ ^" D6 T/ o * @return index位置的Node对象
l1 G: U. V5 | */
( K2 i% k. _1 I public Node get(int index) {
! X# q: u* w( H& a if (index < 0 || index > size) {
/ B$ W v! W8 _7 B2 \* R2 J throw new IndexOutOfBoundsException("超出链表的节点的范围!");
2 q; e1 p! t$ ?* X) c1 v7 Z }
. { f8 C J: |3 M, }) Z- ] Node temp = head;8 a& F2 t/ h# g f7 V' L9 A( m' J0 ]4 ?. z
for (int i = 0; i < index; i++) {
0 Q6 ~9 n0 ^4 e temp = temp.next;
. l% C$ z, Z0 e+ K: q- ~ }
+ z: N6 i. C) q return temp;
# b' I2 Z) T6 ]- C5 N7 S: x+ r4 V: ` }( m$ T/ p' x% d$ y
, d& i$ T( t1 ?0 p) { /**
- Q* D- P. i; x1 c! p! G * 输出链表5 [3 j3 y+ K% b1 o3 Y; r+ \
*/
& k8 X3 @3 V, u3 ] public void output() {
' U# s. K0 J: Y E; h+ ~ Node temp = head;
+ Y$ l0 {" @+ c+ g, K2 m' ]+ Z while (temp != null) {
& `* M/ ^1 u$ D1 l* [% A v System.out.print(temp.data + " ");" q. o8 M$ c6 ?! X# |6 L7 r
temp = temp.next;" O6 `" }" `' X4 u) [
}* [# z( y) E5 e- R% A. Z2 Z
}' R2 s- ^1 \( u* _
# Z+ D' p# e: Q7 F /**- ]% g( ~+ |. F! W
* 链表节点) `! ~6 [! l( } n! s
*/
3 Q9 W0 @& k& ~% K. }7 E class Node {
- |7 Q% W8 u8 F2 T' _5 d! R int data;
1 ?" R+ t% F/ }% S7 L% W6 k Node next;8 S: p- K* K' m, b3 O3 C" P7 q- H5 r
) N+ K+ P2 E1 q, |
Node(int data) {
4 X8 `0 d/ V' Y8 I this.data = data;) S7 Q% k# Y" M
}
: c: _$ U. v5 P: C A2 k8 u }: M7 }" y$ s; B- o& H* h
}# Y: \1 G5 @ Q8 L F
" m% c6 B! A: U% H) K# U0 z c
# W0 ?, e/ W6 f+ T# J* \. K二、双向链表
9 w4 I" Q, s& k% z* h4 f
双向链表比单向链表稍微复杂一些,它的每一个节点除了拥有data和next指针,还拥有指向前置节点的prev 指针。( P/ z$ c1 l- Y9 F' q
$ v: q9 Z. L+ C0 z c# A
* \" ^' {) q: {2 C$ ?
# b* k1 Z' ~! p" w2 e" U
3 S* y5 S O" n' g$ ?/ z————————————————2 b. |; K: L* a
版权声明:本文为CSDN博主「爱做梦的鱼」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。# C8 [: c+ V+ o; S; C% V# U
原文链接:https://blog.csdn.net/weixin_43124279/article/details/105904468
1 [! x% c9 G) k. n2 b; }% J% F3 L, q# a* c
+ l) W4 i1 y+ Q5 D' T
|