在数据结构系列—线性表(二),提到了结点、头指针、头结点、插入方式等概念,今天就围绕这几个概
念实现一个添加、删除、查找等功能的单向链表。废话不多说,直接上代码。白话文字比较多,需要好好消化一下。
/**
* 链式存储:
* 1、插入:头插法,尾插法、根据下标插入
* 2、删除:根据下标删除
* 3、根据下标查找
* 4、清空表
* 5、获取表长度
*/
public class TestLinkedList {
private int size;
private Node header;//头结点
private Node tail;//尾结点
public TestLinkedList() {
size = 0;
header = null;
tail = null;
}
/**
* 返回链表的长度
*/
public int length() {
return size;
}
/**
* 清空线性表
*/
public void clear() {
header = null;
tail = null;
size = 0;
}
/**
* 尾插法——插入元素的尾部即可
* 比如插入 a b c,在链表中的顺序应该是 a b c
*/
public void addTail(Object element) {
if (header == null) {//链表为空
header = new Node(element, null);//创建一个头指针(链表中的第一个结点存储位置叫作"头指针")
tail = header;
} else {
Node newNode = new Node(element, null);//由于插入的是尾部,最后一个结点指针设置为 null 或 ^ 表示。
tail.next = newNode;//尾结点指针指向新插入的结点
tail = newNode;//尾结点替换成新插入的结点
}
size++;
}
/**
* 头插法——插入的元素都放在头部
* 比如插入 a b c,在链表中的顺序应该是 c b a
* 当插入 a结点的时候,如果链表为空,那么直接进行尾结点的赋值。头部也进行了新插入结点a的赋值。
* 当插入 b结点的时候,这时候头结点为a 尾结点为a,新插入的结点b需要当做头结点,那么它的next指向就是上个头结点a。b->next=结点a,结点a->next=null
* 当插入 c结点的时候,目前链表的结构是b->next=结点a,结点a->next=null,就是重复上一个操作,将结点c的指针指向结点b,c->next=结点b
* <p>
* 最终形成了 c->next=结点b, b->next=结点a,a->next=null,
*/
public void addHeader(Object element) {
header = new Node(element, header);//链表为空的话,第一次插入,header肯定为null
if (tail == null) {
tail = header;//如果链表尾空,最后一个节点就是当前插入的节点
}
size++;
}
/**
* 根据下标删除
*
* @param index
* @return
*/
public Object delete(int index) {
if (index < 0 || index > size - 1) {
throw new IndexOutOfBoundsException("链表下标越界");
}
Node del = null;
if (index == 0) {//处理头结点
del = header;
header = header.next;//头结点删除后,将头结点指向的结点进行赋值,(头结点a和结点b,这时候删除头结点a,就需要将a->next=结点b,设置为头结点)
} else {
// 还记得前面讲过的,删除中间节点的赋值顺序了吗?s = p->next,p->next = s->next; 结点s需要被删除。只需要将结点s的前一个结点p的指针域指向结点s下面的结点q即可。
// 1、需要先获取上一个位置的指针域,
Node prev = getNodeLastIndex(index - 1);
del = prev.next;//通过当前位置的上一个结点,获取到当前被删除结点。prev.next=被删除的结点
//2、将del结点的上一个结点的指针,指向del结点下一个结点。完成一个删除
prev.next = del.next;
del.next = null;
}
size--;
return del.data;
}
/**
* 根据索引插入
* 1、获取当前索引前一个结点,
* 2、将前一个结点指向当前新结点,新节点指向下一个结点
*
* @param index
* @param element
* @return
*/
public void insert(int index, Object element) {
if (index < 0 || index > size) {
throw new IndexOutOfBoundsException("链表下标越界");
}
if (header == null) {
addTail(element);//尾插法插入
} else {
if (index == 0) {//插入头部的话,需要用到头插法
addHeader(element);
} else {
//还记得前面讲过的 结点s插入对应位置的步骤吗?目前有两个结点p、q,p.next=q,这时候要加一个结点s。步骤如下
//s->next = p->next,p->next = s; 也就是说,先将新插入"结点s"的指针域指向"结点q",再将"结点p"的指针指向"结点s"即可。
//步骤不能反过来的原因也分析过了,具体看:下面放入的链接
Node prev = getNodeLastIndex(index - 1);//获取当前索引前一个结点
prev.next = new Node(element, prev.next);//拆解开来就是下面两个步骤
// 1、将新结点 element 的指针指向当前结点,也就是 prev.next=当前结点
// Node newNode = new Node(element);
// newNode.next = prev.next;
// 2、将当前位置的上个结点的指针,指向当前newNode结点即可。
// prev.next = newNode;
size++;
}
}
}
/**
* 获取上一个结点
*
* @param index
* @return
*/
private Node getNodeLastIndex(int index) {
if (index < 0 || index > size - 1) {
throw new IndexOutOfBoundsException("链表下标越界");
}
Node current = header;//从头结点开始,一次往下查找
for (int i = 0; i < size && current != null; i++) {
if (i == index) {
return current;
}
//比如当前有三个结点 a b c ,查找的时候会先查找结点a的指向 然后查找结点b的指向 ,最后查找结点c的指向 。如果位置对应则顺利获取上一个结点
current = current.next;
}
return null;
}
/**
* 根据下标获取链表元素
*
* @param index
* @return
*/
public Object get(int index) {
return getNodeLastIndex(index).data;
}
//链表的每个节点类
private class Node {
private Object data;//数据域
private Node next;//指针域 指向下一个节点的引用
public Node(Object data) {
this.data = data;
}
public Node(Object data, Node next) {
this.data = data;
this.next = next;
}
}
}
代码中 insert() 方法里提到的链接,数据结构系列—线性表(二)
测试部分
TestLinkedList testLinkedList = new TestLinkedList();
//尾插法
testLinkedList.addTail(1);
testLinkedList.addTail(2);
testLinkedList.addTail(3);
testLinkedList.addTail(4);
//查找链表中每个index对应的值
searchData(stringBuilder, testLinkedList);
//根据对应位置插入元素
testLinkedList.insert(0, 0);
//插入之后再次查询
searchData(stringBuilder, testLinkedList);
//使用头插法 列表前4位的顺序应该是 4 3 2 1
testLinkedList.addHeader(1);
testLinkedList.addHeader(2);
testLinkedList.addHeader(3);
testLinkedList.addHeader(4);
//头插法之后再次查询
searchData(stringBuilder, testLinkedList);
//删除第4个位置的元素 也就是把0删除
testLinkedList.delete(4);
searchData(stringBuilder, testLinkedList);
Log.d("testLinkedList", stringBuilder.toString());
System.out.println(stringBuilder.toString());
}
private void searchData(StringBuilder stringBuilder, TestLinkedList testLinkedList) {
//获取长度
int length = testLinkedList.length();
stringBuilder.append("\n")
.append("\nlength--")
.append(length);
for (int i = 0; i < testLinkedList.length(); i++) {
int data = (int) testLinkedList.get(i);
stringBuilder.append("\n")
.append("\ndata--")
.append(data);
}
}
打印:
testLinkedList: length--4
data--1
data--2
data--3
data--4
length--5
data--0
data--1
data--2
data--3
data--4
length--9
data--4
data--3
data--2
data--1
data--0
data--1
data--2
data--3
data--4
length--8
data--4
data--3
data--2
data--1
data--1
data--2
data--3
data--4
总结:
本次链表与数组的实战就告一段落了,通过这次实战能够掌握到顺序存储与链式存储的物理区别。一种靠指针移动,一种靠开辟空间,二者的优势劣势前面讲过,这里就不再过多叙述了。
接下来会学习逻辑结构中的:栈、队列。