链表

链表

链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。

主要有以下几种类型:

  1. 单向链表
  2. 双向链表
  3. 循环链表

主要方法:

  1. int getSize(); //得到链表元素个数
  2. boolean isEmpty(); //判断链表是否为空
  3. void add(int index,E e);//链表下标为index中添加元素
  4. void addFirst(E e); //链表头部添加元素
  5. void addLast(E e); //链表尾部添加元素
  6. E get(int index); //获得链表第index位置的元素
  7. E getFirst(); //获取链表第一个元素
  8. E getLast(); //获取链表最后一个元素
  9. void set(int index,E e); //更新链表index位置的元素
  10. boolean contains(E e); //查找链表中是否存在元素e
  11. E delete(int index); //删除角标为index的元素
  12. E deleteFirst(); //删除头结点
  13. E deleteLast(); //删除最后一个元素
设置头结点

在第一个元素结点进行插入或者删除操作时,使其操作与其他操作一致。带头结点,无论删除或者增加那个结点,代码一致。
单链表具体实现:

单链表是一种顺序结构,有一个头结点,没有值域,为空结点。有一个尾结点,为null。
image

import javax.xml.ws.soap.AddressingFeature;

public class LinkedList<E> {
	/*
	 * 链表结点类
	 */
	class Node<E> {
		public E e;
		public Node next;

		/**
		 * 构造函数
		 */
		public Node() {
			e = null;
			next = null;
		}

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

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

		/**
		 * 覆写toString方法
		 */
		@Override
		public String toString() {
			return e.toString();
		}
	}

	// 设置空头节点 利用前驱结点处理后继结点(这样就不需要特殊处理头结点)
	private Node dummyHead;
	private int size; // 链表元素数量

	// 链表构造函数
	public LinkedList() {
		dummyHead = new Node(null, null);
		size = 0;
	}
	
	/**
	 * 返回链表元素数量
	 * @return
	 */
	public int getSize() {
		return	size;
	}
	
	/**
	 * 判断链表是否为空
	 * @return
	 */
	public boolean isEmpty() {
		return	size == 0;
	}
	
	/**
	 * 在链表index(0-based)位置添加元素
	 * 
	 * @param e
	 */
	public void add(int index, E e) {
		if (index > size && index < 0) {
			throw new IllegalArgumentException("Add failed:illegal index");
		}
		//计算前驱结点变量
		Node prev = dummyHead;
		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++;

	}

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

	/**
	 * 在链表末尾添加元素
	 * 
	 * @param e
	 */
	public void addLast(E e) {
		add(size, e);
	}
	
	/**
	 * 获得链表第index位置的元素
	 * @param index
	 * @return
	 */
	public E get(int index) {
		if (index > size && index < 0) {
			throw new IllegalArgumentException("Add failed:illegal index");
		}
		Node currentNode = dummyHead.next;
		for (int i = 0; i < index; i++) {
			currentNode = currentNode.next;
		}
		return	(E) currentNode.e;
	}
	
	/**
	 * 获得链表头结点
	 * @return
	 */
	public E getFirst() {
		return	get(0);
	}
	
	/**
	 * 获取链表最后一个元素
	 * @return
	 */
	public E getLast() {
		return	get(size-1);
	}
	
	/**
	 * 更新链表index位置的元素
	 * @param index
	 */
	public void set(int index,E e) {
		if (index > size && index < 0) {
			throw new IllegalArgumentException("Add failed:illegal index");
		}
		Node currentNode = dummyHead.next;
		for (int i = 0; i < index; i++) {
			currentNode = currentNode.next;
		}
		currentNode.e = e;
	}
	/**
	 * 查找链表中是否存在元素
	 * @param e
	 * @return
	 */
	public boolean contains(E e) {
		Node currentNode = dummyHead.next;
		while(currentNode != null) {
			if(currentNode.e.equals(e))
				return	true;
			currentNode = currentNode.next;
		}
		return	false;
	}
	
