单向java链表_单向链表(单链表)的Java实现

本文介绍了单向链表的数据结构,包括链表的概念、节点组成以及头节点的作用。通过Java代码展示了如何创建和操作单向链表,包括添加、删除节点等基本操作。文章还提供了Person类和PersonChainNode类的实现,用于链表中存储和链接对象。最后,通过一个类比解释了链表中节点的相互连接关系,帮助理解链表的工作原理。
摘要由CSDN通过智能技术生成

最近被问到链表,是一个朋友和我讨论Java的时候说的。说实话,我学习编程的近一年时间里,学到的东西还是挺少的。语言是学了Java和C#,关于Web的学了一点Html+css+javascript。因为比较偏好,学习WinForm时比较认真,数据库操作也自己有所研究。但链表这个东西我还真没有学习和研究过,加上最近自己在看WPF,而课程也到了JSP了,比较紧。

但是我还是抽了一个晚上加半天的时间看了一下单向链表。并且使用Java试着写了一个实例出来。没有接触过链表的朋友可以作为参考,希望大家多提宝贵意见。

当然,我们首先解释一下什么是链表。就我所知,链表是一种数据结构,和数组同级。比如,Java中我们使用的ArrayList,其实现原理是数组。而LinkedList的实现原理就是链表了。我的老师说,链表在进行循环遍历时效率不高,但是插入和删除时优势明显。那么他有着愈十年的编程经验,我是相信的。不过不知道他是否是说双向链表,我们在此呢只对单向链表做一个了解。

链表(Chain本文所说链表均为单向链表,以下均简称单向链表)实际上是由节点(Node)组成的,一个链表拥有不定数量的节点。而向外暴露的只有一个头节点(Head),我们对链表的所有操作,都是直接或者间接地通过其头节点来进行的。

节点(Node)是由一个需要储存的对象及对下一个节点的引用组成的。也就是说,节点拥有两个成员:储存的对象、对下一个节点的引用。

这样说可能大家不是很明白,我贴一张图大家可能更容易理解。

40c8a1999a50323dd998d4d568cb396e.png

那么大家可能清除了,为什么说有了头节点就可以操作所有节点。因为有着不断的引用嘛!

那么在链表里的属性大概就只有两个了:头节点和节点数量。当然,根据需要,我们通常需要更多的属性。

简单的链表可以写为下面的样子:

1 packagemyChain;2

3 /**

4 * (单向)链表5 *6 *@authorJohness7 */

8 public classPersonChain {9 private PersonChainNode head; //头节点

10 private int size; //链表的实体(即节点的数量)

11 private int modCount; //链表被操作的次数(备用)

12

13 /**

14 * 获得链表中节点数量15 *16 *@return链表中节点数17 */

18 public intgetSize() {19 return this.size;20 }21

22 /**

23 * 添加节点的一般方法24 *25 *@paramp26 * 添加到链表节点的对象 由于实现细节,作为唯一标识,同一个编号的Person只能添加一次27 */

28 public voidaddNode(Person p) {29 if (!contains(p.personNo)) { //如果链表中没有该对象,则准备添加

30 if (head != null) { //如果有头节点,则添加新节点作为头节点

31 head = newPersonChainNode((myChain.Person) p, head);32 size++;33 modCount++;34 } else { //如果没有头节点,则添加对象作为头节点

35 head = new PersonChainNode((myChain.Person) p, null);36 size++;37 modCount++;38 }39 }40 }41 }

以上的代码就是一般链表的骨架了。拥有两个重要属性。

那么做为能添加到链表的节点又该长什么样子呢?

我们可以写作如下:

1 packagemyChain;2

3 /**

4 * (节点)实体,封装了'人'这个对象和下一个实体的引用5 * 该实体将作为(单向)链表的节点6 *@authorJohness7 */

8 public classPersonChainNode {9 Person person; //人

10 PersonChainNode nextNode; //该对象('人')保存的下一个对象的引用11

12 //获取当前实体对象('人')

13 publicPerson getPerson(){14 return this.person;15 }16

17 //获取下一个实体

18 publicPersonChainNode getNextNode(){19 return this.nextNode;20 }21

22 //构造方法

23 publicPersonChainNode (Person p,PersonChainNode ep){24 this.person =p;25 this.nextNode =ep;26 }27 }

当然了,这只是个大概的样子。

那么我最后在把层次梳理一下:链表是由不定数量的节点连接(通过相互之间的引用)起来的,由于这种关系,在链表里我们只定义了头节点和节点数量。节点是由存储的对象及对下一个“节点”的引用封装而成。

在添加节点到链表中时,首先添加的节点后置后,新添加的节点作为头节点引用前一个添加的节点。

废话不多说,贴上我的例子,老师说我废话多……

(以下的例子较为简陋,大家不要笑话我哈)

1 packagemyChain;2

3 /**

4 * '人' 类5 *@authorJohness6 *@version1.07 */

