resize
方法使其变成动态的。但是我们所谓的动态时是从用户的出发角度来看的。接下来我们学习链表。他是真正的动态数据结构,也是最简单的动态数据结构(因为还有二分搜索树、Trie等相对复杂的)。
数据存储在“节点(Node)”中,节点中有两个元素,一个是数据,一个是当前节点的下一个节点。
public class Node {
int e;
Node next;
}
优点: 真正的动态,不需要处理固定容量的问题。
**缺点:**丧失了随机访问的能力
private class Node<E>{
public E e;
public 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();
}
}
分析完链表的数据结构,接下来分析他如何向链表中插入元素,若向链首插入元素666
,666
本身是一个节点,将其插入链表,只需要将node的next指向head,然后更新head,指向新插入的节点,最后size++。head = new Node(e, head)
当我们需要在元素的中间添加元素时如何操作呢?这里采用一个变量prev
表示要指向插入节点(node
)的前一个节点。然后将要插入的节点的next
指向prev
指向的节点。再将prev
的next
指向node
。
这样就完成了中间插入操作。这个操作的关键是:要找的添加节点的前一个节点,即prev
。prev.next = new Node(e,prev.next);
但是当我们要在第0个位置插入节点时(也就是头节点),他是没有前一个节点的,这时就要分而治之去调用第一个函数。
//在链表添加新的元素e
public void addFirst(E e){
Node node = new Node(e);
node.next = head;
head = node;
size ++;
//head = new Node(e, head)
}
//在链表的index()添加元素e 头节点为0
public void addIndex(int index,E e){
if (index < 0 || index > size){
throw new IllegalArgumentException("Add failed, Illegal index.");
}
if (index == 0){
addFirst(e);
}else {
Node prev = head;
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 ++;
}
}
public void addLast(E e){
addIndex(size, e);
}
在上面插入的过程中,我们为在头节点之前插入元素做了特殊的处理,原因是:头节点之前没有节点,也就是说头节点内有元素值,此时我们创建一个头节点,其不存储任何的元素为NULL,他叫dummyHead
(虚拟头节点),这样的话,第一个元素就是dummyHead
的next
所对应的节点的元素,而非dummyHead
的节点元素。他只是为了编写代码逻辑方便而出现的,也就是说对用户是屏蔽的。用户不知道其存在。就像我们在处理循环链表时多开辟一个数组空间一样。
此时我们在向第一个元素之前添加节点的时候,就会找到dummyHead.next
,而不用重新改写代码。
private Node dummyhead;
public LinkedList(){
dummyhead = new Node(null,null);
size = 0;
}
public void addIndexPlus(int index,E e){
if (index < 0 || index > size){
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 ++;
}
查找节点区别于插入节点的操作,一个是索引的取值范围,一个是第一个需要的节点从哪里开始,具体分析如下图:
public E getIndex(int index){
if (index < 0 || index >= size){
throw new IllegalArgumentException("Add failed, Illegal index.");
}
//索引为0的元素
Node cur = dummyhead.next;
for (int i = 0; i < index; i ++){
cur = cur.next;
}
return (E) cur.e;
}
public E getFirst(){
return getIndex(0);
}
public E getLast(){
return getIndex(size - 1);
}
public void set(int index, E e){
if (index < 0 || index >= size){
throw new IllegalArgumentException("Add failed, Illegal index.");
}
Node cur = dummyhead.next;
for ( int i = 0; i < index; i ++){
cur = cur.next;
}
cur.e = e;
}
对链表的循环可以采用while循环,起始值为虚拟头节点的下一个节点(第一个元素),只要其不为空就一直输出cur直至为空。
@Override
public String toString() {
StringBuilder res = new StringBuilder();
Node cur = dummyhead.next;
// for (Node cur = dummyhead.next; cur != null; cur = cur.next){
// res.append(cur + "->");
// }
while (cur != null){
res.append(cur + "->");
cur = cur.next;
}
res.append("NULL");
return res.toString();
}
public E remove(int index ){
if (index < 0 || index >= size){
throw new IllegalArgumentException("Add failed, Illegal index.");
}
Node prev = dummyhead;
for (int i = 0; i < index; i++){
prev = prev.next;
}
Node retNode = prev.next;
prev.next = retNode.next;
retNode = null;
size --;
return (E) retNode.e;
}
https://github.com/909913825/LinkedList