链表
本质是一个动态的对象数组,它可以实现若干个对象的存储。
- 传统的数组应用有限,最大的缺点在于长度的固定
- 利用引用的逻辑关系来实现类似于数组的数据处理操作,以一种保存"多"方数据的形式,实现数组类似的功能
思想分析
链表的每个节点作为一个类,该类需要有存储数据的数据,有存储下一个节点引用的节点区。
链表本身可以作为一个类,存放包括增加数据,删除数据等各种链表操作。
保存的数据与链表类无关联,与Node节点类有关联,
而链表类与Node节点类也有关联,因此将Node类作为链表类的内部类,可以实现数据和链表类的关联。
代码实现
(1)定义一个链表类的标准接口
interface ILink<E>{
public void add(E e);//添加数据
public int size();//获得长度
public boolean isEmpty();//是否为空
public Object[] toArray(); // 返回集合数据
public E get(int index);//根据指引获得值
public void set(int index,E data);//根据下标设置数据
public int isExist(E data);//判断数据是否存在 存在返回下标,不存在返回-1
public void remove(E data);//删除指定数据内容
public void clean();//链表数据整体清空
}
(2)定义一个实现接口的链表类
class LinkImpl<E> implements ILink<E>{
//Node作为内部类,作为链表类的成员
private class Node{
private E data; // 保存数据
private Node next; // 保存下一个引用
public Node(E data) { //Node构造函数必须需要一个数据,否则Node实例化对象无意义
this.data = data;
}
//链表类的其他属性成员
private Node root; // 保存根元素(即头结点,指向第一个有效链表节点)
private int count; //记录有效节点个数
private Object[] returnData; //将链表转化为数组
private int foot; //脚标,可以理解为记录第几个节点
}
(3)添加数据方法实现
//定义在链表类中
@Override
public void add(E e) {
if(e == null) { // 保存的数据为空
return;
}
//数据有效时,创建新节点
Node newNode = new Node(e); // 创建一个新节点
if(this.root == null) {
this.root = newNode; //如果根节点为空,将新节点赋值给根节点
}
else {
this.root.addNode(newNode); //利用递归遍历找到最后一个有数据节点
}
this.count++; //每次增加一个节点,count++
}
//定义在内部类Node类中
//通过递归将新节点保存在合适的位置
//第一次调用:LinkImpl.root.addNode(),this = LinkImpl.root 判断root.next是否为空,也就是判断root的下一个节点如果为空,就将新节点挂在root上(root.this = newNode)
//第二次调用:root.next.addNode() this = root.next 判断root.nex.next是否为空。
public void addNode(Node newNode) {
if(this.next == null) {
this.next = newNode;
}
else {
this.next.addNode(newNode);
}
}
(4)获得链表长度
//定义在链表类中
@Override
public int size() {
return this.count;
}
(5)判断链表是否为空
@Override
public boolean isEmpty() {
return this.count == 0;
}
(6)将链表转为数组返回
//定义在链表类中
@Override
public Object[] toArray() {
if(isEmpty()) {
System.out.println("数据为空");
return null;
}
else {
this.foot = 0; //数组脚标重置
this.returnData = new Object[this.count]; //根据有效数据创建数组
this.root.toArrayNode(); //利用Node进行递归数据获取
return this.returnData;
}
}
//定义在内部类Node类中
public void toArrayNode(){
//先将root的值赋给数组的[0],如果next不为null,说明还有下一个节点,递归调用重复赋值操作。
LinkImpl.this.returnData[LinkImpl.this.foot++] = this.data;
if(this.next != null) {
this.next.toArrayNode();
}
}
(7)根据下标获取链表数据
//定义在链表类中 ,index从0开始,和数组下标一致
@Override
//数据获取一个数据的时间复杂度为1,链表获取数据的时间复杂度为n
public E get(int index) {
this.foot = 0;//脚标先清0
if(index < 0 || index >= count) {
System.out.println("下标错误");
return null ;
}
else {
return this.root.traverseNode(index);//有Node类判断index是第几个节点
}
}
//定义在内部类Node中
public E traverseNode(int index) {
if(index == LinkImpl.this.foot) {
return this.data;
}
else {
LinkImpl.this.foot++;//判断下一个节点,因此脚标+1
return this.next.traverseNode(index);
}
}
(8)根据脚标改变数据
//定义在链表类中
@Override
public void set(int index, E data) {
this.foot = 0; //脚标清0
if(index < 1 || index > count) {
System.out.println("下标错误");
return;
}
else {
this.root.setNode(index,data);
}
}
//定义在内部类Node中,同获取数据一致
public void setNode(int index, E data) {
if(index == LinkImpl.this.foot) {
this.data = data;
}
else{
LinkImpl.this.foot++;
this.next.setNode(index, data);
}
}
(9)判断一个数据是否存在,返回脚标
//定义在链表类中
@Override
public int isExist(E data) {
if(isEmpty()) {
System.out.println("链表为空");
return -1;
}
else {
this.foot = 0;
return this.root.ExistNode(data);
}
}
//定义在内部类Node中
public int ExistNode(E data) {
if(this.data == data) {
return LinkImpl.this.foot;
}
else {
LinkImpl.this.foot++;
return this.next.ExistNode(data);
}
}
(9)传入一个数据,判断是否存在,存在即删除
要删除的节点的前一个节点的next指向要删除节点的next即可
//定义在链表类中
//考虑两种情况
//1 要删除的是根节点数据
//2 要删除的不是根节点数据
public void remove(E data) {
if(this.isExist(data) == 0) { //先判断数据是否存在,如果存在返回的是0,这说明要删除的是根节点,直接让根节点指向根节点的下一个节点。
this.root = this.root.next;
}
//如果不存在该数据,则返回-1
else if(this.isExist(data) == -1){
return;
}
//要删除的不是根节点数据,传入要删除节点的前一个节点引用
else{
this.root.next.removeNode(this.root, data);
}
this.count--; //有效数据-1
}
//定义在内部类Node中
//第一次使用,previous是root,this是root.next
public void removeNode(Node previous, E data) {
if(this.data.equals(data)) {
previous.next = this.next;
}
else {
//如果有后续节点
if(this.next != null) {
this.next.removeNode(this, data); //向后继续删除
}
}
}
(10)清空链表
@Override
public void clean() {
this.root = null;
count = 0;
}
测试代码
public class ListDemo {
public static void main(String[] args) {
ILink<String> all = new LinkImpl<String>();
System.out.println(all.size());
all.add("hello");
all.add("world");
all.add("java");
System.out.println(all.size()); //3
Object[] result = all.toArray();
//遍历数组
for(Object temp : result) {
System.out.println(temp);
}
System.out.println("------------根据下标获得数据--------");
System.out.println(all.get(2));
System.out.println("---------替换数据------------");
all.set(2, "nihao");
System.out.println(all.get(2));
System.out.println("---------是否存在------------");
System.out.println(all.isExist("world"));
System.out.println("---------删除根节点------------");
all.remove("world");
Object[] result2 = all.toArray();
for(Object temp : result2) {
System.out.println(temp);
}
}
}
java实现链表结构与C语言不用,隐藏了指针没有办法用循环遍历节点,因此使用递归方法遍历节点。