java和node写接口_链表node中保存的是什么


链表相比于前几个章节讲的数据结构而言,是一个真正的动态数据结构也是一个最简单的动态数据结构,我们在后面还会接触更多的动态数据结构,所以对链表有一个理解非常好的基础,就能够更加容易的学习后面更加复杂的数据结构。

首先我们了解一下什么是链表,链表将数据存储在一种单独的数据结构中,这个结构通常叫做“节点”,对于链表来说,节点通常只有两部分内容,e:存储的数据,next:链表中的节点通过next指向下一个节点,就像一个火车一样,一个节点就好比一个车厢,车厢里存储真正的数据,车厢和车厢之间还需要连接,使数据都整合在一起。


java和node写接口_链表_02


public class LinkedList<E> {

	//定义成私有因为,使用者是无需关心Node的实现,只要使用即可
	private class Node {
		//保存的数据
		private E e;
		//指向下一个Node节点
		private Node next;
		
		public Node (E e,Node next) {
			this.e = e;
			this.next = next;
		}
		
		public Node(E e) {
			this(e,null);
		}
		
		public Node() {
			this(null,null);
		}
		
		@Override
		public String toString() {
			return e.toString();
		}
	}
}


假设我们有个节点,里面的数据为1,它指向了一个数据为2节点,数据为2的节点指向了数据为3的节点,如果一个节点的next指向了null,说明这个节点就是最后一个节点,这个就是链表。


java和node写接口_数据_03


知道了链表这种数据结构,自然要知道对于这个数据结构的一些操作,我们来看下链表中的增删改查吧

在链表头部添加元素

为了能够在链表头部添加元素,我们就需要先维护一个叫head的节点来作为链表的头部,往头部添加元素只需要将节点的next指向head,然后将head替换成添加到头部的node即可


java和node写接口_链表node中保存的是什么_04


public class LinkedList<E> {
	//定义成私有因为,使用者是无需关心Node的实现,只需使用
	private class Node {
		//保存的数据
		private E e;
		//指向下一个Node节点
		private Node next;
		
		public Node (E e,Node next) {
			this.e = e;
			this.next = next;
		}
		
		public Node(E e) {
			this(e,null);
		}
		
		public Node() {
			this(null,null);
		}
		
		@Override
		public String toString() {
			return e.toString();
		}
	}

	//用于指定头结点
	private Node head;
	//链表中元素的数量
	private int size;

	//用于初始化链表
	public LinkedList() {
		head=null;
		size=0;
	}
	//获取链表中元素数量
	public int getSize() {
		return size;
	}
	//判断链表是否为空
	public boolean isEmpty() {
		return size==0;
	}
	//在链表头部添加元素
	public void addFirst(E e) {
		Node node = new Node(e);
		node.next=head;
		head = node;
		size++;
	}
}


在链表指定元素后面添加元素

在链表中间添加元素,我们先要把新的节点的next指向指定节点后面的一个节点( 新节点.next = 指定节点.next ),然后将指定节点的next指向新节点即可( 指定节点.next = 新节点 ),注意先后顺序


java和node写接口_链表node中保存的是什么_05


package com.datastructure;

public class LinkedList<E> {
	//定义成私有因为,使用者是无需关心Node的实现,只需使用
	private class Node {
		//保存的数据
		private E e;
		//指向下一个Node节点
		private Node next;
		
		public Node (E e,Node next) {
			this.e = e;
			this.next = next;
		}
		
		public Node(E e) {
			this(e,null);
		}
		
		public Node() {
			this(null,null);
		}
		
		@Override
		public String toString() {
			return e.toString();
		}
	}

	//用于指定头结点
	private Node head;
	//链表中元素的数量
	private int size;
	
	public LinkedList() {
		head=null;
		size=0;
	}
	//获取链表中元素数量
	public int getSize() {
		return size;
	}
	//判断链表是否为空
	public boolean isEmpty() {
		return size==0;
	}
	//在链表头部添加元素
	public void addFirst(E e) {
		//Node node = new Node(e);
		//node.next=head;
		//head = node;
		//上面三句等同于下面一句
		head = new Node(e,head);
		size++;
	}
	
