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

数据结构(四)——链表

程序员文章站 2022-03-08 23:39:04
...

    之前的数组、栈和队列三篇博客中,都依托封装的静态数组实现了动态扩容,对用户来说达到了相当于动态数据结构的效果;链表则是一种真正的线性动态数据结构,本身就是支持动态扩容的;链表的数据存放在节点中,这个节点还存储了一个next指向下一个节点。如果一个节点的next为空则表示这个链表的最后一个节点;对于链表,不需要进行扩容,直接进行追加即可;删除某个节点时只需要更改这个节点前一个节点的next,更改为此节点的下一个节点即可;但是链表丧失了直接访问的能力,因为数组开辟的空间是连续的,我们可以通过地址偏移直接访问元素,链表只能通过next进行遍历,即链表增删快,查找慢;

    链表是由一个一个的节点连接起来的,显然需要一个Node类表示节点,这个节点中不仅需要存储数据,还需要存储下一个节点;那么Node类如下:

public class Node<E>{
		private Node next;
		private E e;
				
		public Node(E e) {
			this.e = e;
			next = null;
		}
		
		public Node(E e,Node next) {
			this.e = e;
			this.next = next;
		}
		
		public Node getNext() {
			return next;
		}
		public void setNext(Node next) {
			this.next = next;
		}
		public E getE() {
			return e;
		}
		public void setE(E e) {
			this.e = e;
		}
		@Override
		public String toString() {
			return e.toString();
		}
	}

对于一个链表,无非还是增删改查;下面是一个简单的实现:

package com.gby.about_list;

public class LinkedListDemo<E> {
	
	public class Node<E>{
		private Node next;
		private E e;
		
		public Node(E e) {
			this.e = e;
			next = null;
		}
		
		public Node(E e,Node next) {
			this.e = e;
			this.next = next;
		}
		
		public Node getNext() {
			return next;
		}
		public void setNext(Node next) {
			this.next = next;
		}
		public E getE() {
			return e;
		}
		public void setE(E e) {
			this.e = e;
		}
		@Override
		public String toString() {
			return e.toString();
		}
	}
	
	private int size;
	private Node head;
	
	public LinkedListDemo() {
		head = null;
		size = 0;
	}
	
	/**
	 * @return int
	 * 返回链表中元素的个数
	 */
	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++;
	}
	
	/**
	 * @param i
	 * @param e
	 * 在第index+1个元素的位置插入一个数据e
	 */
	public void add(int index ,E e){
		if(index < 0 || index > size){
			throw new IllegalArgumentException("Add failed.Index is illegal.");
		}
		if(index == 0){
			addFirst(e);
		}else{
			Node prev = head;
			for(int i = 0;i<index-1;i++){
				prev = prev.next;
			}

			prev.next = new Node(e,prev.next);
			size++;
		}
	}
	
	public void addLast(E e){
		add(size, e);
	}
	
	@Override
	public String toString() {
		StringBuilder res = new StringBuilder();
		res.append("linkedList:[");
		Node prev = head;
		for(int i=0; i < size;i++){
			res.append(prev);
			prev = prev.next;
			if(i!=size-1){
				res.append(",");
			}
		}
		res.append("]");
		return res.toString();
	}
	
}

思路:

1.   Node是专属于这个LinkedList的,把它放作一个内部类;一个Node节点包含数据和下一个节点,即E e和Node next两个成员变量;

2.    对一个链表,需要链表头和链表的大小。即Node head和int size两个属性;

3.    提供构造方法,判空和获取链表大小的方法:构造方法默认创建一个空的链表,head为null,size为0;

数据结构(四)——链表

4.向链表的某个位置添加元素时,把要存放的数据封装在Node节点中,让新节点的next为该位置下一个节点,该位置前一个节点的next更改为新的节点:比如:原本node1.next  = node3;现在插入node2,让node2.next = node3,再让node1.next=node2;就把node2插入进去了;

数据结构(四)——链表

