实现链表的一些注意点
注意点
一、当操作链表时,要想明白本次要找的n 前一个还是n的本身,也要根据语义起名字:如previous、currrent、delNode等等
二、注意,如果已经设定了head dummyHead tail等等,需要在new一个Node代替它,然后再操作,不要把他们整没了
三、注意插入的顺序!!! 先动newNode.next ,然后再prev.next 反了会出错的
四、当某个节点要被删除时,nnn.next = null, 而不是 n = null ????
多种写法
二、移动节点或循环节点,使其到达最后一个有效的元素的地址
//方法一
for(int i = 1; i < size; i ++) {
currentNode = currentNode.next;
}
//方法二
Node currentNode = dummyHead.next;
while (currentNode.next != null) {
currentNode = currentNode.next;
}
//第三种写法
for (Node current = dummyHead.next; current != null ; current = current.next) {
res.append(current.e + "-> ");
}
链表操作复杂度:
添加:
~~~~
addFirst O(1)
~~~~
addLast O(n)
~~~~
add O(n/2) = O(n)
删除
~~~~
removeFirst O(1)
~~~~
removeLast O(n)
~~~~
remove O(n/2) = O(n)
改
~~~~
set O(n)
查
~~~~
get O(n)
~~~~
contains O(n)
链表的时间复杂度 增删改查全是O(n),
数组的优势在于如果有索引可以快速访问
但链表的优势在于,如果是针对链表头操作,增和删都是O(1)级别的
链表适合做的事情是不去修改,而且查也不查任意元素,只查链表头,增加删除时只对链表头操作。
由于链表是动态的,不会浪费大量内存,具有诸多优点
本章虽然是最基本的链表结构,但重要性在于,说明链表是基础的真正的动态结构!!!,链表是为了后续学习更重要的数据结构,比如二叉树、平衡二叉树、AVL、红黑树等
链表的改进
使用front 和 tail
实现链表(dummyHead)
关于dummyHead的说明:
~~~~
在很多功能操作中,需要找到被变更节点的前一个节点进行操作,这时对head需要单独操作。
为了将所有节点统一处理,可以将head前面弄一个dummyHead,这样一来所有处理都一样了,只要找到前一个节点即可。
LinkedList类中
1、首先创建一个private class Node,私有Node内部类 ,除了LinkedList类其他无法调用
~~~~
里边创建public的E e 和 Node node 并构造
2、LinkedList变量
~~~~
Node dummyHead int size
3、LinkedList构造 构造初始化一个空链表 dummyHead = new Node(null,null); size = 0;
4、LinkedList中的方法
基本方法:getSize isEmpty toString
功能方法:
增 :
~~~~
*add 在“索引”为n的地方添加元素
~~~~
~~~~
如果要删除位置n添加元素,需要知道前面一个元素,这样可以删除
~~~~
~~~~
创建 Node prev = dummyHead; 可以循环找到改n的前一个元素
~~~~
~~~~
不要忘记【删除元素.next】=null 将待删除的元素指向null!!
~~~~
*addFirst 复用add
~~~~
*addLast 复用add
删
~~~~
*remove在“索引”为n的地方添加元素
~~~~
~~~~
如果要位置n添加元素,需要知道前面一个元素,这样可以插入
~~~~
~~~~
创建 Node prev = dummyHead; 可以循环找到改n的前一个元素
~~~~
*removeFirst 复用remove
~~~~
*removeLast 复用remove
改
~~~~
*set
查
~~~~
*get getFirst getLast contains
实现链表(head)
LinkedList类中
1、首先创建一个private class Node,私有Node内部类 ,除了LinkedList类其他无法调用
~~~~
里边创建public的E e 和 Node node 并构造
2、LinkedList变量
~~~~
Node head int size
3、LinkedList构造 构造初始化一个空链表 head = null; size = 0;
4、LinkedList中的方法
~~~~
基本方法:getSize isEmpty toString
public class LinkedList<E> {
//内部类,只能LinkedList内部访问,对外部用户屏蔽底层实现细节
private class Node{
public E e; //做成public,在LinkedList中可以改变和访问
public Node next; //指向下一个Node的引用地址
//构造
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; //链表中有效元素个数
//LinkedList的构造函数,对于空LinkedList,里边都是空
public LinkedList() {
head = null;
size = 0;
}
//传入数组,创建到一个链表
public LinkedList(E[] arrs) {
head = null;
for (int i = arrs.length - 1; i >= 0 ; i --) {
Node node = new Node(arrs[i]);
node.next = head;
head = node;
//另一种写法
// head = new Node(arrs[i],head);
}
size = arrs.length;
}
public int getSize() {
return size;
}
public boolean isEmpty() {
return size == 0;
}
@Override
public String toString() {
StringBuilder res = new StringBuilder();
res.append("LinkedList : head ");
Node currentNode = head;
while (currentNode != null) {
res.append(currentNode.e + "-> ");
currentNode = currentNode.next;
}
res.append("null size:" + getSize());
return res.toString();
}
//增
//在链表头添加元素 addHead addFirst
public void addFirst(E e) {
Node node = new Node(e);
node.next = head;
head = node;
//另一种写法
//①new一个新的Node节点对象 ②使其存入e,指向的第一个节点(即head指向的节点) ③移动head,使其指向新做成的节点
//这里有一个注意点!!!! 对于一个链表,head本身就代表着第一个元素,即head指向的是第一个元素的地址,而head->next就代表了第二个元素
//head = new Node(e,head);
size ++;
//可以直接复用add(e,0);
}
public void addLast(E e) {
Node currentNode = head;
//移动节点,使其到达最后一个有效的元素的地址
// for(int i = 1; i < size; i ++) {
// currentNode = currentNode.next;
// }
//另一种写法
while (currentNode.next != null) {
currentNode = currentNode.next;
}
//添加最后的节点
Node node = new Node(e);
currentNode.next = node;
size ++;
//可以直接复用add(e,size);
}
//在链表中间添加元素
//在“索引”为n的地方添加元素
//索引这一思想在链表的实际使用中并不常用,是为了练习四维和面试
public void add(E e, int index) {
if (index < 0 || index > size) {
throw new IllegalArgumentException("The index need to be between 0 and size.");
}
Node node = new Node(e);
//如果添加位置在0 即头部节点,head是没有prev的,索引这里要特殊处理
if (index == 0) {
node.next = head;
head = node;
}else {
Node prev = head;//创建一个指针引用,命名previous,意为将要插入的位置的前一个node,初始位置是head
for (int i = 1; i < index; i ++) {
prev = prev.next;
}
node.next = prev.next;
prev.next = node;
}
size ++;
}
//优化:上一个方法中需要特殊处理head。(因为head没有previous,后边的都有pre,包括最后的null)
//对于链表头添加和其他位置添加,逻辑上是有区别的
//优化方法!!!:为head设立一个虚拟的头节点,可以将头部操作和其他操作统一起来
public void addWithDummyHead(E e, int index) {
if (index < 0 || index > size) {
throw new IllegalArgumentException("Add fail. Illegal index.");
}
Node dummyHead = new Node(null, head);//虚拟头节点里不放东西,且指向head
Node node = new Node(e);
Node prev = head;//prev代表要找的元素的前一个节点,prev从头节点开始寻找
for (int i = 1; i < index; i ++) {
prev = prev.next;
}
node.next = prev.next;
prev.next = node;
size ++;
}
public static void main(String[] args) {
//测试构造函数传入数组,生成链表的功能
Integer[] array = {1,2,3};
LinkedList<Integer> linkedList = new LinkedList<Integer>(array);
System.out.println(linkedList);
linkedList.addFirst(4);
System.out.println(linkedList);
linkedList.addLast(5);
System.out.println(linkedList);
linkedList.add(666,2);
System.out.println(linkedList);
linkedList.add(777,0);
System.out.println(linkedList);
}
}