关于LinkedList的梳理

关于LinkedList的梳理

  请先参看: 关于LinkedList的梳理

  LinkedList是链式存储的双链表,而LinkedList是基于节点(Node类)组成的链表实现的。


  说明:内部类,即指非static的内部类,或称非静态内部类。嵌套类,即指static的内部类,或称静态内部类。

  不喜欢静态的说法,因为static不是一般意义上的静态动态的意思,static是属于类的意思(而不属于类的任何一个对象)。比如,每个对象都没有static变量、函数,而是每个对象可以访问类的static变量、函数。此外可以通过类的名字去访问static的变量、方法,但是不能访问非static的变量。因此static的变量、方法也被称为类变量、类函数,非static的函数在main中被调用时,前面必须要引用的对象,而类方法可以直接写。
  显然,加了static修饰后,同为static的main函数可以直接调用。
  最后,建议直接称为static的类、方法、内部类。

  在考虑设计方面,LinkedList包含三个类:

  • 1 LinkedList类本身,它应该包含到到两端的链、表的大小和一些方法;
  • 2 Node类,表示节点,就像数组 a[]中的一个元素 a[i],节点包含数据、以及到前一个节点和到下一个节点的链,还有一些适当的构造方法。
  • 3 LinkedListIterator类,和ArrayListIterator一样,该类是Iterator接口的具体实现,抽象了位置的概念。

  关于链的个人理解:
  A到B的链就是任意可以由A联系到B的方式,如数组的每个位置和前后位置间自然存在着基于序号的链,又如Node节点,两节点间通过相互记录彼此的“关系”构建链,A说B在“前”,B说A在“后”,那么这种建立的关系就是实质上的链,这种链具体是通过Node类里表示“前”、“后”关系的成员变量来实现。A、B可以是对等的关系,如各Node节点间就是双向的链(也可单向);也可以不是对等的关系,如集合指向某具体元素的单链,这里的LinkedList到两端节点的链。

  下面说一下前面ArrayList中未包含或未提及的部分:

  • 1 使用了头尾节点,通过这些标记节点(sentinel node),排除了许多特殊情形,极大的简化了编码。
  • 2 应该注意到,在ArrayList中,所有成员变量都是私有的,此外还有一些私有成员方法,实际上一些公开方法对私有成员变量的改动,也是通过调用可以实质改动私有变量的方法来完成的。这样虽然繁琐,但是保证了数据的安全,1是其他类不能自己写方法改变这个类的成员,2是这个类一些最基础的改变变量的方法可以抽象出来,并做成私有的,其他公开方法对该类私有变量的改变基于这些私有方法,更安全,过程也更清晰。这里的LinkedList也一样,而且私有方法更多,因为跟私有嵌套类Node有关,所以公开的add、get、remove方法,都有更基础的基于Node节点的私有方法:addBefore(Node p, T x),remove(Node p)等。
  • 3 Node是私有嵌套类,是private的嵌套类,但是对所在的外部类是可见的,这很好,因为Node只需要也只允许外部类LinkedList来改变其数据。若嵌套类的成员也是private的,仍有外部类对嵌套类的成员是可见的。原因:private的嵌套类,就是外部类的一个private的成员,private是针对外部类以外而言的;同样的,嵌套类里成员的private,也是针对外部类以外而言的;所以Node类的所有成员变量属性在Node为private时,是无关紧要的,一般用public。我们需要外部类可以对Node进行修改、外部类之外的其他类不可改动,已经通过Node的private属性实现了,LinkedList以外的类根本见不到Node类。
  • 4 这里增加了一个新的成员叫做modCount,他是为了检测那种可能使得迭代器无效的结构上的修改,和那种非法的迭代器remove方法,即该数据用于检测迭代是否正常进行的。具体来说,在建立一个新的迭代器后,迭代器将用expectedModCount储存集合的modCount,expectedModCount++和modCount++分别放在迭代器内、外,如果迭代正常,此后每次迭代二者相同,如果迭代异常,则二者不等。每次对迭代法方法(next()和remove())的调用都将比较集合内的modCount和迭代器里的相应expectedModCount,不匹配就抛出异常。可以看到,这个逻辑可以运用在实现Iterable的类中,比较迭代器内外迭代次数是否匹配看迭代是否正常。
  • 5 最后,remove方法其实要求一定要先next(),才能remove,而且只remove一次,remove本次迭代的数据、节点等。因此这里增加错误检测,在LinkedListIterator类中新增一个默认为false的boolean变量okToRemove,next()后为true,remove()先检测okToRemove,若为false直接抛出异常,若为true就remove,然后将okToRemove改为false。PS:Java.util.LinkedList没采用这个,系统库那个还没仔细理解怎么进行错误检测的。

  下面复现上面的流程。同样接口定义远没有官方的完善,只择取了部分方法;同时为和系统库的接口、类相区别,命名前加My。
  特别强调的是,为了简洁地实现反向的迭代,和在一次迭代上 实现新增的 add()和set(),将Iterator接口进行扩展为ListIterator接口。这很重要,我文字举一个例子:
  编程实现逆序的,删除所有的奇数序号的项,且所有偶数序号的项的值减1。

  这是很简单的要求,实际上就是一次迭代就可以完成,走到某一项,进行 O ( 1 ) O(1) O(1)的操作即可。但是如果没有迭代器新增的这几个 O ( 1 ) O(1) O(1)的方法,那实际上就很可能运算复杂度大大提高,因为很可能出现,走到某节点是一次迭代,add方法不是从该节点运行,而是从头绕到该节点,就又是一轮迭代。后面的invertedToString() 方法就是逆向输出双链表,就用到了这里新的抽象方法。
  此外,这里的indexOf(T)等方法可以看到for循环和while循环的改写,下面两段代码是一样的效果:

	public int indexOf(T x) {
		int index = 0;
		
		Node<T> temp = beginMarker.next;
		if (x == null) {
			while(temp != null) {
				if (temp.data == null) {
					return index;
				} // of if
				temp = temp.next;
				index++;
			} // of while
		} else {
			while(temp != null) {
				if (x.equals(temp.data)) {
					return index;
				} // of if
				temp = temp.next;
				index++;
			} // of while
		} // of if else

		index = -1;
		return index;
	} // of indexOf