8 public classPerson {9 String name; //姓名

10 int age; //年龄

11 int personNo; //编号,用作唯一标识12

13 //带参构造方法

14 public Person(String name, int age, intpersonNo) {15 this.name =name;16 this.age =age;17 this.personNo =personNo;18 }19

20 //获取姓名

21 publicString getName(){22 return this.name;23 }24

25 //获取年龄

26 public intgetAge(){27 return this.age;28 }29

30 //获取编号

31 public intgetPersonNo(){32 return this.personNo;33 }34

35 //用于输出的信息

36 publicString toString(){37 return "姓名:" + this.name + "\t年龄:" + this.age +"\t编号" + this.personNo;38 }39 }

1 packagemyChain;2

3 /**

4 * (节点)实体,封装了'人'这个对象和下一个实体的引用5 * 该实体将作为(单向)链表的节点6 *@authorJohness7 */

8 public classPersonChainNode {9 Person person; //人

10 PersonChainNode nextNode; //该对象('人')保存的下一个对象的引用11

12 //获取当前实体对象('人')

13 publicPerson getPerson(){14 return this.person;15 }16

17 //获取下一个实体

18 publicPersonChainNode getNextEntity(){19 return this.nextNode;20 }21

22 //构造方法

23 publicPersonChainNode (Person p,PersonChainNode ep){24 this.person =p;25 this.nextNode =ep;26 }27

28 //构造方法

29 publicPersonChainNode (Person p){30 this.person =p;31 }32 }

1 packagemyChain;2

3 /**

4 * (单向)链表5 *6 *@authorJohness7 */

