欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

JAVA实现单链表超详解(图解)

程序员文章站 2022-05-18 17:42:09
这一篇,说一下java的单链表的实现,如果有C语言的基础的话,应该知道,在C语言中,链表的实现是用指针来实现的,也就是用指针来指向下一个节点的地址来实现链表。那在java中没有指针的这个概念,我们应该怎么实现,怎么来表示地址,怎么指向一个地址;我们来看一个例子:Student stu=new Student();那,我们知道这行代码是一个对象的实例化,那这行代码到底是什么意思呢?我们这样理解:stu这个变量存储在栈内存中,右边的new Student()存储在堆内存中,就像这样:也就是可以...

这一篇,说一下java的单链表的实现,如果有C语言的基础的话,应该知道,在C语言中,链表的实现是用指针来实现的,也就是用指针来指向下一个节点的地址来实现链表。
那在java中没有指针的这个概念,我们应该怎么实现,怎么来表示地址,怎么指向一个地址;
我们来看一个例子:

Student stu=new Student();

那,我们知道这行代码是一个对象的实例化,那这行代码到底是什么意思呢?
我们这样理解:
stu这个变量存储在栈内存中,右边的new Student()存储在堆内存中,就像这样:
JAVA实现单链表超详解(图解)
也就是可以这样理解,左边变量的值,就是右边的地址,也就是左边的变量指向了右边的地址;
所以在我后面写的一些代码,就可以理解成等号左边是变量,右边是地址!

OK,现在我们来说链表,要想实现一个链表,自然少不了节点,那么一个节点应该有什么呢?
和C语言一样,应该有数据项和指向下一个节点的对象。那在C中有结构体这么一说可以来实现这样一个节点,在JAVA中呢,就是类,也就是实现出一个节点类!
JAVA实现单链表超详解(图解)

一、节点类的实现

public class Node<E> {
	Node<E> next;
	E element;
	public Node(Node next,E element){
		this.next=next;
		this.element=element;
	}

}

类中应该有一个next的对象来指向下一个节点,还要有一个element来存储数据,加了一个泛型来表示数据类型!
在构造方法中我们应该有两个参数,一个是节点的数据,还有就是指向下一个节点的next;

二、获取节点的方法

有了这个节点类,我们就可以创建出一个链表类:

public class AloneList<E>{}

那链表类中应该有什么成员变量呢:
是不是应该有一个头结点的意思,这个头节点应该是Node类型,还要有一个size来记录节点的个数:

	private int size;
	private Node first;

然后我们思考一个问题,是不是我们在进行指定位置添加元素,指定位置删除元素,指定位置修改元素,我们都要通过一个索引(我只是说索引,但在链表是没有索引这一个概念的,索引的概念只在数组中出现,但为了方便叙述,就不要纠正我了哈)来操作这个链表,那么我不想在每个方法中都写一遍,我就可以拿出来这么一个方法,实现通过索引查找元素的方法:
这个方法的返回值,应该就是Node类型的节点。
我们举例子来说:
JAVA实现单链表超详解(图解)
看,我们要拿到第三个节点,是不是应该就是head的下一个的下一个,那下一个在节点类中是不是就是next
第三个元素是不是就可以写成head.next.next!这是不是就是通过索引来遍历:
来看代码:

	private Node<E> node(int index){
		Node x=first;
		for(int i=0;i<index;i++) {
			x=x.next;
		}
		return x;
	}

我每次都让x=x的下一个,起到了查找的遍历方法!

三、指定位置添加元素

现在我们有了这个node方法,我们来写一下指定位置添加元素:

public void add(int index,E element){}

函数里面应该怎么写呢,我们先考虑第一种情况,添加第一个元素,也就是index==0的情况:
我在最开始没有添加的时候,只应该有一个节点first吧:
JAVA实现单链表超详解(图解)
并且他的next应该是指向空的;
那我们现在要通过add(0,E element)添加一个节点,那我是不是就要new出来一个节点

new Node<E>(null,element)

JAVA实现单链表超详解(图解)
next为空,对吧,现在两个节点出来了,我们要把他们两个连起来:
那first的next就不应该为空,而是指向这个新节点!
JAVA实现单链表超详解(图解)
这回再想一下我文章开头说的话,实现这一步应该怎么做?

	if(index==0) {
			first=new Node<E>(null,element);
		}

是不是就这一行代码就可以了,等号左边是变量,右边是地址,把他写进add方法中!
然后我们在考虑index不为0的情况怎么写。
JAVA实现单链表超详解(图解)
如果我们想在3这个位置添加一个节点:
JAVA实现单链表超详解(图解)

我们是不是要获取到第二个和第三个这两节点,对吧,因为要在这两个节点中插入嘛,所以一定要获取这两个节点,用什么方法,node啊!

			Node<E> pre=node(index-1);
			Node<E> next=pre.next;

