package test; /*
- 双向链表
- 总结:
- 当时写这个的时候因为有单链表的基础了,写这个还比较顺。
- 坑点主要是添加的时候记得连接前驱节点,跟删除的时候节点要分头尾中间节点位置去删
- 自己还要去画图感受指针断开跟连接,多自己画图感受指针的连接。
- 这个时候我就对指针跟引用还有对象就有了很深的理解了。不只是停留在字面意思了。
- 而且还反推出了反射到底是个啥意思,反射就只是更高级别的引用而已。
*/
public class MyLinkedListTst01 {
public static void main(String[] args) {
MyLinkedList1<String> mll = new MyLinkedList1<>();
mll.add("a");
mll.add("v");
System.out.println(mll);
Object[] objs = mll.toArray();
System.out.println(objs.length);
for(int i=0;i<objs.length;i++) {
System.out.println(objs[i]);
}
}
}
class MyLinkedList1<V>{
//拿到整个双向链表
DoubleNode<V> head;
//链表中元素个数
int size;
//对外提供头结点定义入口
public MyLinkedList1(V value) {
if(value == null) return;
this.head = new DoubleNode<V>(value);
this.size++;
}
public MyLinkedList1() {
super();
}
public boolean contains(V value) {
//遍历。没有写get(V value) 懒得弄了。
if(this.size == 0 || value == null) return false;
DoubleNode<V> node = this.head;
while(node != null) {
if(node.value.equals(value)) {
return true;
}
node = node.next;
}
return false;
}
public boolean add(V value) {
//空拦截
if(value == null) return false;
//头结点直接加
if(this.head == null) {
this.head = new DoubleNode<>(value);
this.size++;
return true;
}
//非头节点循环遍历拿到目标位置再加,记得指针两头都要连。
DoubleNode<V> node = this.head;
while(node != null) {
if(node.next == null) {
break;
}
node = node.next;
}
node.next = new DoubleNode<>(value);
node.next.pre = node;
this.size++;
return true;
}
public boolean insert(int index,V value) {
//精准拦截非链表长度及尾巴的下标插入
//超出的下标也可以让他插,我不想这样写,会对用的人有歧义
if(index < 0 || index > this.size) return false;
DoubleNode<V> node = this.head;
//前驱
DoubleNode<V> pre = null;
//用来定位目标节点
int num = 0;
while(node != null) {
if(num == index) {
break;
}
num++;
pre = node;
node = node.next;
}
//拿到目标节点分头部,尾部,中间,完事别漏了指针双向连接。
//指针的方向多画图。
if(node == null) {
if(pre == null) {
this.head = new DoubleNode<>(value);
}else {
node = new DoubleNode<>(value);
node.pre = pre;
pre.next = node;
}
}else {
if(pre == null) {
this.head = new DoubleNode<>(value);
head.next = node;
node.pre = head;
}else {
DoubleNode<V> newNode = new DoubleNode<>(value);
pre.next = newNode;
newNode.pre = pre;
newNode.next = node;
node.pre = newNode;
}
}
this.size++;
return true;
}
public boolean remove(V value) {
//拦截空
if(value == null || this.size == 0) return false;
DoubleNode<V> node = this.head;
//前驱
DoubleNode<V> pre = null;
while(node != null) {
if(node.value.equals(value)) {
break;
}
pre = node;
node = node.next;
}
//没有找到目标直接返回
if(node == null) return false;
//分头节点,尾部节点,中间节点删除,注意指针连接
if(node.next == null) {
if(pre == null) this.head = null;else pre.next = null;
}else {
if(pre == null) {
this.head = node.next;
this.head.pre = null;
}else {
pre.next = node.next;
node.next.pre = pre;
}
}
//最后统一置空防止野指针。
node.next = node.pre = null;
this.size--;
return true;
}
public boolean set(int index,V value) {
//精准拦截链表长度以外的替换
if(index < 0 || index >= this.size) return false;
DoubleNode<V> node = this.head;
int num = 0;
while(node != null) {
if(index == num) {
node.value = value;
return true;
}
num++;
node = node.next;
}
return false;
}
public V get(int index) {
if(index < 0 || index >= this.size) return null;
DoubleNode<V> node = head;
//通过num定位链表节点的index位置
int num = 0;
while(node != null) {
if(num == index) {
return node.value;
}
num++;
node = node.next;
}
return null;
}
public Object[] toArray() {
//就是遍历链表然后一个一个加到数组。
if(size == 0) return new Object[0];
Object[] objs = new Object[this.size];
DoubleNode<V> node = head;
int index = 0;
while(node != null) {
objs[index] = node.value;
index++;
node = node.next;
}
return objs;
}
@Override
public String toString() {
//StringBuffer是线程安全的性能低,还没自己写过原理不太清楚咋回事。
//不是并发就用StringBuilder,效率高。
//直接String加的话会浪费内存。每次加都会在方法区增加一个String对象。
if(size == 0) return "[]";
StringBuffer sb = new StringBuffer();
DoubleNode<V> node = this.head;
sb.append("[");
while(node != null) {
if(node.next != null) {
sb.append(node.value);
sb.append(",");
}else {
sb.append(node.value);
}
node = node.next;
}
sb.append("]");
return sb.toString();
}
}
class DoubleNode<V>{
//双向链表需要回头节点所以需要定义一个前驱一个后继。也是俄罗斯套娃。
//前驱可以让链表回头访问。
DoubleNode<V> pre;
DoubleNode<V> next;
//存储数据。有序可以重复。
V value;
public DoubleNode(V value) {
super();
this.value = value;
}
public DoubleNode() {
super();
}
@Override
public String toString() {
return this.value.toString();
}
}