目录
1.介绍
1. 链表的存储分类为链式结构,非连续的空间。
2. 链表第一个节点称为头节点,最后一个节点称为尾节点。
2. 分类
1. 单向链表: 如果前一个节点存储了后一个节点地址,后一个节点没有存储前一个节点地址,这种链表称为单向链 表。
2. 双向链表 如果前一个节点存储了后一个节点地址,后一个节点也存储前一个节点地址,这种链表称为双向链表
3.双向非循环链表的优缺点
优势: 链表优势在于中间的添加和删除效率高。
劣势: 链表不具备随机访问的能力。所以每次获取元素的值都从头或从尾一个一个找,可以判断要查询元素 的索引值,如果要查询索引值超过最大索引的一半,从后往前找。如果没有超过最大索引的一半,从前往后找
4.代码实现
1.添加数据
添加节点的方式: 1. 头插法: 新添加的节点作为头节点。
2. 尾插法: 新添加的节点作为尾节点。
代码如下:
接口:
public interface List {
//默认被 public static abstract 修饰
//添加
boolean add(Object o);
//获取某个节点的值
Object get(int value);
//获取链表中有几个元素
int size();
//修改节点中存储的元素值
boolean set(Object o,int value);
//根据位置删除
boolean remove(int var1);
//根据内容删除
boolean remove(Object o);
}
Node类 :
public class Node{
Node pre ;//上个节点地址
Object o;//元素内容
Node next;//下个节点地址
public Node(Node pre, Object o, Node next) {
this.pre = pre;
this.o = o;
this.next = next;
}
}
尾插:
public class LinkedList implements List{
private Node head;//头节点
private Node tail;//尾节点
private int count;//计数器
public LinkedList(Node head, Node tail, int count) {
this.head = head;
this.tail = tail;
this.count = count;
}
@Override
public boolean add(Object o) {
return addLast(o);//默认尾插
}
//尾插
private boolean addLast(Object o) {
try {
Node oldTail = this.tail;
if (oldTail != null){
Node newNode = new Node(oldTail,o,null);
oldTail.next = newNode;
this.tail = newNode;
}else {
Node newNode = new Node(oldTail,o,null);
this.head = newNode;
this.tail = newNode;
}
count++;//计数器
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
头插:
//头插
private boolean addFirst(Object o) {
try {
Node oldHead = this.head;
if (oldHead != null){
Node newNode = new Node(null,o,oldHead);
oldHead.pre = newNode;
this.head = newNode;
}else {
Node newNode = new Node(null,o,oldHead);
this.head = newNode;
this.tail = newNode;
}
count++;//计数器
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
获取某个节点的值:
//获取节点中的元素值(节点从0开始计算)
public Object get(int value) {
Node node = getNode(value);
return node.o;
}
//根据第几个节点,获取对应节点对象
public Node getNode(int value){
checkIndex(value);
Node node = null;
if(value < (count >> 1)){//>>表示二进制向右移位一位
node = this.head;
for (int i = 0; i < value; i++) {
node = node.next;
}
}else {
node = this.tail;
for (int i = count -1 ;i > value;i--){
node = node.pre;
}
}
return node;
}
//检查节点数,有问题时抛出异常
public void checkIndex(int value) {
if (value < 0 || value > count - 1) {
throw new LinkedListOutOfBoundsException("节点个数出现问题,节点数范围:0 ~ " + (count - 1));
}
}
获取链表中节点个数:
//获取个数
@Override
public int size() {
return count;
}
修改节点中存储的元素值:
@Override
public boolean set(Object o, int value) {
try {
Node node = getNode(value);
node.o = o;
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
删除节点:(根据节点位置)
/*
* 1.删除的节点既是头节点又是尾节点
* 1.头节点设置为null
* 2.尾节点设置为null
* 3.删除的节点设置为null
* 2.删除的节点为头节点
* 1.头节点的下个节点存储上个节点的地址设置为null
* 2.头节点的下个节点设置为新的头节点
* 3.删除的头节点设置为null
* 3.删除的节点为尾节点
* 1.尾节点的上个节点存储下个节点的地址设置为null
* 2.尾节点的上个节点设置为新的尾节点
* 3.删除的尾节点设置为null
* 4.删除的节点非头尾节点
* 1.删除节点的上个节点存储下个节点的地址改为删除节点的下个节点
* 2.删除节点的下个节点存储上个节点的地址改为删除节点的上个节点
* */
@Override
public boolean remove(int value) {
try {
Node node = getNode(value);
removeNode(node);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
private void removeNode(Node node) {
Node next = node.next;
Node pre = node.pre;
if (node.pre == null && node.next == null){
this.head = null;
this.tail = null;
node = null;
}else if (pre == null){
next.pre = null;
this.head = next;
node = null;
}else if (next == null){
pre.next = null;
this.tail = pre;
node = null;
}else {
next.pre = pre;
pre.next = next;
node = null;
}
count--;//计数器-1
}
删除节点:(根据内容)
//根据元素内容删除,本质上还是根据节点位置删除
@Override
public boolean remove(Object o) {
for (int i = 0; i < count; i++) {
Node node = getNode(i);
if (node.o.equals(o)) { //占到了要删除的节点 node
removeNode(node);
return true;
}
}
return false;
}
5.总结:
通过该项目的练习可以很好的进一步理解链表的增删改查的过程
数据结构网址: https://www.cs.usfca.edu/~galles/visualization/Algorithms.html(可以模拟各种数据结构的存取过程)