上一篇介绍了顺序表:用Java描述数据结构之线性表的顺序存储(顺序表),ArrayList及其方法的介绍
上一篇博客中说明了什么是线性表——线性表就是一个个数据元素逻辑上以一对一的相邻关系(但是在物理结构上并不一定是连续得到)组织起来的有限序列。
根据物理存储方式的不同可以将线性表分为两种,一种叫顺序表,一种叫链表,这篇博客主要介绍链表。
链表和顺序表不同,链表在存储时并不是连续的,大家可以这么理解:内存上被使用的一段段空间之间会有小段的未被使用的,链表在使用时就可以使用这些小段的未被使用的空间去存储数据,而顺序表需要相比于链表则是一段较长的连续的存储空间。因为顺序表底层其实是使用数组实现的,而数组在声明之后长度是不可变的,所以顺序表在使用之初就会申请一段相对较长的数组,随着顺序表内增加的元素越来越多,数组可用的空间就越来越少,直到顺序表检测到原数组空间不够用时会申请一个比原数组长的新数组,然后将原数组中的数据依次复制到新数组,顺序表就一直重复着这样的操作来满足增加新元素的可持续性,这导致了多数时候顺序表底层的数组有大量未使用的空间,但这些空间又不能被其他程序使用,所以顺序表在空间利用上存在明显的弊端。
链表的出现就完美的解决了顺序表在空间利用的不足之处(虽然链表一个节点存储了除了数据元素外相比于顺序表的一个节点还多了指向当前节点的前后节点的引用,但是多出来的这点空间相比于顺序表底层数组时常存在的未被使用的空间相比,还是小了很多),链表的一个节点除了储存要储存的数据外,还需要存指向在逻辑上该节点的前后两个节点的引用,这因为这多出来可以指向前后节点的引用,才可以使链表在物理存储上可以不连续,因为可以通过当前节点中的引用去找它的前后节点。
为了方便理解节点,我们定义一个节点类:
public class MyListNode {
//该链表可以存储String类型数据
public String val;
//指向当前节点的前节点的引用
public MyListNode prev;
//指向当前节点的后节点的引用
public MyListNode next;
//构造方法
public MyListNode(String val){ this.val = val; }
}
虽然Java有已经实现的LinkedList类,但是为了方便对链表更深入的理解,我建议大家可以自己实现以下LinkedList的常用方法,有些同学可能对节点和引用这一块不太理解,我推荐你去看我这篇博客,看完之你能更好的理解链表:通过链表深入理解Java的引用和对象,下面开始介绍LinkedList的常用方法,以及自己实现它们的思路(思路放在代码中,每个方法实现前),还是将常用方法分成增删查改四个大类:
增 :
方法名 | 作用 |
---|---|
boolean add(element) | 将element尾插在链表中,理论上一定会尾插成功,即一定会返回true。 |
void add(index, element) | 将element插在指定位置index处,index的范围应该在[0, size()]之间。 |
删 :
方法名 | 作用 |
---|---|
E remove(index) | 删除index位置的元素,并返回该元素的值。 |
boolean remove(element) | 删除指定元素,这里就有三种情况了: 1.如果该元素确实在链中,并且只有一个,则删除该元素,最后返回true;2.如果该元素在链表中有多个,则删除第一个,同时返回true;3.如果链表中没有该元素,则什么都不做,返回false。 |
void clear() | 无论链表中原来有多少元素,执行该方法之后, 清空这个顺序表。 |
查 :
方法名 | 作用 |
---|---|
E get(index) | 获取链表中的index位置的元素,并且返回,index的范围为[0, size()]。 |
boolean contains(element) | 判断链表中是否包含元素,包含返回true,否则返回false。 |
int indexOf(element) | 返回该元素从前往后找的首次遇到的下标,如果没有则返回 -1。 |
int lastIndexOf(element) | 返回该元素从后往前找的首次遇到的下标,如果没有则返回 -1。 |
boolean isEmpty() | 判断链表是否为空,为空返回true,否则返回false。 |
int size() | 返回链表中元素的个数。 |
改 :
方法名 | 作用 |
---|---|
E set(index, element) | 向链表的index位置放入元素element,并且返回该下标位置原来的元素。 |
下面是实现代码:
public class MyLinkedList implements MyList {
public MyListNode head;
public MyListNode last;
public int size;
public MyLinkedList(){
head = null;
last = null;
size = 0;
}
//尾插
//当链表为空时,要尾插一个节点时
//需要同时让 head 和 last 两个引用指向新节点;
//当链表不为空时,只需要用 last(last指向最后一个节点)来操作
//最后让last指向新的尾节点
public boolean add(String e) {
MyListNode node = new MyListNode(e);
if(head == null){
head = node;
}else {
last.next = node;
node.prev = last;
}
last = node;
size++;
return true;
}
//指定位置插入节点
//先判断下标的合法性
//分为三种情况,头插,尾插,从中间插
//头插就是index == 0,如果原链表为空链表,index等于0也为合法下标
//所以头插要分为原链表为空表,或者原链表不是空表两种情况
//链表不为空时,需要判断是否是尾插,因为尾插需要改变last
//最后一种情况就是正常插入
public void add(int index, String e) {
if(index < 0 || index > size()){
throw new ArrayIndexOutOfBoundsException();
}
MyListNode node = new MyListNode(e);
if(index == 0){
if(head != null){
node.next = head;
head.prev = node;
head = node;
}else {
head = node;
last = node;
}
} else if(index == size()){
add(e);
}else {
MyListNode preNode = head;
for(int i = 1; i < index ; i++){
preNode = preNode.next;
}
//先让node的prev 和 next 分别指向
//它的前一个和后一个节点
node.prev = preNode;
node.next = preNode.next;
//再让node的下一个节点的prev指向node
preNode.next.prev = node;
//最后让node前一个节点的next指向node
preNode.next = node;
}
size++;
}
//删除指定下标节点
//先判断下标合法性
//分为三种情况:头删,删中间,尾删
//头删又分为两种情况:表中只有一个节点的头删和表中节点数大于1
//删中间正常删就可以
//尾删同上
public String remove(int index) {
if(index < 0 || index >= size()){
throw new ArrayIndexOutOfBoundsException();
}
String oldVal;
if(index == 0){
if(size() == 1){
oldVal = head.val;
head = null;
}
else {
oldVal = head.val;
head = head.next;
head.prev = null;
}
}else if(index == size() - 1){
oldVal = last.val;
last = last.prev;
last.next = null;
}else {
MyListNode prevNode = head;
for(int i = 1; i < index; i++){
prevNode = prevNode.next;
}
oldVal = prevNode.val;
prevNode.next.next.prev = prevNode;
prevNode.next = prevNode.next.next;
}
size--;
return oldVal;
}
public boolean remove(String e) {
int index = indexOf(e);
if (index < 0) {
return false;
}
remove(index);
return true;
}
public String get(int index) {
if (index < 0 || index >= size()) {
throw new ArrayIndexOutOfBoundsException();
}
MyListNode node = head;
for (int i = 0; i < index; i++) {
node = node.next;
}
return node.val;
}
public String set(int index, String e) {
if (index < 0 || index >= size()) {
throw new ArrayIndexOutOfBoundsException();
}
MyListNode node = head;
for (int i = 0; i < index; i++) {
node = node.next;
}
String oldE = node.val;
node.val = e;
return oldE;
}
public int indexOf(String e) {
// 遍历查找
int i = 0;
for ( MyListNode cur = head; cur != null; cur = cur.next) {
if (cur.val.equals(e)) {
return i;
}
i++;
}
return -1;
}
public int lastIndexOf(String e) {
int i = size() - 1;
for (MyListNode cur = last; cur != null; cur = cur.prev) {
if (cur.val.equals(e)) {
return i;
}
i--;
}
return -1;
}
public boolean contains(String e) {
return indexOf(e) >= 0;
}
public int size() {
return size;
}
public boolean isEmpty() {
return size() == 0;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder("[");
for (Node cur = head; cur != null; cur = cur.next) {
sb.append(cur.val);
if (cur != last) {
sb.append(", ");
}
}
sb.append("]");
return sb.toString();
}
}
测试`
public class Demo {
public static void main(String[] args) {
MyLinkedList linkedList = new MyLinkedList();
linkedList.add("A");
linkedList.add("B");
linkedList.add("C");
System.out.println(linkedList); // [A, B, C]
linkedList.add(0, "A");
linkedList.add(0, "B");
linkedList.add(2, "C");
System.out.println(linkedList); // [A, B, C]
linkedList.add(2, "D");
System.out.println(linkedList.size()); // 3
System.out.println(linkedList.isEmpty()); // false
System.out.println("======================");
linkedList.clear();
System.out.println(linkedList); // []
System.out.println(linkedList.size()); // 0
System.out.println(linkedList.isEmpty()); // true
System.out.println("======================");
System.out.println(linkedList); // [B, A, D, C]
System.out.println(linkedList.size()); // 4
System.out.println(linkedList.isEmpty()); // false
System.out.println("======================");
linkedList.add("A");
linkedList.add("B");
linkedList.add("C");
System.out.println(linkedList.get(1)); // B
System.out.println(linkedList.set(1, "b")); // B
System.out.println(linkedList); // [A, b, C]
}
}
运行结果:
以上就是这篇博客所有内容,如果理解有偏差,还望看官在评论区指正,谢谢!