	//在链表指定元素后面添加元素 
	//在链表中不常使用索引,此操作练习用
	public void add(int index,E e) {
		//参数校验
		if(index<0|| index>size)
			throw new IllegalArgumentException("Add failed, Illegal index.");
		if(index == 0)
			addFirst(e);
		Node prev = head;
		//遍历出index前一个Node节点
		for(int i=0;i<index-1;i++) {
			prev = prev.next;
		}
		//Node node = new Node(e);
		//node.next=prev.next;
		//prev.next = node;
		//上面三句可以用下面一句解决
		prev.next = new Node(e,prev.next);
		size++;
	}
}


有了在链表指定元素后面添加元素的方法后,在尾部添加方法就很简单了,只需要调用add方法index传入的参数为size即可


public void addLast(E e) {
	add(size,e);
}


但是,addFirst方法的逻辑和在链表其他位置添加元素的逻辑不同,因为我们在调用add方法添加元素的时候,是通过遍历到待添加位置的前一个节点进行添加操作,由于链表头是第一个节点,没有前一个节点,也就没办法调用add方法在链表头中插入元素,为了能够让代码更加优雅,在链表中有个非常实用的方法来统一add的操作,就是在链表头部定义一个空节点作为头部节点称之为虚拟节点(dummyHead),这个虚拟节点不储存任何数据,这样一来第一个元素就是dummyHead的next所对应的元素了,addFirst方法也自然可以复用add方法了。


java和node写接口_数据_06


下面“ * ”标记了修改了的代码


public class LinkedList<E> {
	//定义成私有因为,使用者是无需关心Node的实现,只需使用
	private class Node {
		//保存的数据
		private E e;
		//指向下一个Node节点
		private Node next;
		
		public Node (E e,Node next) {
			this.e = e;
			this.next = next;
		}
		
		public Node(E e) {
			this(e,null);
		}
		
		public Node() {
			this(null,null);
		}
		
		@Override
		public String toString() {
			return e.toString();
		}
	}

	//用于指定头结点
*	private Node dummyHead;
	//链表中元素的数量
	private int size;
	
	public LinkedList() {
*		dummyHead=new Node();
		size=0;
	}
	//获取链表中元素数量
	public int getSize() {
		return size;
	}
	//判断链表是否为空
	public boolean isEmpty() {
		return size==0;
	}
	//在链表头部添加元素
	//修改之后的addFirst方法
	public void addFirst(E e) {
*		add(0,e);
	}
	
	//在指定位置添加元素
	//在链表中不常使用索引,此操作练习用
	public void add(int index,E e) {
		//参数校验
		if(index<0|| index>size)
			throw new IllegalArgumentException("Add failed, Illegal index.");
*		Node prev = dummyHead;
		//遍历出index前一个Node节点
		//这里把index-1改为index是因为多了一个虚拟节点,就不需要-1操作了
*		for(int i=0;i<index;i++) {
			prev = prev.next;
		}
		//Node node = new Node(e);
		//node.next=prev.next;
		//prev.next = node;
		//上面三句可以用下面一句解决
		prev.next = new Node(e,prev.next);
		size++;
	}
	
	public void addLast(E e) {
		add(size,e);
	}
}


有了上面add操作的遍历思路,我们可以写出根据索引查询某个元素,修改指定索引的元素,查询是否包含某个元素,以及链表的toString方法


public class LinkedList<E> {
	// 定义成私有因为,使用者是无需关心Node的实现,只需使用
	private class Node {
		// 保存的数据
		private E e;
		// 指向下一个Node节点
		private Node next;

		public Node(E e, Node next) {
			this.e = e;
			this.next = next;
		}

		public Node(E e) {
			this(e, null);
		}

		public Node() {
			this(null, null);
		}

		@Override
		public String toString() {
			return e.toString();
		}
	}

	// 用于指定头结点
	private Node dummyHead;
	// 链表中元素的数量
	private int size;

	public LinkedList() {
		dummyHead = new Node();
		size = 0;
	}

	// 获取链表中元素数量
	public int getSize() {
		return size;
	}

	// 判断链表是否为空
	public boolean isEmpty() {
		return size == 0;
	}

	// 在链表头部添加元素
	public void addFirst(E e) {
		add(0, e);
	}

