1. 单向链表的定义:单向链表就是线性结构链式存储方式的具体实现,简称为:单链表。
2. 单向链表的实现:单向链表的方法与线性表的方法相似,因此我们可以使单向链表实现线性表List的接口。
//单向链表的实现
public class LinkedSinglyList<E> implements List<E> {
/*将数据封装成节点 节点包括两部分 数据域和指针域
数据域中存放的是数据内容
指针域中存放的是当前节点的下一跳节点的地址 即指针域指向当前节点的下一跳节点
*/
class Node{
E data; //表示节点的数据域
Node next; //表示节点的指针域
public Node(){ //节点的无参构造
this(null, null);
}
public Node(E element){ //节点的带参构造 参数为数据的内容
this(element, null);
}
public Node(E element, Node next){ //节点的带参构造 参数为数据的内容和下一个节点的地址
this.data = element;
this.next = next;
}
//格式化节点输出时的格式 即输出数据内容即可
@Override
public String toString() {
return data.toString();
}
}
//表示头指针 指向链表中的头节点
private Node head;
//表示尾指针 指向链表中的尾节点
private Node tail;
//表示链表中有效元素的个数
private int size;
public LinkedSinglyList(){ //单向链表的无参构造 为head tail size初始化
head = null;
tail = null;
size = 0;
}
public LinkedSinglyList(E[] arr){ //单向链表的带参构造 参数为一个数组 将数组中的元素依次添加到链表中
if(arr == null || arr.length == 0){
throw new IllegalArgumentException("arr is null");
}
for (int i = 0; i < arr.length; i++){
add(arr[i]);
}
}
//向链表的表尾追加元素
@Override
public void add(E element) {
add(size, element);
}
//向链表的指定位置添加元素
@Override
public void add(int index, E element) {
if(index < 0 || index > size){ //判断索引是否存在
throw new IllegalArgumentException("add index out of range");
}
Node node = new Node(element); //创建节点对象
if(size == 0) { //当表空时 添加元素 只需头尾指针同时指向要添加的节点即可
head = node;
tail = node;
}else if(index == 0) { //向表头添加元素 先要将要添加节点的下一跳指向头节点 再将头指针指向要添加的节点
node.next = head;
head = node;
}else if(index == size) { //向表尾添加元素 先将尾节点的下一跳指向要添加的节点 再将尾指针指向要添加的节点
tail.next = node;
tail = node;
}else { //向表中间添加元素 先遍历到要添加的位置的前驱 将前驱节点的下一跳赋值给要添加节点的下一跳 前驱节点的下一跳指向要添加的节点
Node p = head;
for(int i = 0; i < index - 1; i++){
p = p.next;
}
node.next = p.next;
p.next = node;
}
size++;
}
//删除链表中的指定元素
@Override
public void remove(E element) {
remove(indexOf(element));
}
//删除链表中的指定位置的元素 并返回要删除元素的内容
@Override
public E remove(int index) {
if(index < 0 || index >= size){ //判断索引是否存在
throw new IllegalArgumentException("remove index out of range");
}
E ele = null;
if(size == 1) { //当表中只有一个元素时删除
/*
当表中只有一个元素时删除:只需要先取出那一个元素 再将头指针head 尾指针tail同时指向null即可
*/
ele = head.data;
head = null;
tail = null;
}else if(index == 0) { //删除表头元素
/*
删除表头元素:
1.定义一个新的节点使其也指向头节点
2.取出这个新节点的元素
3.更新头指针 使其指向当先头节点的下一个节点
4.使新节点的指针域指向null
*/
Node n = head;
ele = n.data;
head = n.next;
n.next = null;
}else if(index == size - 1) { //删除表尾元素
/*
删除表尾元素:
1.先定义一个新节点p 使其开始指向头节点 遍历链表 直至新节点p指向链表的尾节点前驱节点
2.取出尾节点的数据
3.使当前新节点p的下一跳指null
4.更新尾指针 是尾指针指向p节点
*/
Node p = head;
while (p.next != tail){
p = p.next;
}
ele = tail.data;
p.next = null;
tail = p;
}else { //删除表中间元素
/*
删除表中间元素:
1.先定义一个新节点p 使其开始指向头节点 遍历链表 直至遍历到指定索引的前驱节点的索引 每轮更新p节点
2.再定义一个节点为要删除的节点n 即当前p节点的下一跳节点 取出要删除的节点n的内容
3.使p节点的下一跳重新指向要删除的节点n的下一跳
4.使要删除的节点n的下一跳指向null
*/
Node p = head;
for(int i = 0; i < index - 1; i++){
p = p.next;
}
Node n = p.next;
ele = n.data;
p.next = n.next;
n.next = null;
}
size--;
return ele;
}
//获取指定索引处的节点的值
@Override
public E get(int index) {
if(index < 0 || index >= size){ //判断索引是否存在
throw new IllegalArgumentException("get index out of range");
}
if(index == 0){ //获取表头元素 即头指针指向的节点的值
return head.data;
}else if(index == size - 1){ //获取表尾元素 即尾指针指向的节点的值
return tail.data;
}else{ //获取表中间某一索引的值
/*
获取表中间某一索引的值:定义一个新节点p使其指向头节点 从表头开始遍历 遍历到索引位置 每次更新p节点
最后p节点就是要获取节点 返回该节点的值即可
*/
Node p = head;
for(int i = 0; i < index; i++){
p = p.next;
}
return p.data;
}
}
//修改指定位置的节点的值 并返回要修改节点的值
@Override
public E set(int index, E element) {
if(index < 0 || index >= size){ //判断索引是否存在
throw new IllegalArgumentException("set index out of range");
}
E ele = null;
if(index == 0){ //修改表头节点的值 即先获取表头结点的值 再将表头结点的值修改为指定的值element即可
ele = head.data;
head.data = element;
}else if(index == size - 1){ //修改表尾节点的值 即先获取表尾结点的值 再将表尾结点的值修改为指定的值element即可
ele = tail.data;
tail.data = element;
}else{ //修改表中间位置的节点的值
/*
修改表中间位置的节点的值:定义一个新节点p使其指向头节点
从头节点开始遍历 遍历到索引位置的前驱位置 每次更新p节点为p节点的下一个节点
遍历结束后 p节点即指定要修改的索引处的节点 获取p节点的值
修改p节点的值为element
*/
Node p = head;
for(int i = 0; i < index; i++){
p = p.next;
}
ele = p.data;
p.data = element;
}
return ele;
}
//获取链表有效元素的个数
@Override
public int size() {
return size;
}
//获取指定元素第一次在链表出现的索引 如果链表中没有 即返回-1
@Override
public int indexOf(E element) {
/*
定义一个新节点p使其指向头节点
遍历链表 直到p节点的数据与指定数据相等即可
每次更新p节点为p节点的下一个节点 索引index的值
*/
int index = 0;
Node p = head;
while (!p.data.equals(element)){
p = p.next;
index++;
if(p == null){
return -1;
}
}
return index;
}
//判断链表中是否包含指定元素
@Override
public boolean contains(E element) {
return indexOf(element) != -1;
}
//判断链表是否为空
@Override
public boolean isEmpty() {
return size == 0 && (head == null && tail == null);
}
//清空链表中的所有元素
@Override
public void clear() {
head = null;
tail = null;
size = 0;
}
//对链表进行排序 选择排序的思想
@Override
public void sort(Comparator<E> c) {
if(c == null){ //判断比较器是否为空
throw new IllegalArgumentException("comparator can not be null");
}
if(size == 0 || size == 1){ //如果链表中有效元素的个数等于0或1 就不需要在对链表进行排序
return;
}
Node nodeA = head; //定义一个节点A 使其指向头节点
Node nodeB = nodeA.next; //定义一个节点B 使其指向节点A的下一个节点
while (true){ //第一层循环 操作节点A
while (true){ //第二层循环 操作节点B
if(c.compare(nodeA.data, nodeB.data) > 0){ //如果节点A的值大于节点B的值 就交换节点A的数据和节点B的数据的位置
swap(nodeA, nodeB);
}
if(nodeB == tail){ //如果节点B等于尾节点 就中断内层循环
break;
}
//更新节点B为当前节点B的下一个节点
nodeB = nodeB.next;
}
if(nodeA.next == tail){ //如果节点A的下一跳指向尾节点 就中断外层循环
break;
}
nodeA = nodeA.next; //更新节点A为当前节点A的下一个节点
nodeB = nodeA.next; //更新节点B为当前节点B的下一个节点
}
}
//交换两个节点的值的位置
private void swap(Node nodeA, Node nodeB) {
E temp = nodeA.data;
nodeA.data = nodeB.data;
nodeB.data = temp;
}
//获取链表中指定索引范围的子链表 [fromIndex, toIndex]
/*
先定义一个节点A 使其指向头节点 遍历链表 更新节点A 使其到达fromIndex位置处
再定义一个节点B 也使其指向头节点 遍历链表 更新节点B 使其到达toIndex位置处
定义一个新节点使其指向节点A 从fromIndex位置遍历到toIndex位置 最后使其指向节点B
将[fromIndex, toIndex]范围内的节点重新依次加入到新的子链表中
*/
@Override
public List<E> subList(int fromIndex, int toIndex) {
if(fromIndex < 0 || fromIndex > toIndex || toIndex >= size){ //判断两个索引是否存在
throw new IllegalArgumentException("must 0 <= fromIndex <= toIndex <= size - 1");
}
LinkedSinglyList<E> linkedList = new LinkedSinglyList<E>();
Node nodeA = head;
for(int i = 0; i < fromIndex; i++){
nodeA = nodeA.next;
}
Node nodeB = head;
for(int i = 0; i < toIndex; i++){
nodeB = nodeB.next;
}
Node p = nodeA;
for(int i = fromIndex; i < toIndex; i++){
linkedList.add(p.data);
p = p.next;
}
return linkedList;
}
//格式化链表输出是的格式
@Override
public String toString() {
StringBuilder str = new StringBuilder();
str.append("LinkedSinglyList: " + size + " [");
if(isEmpty()){
str.append(']');
}
Node p = head;
while (true){
str.append(p.data);
p = p.next;
if(p == tail){
str.append(']');
break;
}
str.append(',');
str.append(' ');
}
return str.toString();
}
//获取当前这个数据结构/容器 的 迭代器
//通过迭代器对象 更方便挨个取出每一个元素
//同时 实现了Iterable 可以让当前的数据结构/容器 被foreach循环遍历
@Override
public Iterator<E> iterator() {
return new LinkedSinglyListIterator();
}
class LinkedSinglyListIterator implements Iterator<E>{
private Node cur; //定义一个游标节点
@Override
public boolean hasNext() { //判断是否有下一个节点
return cur != null; //当cur不为null时 表示有下一个节点
}
@Override
public E next() { //获取下一个节点的值
E ele = cur.data; //先获取节点cur的值
cur = cur.next; //再更新cur节点为当前cur节点的下一个节点
return ele;
}
}
}