概述:众所周知,数据对于数据的存储时连续的,也就是说在计算机的内存中是一个整体的、连续的、不间断的ADT数据结构。伴随的问题也会随之出现,这样其实对于内存的动态分配是不灵活的。而链表具备这个优点。因此链表对于数据的插入和删除是方便的,但是对于数据的查询是麻烦的。因为需要遍历链表,而对于链表的遍历确实极度的麻烦。

1 单向链表的定义

链表主要用来存储引用类型的数据。其结构可以由下图清楚的表示: 

链表结点的定义

  1. class Node{
  2. // 链表中保存的数据
  3. public Object obj;
  4. // 下一个结点的应用
  5. public Node next;
  6. // 该结点中保存的数据
  7. public Node(Object obj){
  8. this.obj=obj;
  9. }
  10. public void setNext(Node next){
  11. this.next=next;
  12. }
  13. public Node getNext(){
  14. return this.next;
  15. }
  16. public Object getObj(){
  17. return this.obj;
  18. }
  19. }

链表的设置和取出

  1. /**
  2. * 对链表设置数据和取出数据
  3. */
  4. public class LinkDemo{
  5. public void static main(String[] args){
  6. //设置数据
  7. Node root= new Node("链表头结点");
  8. Node n1=new Node("链表1");
  9. Node n2=new Node("链表2");
  10. Node n3=new Node("链表3");
  11. // 维护链表的关系
  12. root.setNext(n1);
  13. n1.setNext(n2);
  14. n2.setNext(n3);
  15. // 取出链表的数据
  16. Node currentNode=root;// 创建当前结点对象,将根结点赋值给当前结点
  17. // 判断当前结点是不是为null,如果当前结点为null,则终止循环,否则继续输出
  18. while(currentNode != null){
  19. // 打印出当前结点的数据,然后修改当前结点的引用
  20. System.out.println(currentNode.getObj());
  21. // 将下一个结点设置为当前结点
  22. currentNode=currentNode.getNext();
  23. }
  24. }
  25. }

结果测试如下: 
 
使用递归的方式优化输出:

  1. /**
  2. *对链表设置数据和取出数据
  3. */
  4. public class LinkDemo{
  5. public void static main(String[] args){
  6. //设置数据
  7. Node root= new Node("链表头结点");
  8. Node n1=new Node("链表1");
  9. Node n2=new Node("链表2");
  10. Node n3=new Node("链表3");
  11. // 维护链表的关系
  12. root.setNext(n1);
  13. n1.setNext(n2);
  14. n2.setNext(n3);
  15. // 取出链表的数据
  16. Node currentNode=root;// 创建当前结点对象,将根结点赋值给当前结点
  17. // 判断当前结点是不是为null,如果当前结点为null,则终止循环,否则继续输出
  18. print(root);
  19. }
  20. public static print(Node node){
  21. //递归的结束条件
  22. if(node == null){
  23. return;
  24. }else{
  25. System.out.println(node.getObj());
  26. print(node.getNext());
  27. }
  28. }
  29. }

在实际的开发中,我们希望对于数据的保存和输出应该是由如下的形式出现的:

  1. public class LinkDemo3{
  2. public static void main(){
  3. Link link=new Link();
  4. link.add("A");
  5. link.add("B");
  6. link.add("C");
  7. link.add("D");
  8. // 展示所有的数据
  9. link.print();
  10. }
  11. }