	// 在指定位置添加元素
	public void add(int index, E e) {
		// 参数校验
		if (index < 0 || index > size)
			throw new IllegalArgumentException("Add failed, Illegal index.");
		Node prev = dummyHead;
		// 遍历出index前一个Node节点
		for (int i = 0; i < index; i++) {
			prev = prev.next;
		}
		// Node node = new Node(e);
		// node.next=prev.next;
		// prev.next = node;
		// 上面三句可以用下面一句解决
		prev.next = new Node(e, prev.next);
		size++;
	}

	public void addLast(E e) {
		add(size, e);
	}

	// 根据索引查询某个元素
	public E get(int index) {
		if (index < 0 || index >= size)
			throw new IllegalArgumentException("Get failed, Illegal index.");
		Node cur = dummyHead.next;
		for (int i = 0; i < index; i++) {
			cur = cur.next;
		}
		return cur.e;
	}

	public E getFirst() {
		return get(0);
	}

	public E getLast() {
		return get(size - 1);
	}

	//修改指定索引处的元素
	public void set(int index, E e) {
		if (index < 0 || index >= size)
			throw new IllegalArgumentException("Set failed, Illegal index.");
		Node cur = dummyHead;
		for (int i = 0; i <= index; i++) {
			cur = cur.next;
		}
		cur.e = e;
	}

	//查询是否存在指定元素
	public boolean contains(E e) {
		Node cur = dummyHead.next;
		while (cur != null) {
			if (cur.e.equals(e))
				return true;
			cur = cur.next;
		}
		return false;
	}

	@Override
	public String toString() {
		StringBuilder res = new StringBuilder();
		res.append(String.format("LinkedList      Size:%dn", size));
		Node cur = dummyHead.next;
		while(cur!=null) {
			res.append(cur.e + " -> ");
			cur = cur.next;
		}
		res.append("Null");
		return res.toString();
	}
}


只剩下最后一个删除操作了,想要删除链表中指定的元素,首先要通过遍历找到要删除元素(delNode)的前一个元素(prev),将prev的next指向delNode的next的节点,然后将delNode的next指向null,让GC回收这个空间


java和node写接口_链表node中保存的是什么_07


public E remove(int index) {
		if (index < 0 || index >= size)
			throw new IllegalArgumentException("Remove failed, Illegal index.");
		Node prev = dummyHead;
		for (int i = 0; i < index; i++) {
			prev = prev.next;
		}
		Node delNode = prev.next;
		prev.next = delNode.next;
		delNode.next = null;
		size--;
		return delNode.e;
	}
	
	public E removeFirst() {
		return remove(0);
	}
	
	public E removeLast() {
		return remove(size-1);
	}


写个测试用例跑下代码


public class LinkedListTest {
	public static void main(String[] args) {
		LinkedList<Integer> list = new LinkedList<Integer>();
		for (int i = 0; i <= 5; i++) {
			list.add(i, i + 1);
		}
		list.addFirst(6);
		System.out.println(list);
		System.out.println("是否存在2: " + list.contains(2));
		System.out.println("是否存在7: " + list.contains(7));
		list.set(4, 10);
		System.out.println(list);
		System.out.println("获取索引为4的元素: " + list.get(4));
		System.out.println(list);
		System.out.println("===================================");
		System.out.println("删除最后一个元素: " + list.removeLast());
		System.out.println(list);
		System.out.println("===================================");
		System.out.println("删除第一个元素: " + list.removeFirst());
		System.out.println(list);
	}
}


输出结果


LinkedList      Size:7
6 -> 1 -> 2 -> 3 -> 4 -> 5 -> 6 -> Null
是否存在2: true
是否存在7: false
LinkedList      Size:7
6 -> 1 -> 2 -> 3 -> 10 -> 5 -> 6 -> Null
获取索引为4的元素: 10
LinkedList      Size:7
6 -> 1 -> 2 -> 3 -> 10 -> 5 -> 6 -> Null
===================================
删除最后一个元素: 6
LinkedList      Size:6
6 -> 1 -> 2 -> 3 -> 10 -> 5 -> Null
===================================
删除第一个元素: 6
LinkedList      Size:5
1 -> 2 -> 3 -> 10 -> 5 -> Null