	/**
	 * 从链表中删除index位置的元素 并返回元素
	 * @param index
	 * @return
	 */
	public E remove(int index) {
		if (index > size && index < 0) {
			throw new IllegalArgumentException("Add failed:illegal index");
		}
		Node prev = dummyHead;
		for (int i = 0; i < index; i++) {
			prev = prev.next;
		}
		Node deleteNode = prev.next;
		prev.next = deleteNode.next;
		deleteNode.next = null;
		size--;
		
		return	(E) deleteNode.e;
	}
	
	/**
	 * 删除第一个元素
	 * @return
	 */
	public E removeFirst() {
		return	remove(0);
	}
	
	/**
	 * 删除最后元素
	 * @return
	 */
	public E removeLast() {
		return	remove(size-1);
	}
	@Override
	public String toString() {
		StringBuilder sb = new StringBuilder();
		sb.append("linkedList:");
		Node currentNode = dummyHead.next;
		while(currentNode != null) {
			sb.append(currentNode.e+"->");
			currentNode = currentNode.next;
		}
		sb.append("NULL");
		return	sb.toString();
	}
	public static void main(String[] args) {
		LinkedList<Integer> link = new LinkedList<Integer>();
		for (int i = 0; i < 10; i++) {
			link.addFirst(i);
			System.out.println(link);
		}
		link.set(0, 100);
		System.out.println(link);
		link.remove(1);
		System.out.println(link);
		link.removeFirst();
		System.out.println(link);
		link.removeLast();
		System.out.println(link);
	}
}

循环链表的实现
循环链表与单链表区别主要是单链表尾结点指向NULL,而循环链表尾结点指向头结点。只需要修改add(int index,E e)方法如下:

/**
	 * 在链表index(0-based)位置添加元素
	 * 
	 * @param e
	 */
	public void add(int index, E e) {
		if (index > size && index < 0) {
			throw new IllegalArgumentException("Add failed:illegal index");
		}
		//计算前驱结点变量
		Node prev = dummyHead;
		for (int i = 0; i < index; i++) {
			prev = prev.next;
		}
		//如果为第一个元素 ,则让它指向自己	
		if(size == 0) {
			Node newNode = new Node(e);
			dummyHead.next = newNode;
			newNode.next = newNode;
		}
		//
		prev.next = new Node(e, prev.next);
		size++;

	}
双向链表

双向链表可以从正反方向遍历。
image
改变Node结点为以下:

class Node<E>{
		private E e;
		private Node prev;
		private Node next;
		public Node() {
			this.e  = null;
			this.prev = null;
			this.next = null;
		}
		public Node(E e) {
			this.e = e;
			this.prev = null;
			this.next = null;
		}
		public Node(E e,Node prev,Node next) {
			this.e = e;
			this.prev = prev;
			this.next = next;
		}
	}

双向链表成员变量和构造函数: 设置头结点和尾结点为空,将头结点的后继结点设为尾结点,将尾结点的前驱结点设为头结点。

private Node dummyHead,dummyTail;
	private int size;
	public ReLinkedList() {
		dummyHead = new Node();
		dummyTail = new Node();
		dummyHead.next = dummyTail;
		dummyTail.prev = dummyHead;
		size = 0;
	}

**添加元素:**找到添加位置的前驱结点,将新结点的前驱结点设为添加位置的前驱结点,后继结点设为添加位置的后置结点。然后将添加位置的后继结点的前驱结点设为新结点,最后将添加位置的后继结点设置为新结点。

/**
	 * 在链表index(0-based)位置添加元素
	 * 
	 * @param e
	 */
	public void add(int index, E e) {
		if (index > size && index < 0) {
			throw new IllegalArgumentException("Add failed:illegal index");
		}
		//计算前驱结点变量
		Node prev = dummyHead;
		for (int i = 0; i < index; i++) {
			prev = prev.next;
		}
		// 新结点前驱指向prev 后继指向prev.next进行插入 
		Node node = new Node(e,prev,prev.next);
		//将新结点的后继结点的前驱结点设为新结点
		prev.next.prev = node;
		//将新结点的前驱结点的后继结点设为新结点
		prev.next = node;
		size++;

	}

删除元素:
找到删除结点的前驱结点,将删除结点的前驱结点的后继结点设为删除结点的后继结点,将删除元素的后继结点的前驱结点设为删除结点的前驱结点,最后将删除结点的前驱结点和后继结点设为null。