此时我们希望让Node结点类来负责对结点的操作,而Link类类负责对数据的操作。

  1. // 节点类对象
  2. class Node{
  3. // 结点中的数据
  4. public Object obj;
  5. // 结点的下一个引用
  6. public Node next;
  7. // 结点中保存数据
  8. public Node(Object obj){
  9. return this.obj=obj;
  10. }
  11. public Node getNext(){
  12. return this.next;
  13. }
  14. public void setNext(Node next){
  15. this.next=next;
  16. }
  17. public Object getObj(){
  18. return this.obj;
  19. }
  20. // 添加结点
  21. public void addNode(Node node){
  22. // 对于添加结点来说,首先判断头节点是否存在下一个的引用,如果为null,此时就可以添加
  23. // 第一次调用该方法,this表示的就是Link.root对象
  24. if(this.next==null){
  25. //为空,那么将该结点挂到头结点的下一个引用上
  26. this.next=node;
  27. }else{
  28. //头节点的下一个引用不为空,那么应该是将当前结点的下一个引用为此node结点
  29. //递归循环
  30. this.next.addNode(node);
  31. }
  32. }
  33. // 展示所有的结点
  34. public void printNode(){
  35. System.out.println(this.obj);
  36. if(this.next!=null){
  37. // 如果当前结点对于下一个结点的引用不为空,那么当前结点对象递归调用打印方法。
  38. // 当前结点的下一个引用this.next
  39. this.next.printNode();
  40. }
  41. }
  42. }
  43. // 对链表的操作
  44. class Link{
  45. // 初始化结点对象,即头结点
  46. public Node root;
  47. // 添加数据
  48. public void add(Object obj){
  49. //创建当前结点对象,然后保存数据
  50. Node currentNode=new Node(obj);
  51. // 如果要添加数据,就要首先判断根结点是否为空
  52. if(root==null){
  53. //头节点为空,则将创建的当前结点赋值给根结点
  54. root=currentNode;
  55. }else{
  56. //如果头结点不为空,此时应该由结点自己来判断
  57. //头节点不为空值,要保存的数据应该在之后的结点上,调用添加结点的方法
  58. root.addNode(currentNode);
  59. }
  60. }
  61. // 展示所有的数据
  62. public void print(){
  63. if(root!=null){
  64. root.printNode();
  65. }
  66. }
  67. }

当我们创建结点对象的时候,对于保存数据和输出输出,我们每次都会判断根节点是不是空值。这个很重要。当前结点不是根节点这个应该注意到。进一步的优化,注意到我们不希望调用者直接操作结点对象,单纯的Node类会被直接操作,这样不符合Java的封装思想,我们可以使用内部类,并且直接对Node内部类私有化

  1. // 对链表的操作
  2. class Link{
  3. // 初始化结点对象,即头结点
  4. public Node root;
  5. // 添加数据
  6. public void add(Object obj){
  7. //创建当前结点对象,然后保存数据
  8. Node currentNode=new Node(obj);
  9. // 如果要添加数据,就要首先判断根结点是否为空
  10. if(root==null){
  11. //头节点为空,则将创建的当前结点赋值给根结点
  12. root=currentNode;
  13. }else{
  14. //如果头结点不为空,此时应该由结点自己来判断
  15. //头节点不为空值,要保存的数据应该在之后的结点上,调用添加结点的方法
  16. root.addNode(currentNode);
  17. }
  18. }
  19. // 展示所有的数据
  20. public void print(){
  21. if(root!=null){
  22. root.printNode();
  23. }
  24. }
  25. // 节点类对象
  26. private class Node{
  27. // 结点中的数据
  28. public Object obj;
  29. // 结点的下一个引用
  30. public Node next;
  31. // 结点中保存数据
  32. public Node(Object obj){
  33. return this.obj=obj;
  34. }
  35. // 添加结点
  36. public void addNode(Node node){
  37. // 对于添加结点来说,首先判断头节点是否存在下一个的引用,如果为null,此时就可以添加
  38. // 第一次调用该方法,this表示的就是Link.root对象
  39. if(this.next==null){
  40. //为空,那么将该结点挂到头结点的下一个引用上
  41. this.next=node;
  42. }else{
  43. //头节点的下一个引用不为空,那么应该是将当前结点的下一个引用为此node结点
  44. //递归循环
  45. this.next.addNode(node);
  46. }
  47. }
  48. // 展示所有的结点
  49. public void printNode(){
  50. System.out.println(this.obj);
  51. if(this.next!=null){
  52. // 如果当前结点对于下一个结点的引用不为空,那么当前结点对象递归调用打印方法。
  53. // 当前结点的下一个引用this.next
  54. this.next.printNode();
  55. }
  56. }
  57. }
  58. }

确定链表的数据结构

  1. class Link {
  2. // 需要结点对象
  3. private Node root;//根节点对象
  4. // 结点对象
  5. //***************内部类*******************
  6. private class Node {
  7. private Object obj;//结点中保存的数据
  8. private Node next;//下一个结点的引用
  9.  
  10. // 结点中的数据
  11. public Node(Object obj) {
  12. this.obj = obj;
  13. }
  14. }
  15. //***************内部类*******************
  16. }