这两个节点获取到了怎么办,我是不是应该要新new出来一个节点,然后这个new节点的next要指向3这个位置!

new Node<E>(next,element)

是不是这样(看参数!);
然后呢,再让2这个节点,指向这个new节点,是不是就完事了!

pre.next=new Node<E>(next,element);

最后一步再让size++,并且判断一下index的索引是否越界,是不是就可以了!
最后再看一下完整的代码:

	public void add(int index,E element) {
		if(index<0||index>size) {
			throw new IndexOutOfBoundsException("索引越界异常");
		}
		if(index==0) {
			first=new Node<E>(null,element);
		}
		else {
			Node<E> pre=node(index-1);
			Node<E> next=pre.next;
			pre.next=new Node<E>(next,element);
			
		}
		
		size++;
	}

四、在末尾添加节点

public void add(E element) {}

就是这么一个方法,那你说,在链表的末尾添加节点的话,是不是就可以这样写:

add(size,element);

是不是,末尾嘛,在size的位置上添加元素啊,多么简单!

	public void add(E element) {
		add(size,element);
	}	
	

五、获取方法

	public E get(int index) {
		if(index<0||index>size) {
			throw new IndexOutOfBoundsException("索引越界异常");
		}
		return node(index).element;
	}

这这这,是不是也觉得真的没啥好说的,判断索引是否越界,再直接索引调用node方法,在返回element完事,是不是很简单!

六、修改方法

	public E set(int index,E element) {
		if(index<0||index>size) {
			throw new IndexOutOfBoundsException("索引越界异常");
		}
		Node<E> node=node(index);
		E oldElement=node.element;
		node.element=element;
		return oldElement;
		
	}

这这这这,是不是也很简单,判断索引是否越界,然后调用node方法,设置一下element是不是也就完事了!

七、删除方法

好,又来恶心的了,删除方法,我们还是先来特殊的,删除第一个元素,来仔细看好!
JAVA实现单链表超详解(图解)
来看这张图,我first的值,是值,是不是等于0号元素的地址,也就是说,我0号元素的地址,是不是等于first,那1号元素的地址,是不是等于first.next;
我现在要干的是什么,是不是想让,first的值,等于1号元素的地址,从而把0号元素给跳过去。有没有点绕,多看两遍;
JAVA实现单链表超详解(图解)

如果想要实现这个功能,我要怎么写?

		if(index==0) {
			first=first.next;
		}

是不是就这一行代码就完事了,仔细看第一张图,是不是很牛逼!

然后我们再看不是删除第一个元素的方法实现:
JAVA实现单链表超详解(图解)
如果我们要删除1号元素:
JAVA实现单链表超详解(图解)
我就让0号元素指向2号元素的地址就可以了吧。
然后获取0号元素和2号元素(就是index的上一个和下一个)
然后让0号元素指向2号元素的地址

		else {
			Node<E> pre=node(index-1);
			Node<E> oldNode=pre.next;
			pre.next=oldNode.next;
		}

是不是就是这样,完全没问题啊,然后我们再返回一下被删除的元素,判断一下索引的范围,这个方法是不是也就完成了!

	public E remove(int index) {
		if(index<0||index>size) {
			throw new IndexOutOfBoundsException("索引越界异常");
		}
		
		if(index==0) {
			first=first.next;
			size--;
			return node(0).element;
		}
		else {
			Node<E> pre=node(index-1);
			Node<E> oldNode=pre.next;
			pre.next=oldNode.next;
			size--;
			return oldNode.element;
		}

	}

八、toString方法

啊,最后一个方法,不想说了,直接看代码吧:

	public String toString() {
		StringBuilder str=new StringBuilder();
		if(size==0) {
			return "[]";
		}
		else {
			str.append("[");
			Node x=first;
			for(Node i=x;i!=null;i=i.next) {
				if(i.next==null) {
					str.append(i.element).append("]");
				}
				else {
					str.append(i.element).append(" ");
				}
			}
		}
		return str.toString();
	}

那,这个方法我再自定义ArrayList中也写过:
https://blog.csdn.net/weixin_46726346/article/details/107580973
就是这篇文章,OK,到此一个单链表的实现就完成了!
最后再测试一下:

九、测试

public class Test {
	public static void main(String[] args) {
		AloneList<String> list=new AloneList<String>();
		list.add("123");
		list.add("456");
		list.add("789");
		System.out.println(list);
		list.add(1,"456");
		System.out.println(list);
		System.out.println(list.get(3));
		list.set(0, "abc");
		System.out.println(list);
		list.remove(0);
		System.out.println(list);
		
	}

}

看一下输出结果吧:
[123 456 789]
[123 456 456 789]
789
[abc 456 456 789]
[456 456 789]

测试也是没有问题的呢,OK,到此就结束了!

本文地址:https://blog.csdn.net/weixin_46726346/article/details/107687955