数据结构(四)——链表

这种方式无法在下标为0的位置添加元素,因为需要依赖index=0的前一个节点即prev;怎么解决呢?:

数据结构(四)——链表

下标为0的位置即为head的位置;新建一个节点,将新节点node的next指定为head.next,再把node当做新的head;

5.删除操作:

数据结构(四)——链表

把前一个节点的next修改为index的后一个节点,被删除节点的next赋值为null,那么这个节点就不在链表中了;显然,这种删除还是需要新建一个删除方法来删除原来的链表头;

怎么使用一种统一的方法进行删除和插入,不必考虑是否是同一个元素呢?上面的方法之所以需要分开讨论是因为头结点head为第一个node,他没有前驱节点prev;如果我们把第一个数据所在的节点之前加入一个虚拟头结点,把第一个数据和后面的数据一视同仁就可以解决这个问题;修改后的代码如下:

package com.gby.about_list;

public class LinkedList<E> {
	
	public class Node<E>{
		private Node next;
		private E e;
		
		
		public Node(E e) {
			this.e = e;
			next = null;
		}
		
		public Node(E e,Node next) {
			this.e = e;
			this.next = next;
		}
		
		public Node getNext() {
			return next;
		}
		public void setNext(Node next) {
			this.next = next;
		}
		public E getE() {
			return e;
		}
		public void setE(E e) {
			this.e = e;
		}
		@Override
		public String toString() {
			return e.toString();
		}
	}
	
	private int size;
	private Node dummyHead;
	
	public LinkedList() {
		dummyHead = new Node<E>(null,null);
		size = 0;
	}
	
	/**
	 * @return int
	 * 返回链表中元素的个数
	 */
	public int getSize(){
		return size;
	}
	
	public boolean isEmpty(){
		return size == 0;
	}
	
	//在链表头添加节点
	public void addFirst(E e){
		add(0, e); 
	}
	
	/**
	 * @param i
	 * @param e
	 * 在第index+1的元素的位置插入一个数据e,即想象一个数组,在下标为index的位置插入一个数据e
	 */
	@SuppressWarnings("unchecked")
	public void add(int index ,E e){
		if(index < 0 || index > size){
			throw new IllegalArgumentException("index is illegal.");
		}
	
		Node prev = dummyHead;
		for(int i = 0;i < index;i++){
			prev = prev.next;
		}

		prev.next = new Node(e,prev.next);
		size++;
	}
	
	public void addLast(E e){
		add(size, e);
	}
	
	@Override
	public String toString() {
		StringBuilder res = new StringBuilder();
		res.append("linkedList:");
		Node prev = dummyHead;
		for(int i=0; i < size;i++){
			prev = prev.next;
			res.append(prev);

			if(i!=size-1){
				res.append("->");
			}
		}
		return res.toString();
	}
	
	public E get(int index){
		if(index<0 || index >size){
			throw new IllegalArgumentException("index is illegal.");
		}
		Node cur = dummyHead.next;
		for(int i=0;i < index;i++){
			cur = cur.next;
		}
		return (E) 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.Index is illegal.");
		}
		Node cur = dummyHead.next;
		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;
	}
	
	public E remove(int index){
		if(index<0 || index >size){
			throw new IllegalArgumentException("Remove failed.Index is illegal.");
		}
		Node prev = dummyHead;
		for(int i=0;i<index;i++){
			prev = prev.next;   //每次遍历prev变为下标为i的位置;最终为index-1的位置
		}
		Node deleteNode = prev.next;
		E e = (E) deleteNode.e;
		prev.next = deleteNode.next;
		deleteNode.next = null;
		size--;
		return e;
	}
}

虚拟头结点dummyHead的e为null;这样第一个数据节点就也有了一个前驱节点;只是,之后对链表的遍历就需要多遍历一次了。但是插入和删除就不必考虑是否是在头结点的位置了;

以上就是链表的实现。

相关标签: 链表