单链表及其结点:
链表是一系列存储元素的单元通过指针串接起来实现的,这些单元称为结点,每个单元有两个域:
- 值域:用于存储数据元素
- 指针域:指向下一个具有相同结构的结点
因为只有一个指针结点,称为单链表
特点:
- 尾结点的特征是其next引用为空
- 链表中每个结点的next引用都相当于一个指针,指向另一个结点。借助next可完成链表首结点移动到尾结点。
- 单链表中通常用head引用来指向链表的首结点,由head引用可以完成对整个链表中所有结点的访问。
- 单链表只能通过前驱结点找到后续结点,而无法从后续结点找到前驱结点。
与数组相似,单链表中的结点也具有一个线性次序,即如果节点p的next引用指向结点S,则p就是S的直接前驱,反之是直接后继。
单链表的查询、添加、删除操作分析:
查询操作:在单链表中查找是否包含某个数据元素e,只能从链表的首结点开始,通过每个结点的next引用来依次访问链表中的每个结点,完成相应的查找操作。
1 起始条件:p=head;
2 结束条件:找到e.equals(p.getData())==true
未找到:p==null
3 p指向下一个结点:p=p.getNext();
缺点:逐个比较,频繁移动指针,导致效率低下
如果查询第i个元素的值,同样无法直接定位,只能从首结点开始逐个移动到第i个结点,效率同样低下。
添加操作:
在单链表中元素的插入,是通过在链表中插入数据元素所属的结点来完成的。
对于链表的不同位置,插入的过程会有细微的差别。中间,末尾的添加过程是一样的,关键是首部添加,会有不同。
首部添加会改变整个单链表的起始终点。
1 指明新结点的后继
2 指明新结点的前驱
添加结点不需要移动元素,只需要修改元素的指针,效率高。
删除操作:改变指针,使该结点的前驱指向下个结点
在用单链表实现线性表的时候,为了使程序更加简洁,我们通常在单链表的最前面添加一个头结点。
头结点值域为空,next指向线性表中0号元素所在结点,值域为null。可以对空表、非空表首元结点进行统一处理,简化代码,很常用。
代码实现:
1 新建Node类,内有data和next属性,分别代表值域和指针域;
package com.lt.datastructure.linetable; /** * 单链表的结点 */ public class Node { Object data;//要存储的数据 Node next;//指向下一个元素 public Node() { } public Node(Object data) { this.data = data; } public Node(Object data, Node next) { super(); this.data = data; this.next = next; } public Object getData() { return data; } public void setData(Object data) { this.data = data; } public Node getNext() { return next; } public void setNext(Node next) { this.next = next; } }
2 实现简单的功能
1 package com.lt.datastructure.linetable; 2 3 public class SingleLinkedList implements List{ 4 5 private Node head = new Node();//头结点,不存储数据,为了编程方便 6 7 private int size; //一共有几个结点 8 9 10 public int size() { 11 return size; 12 } 13 14 @Override 15 public Object get(int i) { 16 //和顺序表不一样,不能通过索引计算定位,需要从头结点逐个查找 17 Node p = head; 18 for(int j = 0 ; j<=i;j++){ 19 p = p.next; 20 //p.next就是head.next,head.next指向0结点的地址 21 // p=0时,head.next赋给p,p指向0结点的地址,p=i时,p指向i的地址 22 } 23 return p.data;//得到i位置的数据 24 } 25 26 @Override 27 public void add(int i, Object e) { 28 //p是头结点 29 Node p = head; 30 for(int j=0;j<i;j++){ 31 //j=0,p.next从head变为head.next指向0结点地址,p是0结点,j=i-1,p指向i-1的地址,p是i-1结点 32 p = p.next; 33 } 34 Node newNode = new Node(); 35 newNode.data = e; 36 newNode.next = p.next; // 此处p.next指向i结点,newNode的后驱指向i结点 37 p.next = newNode; //原本指向1结点的指向newNode结点的地址,newNode成功插入i结点 38 //newNode成功成为为1结点 39 size++; 40 } 41 42 @Override 43 public void add(Object e) { 44 this.add(size,e); 45 } 46 }