添加数据 public void add(Object obj);

  1. class Link {
  2. // 需要结点对象
  3. private Node root;//根节点对象
  4. // 结点对象
  5. //***************内部类*******************
  6. private class Node {
  7. private Object obj;//结点中保存的数据
  8. private Node next;//下一个结点的引用
  9.  
  10. // 结点中的数据
  11. public Node(Object obj) {
  12. this.obj = obj;
  13. }
  14. //结点的添加
  15. public void addNode(Node node){
  16. if(this.next==null){
  17. //把当前结点赋值给this.next
  18. this.next=node;
  19. }else{
  20. //添加一个结点
  21. this.next.addNode(node);
  22. }
  23. }
  24. }
  25. //***************内部类*******************
  26. public void add(Object obj){
  27. //对于数据为null,是可以保存的,在这里我假设数据为null是不可以保存的
  28. if(obj==null){
  29. return;
  30. }
  31. Node node=new Node(obj);
  32. if(this.root==null){
  33. root=node;
  34. }else{
  35. //注意:Link类负责根节点的维护和结点的创建,对于结点的具体操作,应该是Node类操作
  36. this.root.addNode(node);
  37. }
  38. }
  39. }

获取链表的长度 public int size();

每一次的数据保存都需要长度加1.因此我们在Link类中添加组成员变量size.保存时让它自增运算;

  1. class Link {
  2. // 需要结点对象
  3. private Node root;//根节点对象
  4. private int size;//链表的长度
  5. // 结点对象
  6. //***************内部类*******************
  7. private class Node {
  8. private Object obj;//结点中保存的数据
  9. private Node next;//下一个结点的引用
  10.  
  11. // 结点中的数据
  12. public Node(Object obj) {
  13. this.obj = obj;
  14. }
  15. //结点的添加
  16. public void addNode(Node node){
  17. if(this.next==null){
  18. //把当前结点赋值给this.next
  19. this.next=node;
  20. }else{
  21. //添加一个结点
  22. this.next.addNode(node);
  23. }
  24. }
  25. }
  26. //***************内部类*******************
  27. /**数据的添加*/
  28. public void add(Object obj){
  29. //对于数据为null,是可以保存的,在这里我假设数据为null是不可以保存的
  30. if(obj==null){
  31. return;
  32. }
  33. Node node=new Node(obj);
  34. if(this.root==null){
  35. root=node;
  36. }else{
  37. //注意:Link类负责根节点的维护和结点的创建,对于结点的具体操作,应该是Node类操作
  38. this.root.addNode(node);
  39. }
  40. this.size++;//长度自增运算
  41. }
  42. /**
  43. *链表的长度
  44. */
  45. public int size(){
  46. return this.size;
  47. }
  48.  
  49. }

判断链表是否为null,public boolean isEmpty();

  1. 原理1:如果root为null,则链表的长度为null

  2. 原理2:如果链表的size==0.则链表的长度为空

  1. class Link {
  2. // 需要结点对象
  3. private Node root;//根节点对象
  4. private int size;//链表的长度
  5. // 结点对象
  6. //***************内部类*******************
  7. private class Node {
  8. private Object obj;//结点中保存的数据
  9. private Node next;//下一个结点的引用
  10.  
  11. // 结点中的数据
  12. public Node(Object obj) {
  13. this.obj = obj;
  14. }
  15. //结点的添加
  16. public void addNode(Node node){
  17. if(this.next==null){
  18. //把当前结点赋值给this.next
  19. this.next=node;
  20. }else{
  21. //添加一个结点
  22. this.next.addNode(node);
  23. }
  24. }
  25. }
  26. //***************内部类*******************
  27. /**数据的添加*/
  28. public void add(Object obj){
  29. //对于数据为null,是可以保存的,在这里我假设数据为null是不可以保存的
  30. if(obj==null){
  31. return;
  32. }
  33. Node node=new Node(obj);
  34. if(this.root==null){
  35. root=node;
  36. }else{
  37. //注意:Link类负责根节点的维护和结点的创建,对于结点的具体操作,应该是Node类操作
  38. this.root.addNode(node);
  39. }
  40. this.size++;//长度自增运算
  41. }
  42. /**
  43. *链表的长度
  44. */
  45. public int size(){
  46. return this.size;
  47. }
  48. /**
  49. *判读链表是否为空
  50. */
  51. public boolean isEmpty(){
  52. return this.size==0?true:flase;
  53. }
  54. }

判断链表中是否存在某个元素 public boolean contains(Object obj);