改写如下,加一个first只为了让逻辑显示更清晰:

	public int indexOf(T x) {
		int index = 0;

		Node<T> first = beginMarker.next;
		if (x == null) {
			for (Node<T> temp = first; temp != null; temp = temp.next) {
				if (temp.data == null) {
					return index;
				} // of if
				index++;
			} // of for
		} else {
			for (Node<T> temp = first; temp != null; temp = temp.next) {
				if (x.equals(temp.data)) {
					return index;
				} // of if
				index++;
			} // of for
		} // of if else
		
		index = -1;
		return index;
	} // of indexOf

  下面梳理整个过程。接口从小到大是:

public interface MyIterator<T> {
	boolean hasNext();
	T next();
	void remove();
}

  MyListIterator接口:

public interface MyListIterator<T> extends MyIterator<T> {
	boolean hasNext();
	T next();
	void remove();
	
	boolean hasPrevious();
	T previous();
	
	void add(T x);
	void set(T newValue);
}
public interface MyIterable<T> {
	MyIterator<T> myIterator();
}
public interface MyCollection<T> extends MyIterable<T> {
	int size();
	boolean isEmpty();
	void clear();
	boolean contain();
	boolean add(T x);
	boolean remove(T x);
}
public interface MyList<T> extends MyCollection<T> {
	MyIterator<T> myIterator();
	int size();
	boolean isEmpty();
	void clear();
	boolean contain();
	boolean add(T x);
	boolean remove(T x);
	
	T get(int idx);
	T set(int idx, T newVal);
	void add(int idx, T x);
	void remove(int idx);
}

  然后给出MyLinkedList类的框架:

public class MyLinkedList<T> implements MyList<T> {

	private static class Node<T> {} // of class Node

	private int theSize;
	private int modCount = 0;

	private Node<T> beginMarker;
	private Node<T> endMarker;

	public MyLinkedList() {} // of the first constructor
	private void doClear() {} // of doClear
	public MyLinkedList(T[] paraArray) {} // of the second constructor
	public int size() {} // of size
	public boolean isEmpty() {} // of isEmpty
	public void clear() {} // of clear
	public boolean contain(T x) {} // of contain
	public boolean add(T x) {} // of add(T)
	public void add(int idx, T x) {} // of add(int)
	private void addBefore(Node<T> p, T x) {} // of addBefore
	private Node<T> getNode(int idx, int lower, int upper) {} // of getNode(int, int, int)
	private Node<T> getNode(int idx) {} // of getNode(int)
	public boolean remove(T x) {} // of remove(T)
	public T remove(int idx) {} // of remove(int)
	private T remove(Node<T> p) {} // of remove(Node<T>)
	public T get(int idx) {} // of get
	public int indexOf(T x) {} // of indexOf
	public T set(int idx, T newValue) {} // of set
	public MyIterator<T> myIterator() {} // of myIterator

	private class LinkedListIterator implements MyListIterator<T> {
		private Node<T> nextNode = beginMarker.next;
		private int expectedModCount = modCount;
		private boolean okToRemove = false;

		public boolean hasNext() {} // of hasNext
		public T next() {} // of next
		public void remove() {} // of remove
		
		private Node<T> prevNode = endMarker.prev;
		public boolean hasPrevious() {} // of hasPrevious
		
		public T previous() {} // of previous
		public void add(T x) {} // of add
		public void set(T newValue) {} // of set
	} // of classLinkedListIterator

	public String toString() {} // of toString
	public String invertedToString() {} // of toString

	public static void main(String[] args) {} // of main

} // of class MyLinkedList<T>

  最后对上述框架进行完善,得到最后的 LinkedList<T> 为:
  2021/7/21进行修订,没再使用自己的接口,而是采用Java的接口,主要是为了用Java编译器自动实现for-each循环,每个for-each都自己改写成迭代器太麻烦了。
  修订前:

package myLinkedList;

import myAPI.MyIterator;
import myAPI.MyList;
import myAPI.MyListIterator;

	public MyIterator<T> myIterator() {
		return new LinkedListIterator();
	}// of myIterator()

	// The time complexities of the methods in the Iterator are all O(1).
	private class LinkedListIterator implements MyListIterator<T> {
	
	}// of class LinkedListIterator

}// of class MyLinkedList<T>

  修订后:

package myList;

import java.util.ConcurrentModificationException;
import java.util.Iterator;
import java.util.NoSuchElementException;

/**
 * @author CatInCoffee.
 * @version 1.2 Revised on 2021/7/21.
 */
public class MyLinkedList<T> implements Iterable<T> {

	private static class Node<T> {
		public Node<T> prev;
		public T data;
		public Node<T> next;

		public Node(T paraData) {
			prev = null;
			data = paraData;
			next = null;
		}// of Node's first constructor

		public Node(Node<T> paraPrev, T paraData, Node<T> paraNext) {
			prev = paraPrev;
			data = paraData;
			next = paraNext;
		}// of Node's second constructor
	}// of class Node<T>

	private Node<T> beginMarker;
	private Node<T> endMarker;
	private int theSize;
	private int modCount = 0;

	// *****************************************************************************************

	public MyLinkedList() {
		clear();
	}// of the first constructor

	public void clear() {
		beginMarker = new Node<T>(null);
		endMarker = new Node<T>(beginMarker, null, null);
		beginMarker.next = endMarker;

		theSize = 0;
		modCount++;
	}// of clear()

	// O(N)
	public MyLinkedList(T[] paraArray) {
		clear();

		for (int i = 0; i < paraArray.length; i++) {
			addBefore(endMarker, paraArray[i]);
		} // of for i
	}// of the second constructor