/**
	 * 从链表中删除index位置的元素 并返回元素
	 * @param index
	 * @return
	 */
	public E remove(int index) {
		if (index > size && index < 0) {
			throw new IllegalArgumentException("Add failed:illegal index");
		}
		Node prev = dummyHead;
		for (int i = 0; i < index; i++) {
			prev = prev.next;
		}
		Node deleteNode = prev.next;
		prev.next = deleteNode.next;
		deleteNode.next.prev = prev;
		deleteNode.prev = null;
		deleteNode.next = null;
		size--;
		
		return	(E) deleteNode.e;
	}

单链表实现栈

package stack;

import java.util.LinkedList;

public class LinkedListStack<E> implements Stack<E> {
	
	private stack.LinkedList<E> list = new stack.LinkedList<E>();

	@Override
	public int getSize() {
		return list.getSize();
	}

	@Override
	public boolean isEmpty() {
		return list.isEmpty();
	}

	@Override
	public void push(E e) {
		list.addFirst(e);
	}

	@Override
	public E pop() {
		return list.removeFirst();
	}

	@Override
	public E peek() {
		return list.getFirst();
	}

	@Override
	public String toString() {
		StringBuilder sb = new StringBuilder();
		sb.append("linkedList:");
		sb.append(list);
		sb.append("NULL");
		return sb.toString();
	}
	
	public static void main(String[] args) {
		
		LinkedListStack<Integer> listStack = new LinkedListStack<>();
		for (int i = 0; i < 10; i++) {
			listStack.push(i);
			System.out.println(listStack);
		}
		listStack.pop();
		System.out.println(listStack);
	}
}

数组实现栈主要是需要动态的创建空间耗时,而链表需要不停地new新的Node耗时。

链表实现队列
public class LinkedListQueue<E> implements Queue<E> {
	/*
	 * 链表结点类
	 */
	class Node<E> {
		public E e;
		public Node next;

		/**
		 * 构造函数
		 */
		public Node() {
			e = null;
			next = null;
		}

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

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

		/**
		 * 覆写toString方法
		 */
		@Override
		public String toString() {
			return e.toString();
		}
	}

	private Node head, tail;
	private int size;

	public LinkedListQueue() {
		head = tail = null;
		size = 0;
	}

	@Override
	public void enqueue(E e) {
		//不需要头空结点
		if(tail == null) {
			tail = new Node(e);
			head = tail;
		}else {
			//往队尾添加元素 只需要移动尾结点  头结点不变
			tail.next = new Node(e);
			tail = tail.next;
		}
		size++;
	}

	@Override
	public E dequeue() {
		if (isEmpty()) {
			throw new IllegalArgumentException("cannot dequeue from an empty queue");
		}
		Node curNode = head;
		head = head.next;
		curNode.next = null;
		//如果只有一个结点 需要让尾结点为null
		if(head == null) {
			tail = null;
		}
		return null;
	}

	@Override
	public E getFront() {
		return null;
	}

	@Override
	public int getSize() {
		return size;
	}

	@Override
	public Boolean isEmpty() {
		return size == 0;
	}
	
	@Override
	public String toString() {
		StringBuilder sb = new StringBuilder();
		sb.append("linkedList:");
		Node currentNode = head;
		while(currentNode != null) {
			sb.append(currentNode.e+"->");
			currentNode = currentNode.next;
		}
		sb.append("NULL");
		return	sb.toString();
	}
	public static void main(String[] args) {
		LinkedListQueue<Integer> link = new LinkedListQueue<Integer>();
		for (int i = 0; i < 10; i++) {
			link.enqueue(i);
			System.out.println(link);
		}
		link.dequeue();
		System.out.println(link);
	}
}

总结:

通过学习单向链表,循环链表,双向链表,使我对链表有了深刻的认识,通过将链表应用到栈和队列中,学会了怎样使用链表。将数组和链表进行对比,发现如果要多次进行增删操作的时候,使用链表则节省很多的时间。通过分析,链表增加或删除元素,只需要操作一个结点,不需要大量移动元素,节省了更多的时间。但缺点是浪费了一些空间。如果需要进行多次遍历和搜索,使用数组比较方便,因为数组可以使用索引来查找,原理等同于C语言中的指针。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值