4 Z+ ?8 l8 Z8 z- }+ V( E
【Java演示】什么是链表?数据结构
5 W _5 ?, ]( X- ^5 N" v5 [一、单向链表( `+ |$ p4 B, j6 J, l
1 k9 w* Q3 }1 {- ^
0 p0 m' _! i* t6 ]# p0 }: L
链表(linked list)是一种在物理上非连续、非顺序的数据结构,由若干节点(node)所组成。1 m; Q9 m B( `
单向 链表的每一个节点又包含两部分,一部分是存放数据的变量data,另一部分是指向下一个节点的指针next。% X: G# K( `; C/ T
链表的第1个节点被称为头节点,最后1个节点被称为尾节点,尾节点的next指针指向空。
u6 Q' V* @, v$ c" S9 F3 l j6 |% t; F: t Z* x: k4 ^- v, a4 E3 b
什么叫随机存储呢?
" m" k, I- I, J: _- @! W( {* s" ?) H4 K; G( Z# w
如果说数组在内存中的存储方式是顺序存储,那么链表在内存中的存储方式则是随机存储 。, b+ x/ Y: i; e9 U( U5 P- T, y
上一节我们讲解了数组的内存分配方式,数组在内存中占用了连续完整的存储空间。而链表则采用了见缝插针的方式,链表的每一个节点分布在内存的不同位置,依靠next指针关联起来。这样可以灵活有效地利用零散的碎片空间。
. o: Y( L1 }7 n
( b. ]5 e2 V. y" A- {0 S/ a( P$ P7 J1 B) Q0 x
图中的箭头代表链表节点的next指针。 链表的基本操作1. 查找节点在查找元素时,链表不像数组那样可以通过下标快速进行定位,只能从头节点开始向后一个一个节点逐一查找。
- u( f$ Z2 Z! m- B P
6 j, F: s( T7 S& l* A& |
/**/ S; S0 m6 O7 N# J( t2 v
* 链表查找元素
+ Q- o# F' {& {9 Q; a, ]2 U* R# ?3 L *
0 x9 }( `7 n2 T, [) } * @param index 查找的位置
9 T# P/ p; Y3 w9 e! } * @return index位置的Node对象
' g! c# f9 f, Y4 W6 Y: m */+ S, ^# }2 z8 K5 z3 {7 R7 r0 N! v
public Node get(int index) {) {7 G% h: ~) {) W: U
if (index < 0 || index > size) {
% L8 M* Z+ U. n/ Q( l# a7 q6 v V throw new IndexOutOfBoundsException("超出链表的节点的范围!");0 B" Y! Z% j" @. `' O2 Y: l8 F9 e$ ~
}4 G& a3 U+ W9 Q
Node temp = head;9 F8 [9 X" u$ {" n$ S0 i' z
for (int i = 0; i < index; i++) {
% ^. ~. b) a d temp = temp.next;' [0 i8 T: X9 E3 v% f7 @
}' k M* q. j" j
return temp;
" N0 J& \- R, _ }
2 ^' v/ G+ [% A& p" G: |2 G; Z6 U$ `/ ~9 [7 Q
链表中的数据只能按顺序进行访问,最坏的时间复杂度是O(n) 2. 更新节点
9 w, ^& E9 y7 P8 M8 e$ _5 D6 o8 P+ R- W' w( l h) T# C# n$ u, h+ f1 h5 b
如果不考虑查找节点的过程,链表的更新过程会像数组那样简单,直接把旧数据替换成新数据即可。/ z& A6 O9 U2 d) `# }
如果不考虑查找元素的过程,只考虑纯粹的更新节点操作,时间复杂度是O(1) i. I- f: W0 P3 C8 y e5 s
/**, b0 F' a. R9 e; z' }
* 更新节点 将列表中指定位置的节点的data替换为指定的data。
; f3 V+ Z1 {9 X *
0 _7 c$ {9 P/ D$ v& S, [ {, c * @param index 需要更新的节点的位置
$ j5 o4 @$ \8 Y2 k! p * @param data 新data, k' g- F% `0 X' k% A
* @return 旧data
0 k1 C/ I4 B2 n4 C */
- D' v* e& p+ i. l* `8 j public int set(int index, int data) {
[8 W- h H8 k% G0 O& Z. N- E' u; \ Node x = get(index);" ] b ^: V1 {
int oldVal = x.data;
5 i& p& a5 z$ y7 r" |/ S x.data = data;
3 u3 [7 v j1 e; S! t9 A/ L! r. k return oldVal;
$ E4 q) G$ S$ h& L: |9 {: l }1 q4 B9 d* t% K7 g
1 u8 @0 C4 s: r, K3. 插入节点只要内存空间允许,能够插入链表的元素是无穷无尽的,不需要像数组那样考虑扩容的问题。 与数组类似,链表插入节点时,同样分为3种情况。 - 尾部插入
- 头部插入
- 中间插入7 X0 L8 ]9 J& K
3.1. 尾部插入尾部插入,是最简单的情况,把最后一个节点的next指针指向新插入的节点即可。
1 O- S R2 ]7 i! p8 W
( n& x d P( \1 K- Y( _3.2. 头部插入头部插入,可以分成两个步骤。 - 第1步,把新节点的next指针指向原先的头节点。
- 第2步,把新节点变为链表的头节点。' m1 A7 K% G' p
+ K! b2 ^, u9 Q0 X5 M; G
: y# A) d6 D2 `2 I8 s) r% g) x
3.3. 中间插入中间插入,同样分为两个步骤。 - 第1步,新节点的next指针,指向插入位置的节点。
- 第2步,插入位置前置节点的next指针,指向新节点。
6 a# \2 e$ o7 Z' |& O
: O$ O/ T6 E3 C/ m. U% ~7 M
8 m# m! ^+ N/ C7 h9 v/ d三钟情况的代码合到一起8 W# B7 H" b* I! b. F) j
! A* v1 Q) i1 z* L7 W- }( { S$ w
/**" s9 H+ W- U1 ^& M
* 链表插入元素
. f0 U1 |/ m2 [; | *4 m4 @4 Y( b7 R; e* e8 u9 I/ A, q
* @param index 插入位置
5 y6 v0 U1 K4 g9 f, a5 G' o. b/ D * @param data 插入元素 被插入的链表节点的数据5 G8 g) U, N0 m! r$ E
*/ ^7 W/ W7 B4 z i* c- v
public void insert(int index, int data) { y. P/ }% q* s I
if (index < 0 || index > size) {
9 t* v/ F1 X; x3 m, b8 s throw new IndexOutOfBoundsException("超出链表节点范围!");
- {6 r; a9 R, q. v8 q! |1 Q }
5 a% ?& x* T1 @: u' o Node insertedNode = new Node(data);" o6 K+ ?6 ~5 R; A6 S! R
if (size == 0) {* z5 N3 U2 V d# ^+ ~& u* H
//空链表: a# i' ?8 J) [" S8 _ H" c
head = insertedNode;
" z7 k: `0 X# Z* Q last = insertedNode;
+ y1 l, X4 m n3 `6 s: z% R } else if (index == 0) {
) @$ |( S8 j6 j# L' u0 u1 Z2 M+ k //插入头部0 ]) `/ n1 N% R, g# m3 ?1 h
insertedNode.next = head;
9 M% Q! B- r0 m; n head = insertedNode;
0 c) N+ c) u6 q1 W* I9 T% L- f } else if (size == index) {& B# f# T, s- D
//插入尾部8 M* _* W1 E. ~# E/ t; O- W
last.next = insertedNode;- Q; P, D( C' m: _2 Y2 \
last = insertedNode;* s! L( E: ^. u( N
} else {% ?: k6 b+ p5 e6 R- `. v1 }
//插入中间
+ f6 W; p! y& m, H Node prvNode = get(index - 1);& e" l$ D8 Z7 u$ ]9 Y$ v
insertedNode.next = prvNode.next;
( @1 B* W8 D% ~6 [* u& b prvNode.next = insertedNode;; y6 s/ H6 B1 b( b
}
* R' I% I" ]6 h0 Q% G size++;
; @ Z9 r/ r, P }
O$ K$ J9 k& n5 w
# h, O. K* H( D( c/**/ m H8 s9 d9 ^1 m! R; [" B
* 链表插入元素
* w) p& S w* U *' {; s5 L! |6 F
* @param index 插入位置
c8 k5 I! j" K* }6 f+ t * @param data 插入元素 被插入的链表节点的数据
: X- M+ P9 v# v' G2 J& W: ` */
; F9 p# P4 l8 S( D1 i* g& x- Y& o public void insert(int index, int data) {( ^5 o* d7 G" P( N
if (index < 0 || index > size) {
6 s( W; \: F6 n) d9 | throw new IndexOutOfBoundsException("超出链表节点范围!");
( a: \3 M4 ]$ f0 _% _; S# R }" \' F( J+ ~6 O) z; x# d
Node insertedNode = new Node(data);0 n, }( M7 c. Q+ \
if (size == 0) {
# G. z& ?% G9 ^4 _ //空链表
. I$ s. i/ ^$ I. B/ J head = insertedNode;5 h' X' F7 G9 b$ M1 D3 [( \
last = insertedNode;/ V9 ^1 R: b- e* c; y, O
} else if (index == 0) {
$ P2 Y$ N1 d) ? H' u( c //插入头部
; a7 [6 s* X& L0 h3 C insertedNode.next = head;
/ n5 C+ \, f6 v head = insertedNode;: ^& V* d" A3 o+ a: K; A
} else if (size == index) {
0 f: ]! q8 O$ t+ v //插入尾部
& g4 E0 d9 n* K( l/ A, B) t5 D last.next = insertedNode;& d; j6 K* C: H. a) P& z: z& {
last = insertedNode;* b3 k+ h! {( q2 ]8 Q2 h
} else {7 }1 N7 g g, w5 y& D
//插入中间! k; G v! B" s7 H7 R
Node prvNode = get(index - 1);
# ]0 |: x/ ?2 o6 H' M8 d insertedNode.next = prvNode.next;- q9 i9 {+ d6 L" D
prvNode.next = insertedNode;
! [5 h+ V# N" [0 p: y# X7 i! z }# Q1 ]9 r* J A6 y8 B m
size++;
& P# \& L9 C' b& ]" U r# k$ C }; \" N+ R/ l) l$ P( g/ q( g
4. 删除元素链表的删除操作同样分为3种情况。 - 尾部删除
- 头部删除
- 中间删除
4 A# Y/ C0 T/ {/ \5 `6 R H 4.1. 尾部删除尾部删除,是最简单的情况,把倒数第2个节点的next指针指向空即3 }) Q- P+ d. i- E. ^$ o) M
可。
; Y+ Y2 z3 k' w: v+ b8 T: |" Q6 @ v( b7 a, J! L: j
4.1. 头部删除头部删除,也很简单,把链表的头节点设为原先头节点的next指针即可。
1 t! o* M9 t( [) A" D$ ?
0 ^& g+ Z* c Q( d+ \4 |. z. V* Y' n/ f4.1. 中间删除中间删除,同样很简单,把要删除节点的前置节点的next指针,指向要# d5 y6 n$ @ _, C2 D% _8 z
删除元素的下一个节点即可。
% [0 a6 U% g0 _" [& \! t# L
' ^. G4 [9 _; _) @. Y- H
+ ~- ?1 [! X+ u& \+ m, |+ I4 \这里需要注意的是,许多高级语言,如Java,拥有自动化的垃圾回收机制,所以我们不用刻意去释放被删除的节点,只要没有外部引用指向它们,被删除的节点会被自动回收。
* b+ O; M0 g6 X9 b: K; M5 m如果不考虑插入、删除操作之前查找元素的过程,只考虑纯粹的插入和删除操作,时间复杂度都是O(1)
3 z8 P" h0 E* [$ y% N1 e, t! P/**: T; [! l3 U! d) H5 D! G
* 链表删除元素6 ~1 ^; o5 [- G+ @# L
*& l. |8 p( w5 j: H/ p& [& ^$ S
* @param index 删除的位置
* Q$ E2 O x, @* f# U * @return 被删除的节点3 b% j1 y8 L0 k( L2 {
*/
8 W: _9 `3 J7 G/ ~. l: ?6 q public Node remove(int index) {2 X! Q! Q' H9 h& J0 ~3 W
if (index < 0 || index > size) {
) E7 ]* g% D+ f! ~, b2 P throw new IndexOutOfBoundsException("超出链表节点范围");
& T$ z, |, k5 N& U9 k, h n }
) X6 K e. Q% [- _$ } ~+ x4 @ Node removeNode;" G% _/ N( C1 c z' {
if (index == 0) {
2 v" J* ]* w c$ ^& E' ` if (size == 0) {
! s+ I( V4 ]7 D& \5 i! ]; N8 | throw new NullPointerException("当前链表为空,不可以进行删除操作");
; j; `, o. D4 g" f8 a }
6 ]8 b# c2 x1 v" r //删除头节点
; x' ?- N$ k8 E i# l# W removeNode = head;; t3 z% s5 v# Z: T* A/ Q* T9 \
head = head.next;9 c& \% U. s8 j- v) V& I8 B
} else if (index == size - 1) {
* v! O' @9 e$ s: B //删除尾节点
# }- j7 C- `+ J% {* l8 w* b3 i Node preNode = get(index - 1);
, V6 W T- V4 a1 ?& R: w removeNode = preNode.next;
1 v# o( Y5 N6 I4 a$ W# ]4 b; p5 z preNode.next = null;
3 S, C8 i, c1 T n last = preNode;+ ]! {- Y' L" c+ ]8 ~9 Q7 s! u' w; k
} else {" U; c7 A( f8 m* j S8 b; M* E! p' M
//删除中间节点 V- x1 T6 b1 @. ^/ W
Node prevNode = get(index - 1);
+ a& f; m# W9 Z" r U( ]- L3 k* ^ removeNode = prevNode.next;! \( R6 P" i& {, j4 B3 m
prevNode.next = prevNode.next.next;7 F2 g+ v f8 o1 n
}+ p+ b) }4 s- [
size--;7 {! @# h8 K( {9 L8 ]
return removeNode;
7 Z; P. b$ D9 }' i/ w! z0 I }
& i7 G+ Q s. @2 g6 I$ OJava实现链表的完整代码package chapter2.part2;" _' B1 t* N9 \0 o3 c& O8 Y: E! d
D0 Z2 O" e* i5 G$ N7 r- U
/**
# T, r. h: N3 {, |; s$ p * Created by IntelliJ IDEA.$ N! h6 T, I, z! ?$ z- _
*- P* @! [- J$ w8 U* _/ [
* @Author: 张志浩 Zhang Zhihao
# w7 u- O! X+ s" u4 J7 P4 h* h * @Email: 3382885270@qq.com! _# l3 i9 g8 N' P& l
* @Date: 2020/5/3+ }. p9 a! D, t1 H$ b
* @Time: 13:39, _5 R! A/ l8 a9 t' {2 O) i1 _
* @Version: 1.0# m4 k8 N2 M; [: _$ M. a- |
*/; U7 O5 g1 [8 ]1 f8 z
public class MyLinkedList2 {
: i/ ~! G4 J5 r' d9 \; y private Node head; //头节点) r) u( H! i/ {' L' s+ L
private Node last; //尾节点
% a6 {- R8 j; M0 u1 Y! [. _* d private int size; //链表实际长度; O( W1 n6 I$ ]
! H% l+ D2 L, V# `4 A; n public static void main(String[] args) {4 a4 Y% g# T9 [3 B
MyLinkedList2 myLinkedList = new MyLinkedList2();
) s6 o. M/ \' r* E0 M// myLinkedList.remove(0); // java.lang.NullPointerException: 当前链表为空,不可以进行删除操作 M: t" v9 |; U9 Z& W& V
// myLinkedList.remove(3); // java.lang.IndexOutOfBoundsException: 超出链表节点范围
6 E2 s" I# g9 B( c- z% b myLinkedList.insert(0, 3);% N0 W# b }; Z
myLinkedList.insert(1, 7);
9 G9 D4 O8 N5 \* |% f8 N9 O M& j myLinkedList.insert(2, 9);
) l E n( _, a7 M' K myLinkedList.insert(3, 5);" e! f9 M( W+ n5 ^! L) V+ p
myLinkedList.insert(1, 6); b: f3 K; t2 P) j
myLinkedList.remove(0);8 F2 K9 F9 y) F/ m
myLinkedList.set(0, 23);
- E7 Z5 K: K' R* C% J3 A+ N1 w myLinkedList.output();! ]* y8 Z; K9 {! s7 A: g( M
}
" v2 u+ C+ z: H/ s$ f6 [, `0 w9 W- \/ d1 d; u* ~
/**( O+ R- U" y, r1 `
* 链表插入元素4 F& [* a* a$ F
*
& T x9 t" F# T1 z$ p+ [/ ] * @param index 插入位置
8 V h# j1 S& ]4 S0 n* c8 I * @param data 插入元素 被插入的链表节点的数据! Z. y) |5 h0 `. L
*/
/ |: H H1 r& q1 ` public void insert(int index, int data) {
0 V O8 B' T8 e# R6 N if (index < 0 || index > size) {# A& b# p4 J! u# u
throw new IndexOutOfBoundsException("超出链表节点范围!");
1 Z! @- ]' x" V }
) }/ U0 Y3 b/ R+ O' y1 T7 Q2 o Node insertedNode = new Node(data); m) [* T! z* H9 {2 ]2 E
if (size == 0) {2 a4 L( _: W# k( y) C
//空链表
" Z" z# J; P# u2 U- z% K head = insertedNode;0 h0 r2 {8 ?/ R
last = insertedNode;/ s$ {7 l4 e! A6 c4 }
} else if (index == 0) {1 R7 g* ~0 M7 a1 M7 @6 ?
//插入头部 U" D8 g" A2 u" L
insertedNode.next = head;5 I/ S5 V; r5 d, a% O
head = insertedNode;
; q( z" [4 W- q" T } else if (size == index) {/ f! Q' y% V( d. G" x O$ T
//插入尾部
( o5 _" M6 {- b last.next = insertedNode;6 W3 b, C$ W6 E$ J: n l, d
last = insertedNode;4 x1 T9 G: n8 P' y# X) j0 B' ~
} else {
6 ]9 T8 n: g/ M+ g2 Z' l9 T) t, N* u //插入中间% K7 G) f0 i5 E/ I% `" |( L
Node prvNode = get(index - 1);
1 q) \' A- d5 i) F( a insertedNode.next = prvNode.next;8 T b3 S3 i& ]6 m
prvNode.next = insertedNode;
. R) e- X/ o, v$ `2 i* X$ [, ~ }
/ Z! l6 A y" }: J% V size++;0 \/ d. o3 Q( t0 G% L
}
0 C& ^2 z% O% _" g* b3 ?2 m- @8 L& r C% O6 N+ T
/**
' W. r# e4 _ V1 ^ * 链表删除元素
+ z5 G5 s% w5 ~% j *6 |. i* i* M+ b, E& c
* @param index 删除的位置
. A! s- {% z* i0 r' W" n% u * @return 被删除的节点' P! [8 M/ V% n% }2 _$ X
*/ A7 O) r/ _& Z: l& ]/ Z. K
public Node remove(int index) {7 I) y |- d1 B7 C/ R
if (index < 0 || index > size) {! d' L/ l+ ^ s" h& o3 S) r w
throw new IndexOutOfBoundsException("超出链表节点范围");
* P/ T( w: v0 Q( J# m& G }& @5 z9 U, T+ M. `+ @3 F- f
Node removeNode;/ Y) D0 X% Z( L# q2 r2 b
if (index == 0) {
4 v; e5 w' f2 t$ Z. X6 w+ x3 Q if (size == 0) {
7 c1 U* j# J5 }8 C9 y" v throw new NullPointerException("当前链表为空,不可以进行删除操作");
- r" r1 x# L, T+ a; H }# ^' {$ p R9 S; r$ J0 e. }
//删除头节点
* ]) L2 K$ Y0 b; `2 w4 {) o8 ~3 P removeNode = head;
# J. a/ u" o7 A3 d head = head.next;: Q9 O1 q- c1 P. J
} else if (index == size - 1) {- z7 h7 ]8 U8 f; K* a3 |, \7 k
//删除尾节点
, p9 O( W; @0 u Node preNode = get(index - 1);' d |" B% q% T6 ^! g# S$ S
removeNode = preNode.next;
: s/ `* b0 l# I- E% i preNode.next = null;
% Y& {$ n" @4 v' ~6 \2 G last = preNode;
$ V( c4 {7 T0 _9 }# b. U } else {
b" E6 a0 r& s) K9 E* D2 V4 X+ L t //删除中间节点% F- a9 i' e1 Z, s. x( K) U
Node prevNode = get(index - 1);9 ^8 Y$ L. L; U8 F4 Z! r4 E
removeNode = prevNode.next;
! Y9 \! a- c) o/ T0 g prevNode.next = prevNode.next.next;
9 C h, }. Q" L# ]# ]) Q }2 r; W. j, V% ]
size--;
: S+ E6 h2 K: O' q' Y return removeNode;
1 ?3 H0 }4 I2 r& ~) g' y; T }
& c( l ]1 K: P5 J6 i* u
2 @" x+ b+ W5 d" R! ?& ^ /**
9 z+ u* l7 e( ? * 更新节点 将列表中指定位置的节点的data替换为指定的data。: | A- g% F) t: T' b
*
- B! F3 A$ u5 [+ v9 M5 N5 b: W& ^ * @param index 需要更新的节点的位置; `9 @2 P/ n3 Q y) I& X
* @param data 新data& p4 ^: y: C9 G) a5 g$ A: E
* @return 旧data
) y5 l, Z. `( |( X. n4 N" o */2 a8 I( d2 V0 X
public int set(int index, int data) { R( |- b2 a' ?3 n$ j
Node x = get(index);
% ^/ m; [; p% Q+ ^+ I( T int oldVal = x.data;* l2 p$ K% p+ s8 y6 r$ w% q
x.data = data;2 ^5 E5 G3 N& W
return oldVal;
, A, V2 ]# E+ P, Y3 p5 d3 { }0 x2 o! `5 ?( n7 T' z
" A+ L9 p% H+ r; w9 f, z: g, J; T
/**
' L& j: S$ `+ l, F * 链表查找元素
6 ?- _' p* G1 v4 U4 W9 u) | *
" f- S G |9 S9 P6 _/ ]2 p i' R * @param index 查找的位置
4 X0 Y) w: i0 y- ~4 w7 m# m- M * @return index位置的Node对象
# V- J1 s' L9 | u% `! X1 G */
1 i2 Y4 B. A9 w2 x+ a: E8 H, v( O/ o public Node get(int index) {
/ E# N: I' ?% G if (index < 0 || index > size) {
& V4 z) D# E X" F throw new IndexOutOfBoundsException("超出链表的节点的范围!");2 o9 x" ^& p4 M
}& x+ r0 b$ I; D( B5 A
Node temp = head;8 u' F8 P. O! N9 O1 w
for (int i = 0; i < index; i++) {* n! R9 @; x4 T; X d2 J2 ^
temp = temp.next;, N- @4 w: `+ a# Y- T
}
: C) x+ {$ n( Y7 h3 m! }- g return temp;
# i, }' a- a0 A* N }
1 x3 ~! u5 Q3 M, p5 L9 i/ M" R; b3 l2 d0 f% s( Z# m+ @
/**" ?/ Y: z- Y" }; A+ `- j
* 输出链表 u4 ?, r/ L3 y- Q6 t+ u/ g3 m
*/
; H1 w% V9 w" j& d# B8 N public void output() {
+ t( s) r0 v: p( U l( W6 n$ ^ d Node temp = head;
1 J; y: U) {4 u4 L9 Q* Q. a while (temp != null) {
0 x: R( J/ U+ q) q8 v System.out.print(temp.data + " ");
! U; ~$ y. W4 b5 y' O temp = temp.next;6 E8 y1 m" e! ]6 b
}# c" Q4 W8 y$ K, j4 j% m
}" y/ G+ Z/ U8 H8 g+ X
. j4 d* m8 v0 l- ?
/**: j- K# K. {3 A: p+ R/ Q) L
* 链表节点
; Q4 S" P1 z4 @6 p */3 j5 o) o! O& m% J* G v1 R. N) h- e
class Node {
9 k% i! @' A/ D6 ]6 s int data;
% x0 X8 S7 ^, P' @! w9 Y Node next;3 O- @4 x/ n/ h) n
# V' l- e; a l8 l5 z Node(int data) {4 [/ M; o9 g( s; g
this.data = data;
& d9 `: o7 t& X. d0 N* P! B) z }
; s* F5 p8 Q3 O' l; E$ T }/ \, G3 M& }/ f4 T/ p
}
3 S/ p l" T" O6 ~: T4 s2 J
; d2 G( e" E. h. A) q+ }7 I- e
{7 n6 R% k- W$ N" |, z二、双向链表
, I8 u1 U U* N! r \0 |0 [
双向链表比单向链表稍微复杂一些,它的每一个节点除了拥有data和next指针,还拥有指向前置节点的prev 指针。
/ c- j: P9 Y- ^' n4 _
8 v* e% u9 F0 l; w6 u) G. o* P# {% s. f$ X' @& l. s" [( C
0 i" b; m. g& D! E) r$ D
0 Z* L$ R# a1 }( k————————————————
) S& v: I# C8 K: I T( w版权声明:本文为CSDN博主「爱做梦的鱼」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
2 u8 d7 e* Y L原文链接:https://blog.csdn.net/weixin_43124279/article/details/105904468
- ~7 D& E0 Z' J0 ~9 ?, R z; I4 s1 ?0 u1 m
: C7 A n* h. F& F N: }6 ^0 P |