8 public classPersonChain {9 private PersonChainNode head; //头节点

10 private int size; //链表的实体(即节点的数量)

11 private int modCount; //链表被操作的次数(备用)

12

13 /**

14 * 获得链表中节点数量15 *16 *@return链表中节点数17 */

18 public intgetSize() {19 return this.size;20 }21

22 /**

23 * 添加节点的一般方法24 *25 *@paramp26 * 添加到链表节点的对象 由于实现细节,作为唯一标识,同一个编号的Person只能添加一次27 */

28 public voidaddNode(Person p) {29 if (!contains(p.personNo)) { //如果链表中没有该对象,则准备添加

30 if (head != null) { //如果有头节点,则添加新节点作为头节点

31 head = newPersonChainNode((myChain.Person) p, head);32 size++;33 modCount++;34 } else { //如果没有头节点,则添加对象作为头节点

35 head = new PersonChainNode((myChain.Person) p, null);36 size++;37 modCount++;38 }39 }40 }41

42 /**

43 * 通过编号删除对象44 *45 *@parampersonNo46 * 要删除对象的编号47 */

48 public void deleteNode(intpersonNo) {49 if (size == 0) { //如果当前链表节点数为零

50 return;51 }52 if (size == 1) {53 //如果只有一个节点并且正是需要删除的对象

54 if (head.person.personNo ==personNo) {55 head = null;56 size = 0;57 }58 return;59 }60 //如果不存在该对象编号

61 if (!contains(personNo)) {62 return;63 }64

65 //较为复杂的删除,定义整型保存被删除的节点的索引66 //(删除和索引都是不存在的,这里只是一个说法)

67 int index = 0;68 //循环遍历,找到删除节点的索引

69 for (PersonChainNode p = head; p != null; p =p.nextNode) {70 if (!(p.person.personNo ==personNo)) {71 index++;72 } else{73 break;74 }75 }76 //如果删除的是第一个节点(即头节点)

77 if (index == 0) {78 head = newPersonChainNode(head.nextNode.person,79 head.nextNode.nextNode); //设置头节点后一个节点为新的头节点

80 size--; //链表节点数减一

81 modCount++; //链表被操作次数加一

82 return;83 }84

85 //如果删除的节点不是第一个节点86 //循环遍历,找到被删除节点的前一个节点87 //其索引下标用count保存

88 int count = 0;89 for (PersonChainNode p = head; p != null; p =p.nextNode) {90 if (count == index - 1) { //如果找到了被删除节点的前一个节点

91 if (index == size - 1) { //如果被删除节点是最后一个节点

92 p.nextNode = null; //将被删除节点的前一个节点的引用指向空引用

93 } else { //如果被删除节点不是最后一个节点

94 p.nextNode = p.nextNode.nextNode; //将被删除节点前一个节点对其引用指向被删除节点的下一个节点

95 }96 size--; //减一数量

97 modCount++; //加一操作次数

98 return;99 }100 count++; //没有找到,索引加一

101 }102 }103

104 /**

105 * 通过姓名查找对象106 * 未实现107 *@paramname108 * 对象姓名109 *@return对象数组,可能多个110 */

111 publicPerson[] searchNodeByPersonName(String name) {112 return null;113 }114

115 /**

116 * 通过年龄查找对象117 * 未实现118 *@paramage119 * 对象年龄120 *@return对象数组,可能多个121 */

122 public Person[] searchPersonByAge(intage) {123 return null;124 }125

126 /**

127 * 通过编号查找对象128 * 由于编号是唯一标识,循环遍历查找并返回即可129 *@parampersonNo130 * 对象编号131 *@return查找到的对象或者null132 */

133 public Person searchNode(intpersonNo) {134 Person p = null;135 for (PersonChainNode pcn = head; pcn != null; pcn =pcn.nextNode) {136 if (pcn.person.personNo ==personNo) {137 p =pcn.person;138 }139 }140 returnp;141 }142

143 /**

144 * 通过原对象修改及传入值修改该对象属性145 *146 *@parampersonNo147 * 要修改的对象编号148 *@paramvalue149 * 通过传入值的类型判断修改 只能修改姓名或年龄150 */

151 public void editNode(intpersonNo, Object value) {152 //通过作为唯一标识的编号查找到对象

153 Person target =searchNode(personNo);154 if (target == null) { //如果对象为null

155 return;156 }157 if (value == null) { //如果传入参数为null

158 return;159 }160 //如果传入参数为字符串类型

161 if (value.getClass() == java.lang.String.class) {162 target.name =value.toString();163 return;164 }165 try{166 //如果传入参数为整型

167 target.age =Integer.parseInt(value.toString());168 return;169 } catch(Exception ex) {170 //如果传入参数类型错误

171 return;172 }173 }174

175 /**

176 * 通过对象编号打印对象177 *178 *@parampersonNo179 * 对象编号180 */

181 public void printNode(intpersonNo) {182 Person target =searchNode(personNo);183 if (target == null) {184 return;185 }186 System.out.println(target.toString());187 }188

189 /**

190 * 判断指定对象是否存在链表中191 *192 *@parampersonNo193 * 对象编号194 *@returntrue表示存在该对象,false表示不存在该对象195 */

196 public boolean contains(intpersonNo) {197 if (size != 0) {198 for (PersonChainNode pcn = head; pcn != null; pcn =pcn.nextNode) {199 if (pcn.person.personNo ==personNo) {200 return true;201 }202 }203 }204 return false;205 }206

207 //排序方法

208

209 /**

210 * 通过姓名排序211 */

212 public voidsortByPersonName() {213 }214

215 /**

216 * 通过年龄排序217 */

218 public voidsortByPersonAge() {219 }220

221 /**

222 * 默认排序,通过编号排序223 * 使用冒泡排序,增加了判断以提高效率224 */

225 public voidsort() {226 boolean jx = true; //冒泡排序的简化方法

227 for (PersonChainNode pcn = head; pcn != null && jx; pcn =pcn.nextNode) {228 jx = false;229 for (PersonChainNode pc = head; pc != null && pc.nextNode != null; pc =pc.nextNode) {230 if (pc.person.personNo >pc.nextNode.person.personNo) {231 Person temp =pc.person;232 pc.person =pc.nextNode.person;233 pc.nextNode.person =temp;234 jx = true;235 }236 }237 }238 }239

240 /**

241 * 打印整个链表242 */

243 public voidprintAll() {244 if (size != 0) {245 for (PersonChainNode pcn = head; pcn != null; pcn =pcn.nextNode) {246 System.out.println(pcn.person.toString());247 }248 }249 }250 }

2012-04-11 21:33:32

那么实际上呢,我们在Java编程中使用的LinkedList就是在其内部维护着一个链表。

实际上的操作不外乎增删改查,不同的是由于高度封装,Jdk的LinkedList是使用索引来取得对象并进行操作的。

和以上我的例子是不尽相同的,因为我是安全按照自己的需求来做的,这样比较简单,实际上为了代码重用复用和扩展。Jdk内的链表节点不知道保存的信息,因此没有办法以除了索引之外的方式获取元素。

今天课程到了Java集合框架,老师略微提了一下单向不循环链表。

我将老师的举例改编一下,帮助大家理解:

老师需要找某一个同学,但是碰巧实在放假的时间内。同学们所处的位置都不一样。老师知道班长的手机号码,所以老师打电话给了班长,班长说他也不知道,但是他知道'我'的电话,他又打给我,我也不知道那位同学的地址,我又继续向下一个同学打电话,直到找到他。

那么在以上示例中,加入每一个同学都有另一个同学的电话(有且仅有一个)。

我们就可以说符合单向链表的环境了。大家可以理解记忆。

大家都知道,我们所创建的对象是保存在内存中的。数组是如此,链表也是如此。

但是数组是一个整体空间,所有元素共用。就比如教室内上课的同学们。教室的容量是固定的,同学可少不可多,老师若希望进行查询,能够一眼看出来。

链表和其节点是不同的存储位置,比如我们一个班毕业了。有的同学去了国外,有的去了北京。大家的位置不同,但是有一定联系的。

2012-04-23 19:16:20

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值