目录
1.车厢类
/**
* 车厢类
*/
class Node {
//存储具体元素
int data;
//存储下一个节点的地址
Node next;
//构造方法
public Node(int data) {
this.data=data;
}
public Node(int data, Node next) {
this.data = data;
this.next = next;
}
}
2.火车类
/**
* 带头单链表(头节点不存储具体元素)
*/
public class SingleLinkedListWithHead {
//虚拟头节点 在内存中实实在在存在的节点,有一个引用指向
private Node dummyHead = new Node(-1); //-1 表示这个节点的data值无意义,这个节点就是来连接/脱钩其他节点的。若data不赋值,int默认就是0,而有时0恰好又是需要存储的元素,而负数较少见。
// 只声明一个Node类的引用,无具体节点,在内存中没开空间
// private Node dummyHead;
//单链表中具体节点的个数(不包含虚拟头节点)
private int size;
//具体方法实现
//...
}
2.1.在链表任意位置index处插入节点
/**
* 在链表任意位置index处插入节点
* @param index
* @param data
*/
public void addIndex(int index, int data) {
if(index < 0 || index > size) {
System.err.println("add index illegal!");
return;
}
//此时无论index为何值,都存在前驱节点
Node prev = dummyHead;
for (int i = 0; i < index; i++) {
prev = prev.next;
}
//prev就指向了待插入的前驱节点
Node node = new Node(data);
node.next = prev.next;
prev.next = node;
size++;
}
2.2.在链表头部添加节点
/**
* 在链表头部添加节点
* @param data
*/
public void addFirst(int data) {
addIndex(0, data);
}
2.3.在链表尾部添加节点
/**
* 在链表尾部添加节点
* @param data
*/
public void addLast(int data) {
addIndex(size, data);
}
2.4.在链表任意位置index处删除节点
/**
* 在链表任意位置index处删除节点
* @param index
* @return 返回删除前的节点值
*/
public int removeIndex(int index) {
if(index < 0 || index >= size) {
System.err.println("remove index illegal!");
return -1;
}
Node prev = dummyHead;
//先找到待删除节点的前驱
for (int i = 0; i < index; i++) {
prev = prev.next;
}
//prev指向了待删除节点的前驱
Node node = prev.next;
prev.next = node.next;
node.next = null;
size--;
return node.data;
}
2.5.删除链表头部节点
/**
* 删除链表头部节点
* @return
*/
public int removeFirst() {
return removeIndex(0);
}
2.6.删除链表尾部节点
/**
* 删除链表尾部节点
* @return
*/
public int removeLast() {
return removeIndex(size - 1);
}
2.7.toString()方法
public String toString() {
String ret = "";
Node node = dummyHead.next;
while (node != null) {
ret += node.data + "->";
node = node.next;
}
ret += "NULL";
return ret;
}
3.总代码实现
/**
* 车厢类
*/
class Node {
//存储具体元素
int data;
//存储下一个节点的地址
Node next;//next是自定义的Node引用类型,存储Node类型的对象的地址,即车厢地址
//所有类以及数组都是引用类型,所有引用数据类型存储的都是一个类的对象的地址(起个别名)
//所有引用类型的默认值都是NULL
//当new一个对象时,它才有意义
//构造方法 alt+insert 但此电脑无法使用
public Node(int data) {
this.data=data;
}
public Node(int data, Node next) {
this.data = data;
this.next = next;
}
}
/**
* 火车类 带头单链表(头节点不存储具体元素)
*/
public class SingleLinkedListWithHead {
//虚拟头节点 在内存中实实在在存在的节点,有一个引用指向
private Node dummyHead = new Node(-1); //-1 表示这个节点的data值无意义,这个节点就是来连接/脱钩其他节点的。若data不赋值,int默认就是0,而有时0恰好又是需要存储的元素,而负数较少见。
// 只声明一个Node类的引用,无具体节点,在内存中没开空间
// private Node dummyHead;
//单链表中具体节点的个数(不包含虚拟头节点)
private int size;
/**
* 在链表任意位置index处插入节点
* @param index
* @param data
*/
public void addIndex(int index, int data) {
if(index < 0 || index > size) {
System.err.println("add index illegal!");
return;
}
//此时无论index为何值,都存在前驱节点
Node prev = dummyHead;
for (int i = 0; i < index; i++) {
prev = prev.next;
}
//prev就指向了待插入的前驱节点
Node node = new Node(data);
node.next = prev.next;
prev.next = node;
size++;
}
/**
* 在链表头部添加节点
* @param data
*/
public void addFirst(int data) {
addIndex(0, data);
}
/**
* 在链表尾部添加节点
* @param data
*/
public void addLast(int data) {
addIndex(size, data);
}
/**
* 在链表任意位置index处删除节点
* @param index
* @return
*/
public int removeIndex(int index) {
if(index < 0 || index >= size) {
System.err.println("remove index illegal!");
return -1;
}
Node prev = dummyHead;
//先找到待删除节点的前驱
for (int i = 0; i < index; i++) {
prev = prev.next;
}
//prev指向了待删除节点的前驱
Node node = prev.next;
prev.next = node.next;
node.next = null;
size--;
return node.data;
}
/**
* 删除链表头部节点
* @return
*/
public int removeFirst() {
return removeIndex(0);
}
/**
* 删除链表尾部节点
* @return
*/
public int removeLast() {
return removeIndex(size - 1);
}
public String toString() {
String ret = "";
Node node = dummyHead.next;
while (node != null) {
ret += node.data + "->";
node = node.next;
}
ret += "NULL";
return ret;
}
}
4.动态数组VS单链表
4.1.动态数组
①查找指定index位置的元素arr[1] -> 时间复杂度是O(1)。
②修改index位置的元素arr[1] = newVal; -> O(1)。
有索引,数据根据下标得到数据以及修改数据。
③在任意位置插入和删除元素
- 在数组头部删除元素,在尾部添加元素 -> O(1)。
- 一般而言,在数组除了头尾位置插入与删除元素都要进行元素搬移,耗时 -> O(n)。
4.2.单链表
①查找index = x位置的元素 -> O(n)。
②修改index = x位置的元素 -> O(n)。
由于单链表只能从头向后遍历,因此查找指定位置时,需要走n步。
③在单链表中插入和删除元素只需要修改几个引用的指向即可,不牵扯到数据的搬移。
④由于单链表物理不连续,因此需要存储数据时,比数组(需要扩容)要省空间。
4.3.All in all
①一般数据集只是频繁地查找而不添加或者修改,用数组比较高效。
②数据集牵扯到频繁地添加与删除时,用链表更高效。
如果一个节点已知在链表的靠后位置,但单链表只能从前向后遍历,因此还得从头来过。
故引入双向链表。