	// *****************************************************************************************

	public int size() {
		return theSize;
	}// of size()

	public boolean isEmpty() {
		return size() == 0;
	}// of isEmpty()

	// O(N)
	public boolean contain(T x) {
		return indexOf(x) >= 0;
	}// of contain(T)

	// *****************************************************************************************

	// Append the specified element to the end of this list.
	public boolean add(T x) {
		add(size(), x);
		return true;
	}// of add(T)

	// O(N)
	public void add(int idx, T x) {
		addBefore(getNode(idx, 0, size()), x);
	}// of add(int)

	/**
	 * Add an item to this collection at specified position p. Items at or after
	 * that position are slid one position higher. O(1)
	 * 
	 * @param p The node at position p, and it's the Node to add before.
	 * @param x the item to add.
	 */
	private void addBefore(Node<T> p, T x) {
		Node<T> newNode = new Node<>(p.prev, x, p);
		newNode.prev.next = newNode;
		p.prev = newNode;

		theSize++;
		modCount++;
	}// of addBefore(Node<T>, T)

	/**
	 * O(N) Here we cannot use remove(indexOf(T)) = remove(getNode(indexOf(T)))
	 */
	public boolean remove(T x) {
		try {
			remove(getNode(x));
			return true;
		} catch (NoSuchElementException e) {
//			System.out.println("No such element.");
			return false;
		} // of try-catch
	}// of remove(T)

	public T remove(int idx) {
		return remove(getNode(idx));
	}// of remove(int)

	private T remove(Node<T> p) {
		p.next.prev = p.prev;
		p.prev.next = p.next;
		theSize--;
		modCount++;

		return p.data;
	}// of remove(Node<T>)

	// *****************************************************************************************

	public T get(int idx) {
		return getNode(idx).data;
	}// of get(int)

	/**
	 * Get the Node at position idx, which must range from 0 to size() - 1. O(N)
	 */
	private Node<T> getNode(int idx) {
		return getNode(idx, 0, size() - 1);
	}// of getNode(int)

	/**
	 * Get the Node at position idx, which must range from lower to upper. O(N)
	 * 
	 * @param idx   index to search at.
	 * @param lower lowest valid index.
	 * @param upper highest valid index.
	 * @return internal node corresponding to idx.
	 */
	private Node<T> getNode(int idx, int lower, int upper) {
		Node<T> p;

		if (idx < lower || idx > upper) {
			throw new IndexOutOfBoundsException();
		} // of if

		if (idx < size() / 2) {
			p = beginMarker.next; // idx 0 corresponding to beginMarker.next
			for (int i = 0; i < idx; i++) {
				p = p.next;
			} // of for i
		} else {
			p = endMarker; // idx theSize corresponding to endMarker
			for (int i = size(); i > idx; i--) {
				p = p.prev;
			} // of for i
		} // of if-else

		return p;
	}// of getNode(int, int, int)

	// O(N)
	private Node<T> getNode(T x) {
		Node<T> first = beginMarker.next;

		if (x == null) {
			for (Node<T> temp = first; temp != endMarker; temp = temp.next) {
				if (temp.data == null) {
					return temp;
				}
			} // of for temp
		} else {
			for (Node<T> temp = first; temp != endMarker; temp = temp.next) {
				if (x.equals(temp.data)) {
					return temp;
				} // of if
			} // of for temp
		} // of if-else

		throw new NoSuchElementException();
	}// of getNode(T)

