6 i5 H; A5 h: ]8 V: m7 g【Java演示】什么是链表?数据结构, F; H/ p9 D: n" Q: t
一、单向链表( T1 W; F8 A9 ~ M, M$ O1 V( U% q$ o1 Z
: z: f+ ]% P% |+ O! v5 i1 a0 v* D
+ S( f3 b B3 @9 `2 H$ |链表(linked list)是一种在物理上非连续、非顺序的数据结构,由若干节点(node)所组成。6 {6 [( I* l8 l6 w
单向 链表的每一个节点又包含两部分,一部分是存放数据的变量data,另一部分是指向下一个节点的指针next。7 r% U" z; ?( I
链表的第1个节点被称为头节点,最后1个节点被称为尾节点,尾节点的next指针指向空。2 b/ X* \0 T5 B: _, X; @1 {7 D/ @
Q- V/ m" }' Y# }0 R: G Q什么叫随机存储呢?+ |7 g$ S; x. \1 B# u
7 {' g5 s. |2 O( s0 K/ T2 l如果说数组在内存中的存储方式是顺序存储,那么链表在内存中的存储方式则是随机存储 。
: ?9 V% C+ j4 t+ M" x% j0 M上一节我们讲解了数组的内存分配方式,数组在内存中占用了连续完整的存储空间。而链表则采用了见缝插针的方式,链表的每一个节点分布在内存的不同位置,依靠next指针关联起来。这样可以灵活有效地利用零散的碎片空间。 c' @, t- C) U, I( u
2 _% e* y4 J) H/ h1 r
4 q5 c2 g+ H. Z" v- ^+ D图中的箭头代表链表节点的next指针。 链表的基本操作1. 查找节点在查找元素时,链表不像数组那样可以通过下标快速进行定位,只能从头节点开始向后一个一个节点逐一查找。
% |/ H& ?9 s4 a! s6 d, V1 K
( y/ y4 C3 g( T/ m9 {: x& I3 `
/**- c/ g, K+ q' ]8 j. s( c( i9 k3 U
* 链表查找元素2 j' t0 [) y5 \+ u9 o
*7 m' z- z+ h* e8 c& R8 |9 t3 [
* @param index 查找的位置
) Q) y* @ p% Z * @return index位置的Node对象$ ^$ I6 j/ H W
*/$ U$ j$ i; Y0 Q' D
public Node get(int index) {9 s5 X6 P6 V1 ^, t3 H, `! N( d, w" S
if (index < 0 || index > size) {- n9 o( V% A4 |: E) t. U/ y: x% T& D
throw new IndexOutOfBoundsException("超出链表的节点的范围!");
; Z# x2 @0 K6 p3 W }! O+ K" c/ O( t& {# X
Node temp = head;+ K& r, d4 d% ~+ G& {
for (int i = 0; i < index; i++) {
0 B2 i5 k+ x6 ~$ o2 P temp = temp.next;) t8 Z$ e; B6 I4 _' d" c
}2 x N5 C$ N2 c1 A% C$ n) ~
return temp;2 H* v. v- }! r. m D
}7 C$ Z# }% U! h7 j; _0 G, `
1 {: {: V7 O% [* C8 f链表中的数据只能按顺序进行访问,最坏的时间复杂度是O(n) 2. 更新节点
; s" @5 H! A# t* Q7 ]0 h
9 R6 l X8 d6 Y( q9 t! @如果不考虑查找节点的过程,链表的更新过程会像数组那样简单,直接把旧数据替换成新数据即可。
4 s" i/ U+ _* N/ |' c* P2 q* p如果不考虑查找元素的过程,只考虑纯粹的更新节点操作,时间复杂度是O(1)
7 n7 c" ?& @" ?4 e/**
M8 I! o3 O" e q1 r0 K* X+ t# Q: P * 更新节点 将列表中指定位置的节点的data替换为指定的data。7 \# D: }" V: S( c. s7 ~
*7 i6 B% l$ s$ p/ f* |: b6 h
* @param index 需要更新的节点的位置" h: \% i1 |2 z0 ^
* @param data 新data
$ A4 {. |8 o$ g2 g5 k( @ * @return 旧data6 ~8 }( ?& D. v& W
*/7 V, e$ {6 N) A6 w! I
public int set(int index, int data) {
6 D6 d9 i0 J, o- L Node x = get(index);
6 r( ~5 B: ]2 W int oldVal = x.data;
/ Y7 b6 u9 G% `- M x.data = data;" F& o; s- x$ t; Y
return oldVal;
" f5 B0 P: G; @! L }
3 n6 f, p, b3 A, Y2 c' l
@0 ^$ P8 y8 Z! K# |8 W/ n3. 插入节点只要内存空间允许,能够插入链表的元素是无穷无尽的,不需要像数组那样考虑扩容的问题。 与数组类似,链表插入节点时,同样分为3种情况。 - 尾部插入
- 头部插入
- 中间插入3 v0 y" m, f" l! r6 i6 J
3.1. 尾部插入尾部插入,是最简单的情况,把最后一个节点的next指针指向新插入的节点即可。 + q( x/ g/ I3 L' ~
+ n# w" {' F w2 Z7 f4 ~7 J3.2. 头部插入头部插入,可以分成两个步骤。 - 第1步,把新节点的next指针指向原先的头节点。
- 第2步,把新节点变为链表的头节点。+ x1 ^# h+ d: T0 D+ `
1 S( Z/ D$ G; E- y n# |5 {& K) T
% w! Z( l+ W. [. M% H# g0 U$ p8 W: H3.3. 中间插入中间插入,同样分为两个步骤。 - 第1步,新节点的next指针,指向插入位置的节点。
- 第2步,插入位置前置节点的next指针,指向新节点。" L& z- p2 A# s) d+ F& x5 i) X/ b
0 F4 b- G% D3 m; x: A& Y2 v
$ C0 Y& |) u6 e. x, r" n+ I
三钟情况的代码合到一起
* S* c5 N2 {9 X& j. \/ \6 Z* Z& s, F6 W/ Q" Y
/**
! e7 ]1 e7 f; f4 f * 链表插入元素
! x) w+ r+ y4 S5 N- L3 f+ { *
0 ?9 h) u) }2 ^9 T! A, m, C * @param index 插入位置
; s/ F3 q s, i- K' W! G( J * @param data 插入元素 被插入的链表节点的数据$ s2 J* L# C/ }
*/; m* d' j1 O& T6 ?
public void insert(int index, int data) {, V8 w2 W; K* H5 I- c/ I9 _
if (index < 0 || index > size) {
( S9 B( R- @+ P throw new IndexOutOfBoundsException("超出链表节点范围!");
! H8 O" ?1 w8 J: E s$ u3 l }
# z; \: ?! a4 u7 M' b Node insertedNode = new Node(data);7 m8 d: u3 s+ M& j
if (size == 0) {; m/ E0 E7 D: k9 m$ }
//空链表3 B' U3 `( [ H" z0 y d: G: @: a4 B
head = insertedNode;5 V$ B* v6 {8 [# T6 t4 n G
last = insertedNode;
* f& w. v& @8 R3 Z$ i @. p% H6 l } else if (index == 0) {2 n" h1 T* ]* I) ?1 @
//插入头部
" `/ F5 S! w1 V) H' } insertedNode.next = head;
0 L+ `/ f6 ~! K! f* y a1 w head = insertedNode;
- D9 B# w f5 m& C) j } else if (size == index) {! A' b" y. l2 G* J2 c8 E
//插入尾部
- T. R* V( @( W4 q+ V2 I( Z last.next = insertedNode;. g+ I6 `3 q3 `/ t$ A' O
last = insertedNode;2 F( Z2 p' @: T2 z" z9 }9 R& v
} else {/ B$ z' x5 Y, j# n _8 |) H
//插入中间2 g4 E8 \1 u( g! U
Node prvNode = get(index - 1); C- Z1 ]0 h/ F& ^- s' I
insertedNode.next = prvNode.next;) c$ y. o) }* M( H- y! {4 m
prvNode.next = insertedNode;8 X( a( G3 H9 }- X- ~+ r Q' }& i
}
+ I) D8 E/ ?, P7 @' | size++;
- |7 A8 w4 r2 h2 g" h+ b }1 S, }5 x+ M( @; c( t6 F( _* N
0 u0 j/ T1 i5 w* X, Y6 S' j
/**/ `- r T3 e4 {( n/ d9 z, @
* 链表插入元素; B2 \1 ^7 z; y5 D. T/ a# P s
*4 r3 F2 ?6 ~1 [$ d' F$ F1 {
* @param index 插入位置
9 \1 z5 s ]: o * @param data 插入元素 被插入的链表节点的数据4 k* X% g( `, J0 w2 W
*/& M0 w3 d2 f S; s
public void insert(int index, int data) {) }" n6 B/ E5 X
if (index < 0 || index > size) {
( W% L4 z4 V3 P throw new IndexOutOfBoundsException("超出链表节点范围!");3 C8 n8 g: _$ V! ^7 s( i
}
* B5 A& l: b! {$ U' E1 d$ F5 K3 j) u Node insertedNode = new Node(data);
" A! C* H% H! c% m if (size == 0) {7 Q& |$ B8 c2 m* A
//空链表
7 G4 x' ^) L9 z; c* O head = insertedNode;! k$ c( _; j2 b1 ?
last = insertedNode;
) A0 I8 w* n! q. W } else if (index == 0) {
7 Q. K9 O1 _& x5 G9 U5 R) K0 ] //插入头部
: U, m1 C2 n' x5 d$ F insertedNode.next = head;2 f4 o6 x2 J x* A, d& ]# J
head = insertedNode;) l, m4 ]1 C5 \7 e( K& r
} else if (size == index) {
. l) y2 K) I$ M" K- G; b; k //插入尾部
) y B& p" \! a- o# ]) F( Q last.next = insertedNode;
" ^ W6 \' n* m% B* D- {& S k last = insertedNode;: \0 Q, j' k) O
} else {7 x1 |8 a: R: \' X4 x
//插入中间
0 }; B% o4 D; b# S# k5 F1 n3 d- d Node prvNode = get(index - 1);
2 ]! s& m# B9 M4 D$ h insertedNode.next = prvNode.next;
. W& a+ J# P; v1 H8 ~ p7 ~* {4 r prvNode.next = insertedNode;1 ]% j8 p0 {/ a$ [6 b
}, G k+ s# C6 ]3 m
size++;( f9 p4 r, H( q9 K0 `
}( d: {2 a1 J9 j. h. ^6 D, p* F
4. 删除元素链表的删除操作同样分为3种情况。 - 尾部删除
- 头部删除
- 中间删除
: w8 R- s" A1 j8 M$ b2 l 4.1. 尾部删除尾部删除,是最简单的情况,把倒数第2个节点的next指针指向空即
2 P4 ?( I" f4 K* ^& Q可。
$ [1 P) p: l+ Z3 f' h
: E( h! q0 Z& _* M6 R7 X n9 D4.1. 头部删除头部删除,也很简单,把链表的头节点设为原先头节点的next指针即可。
5 B! ?3 C9 ^$ t( G- S* e
( I1 W- A. ]0 t3 h" E4.1. 中间删除中间删除,同样很简单,把要删除节点的前置节点的next指针,指向要
' G+ V& [5 @7 q0 n8 ^$ V删除元素的下一个节点即可。
/ J$ e/ F8 M; g1 u* J
3 R. D% p" S" z1 |- l- G1 _
( t6 u* {; q! p* \# I8 P
这里需要注意的是,许多高级语言,如Java,拥有自动化的垃圾回收机制,所以我们不用刻意去释放被删除的节点,只要没有外部引用指向它们,被删除的节点会被自动回收。+ A2 e6 v& M# p2 e" ?7 j0 v
如果不考虑插入、删除操作之前查找元素的过程,只考虑纯粹的插入和删除操作,时间复杂度都是O(1)% w( K/ [* X9 [$ n" x
/**/ S3 k' h! Y9 V. v* C, u: Q- x; w3 O
* 链表删除元素2 E P& y# q0 F
** x" A T+ q: N' _3 W
* @param index 删除的位置
# o2 a' T- x: q! e- M9 M% i, ]/ D * @return 被删除的节点
6 V- W& O# v' Y) P3 M */
7 Z+ N% l) q8 Q8 C$ k public Node remove(int index) {8 V- Y2 ^% R& @4 T" p
if (index < 0 || index > size) {
" L) Y e- D# F. F( y% Z throw new IndexOutOfBoundsException("超出链表节点范围");
+ z% K2 B6 ~6 e. h3 l }
- h4 `( y6 e; v Node removeNode;# g! p: X$ `/ e" m7 z
if (index == 0) {
9 |! M- Z8 o0 u4 B" ^2 x: g if (size == 0) {
1 V$ a6 w( W) P" {' w/ k- _ throw new NullPointerException("当前链表为空,不可以进行删除操作");6 X: k. r/ j8 M) ?" ^
}4 I7 L0 @3 o l! \
//删除头节点2 F+ `- g5 W8 n* ]) F
removeNode = head;
( m# S/ o# I; I, t2 v head = head.next;! M1 w( f+ J! R; D& W
} else if (index == size - 1) {' \2 L' z; U) d: S9 @
//删除尾节点
O8 H! y( h# U' V3 z% a0 v% L Node preNode = get(index - 1);
+ ]% I7 C2 g, m7 Y% f removeNode = preNode.next;& `- v" p9 |' V0 O* \9 d
preNode.next = null;4 j8 \2 G. ^2 `
last = preNode;
9 R9 z- U+ s0 W% r } else {
0 m! k9 Q1 p q1 {% Z4 Z+ s: P$ P+ A //删除中间节点
' W* R* D' y7 F: E Node prevNode = get(index - 1);
9 W9 e9 P; A n& i2 H removeNode = prevNode.next;. U2 z) P& a' r' q0 t' @* n
prevNode.next = prevNode.next.next;
/ R: X. f0 u, U3 D" V }
8 w* Q: r0 Q$ T& N( [. J! l size--;# X" [" q8 T! v2 ~
return removeNode;
1 j* A6 W; u0 ]" A$ S4 i$ b: N' `- v u }
* R, r& P9 q0 C0 l# T4 kJava实现链表的完整代码package chapter2.part2;- D B) b: S! `* J) |, @6 M
8 f: t3 \. S# r& \# e% l7 J/**( }+ l3 }# S1 I: Y1 t
* Created by IntelliJ IDEA.0 j' L+ J( V, S& W* e2 J
*% L; c+ Y- d2 V* t" l2 J
* @Author: 张志浩 Zhang Zhihao
' Z3 \& p6 e8 m1 Z: z0 B4 O2 A& @ * @Email: 3382885270@qq.com( G _7 t7 `7 @& @, w6 T
* @Date: 2020/5/3, Q1 ?6 z( H, @# }6 T4 G- J- t( p
* @Time: 13:39$ \8 q6 _/ z- T n$ M
* @Version: 1.0% ^9 a; }, ^% l+ Q
*/
6 @7 F6 ^- J& T u) ]% E% v( w# \public class MyLinkedList2 {# |9 L% F4 Y! W r
private Node head; //头节点0 L/ ]' ~, e+ W9 k1 _: H( f
private Node last; //尾节点
; R# `6 D% b$ u5 p9 m/ Y2 d4 U private int size; //链表实际长度* t6 n0 r% S- f5 v
, t" a# @' t$ Y. Q8 ^ public static void main(String[] args) {
" C4 p( ` c) Z9 [& Q3 h MyLinkedList2 myLinkedList = new MyLinkedList2();+ G: J V+ H; t% ~+ S" I
// myLinkedList.remove(0); // java.lang.NullPointerException: 当前链表为空,不可以进行删除操作" Q8 y$ |7 a* s: g0 T% _0 w
// myLinkedList.remove(3); // java.lang.IndexOutOfBoundsException: 超出链表节点范围1 D; X" f( q; B$ T/ M5 i
myLinkedList.insert(0, 3);9 W/ M$ x! C4 S: v
myLinkedList.insert(1, 7);
+ m0 v0 L) |* X myLinkedList.insert(2, 9);- A- ^" a& T$ X- L" [( |. k+ J
myLinkedList.insert(3, 5);
# d2 I' E2 ^/ P myLinkedList.insert(1, 6);
) a4 e, j, b$ _4 L myLinkedList.remove(0);
( F! }" B" k2 j) c. ]6 Y myLinkedList.set(0, 23);+ Z- j" h1 a% `! [+ A6 ?7 j
myLinkedList.output();
: m. A- x V \) [, _ }
k7 x2 V5 h+ o; c" M# F h, j% d3 ?+ B1 `
/**- `# J. ~1 F! [- S/ t
* 链表插入元素- b6 e) B% Z# H) t) Q( H
*
2 K. j: s1 ^3 G3 F9 i9 W! _) r * @param index 插入位置$ e2 [; r! H. t+ w
* @param data 插入元素 被插入的链表节点的数据3 R6 X/ e9 g' X# w! [/ i v
*/
& c7 I2 w5 D5 t+ U public void insert(int index, int data) {
$ U2 @: J2 z0 ?( o if (index < 0 || index > size) {7 s; w% d1 H3 q1 C
throw new IndexOutOfBoundsException("超出链表节点范围!");
5 F: j: Z8 H! {& v' i- k* x }
8 t4 @$ X+ O$ g* m' l1 z1 g Node insertedNode = new Node(data);5 N( ^. S$ F' z2 ]( h
if (size == 0) {
9 O/ a0 ^3 _5 V( M! U7 S //空链表
- s, a7 {0 b4 G \ head = insertedNode;/ a& h' V' O1 P2 \8 h
last = insertedNode;
# j# Q+ l0 Q0 E# p } else if (index == 0) {
% I6 Y2 c! K; s; \9 E0 J( X //插入头部6 l$ U: B# R% k; l _
insertedNode.next = head;
# l) ]" x. l% a" ^$ Y9 Y! V2 D head = insertedNode;& K% Q, \- C4 j/ a- R
} else if (size == index) {
; S: r7 t$ g$ x' A: z //插入尾部
6 f& r0 l. `3 p1 U8 G* q last.next = insertedNode;- P+ u Q; m7 I/ w6 J Z% a$ {
last = insertedNode;: P3 v& j: l" m) D
} else {8 |5 J# {- f* I- y/ f7 I5 t
//插入中间5 F( a9 {$ T. W M( g
Node prvNode = get(index - 1);
8 q0 Q, R5 ?+ a' |! M: T insertedNode.next = prvNode.next;9 M3 Y2 @9 T2 ~* Y" U
prvNode.next = insertedNode;, ~+ T3 Q3 l- D. I: J
}: o+ C. z% K# N' B
size++;
; e/ Z1 [0 |; T; ?9 |# ] }! M Z2 ]7 D7 O! Y5 y0 w/ }
* d2 v# k0 P/ L/ j' M( e( ` /**8 y: H' C; s9 B5 e2 R
* 链表删除元素% f" ^ [( C" u7 @9 M4 [
*5 q8 X ]# W7 q9 i: s5 n
* @param index 删除的位置
+ I# t) v, M# d) I0 R * @return 被删除的节点% Z, ^% f, W1 h$ j1 C0 V1 `6 W' M
*/" O) o; R4 P7 Z }
public Node remove(int index) {
, }8 {2 {1 a9 o+ g if (index < 0 || index > size) {
" X" N2 Y& ~ |' J throw new IndexOutOfBoundsException("超出链表节点范围");
, ]/ ~1 B+ z# w! V5 x9 z. G }' o; ?' n: p5 R
Node removeNode;
( w& J- t0 n1 k1 R; _6 ^ if (index == 0) {
0 N1 [4 |( c: {) n. W; T8 y if (size == 0) {
( a* c/ e$ { E' z4 L3 L# S throw new NullPointerException("当前链表为空,不可以进行删除操作");
( i/ i" ?$ y }- ?+ D# L }
{, _3 q S" c# y( s //删除头节点/ a) _7 ]0 a/ a# b l
removeNode = head;
, w1 K6 V! z* f0 M2 m6 D6 | head = head.next;- A; D: D, H; k; U6 K
} else if (index == size - 1) {
6 m9 f6 v) E! t( B( E6 c //删除尾节点" o. t6 H, Z. f5 z. y
Node preNode = get(index - 1);' E, Q' Q1 X z1 s0 o
removeNode = preNode.next;
, E% J' T+ p+ i @: | preNode.next = null;0 g) M3 d+ S; t. R+ Y$ x+ Z1 ] A+ O8 n
last = preNode;: }5 g5 J" Z$ K" u
} else {$ _1 B5 O5 i0 s4 n! p. x
//删除中间节点
1 N0 F3 P4 P; G Node prevNode = get(index - 1);, S' F* g; l- S5 [
removeNode = prevNode.next;8 \2 I) e6 z- Z
prevNode.next = prevNode.next.next;
: G6 k# Y, P, H; u, l" u: ?' o2 `3 o }7 N4 X- g5 z3 V! W7 \+ n( q8 Q1 V% T
size--;
5 A+ O: R4 B( d- `# M# C return removeNode;, ]) b; H! C' n
}
( M% A, S, U: A. E) ?+ a) `* n. r% c2 B# L" Y' n
/**
* S9 g9 q/ I% M * 更新节点 将列表中指定位置的节点的data替换为指定的data。. l T. j1 M! K+ o9 |# N5 G; M
*
% s' S! B; f% ?6 |4 R ^$ O * @param index 需要更新的节点的位置3 [8 d! @* J- ?; S7 @* x; O
* @param data 新data
1 I4 l$ ?1 x- q, D * @return 旧data
" B5 R) g3 \/ H5 n; h D0 \ */0 W/ R) u* a) X
public int set(int index, int data) {
: \9 u" d4 D" T, I! ~ Node x = get(index);' d }9 M- [' m5 H( k( @& {. O3 T
int oldVal = x.data;
$ K2 b7 K0 K8 x; h1 w5 F4 K x.data = data;
# c) W- I% P$ M' e; o; G/ M2 l, E2 L return oldVal;
) e) ?2 `7 j6 k: x! |' ~- R }. d7 k4 C' v& c" v
% k) ~; }! s/ O( `+ d, \' M
/**
. a, w4 X. w/ m4 l. j3 Y * 链表查找元素
& j% L; Y) @! W e ** M/ f) X, K }+ b: M8 I2 O: z
* @param index 查找的位置: ]5 [- N& {7 `9 J- A1 ~% s
* @return index位置的Node对象* E2 C6 B' t: Z D3 i9 j
*/* z- ?) P( X e3 J- h& P
public Node get(int index) {
+ ]8 x/ `# T' r' d' E) l9 m if (index < 0 || index > size) {
6 Z& j% T4 w/ S0 s8 |2 M# `) r3 O7 G# K throw new IndexOutOfBoundsException("超出链表的节点的范围!");
" N! A a' \# Q' ?) g/ A! S+ G }
' s5 H1 K9 c; g# U" p5 k Node temp = head;
% T d2 d" Y3 P& V' F4 { for (int i = 0; i < index; i++) {
7 t5 k' Z0 g, h7 X# W7 A temp = temp.next;
- Q3 C+ R) Q I( J }6 e: O( c* G2 Y6 |' r( y p
return temp;
7 L- z. |% _/ P( }4 I) c9 J. J4 ` }
+ R# F+ S" Q% g) ^6 }
& H, L+ N0 k# |" P /**
2 z7 i# l+ F/ U, {( [ * 输出链表
$ s/ e* u4 Q$ q! s) x" B; `2 W1 F */' Z% Z7 r; F1 u, \" e* A
public void output() {* S% x+ p% \! a) b( ^
Node temp = head;3 n8 a+ a# m1 o' }4 N+ q
while (temp != null) {
: ^3 Q0 }' N$ r7 a8 T9 [8 W8 j: a System.out.print(temp.data + " ");
+ Y. m+ r; `7 x. T# c6 Y# T' a temp = temp.next;
7 t/ b1 {1 C. w* t' M }4 J5 I: P0 J1 B; p! Y) K
}
% `* e. V2 K* a% G
; K5 e3 U6 U+ Q" X' A1 A) l) I% G /**
5 U$ D) E# i, W; d/ |# ` * 链表节点
+ D, R& B" u# ?3 A w( i */
4 y, B X9 e2 {" B2 X3 B- s class Node {, W$ E' ~' w5 ^7 ~
int data;5 P% E8 ?, ~/ w7 W* k5 p0 ]! W: S
Node next;6 ~; F) @+ ]. U6 I5 `+ t4 t
7 [, L6 b6 ?% S" C; g0 ~( }
Node(int data) {
: }; } o$ P% K. } this.data = data;
" j8 C9 \9 e# L4 n* |# g S: g }4 I1 ^5 s& G" F1 Q8 L# V6 N7 a
}9 A1 S2 Z9 r8 B) D' m
}
; t8 x/ g# y6 c- ^& R5 l+ O' p, R6 P2 `; @, H k$ p0 B* L8 z
" j: U( }& q) r) M6 D6 A
二、双向链表
& t" c; Q' f8 {+ x3 @9 N, z, k+ ]
双向链表比单向链表稍微复杂一些,它的每一个节点除了拥有data和next指针,还拥有指向前置节点的prev 指针。
D& p. n$ c# m) t7 \9 ?7 _
+ E, u0 J7 J9 y% r$ B! \6 {: h+ f9 F( d. H, c/ b
7 V7 J+ l' ` g& {
9 E$ F/ p! O2 f2 Y' _————————————————+ O2 e9 k- u) b6 R6 q8 j5 p* D+ |2 L3 ~
版权声明:本文为CSDN博主「爱做梦的鱼」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
3 {) T* b# v9 a X原文链接:https://blog.csdn.net/weixin_43124279/article/details/105904468
% Q: f; G3 l0 `& A2 ]& \, s& j, k- o8 @: n* X% m) x
6 k1 \, H- p, X( n5 a' Y! t! g
|