为什么需要使用链表 ?
数组是我们常用的一种数据结构,它的使用是非常方便的,但是一旦数组的大小确定下来,就不能再次改变。所以数组队列就应运而生了 ,它可以不断的改变大小,但是它的使用需要一段连续的内存,如果我们的内存中没有一段连续的内存的话,就无法使用这种数据结构了。因此,链表就产生了,那么,链表有哪些特异功能呢?
链表的优点:
- 链表的长度可以改变
- 无需使用连续的内存来存放数据
什么是链表 ?
链表的存储原理如下图所示:
上面展示的是一个单链表的存储原理图,简单易懂,head为头节点,他不存放任何的数据,只是充当一个指向链表中真正存放数据的第一个节点的作用,而每个节点中都有一个next引用,指向下一个节点,就这样一节一节往下面记录,直到最后一个节点,其中的next指向null。
链表有很多种,比如单链表,双链表等等。我们就对单链表进行学习,其他的懂了原理其实是一样的。
在实现单链表之前,先简单的介绍一下什么是泛型。
泛型
Java 泛型(generics)是 JDK 5 中引入的一个新特性, 泛型提供了编译时类型安全检测机制,该机制允许程序员在编译时检测到非法的类型。泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。
由于我们不确定我们写的链表会被用来接收什么类型的数据,所以引入泛型,它可以接收一切的引用数据类型。常用的泛型有 E、K、V。
链表的实现
- 定义一个接口,用来定义我们链表所需要实现的功能。链表包括基础的增、删、改、查的功能
public interface MyList<E> {
//增加元素
public void add(E e);
//删除元素
public void delete(int index);
//修改元素
public void change(int index,E e);
//查找元素
public E getdata(int index);
//插入元素
public void insert(int index,E e);
//获取元素个数
public int getSize();
}
- 定义一个类实现接口,并重写其中的方法。
public class Link<E> implements MyList<E>{
Node<E> head,tail;
int size;
public Link(){
head = new Node<>();
tail = head;
}
//添加数据
@Override
public void add(E e) {
// TODO Auto-generated method stub
//创建新节点
Node<E> node = new Node<>(e);
//尾节点指向新节点
tail.next = node;
//新节点成为尾节点
tail = node;
size++;
}
//删除节点的元素
@Override
public void delete(int index) {
// TODO Auto-generated method stub
if(index>0 && index<=size){
Node<E> node = head;
//推进到删除 的位置
for(int i=0;i<index-1;i++){
node = node.next;
}
Node<E> temp = node.next;
node.next = temp.next;
size--;
}else {
System.out.println("索引值有误,请重新输入!");
}
}
//改变节点的数据
@Override
public void change(int index, E e) {
// TODO Auto-generated method stub
if(index>0 && index<=size){
Node<E> node = head;
//推进到修改的位置
for(int i=0;i<index;i++){
node = node.next;
}
node.data = e;
}else {
System.out.println("索引值有误,请重新输入!");
}
}
//在指定位置插入数据
@Override
public void insert(int index, E e) {
// TODO Auto-generated method stub
if(index>0 && index<=size+1){
Node<E> node = head;
//推进到插入的位置
for(int i=0;i<index-1;i++){
node = node.next;
}
Node<E> temp = new Node<>(e);
temp.next = node.next;
node.next = temp;
size++;
}else {
System.out.println("索引值有误,请重新输入!");
}
}
//获取节点的 数据
@Override
public E getdata(int index) {
// TODO Auto-generated method stub
Node<E> node = head.next;
for(int i=0;i<index;i++){
node = node.next;
}
return node.data;
}
//得到元素的个数
@Override
public int getSize() {
// TODO Auto-generated method stub
return size;
}
}
- 节点类
节点类中需要定义2个属性:要存储的数据 data 和下一个节点 next
public class Node<E> {
E data;
Node<E> next;
public Node(){}
public Node(E data){
this.data = data;
}
}
- 主类(main方法所在的类)
public class Main<E> {
public static void main(String[] args){
Link<String> link = new Link<>();
link.add("a");
link.add("b");
link.add("c");
link.add("d");
link.add("e");
link.add("f");
System.out.println("原始数据:");
for(int i=0;i<link.getSize();i++){
System.out.println(link.getdata(i));
}
link.delete(3);
System.out.println("删除3后:");
for(int i=0;i<link.getSize();i++){
System.out.println(link.getdata(i));
}
link.change(3, "change!");
System.out.println("改变后:");
for(int i=0;i<link.getSize();i++){
System.out.println(link.getdata(i));
}
link.insert(8, "insert!");
System.out.println("插入后:");
for(int i=0;i<link.getSize();i++){
System.out.println(link.getdata(i));
}
}
}
这就是一个简单的链表了,小伙伴赶快实现起来吧!