	/**
	 * Return the index of the first occurrence of the specified element in this
	 * list, or -1 if this list doesn't contain the element. More formally, returns
	 * the lowest index, or -1 if there is no such index. O(N)
	 *
	 * @param paraValue element to search for
	 * @return the index of the first occurrence of the specified element in this
	 *         list, or -1 if this list doesn't contain the element.
	 */
	public int indexOf(T x) {
		int index = 0;

//		Node<T> first = beginMarker.next;
//		if (x == null) {
//			for (Node<T> temp = first; temp != endMarker; temp = temp.next) { // Here temp as a node is not null, but
//																			// temp.data could be null.
//				if (temp.data == null) {
//					return index;
//				} // of if
//				index++;
//			} // of for
//		} else {
//			for (Node<T> temp = first; temp != endMarker; temp = temp.next) {
//				if (x.equals(temp.data)) {
//					return index;
//				} // of if
//				index++;
//			} // of for
//		} // of if else

		Node<T> temp = beginMarker.next;
		if (x == null) {
			while(temp != endMarker) {
				if (temp.data == null) {
					return index;
				} // of if
				temp = temp.next;
				index++;
			} // of while
		} else {
			while(temp != endMarker) {
				if (x.equals(temp.data)) {
					return index;
				} // of if
				temp = temp.next;
				index++;
			} // of while
		} // of if else

		index = -1;
		return index;
	}// of indexOf

	// O(1)
	public T set(int idx, T newValue) {
		Node<T> p = getNode(idx);
		T oldValue = p.data;
		p.data = newValue;

		return oldValue;
	}// of set(int, T)

	// *****************************************************************************************

	public Iterator<T> iterator() {
		return new LinkedListIterator();
	}// of myIterator()
	
	// The time complexities of the methods in the Iterator are all O(1).
	private class LinkedListIterator implements Iterator<T> {

		private Node<T> nextNode = beginMarker.next;
		private int expectedModCount = modCount;
		private boolean okToRemove = false;

		public boolean hasNext() {
			return nextNode != endMarker;
		}// of hasNext()

		public T next() {
			if (modCount != expectedModCount) {
				throw new ConcurrentModificationException();
			} // of if
			if (!hasNext()) {
				throw new NoSuchElementException();
			} // of if

			T nextItem = nextNode.data;
			nextNode = nextNode.next;
			okToRemove = true;
			return nextItem;
		}// of next()

		public void remove() {
			if (modCount != expectedModCount) {
				throw new ConcurrentModificationException();
			} // of if
			if (!okToRemove) {
				throw new IllegalStateException();
			} // of if

			MyLinkedList.this.remove(nextNode.prev);
			expectedModCount++;
			okToRemove = false;
		}// of remove()

		private Node<T> prevNode = endMarker.prev;

		public boolean hasPrevious() {
			return prevNode != beginMarker;
		}// of hasPrevious()

		public T previous() {
			if (modCount != expectedModCount) {
				throw new ConcurrentModificationException();
			} // of if
			if (!hasPrevious()) {
				throw new NoSuchElementException();
			} // of if

			T prevItem = prevNode.data;
			prevNode = prevNode.prev;
			okToRemove = true;
			return prevItem;
		}// of previous()

		public void backwardRemove() {
			if (modCount != expectedModCount) {
				throw new ConcurrentModificationException();
			} // of if
			if (!okToRemove) {
				throw new IllegalStateException();
			} // of if

			MyLinkedList.this.remove(prevNode.next);
			expectedModCount++;
			okToRemove = false;
		}// of backwardRemove()

		public void add(T x) {
			addBefore(nextNode.prev, x);
		}// of add(T)

		public void set(T newValue) {
			nextNode.prev.data = newValue;
		}// of set(T)
	}// of class LinkedListIterator

	// *****************************************************************************************

	// O(N)
	public String toString() {
		LinkedListIterator itr = (LinkedListIterator) this.iterator();
		String s = "empty.";
		if (!isEmpty()) {
			s = "{ ";
			while (itr.hasNext()) {
				s += itr.next() + " ";
			} // of while
			s += "}";
		} // of if

		return s;
	}// of toString()

	// O(N)
	public String backwardToString() {
		LinkedListIterator itr = (LinkedListIterator) this.iterator();
		String s = "empty.";
		if (!isEmpty()) {
			s = "{ ";
			while (itr.hasPrevious()) {
				s += itr.previous() + " ";
			} // of while
			s += "}";
		} // of if

		return s;
	}// of backwardtoString()

