链式存储结构的线性表(简称为链表)将采用一组地址任意的存储单元存放线性表中的数据元素。链式存储结构的线性表不会按线性的逻辑顺序来保存数据元素,它需要在每个数据元素中保存一个引用下一个数据元素的引用(或者叫做指针)。
对于链式存储结构的线性表而言,它的每个节点都必须包含数据元素本身和一个或者两个用来引用上一个或者下一个的引用,也就是说,有如下公式:
节点 = 数据元素 + 引用下一个节点的引用 + 引用上一个节点的引用
单链表是指每个节点只保留一个引用,该引用指向当节点的下一个节点,没有引用指向头节点,尾节点的next引用为null。如下图所示:
对于单链表通常有两种建表方法:
1、头插法建表:该方法从一个空表开始,不断的创建新节点,将数据元素存入新节点的data域中,然后不断的以新节点为头节点,让新节点指向原有的头节点。
2、尾插法建表:该方法是将新节点插入到当前链表的尾部上,因此需要为链表来定义一个尾部变量来保存链表的最后一个节点。
对于单链表而言,常用的操作有如下三种:
{1、查找}
查找分两种情况下的查找:
(1/)安序号查找第index个节点
(2/)在链表中查找指定的element元素
{2、插入}
示意图如下:
{3、删除}
示意图如下:
代码实现:
数据元素用Student类:
public class Student {
private String name;
private String school;
private Integer age;
public Student() {
}
public Student(String name, String school, Integer age) {
this.name = name;
this.school = school;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSchool() {
return school;
}
public void setSchool(String school) {
this.school = school;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
@Override
public String toString() {
return "S{name=" + name + ", school=" + school + ", age=" + age + "}";
}
@Override
public boolean equals(Object obj) {
if(obj == null){
return false;
}else{
Student stemp = (Student) obj;
return this.age.equals(stemp.age) && this.name.equals(stemp.name) && this.school.equals(stemp.school)
? true : false;
}
}
}
public class LinkList<T>{ //这是一个链表
//定义一个内部类Node,Node实例代表链表的节点
private class Node{
private T data;
private Node next;
@SuppressWarnings("unused")
public Node(){
}
public Node(T data, Node next){
this.data = data;
this.next = next;
}
}
//保存该链表的头节点
private Node header;
//保存该链表的尾节点
private Node tail;
//保存该链表的节点数
private int size;
//创建空链表
public LinkList(){
header = null;
tail = null;
}
//以指定元素创建该单链表,该单链表只有一个元素
public LinkList(T element){
header = new Node(element, null);
//只有一个节点,header 和 tail 都指向该节点
tail = header;
size ++;
}
//返回该单链表的长度
public int length(){
return size;
}
//获取该单链表中索引为 index的元素
public T get(int index){
return getNodeByIndex(index).data;
}
//获取该单链表中索引为index的节点
private Node getNodeByIndex(int index) {
if(index < 0 || index > size - 1){
throw new IndexOutOfBoundsException("线性表索引越界:"+index);
}
//从header节点开始
Node current = header;
for(int i = 0; i < size && current != null; i ++,current = current.next){
if(index == i){
return current;
}
}
return null;
}
//查找该单链表中指定元素的索引
public int indexOf(T element){
//从header节点开始
Node current = header;
for(int i = 0; i < size && current != null; i ++,current = current.next){
if(current.data == element){
return i;
}
}
return -1;
}
//查找单链表中数据相同元素的索引
public int locate(T element){
//从header节点开始
Node current = header;
for(int i = 0; i < size && current != null; i ++,current = current.next){
if(current.data.equals(element)){
return i;
}
}
return -1;
}
//向该单链表中指定索引位置插入元素
public void insert(int index, T element){
if(index < 0 || index > size){
throw new IndexOutOfBoundsException("线性表索引越界:"+index);
}
if(header == null){
addAtTail(element);
}else{
if(index == 0){
addAtHeader(element);
}else{
/*//索引index-1处的节点
Node preNode = getNodeByIndex(index - 1);
//索引index处的节点
Node indexNode = getNodeByIndex(index);
Node newNode = new Node(element, null);
preNode.next = newNode;
newNode.next = indexNode;
size ++;*/
//索引index-1处的节点
Node preNode = getNodeByIndex(index - 1);
//让新节点指向index处节点
//让preNode指向新节点
preNode.next = new Node(element, preNode.next);
size ++;
}
}
}
//采用尾插法向该单链表插入元素
public void addAtTail(T element){
//如果此时单链表为空链表
if(header == null){
header = new Node(element, null);
//只有一个节点,header 和 tail 都指向该节点
tail = header;
}else{
Node newNode = new Node(element, null);
//将newNode插入到单链表的尾部
tail.next = newNode;
//保存单链表尾部的节点
tail = newNode;
}
size ++;
}
//采用头插法向该单链表插入元素
public void addAtHeader(T element){
//创建新节点,并让新节点的next指向header
//然后将header指向新节点,也就是将新节点当做单链表的header
header = new Node(element, header);
//如果插入之前是空链表
if(tail == null){
tail = header;
}
size ++;
}
//删除该线性表中指定索引处的元素 并返回该元素
@SuppressWarnings("unused")
public T delete(int index){
if(index < 0 || index > size - 1){
throw new IndexOutOfBoundsException("线性表索引越界:"+index);
}
Node del = null;
if(index == 0){
del = header;
header = header.next;
//释放内存
Node preNode = getNodeByIndex(0);
preNode = null;
}else if(index == size - 1){
Node preNode = getNodeByIndex(index - 1);
del = tail;
tail = preNode;
preNode.next = null;
}else{
//获取删除节点的前一个节点
Node preNode = getNodeByIndex(index - 1);
//获取要删除的节点并给del
del = preNode.next;
//将preNode指向index + 1 处的节点
preNode.next = del.next;
//删除index处节点(即指向null)
del.next = null;
}
size --;
return del.data;
}
//删除单链表中最后一个元素 并返回该元素
public T remove(){
return delete(size - 1);
}
//判断单链表是否为空链表
public boolean isEmpty(){
return size == 0;
}
//清空单链表
@SuppressWarnings("null")
public void clear(){
if(size > 0){
Node clearNode = header;
for(int i = 0; i < size && clearNode != null; i ++,clearNode = clearNode.next){
clearNode = null;
}
header = null;
tail = null;
size = 0;
}
}
//toString方法
public String toString(){
if(isEmpty()){
return "[]";
}else{
StringBuffer sb = new StringBuffer("[");
Node currNode = header;
for(int i = 0; i < size && currNode != null; i ++, currNode = currNode.next){
sb.append(currNode.data.toString() + ",");
}
return sb.toString().substring(0, sb.length() - 1) + "]";
}
}
}
import com.yc.list.LinkList;
public class LinkListTest {
public static void main(String[] args) {
LinkList<Student> list = new LinkList<Student>();
Student s = new Student("WB","湖南工学院",22);
list.addAtTail( s );
list.addAtTail(new Student("LS","湖南工学院",22));
list.addAtTail(new Student("SJX","高铁学院",22));
System.out.println( list.toString());
list.insert(2, new Student());
System.out.println( list.toString());
list.delete(3);
System.out.println( list.toString());
System.out.println("此时单链表中索引为2的元素数据为:" + list.get(2));
System.out.println("此时单链表中姓名叫WB的元素索引为:" + list.indexOf( new Student("WB","湖南工学院",22) ));
System.out.println("此时单链表中姓名叫WB的元素索引为:" + list.locate( new Student("WB","湖南工学院",22) ));
}
}
测试结果如下:
同样的,这里的单链表也没用实现线程安全。
对于单链表而言,它的功能与前面的顺序线性表基本相同,因为他们都是线性表,只是底层的实现不同而已。因此,链表和顺序表只是性能上的差异:顺序表在随机存储(查询)时性能很好,但插入、删除操作就不如链表;链表的插入删除操作很好,但随机存储(查询)时性能就不如顺序表。