对象的匹配可以使用equals()方法,但是对于自定义对象而言,需要写compare()方法来自定义匹配结果。 

  1. class Link {
  2. // 需要结点对象
  3. private Node root;//根节点对象
  4. private int size;//链表的长度
  5.  
  6. public void add(Object obj) {
  7. // 创建结点对象,并且保存数据
  8. Node newNode = new Node(obj);
  9. if (root == null) {
  10. root = newNode;
  11. } else {
  12. //此时根节点不为空,需要添加结点
  13. root.addNode(newNode);
  14. }
  15. this.size++;//每次保存数据,链表自增
  16. }
  17.  
  18. /**
  19. * 链表的长度
  20. * @return
  21. */
  22. public int size(){
  23. return this.size;
  24. }
  25.  
  26. /**
  27. * 判断链表是否为空
  28. * @return
  29. */
  30. public boolean isEmpty(){
  31. return this.size==0?true:false;
  32. }
  33. public boolean contains(Object obj){
  34. if(obj==null || this.root==null){
  35. return false;
  36. }else {
  37. // 此时的判断应该交给Node结点完成
  38. return root.containsNode(obj);
  39. }
  40. }
  41.  
  42. public void print() {
  43. if (root != null) {
  44. root.printNode();
  45. }
  46. }
  47. private class Node {
  48. private Object obj;//结点中保存的数据
  49. private Node next;//下一个结点的引用
  50.  
  51. // 结点中的数据
  52. public Node(Object obj) {
  53. this.obj = obj;
  54. }
  55. // 结点的保存
  56. public void addNode(Node node) {
  57. if (this.next == null) {//当前结点的下一个结点为空,此时就可以添加结点
  58. this.next = node;
  59. } else {
  60. //否则,此时应该循环递归添加结点
  61. // 当前结点对象应该添加新的结点
  62. this.next.addNode(node);
  63. }
  64. }
  65.  
  66. // 输出一个结点
  67. public void printNode() {
  68. //打印数据
  69. System.out.println(this.obj);
  70. if (this.next != null) {
  71. this.next.printNode();//递归调用输出
  72. }
  73. }
  74.  
  75. /**
  76. * 判断链表中受否包含某个元素
  77. * @param obj
  78. * @return
  79. */
  80. public boolean containsNode(Object obj) {
  81. if(this.next==null){
  82. return false;
  83. }else {
  84. // 链表结点不为空。此时要判断元素是否匹配
  85. if(this.obj.equals(obj)){
  86. return true;
  87. }else {
  88. return this.next.containsNode(obj);
  89. }
  90. }
  91. }
  92. }
  93. }

根据索引查询元素

然后定义get方法;

  1. 查询有多次,但是每一次的查询都要将foot属性设置为0;
  2. 如果当前查询的索引大于Link类的编号size.此时查询不到
  1. public Object get(int index){
  2. //如果当前要查询的索引大于链表的额size.那么直接返回null
  3. if(index>this.size){
  4. return null;
  5. }
  6. // 每一的查询都要将foot从0开始
  7. this.foot=0;
  8. // 此后交给Node结点来判断
  9. return this.root.getNode(index);
  10. }

getNode(index)的实现

  1. public Object getNode(int index) {
  2. //外部类Link调用内部对象this.foot,外部类直接对内部类的访问
  3. // 如果当前结点的编号自增==当前索引
  4. if(Link.this.foot++==index){
  5. // 返回数据
  6. return this.obj;
  7. }else {
  8. return this.next.getNode(index);
  9. }
  10. }

修改链表元素,和上述的查询实现基本一致 public void set(int index,Object obj);

  1. public void set(int index,Object obj){
  2. if(index>this.size){
  3. return;
  4. }
  5. this.foot=0;
  6. this.root.setNode(index,obj);
  7. }
  8. public void setNode(int index, Object obj) {
  9. if(Link.this.foot++==index){
  10. this.obj=obj;//数据的设置
  11. }else {
  12. this.next.setNode(index,obj);
  13. }
  14. }

总结

NO 方法名称 类型 备注
1 public void add(Object obj) 普通方法 向链表之中添加数
2 public int size() 普通方法 取得链表的长度
3 public boolean isEmpty() 普通方法 判断链表是否为空
4 public boolean contains(Object obj) 普通方法 判断链表是否存在某个元素
3 public Object get(int index) 普通方法 根据链表索引查询某个元素
3 public void set(int index,Object obj) 普通方法 修改某个元素