	// *****************************************************************************************

	public static void main(String[] args) {
		Character[] tempArray = { 'd', 'e', 'f', 'a' };
		MyLinkedList<Character> testLinkedList = new MyLinkedList<>(tempArray);
		System.out.println("After being initialized, the list is: " + testLinkedList.toString());

		Character tempValue = 'i';
		System.out.println(
				"The judgement that the list contains " + tempValue + " is :" + testLinkedList.contain(tempValue));
		int tempPosition = testLinkedList.indexOf(tempValue);
		System.out.println("The position of " + tempValue + " is " + tempPosition);

		tempValue = 'f';
		System.out.println(
				"The judgement that the list contains " + tempValue + " is :" + testLinkedList.contain(tempValue));
		tempPosition = testLinkedList.indexOf(tempValue);
		System.out.println("The position of " + tempValue + " is " + tempPosition + "\n");

		tempPosition = 1;
		tempValue = 'c';
		testLinkedList.add(tempPosition, tempValue);
		System.out.println(
				"After adding " + tempValue + " to position " + tempPosition + ", the list is: " + testLinkedList);

		testLinkedList.add(Character.valueOf('z'));
		System.out.println("After adding z to the end, the list is: " + testLinkedList);

		tempPosition = 3;
		tempValue = 'b';
		testLinkedList.set(tempPosition, tempValue);
		System.out.println(
				"After seting " + tempValue + " at position " + tempPosition + ", the list is: " + testLinkedList);

		tempPosition = 5;
		testLinkedList.remove(tempPosition);
		System.out
				.println("After removing data at position " + tempPosition + ", the list is: " + testLinkedList + "\n");

		tempValue = 'e';
		System.out.println("removing value " + tempValue + " could be : " + testLinkedList.remove(tempValue)
				+ ", and the list is: " + testLinkedList);

		tempValue = 'f';
		System.out.println("removing value " + tempValue + " could be : " + testLinkedList.remove(tempValue)
				+ ", and the list is: " + testLinkedList);

		System.out.println("After inverting the sequence, the list is: " + testLinkedList.backwardToString());

		testLinkedList.clear();
		System.out.println("After the clear() method, the list is: " + testLinkedList);
	}// of main

}// of class MyLinkedList<T>

  结果和修订前一致:

After being initialized, the list is: { d e f a }
The judgement that the list contains i is :false
The position of i is -1
The judgement that the list contains f is :true
The position of f is 2

After adding c to position 1, the list is: { d c e f a }
After adding z to the end, the list is: { d c e f a z }
After seting b at position 3, the list is: { d c e b a z }
After removing data at position 5, the list is: { d c e b a }

removing value e could be : true, and the list is: { d c b a }
removing value f could be : false, and the list is: { d c b a }
After inverting the sequence, the list is: { a b c d }
After the clear() method, the list is: empty.

  PS:注意到链表的 getNode(int) 方法,即通过下标找到节点,有了该核心方法,对链表的get、add、set、delete就只需要进行 O ( 1 ) O(1) O(1)的操作。而getNode(int)是通过头节点或尾节点一个一个.next找到需要的节点。那么,如果我们存储中间的关键节点,是否就有可能实现大大降低找到该节点的复杂度呢?
  比如,存有917个数据的链表,对第477个数据进行查找、增加、删除等操作。如果在存链表时,每隔256个节点就额外记录该节点,那么序号477的节点就不需要从初始节点开始查找,而是从额外存储的512这个节点查找。怎么确定分多少组每组多少元素(即储存哪些节点),怎么存储和覆盖之前的某些节点,都是繁琐的细节问题。我想的是这样的设计是不是背离了链表这种基础结构本身的意义,而是应该放在具体问题上作为具体的一种优化方法?

  啊,后面来看,上面的想法最终的实现就会趋向二叉树……

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值