java对单向单向链表的操作的更多相关文章

  1. Java数据结构之单向环形链表(解决Josephu约瑟夫环问题)

    1.Josephu(约瑟夫.约瑟夫环)问题: 设编号为1,2,… n的n个人围坐一圈,约定编号为k(1<=k<=n)的人从1开始报数,数到m 的那个人出列,它的下一位又从1开始报数,数到m ...

  2. 3,java数据结构和算法:约瑟夫环出队顺序, 单向环形链表的应用

    什么是约瑟夫环? 就是数小孩游戏: 直接上代码: 要实现这个,只需要理清思路就好了 孩子节点: class Boy{ int no;//当前孩子的编码 Boy next; // 下一节点 public ...

  3. 数据结构与算法——链表 Linked List(单链表、双向链表、单向环形链表-Josephu 问题)

    链表是有序的列表,但是在内存中存储图下图所示 链表是以 节点 的方式来存储,是 链式存储 每个节点包含 data 域.next 域,指向下一个节点 链表的各个节点 不一定是连续存储,如上图所示 链表还 ...

  4. java实现单链表常见操作

    一.概述: 本文主要总结单链表常见操作的实现,包括链表结点添加.删除:链表正向遍历和反向遍历.链表排序.判断链表是否有环.是否相交.获取某一结点等. 二.概念: 链表: 一种重要的数据结构,HashM ...

  5. 单向环形链表解决约瑟夫环(Josephus)问题

    一.约瑟夫环问题 Josephu 问题为:设编号为1,2,- n的n个人围坐一圈,约定编号为k(1<=k<=n)的人从1开始报数,数到m 的那个人出列,它的下一位又从1开始报数,数到m的那 ...

  6. 数据结构Java实现04----循环链表、仿真链表

    单向循环链表 双向循环链表 仿真链表 一.单向循环链表: 1.概念: 单向循环链表是单链表的另一种形式,其结构特点是链表中最后一个结点的指针不再是结束标记,而是指向整个链表的第一个结点,从而使单链表形 ...

  7. 数据结构Java实现03----单向链表的插入和删除

    文本主要内容: 链表结构 单链表代码实现 单链表的效率分析 一.链表结构: (物理存储结构上不连续,逻辑上连续:大小不固定)            概念: 链式存储结构是基于指针实现的.我们把一个数据 ...

  8. 数据结构Java实现02----单向链表的插入和删除

    文本主要内容: 链表结构 单链表代码实现 单链表的效率分析 一.链表结构: (物理存储结构上不连续,逻辑上连续:大小不固定)            概念: 链式存储结构是基于指针实现的.我们把一个数据 ...

  9. Java描述数据结构之链表的增删改查

    链表是一种常见的基础数据结构,它是一种线性表,但在内存中它并不是顺序存储的,它是以链式进行存储的,每一个节点里存放的是下一个节点的"指针".在Java中的数据分为引用数据类型和基础 ...

随机推荐

  1. LeetCode:位运算实现加法

    LeetCode:位运算实现加法 写在前面 位运算符 实现加法的思路 两个加数,比如5(101)和6(110),如何不用加法就能得出两者之和呢? 我们知道二进制计算中,如果使用异或将会产生无进位的两者 ...

  2. python 删除文件中指定行

    代码适用情况:xml文件,循环出现某几行,根据这几行中的某个字段删掉这几行这段代码的作用删除jenkins中config.xml中在自动生成pipline报错的时的回滚 start = '<se ...

  3. Data Structure Binary Search Tree: Find k-th smallest element in BST (Order Statistics in BST)

    http://www.geeksforgeeks.org/find-k-th-smallest-element-in-bst-order-statistics-in-bst/ #include < ...

  4. 第三章 python中的字符串

    一.字符串的基本操作 所有标准的序列操作对字符串同样适用,如索引.分片.乘法.判断成员是否存在.求长度.最大值和最小值等.记住一点,字符串是不可变的. 二.字符串中重要的方法 1.find(subst ...

  5. css3条纹边框效果

    在线演示 本地下载

  6. CSS3自定义Checkbox特效

    在线演示 本地下载

  7. Kuhn-Munkres算法 (剪辑)(备用)

    KM算法是通过给每个顶点一个标号(叫做顶标)来把求最大权匹配的问题转化为求完备匹配的问题的. 设顶点Xi的顶标为A[i],顶点Yi的顶标为B[i],顶点Xi与Yj之间的边权为w[i,j]. 在算法执行 ...

  8. sqlserver 函数里并返回一个表格数据拼接的字符串

    Create function [dbo].[GetChildWorkerExtension](     @ChildId int)returns nvarchar(100)asbegin       ...

  9. vmware在桥接模式下配置centos7网络

    首先要将Vmware10.0.3设置为桥接模式. CentOS 7.0默认安装好之后是没有自动开启网络连接的! cd  /etc/sysconfig/network-scripts/  #进入网络配置 ...

  10. javaScript-进阶篇(二)

    JavaScript的内置对象 JavaScript 中的所有事物都是对象,如:字符串.数值.数组.函数等,每个对象带有属性和方法 对象的属性:反映该对象某些特定的性质的,如:字符串的长度.图像的长宽 ...