目录
🌟前言:链表的介绍
🌈链表的概念及结构
链表是一种在物理存储结构上非连续存储的结构,数据元素的逻辑顺序是通过链表中的引用链接次序实现的。链式结构在逻辑上是连续的,在物理上不连续。
链表的特点总结:
- 对于单链表来说,每个节点不仅存储自身的值,还存储下一个节点的地址值;
- 带头单链表就是比无头单链表多了一个头结点,我们把它叫做"虚拟头结点";
- 对于双链表来说,每个节点存储上一个节点的地址值,自身节点的值和下一个节点的地址值。
我们主要学习以下三种链表的模拟实现:
🌟 1、无头单链表
需要知道:
1、链表中有两个特殊节点:头结点没有前驱,尾结点没有后继节点。
2、同时注意:遍历链表的时候,从当前位置走到下一个位置,但是不能直接用head头结点,因为会造成链表的丢失,所以要用临时的头结点。
🌈 链表的初始化定义
private Node head;//定义头结点
private int size;//定义节点的个数
/**
* 定义节点类:作为链表的私有内部类,对外部完全隐藏
*/
private class Node{
E val;//节点存储自身的值
Node next;//节点存储下一节点的地址
Node(E val){
this.val = val;
}
}
🌈1、 链表的头插
头插:在当前链表头部添加元素。产生新节点——>让新节点指向head节点——>更改头结点——>更新元素个数。
public void addFirst(E val) {
//要插入一个元素,首先要产生一个新节点
Node node = new Node(val);//将要添加的元素值添加到这个节点
node.next = head;//新添加的节点的地址指向现有的头结点
head = node;//更换现有的头结点:为新添加的这个节点
size++;//添加的节点个数+1
}
🌈2、链表的尾插
直接增加该值,则为尾插操作:size则表示元素的下一个位置。
@Override
public void add(E element) {
add(size,element);//调用add方法,在size位置直接插入该值
}
🌈 3、在index位置插入值为element的元素
思想:判断要插入元素的Index位置是在链表的哪个位置?头插还是中间插入(找前驱)。
@Override
public void add(int index, E element) {
//(1)判断索引的合法性
if(!indexCheck(index)){
throw new IllegalArgumentException("add index illegal");
}
//(2)判断索引插入的位置:如果是头插,直接调用头插方法
if(index == 0){
addFirst(element);
return;
}
//(3)索引在中间正常位置下插入元素:找到索引位置的前驱,更改指向
Node temp = head;
//产生新节点
Node node = new Node(element);
//遍历链表,走到要添加节点的前驱
for (int i = 0; i < index-1; i++) {
temp = temp.next;
}
//记录被要增加节点的前驱
Node prev = temp;
//记录要增加节点的后一个节点
Node next = prev.next;
//更改指向
prev.next = node;
node.next = next;
//(4)节点数加加
size++;
}
🌈 4、删:删除索引位置上的值,并返回被删除的值 (重点)
思想:找被删除节点的前驱,更改节点的指向。
@Override
public E removeByIndex(int index) {
//确定索引的合法性
if(!indexCheck(index)){
throw new IllegalArgumentException("remove index illegal");
}
//如果要删除的节点是头结点
if(index == 0){
Node node = head;
head = node.next;
node.next = null;
size--;
return node.val;
}
//用新的节点代替head不断向后移动
Node prev = head;
//遍历链表到被删除节点的前驱
for (int i = 0; i < index-1; i++) {
prev = prev.next;
}
//此时的prev就是被删除的节点的前驱
Node node = prev.next;
//记录被删除节点的值
E removeVal = node.val;
//更改节点的指向
prev.next = node.next;
node.next = null;
//更改节点数
size--;
return removeVal;
}
🌈 5、删:删除第一个值为element的元素(重点)
@Override
public void removeByValue(E element) {
//特殊情况:头结点为Null
if(head == null){
return;
}
//如果删除的节点是头结点
if(head.val.equals(element)){
head = head.next;
size--;
return;
}
//如果删除的节点是在中间正常位置:遍历链表,判断链表中的节点是否与被删除的值相等
Node prev = head;
while (prev.next != null){
if(prev.val.equals(element)){
prev.next = prev.next.next;
size--;
return;
}
prev = prev.next;//不断移动
}
// 链表中没有待删除的节点
System.out.println("当前链表中不存在值为" + element + "的节点");
}
🌈 6、删:删除所有值为element的元素(重点)
元素的删除还是比较难的,尤其是删除所有元素,情况比较复杂。这里要注意指针的移动和边界条件的出现。
@Override
public void removeAllValue(E element) {
//边界条件
if(head == null){
return;
}
//如果要删除的元素值在头结点且出现连续待删除的节点
while (head !=null && head.val.equals(element)){
head = head.next;
size--;
}
//---------经过上面的删除头结点操作,如果此时整个链表已经不存在全部删除了
if(head == null){
return;
}
//正常位置的删除
Node prev = head;
while (prev.next != null){
if(prev.next.val.equals(element)){
//此时的prev就是被删除节点的前驱
prev.next= prev.next.next;
size--;
}else{
//----------只有当后继节点不是被删除的节点的时候才能移动prev引用
prev=prev.next;
}
}
}
🌈 7、修改元素
@Override
public E set(int index, E elemnet) {
//用一个节点表示head,不断移动
Node node = head;
//遍历集合,走到索引为Index的位置
for (int i = 0; i < index; i++) {
node = node.next;
}
//此时node节点就是要被设置新的值的节点
//用一个变量接收旧值
E oldVal = node.val;
//设置新的值
node.val = elemnet;
//返回旧值
return oldVal;
}
/**
* 方法:索引的合法性判断
*/
private boolean indexCheck(int index) {
if(index<0 || index>size){
return false;
}else{
return true;
}
}
🌈 8、获取index下的元素
@Override
public E get(int index) {
//用一个节点表示head,不断移动
Node node = head;
//遍历集合,走到索引为Index的位置
for (int i = 0; i < index; i++) {
node = node.next;
}
//此时node节点就是要被获取值的节点
E getVal = node.val;
return getVal;
}
🌈 9、判断是否包含元素
@Override
public boolean contains(E element) {
Node node = head;
while (node.next != null){
if(node.val.equals(element)){
return true;
}
node = node.next;
}
return false;
}
🌈 10、返回元素的索引
@Override
public int indexOf(E element) {
//遍历链表,判断链表中的每个节点的元素值是否与传入的element值相等
Node node = head;//用node代替head
for (int i = 0; i < size; i++) {
if(node.val.equals(element)){
return i;
}
node = node.next;//不断移动到下一个节点
}
throw new IllegalArgumentException("没有找到该元素");
}
🌈 11、链表的遍历
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
/**
* 方法1:while循环方式
*/
//链表中的最后一个元素的next地址为空
//理论上:head是头结点,但是不能一直用head.next指向下一个节点,这样遍历完成,head就到了最后的节点,节点就没了
//因此要用一个临时变量存储head
Node temp = head;
while (temp!=null){//说明该temp还没有遍历完节点,当前的节点还有值
sb.append(temp.val);//将当前节点的值val添加到字符串容器中
sb.append("——>");
if(temp.next==null){
sb.append("null");
}
temp= temp.next;//让temp指向下一个节点
}
/**
* 方法2: for循环方式
*/
// for (Node i = head; i !=null ; i=i.next) {
// sb.append(i.val);//将当前节点的值val添加到字符串容器中
// sb.append("——>");
// if(i.next==null){
// sb.append("null");
// }
// }
return sb.toString();
}
🌟完整代码
(1)SingleLinkedList类实现SeqList接口,重写接口中的所有抽象方法
*/
/** 主要知道单链表是由节点组成的,有一个头结点,但是没有前驱。每个节点不仅存储自身的值,还保存指向下一个节点的地址值。最后一个节点的地址为Null。
*/
public class SingleLinkedList<E> implements SeqList<E> {
private Node head;//定义头结点
private int size;//定义节点的个数
/**
* 定义节点类:作为链表的私有内部类,对外部完全隐藏
*/
private class Node{
E val;//节点存储自身的值
Node next;//节点存储下一节点的地址
Node(E val){
this.val = val;
}
}
/**
* 个人感觉这里的数据处理操作主要分为三步:产生新的节点——>更改指向——>更新节点数
* 链表中的关键在于找节点的前驱!而这里注意的一点就是头节点是没有前驱的。
*/
/**
* 头插:在当前链表头部添加元素
*/
public void addFirst(E val) {
//要插入一个元素,首先要产生一个新节点
Node node = new Node(val);//将要添加的元素值添加到这个节点
node.next = head;//新添加的节点的地址指向现有的头结点
head = node;//更换现有的头结点:为新添加的这个节点
size++;//添加的节点个数+1
}
/**
* 尾插:增加值为element的节点
* 思想:直接增加该值,则为尾插操作:size则表示元素的下一个位置
*/
@Override
public void add(E element) {
add(size,element);//调用add方法,在size位置直接插入该值
}
/**
* 增:在index位置插入值为element的元素
*思想:判断要插入元素的Index位置是在链表的哪个位置?头插还是中间插入(找前驱)
*/
@Override
public void add(int index, E element) {
//(1)判断索引的合法性
if(!indexCheck(index)){
throw new IllegalArgumentException("add index illegal");
}
//(2)判断索引插入的位置:如果是头插,直接调用头插方法
if(index == 0){
addFirst(element);
return;
}
//(3)索引在中间正常位置下插入元素:找到索引位置的前驱,更改指向
Node temp = head;
//产生新节点
Node node = new Node(element);
//遍历链表,走到要添加节点的前驱
for (int i = 0; i < index-1; i++) {
temp = temp.next;
}
//记录被要增加节点的前驱
Node prev = temp;
//记录要增加节点的后一个节点
Node next = prev.next;
//更改指向
prev.next = node;
node.next = next;
//(4)节点数加加
size++;
}
/**
* 删:删除索引位置上的值,并返回被删除的值
* 思想:找被删除节点的前驱,更改节点的指向
*/
@Override
public E removeByIndex(int index) {
//确定索引的合法性
if(!indexCheck(index)){
throw new IllegalArgumentException("remove index illegal");
}
//如果要删除的节点是头结点
if(index == 0){
Node node = head;
head = node.next;
node.next = null;
size--;
return node.val;
}
//用新的节点代替head不断向后移动
Node prev = head;
//遍历链表到被删除节点的前驱
for (int i = 0; i < index-1; i++) {
prev = prev.next;
}
//此时的prev就是被删除的节点的前驱
Node node = prev.next;
//记录被删除节点的值
E removeVal = node.val;
//更改节点的指向
prev.next = node.next;
node.next = null;
//更改节点数
size--;
return removeVal;
}
/**
* 删除第一个值为element的元素
*/
@Override
public void removeByValue(E element) {
//特殊情况:头结点为Null
if(head == null){
return;
}
//如果删除的节点是头结点
if(head.val.equals(element)){
head = head.next;
size--;
return;
}
//如果删除的节点是在中间正常位置:遍历链表,判断链表中的节点是否与被删除的值相等
Node prev = head;
while (prev.next != null){
if(prev.val.equals(element)){
prev.next = prev.next.next;
size--;
return;
}
prev = prev.next;//不断移动
}
// 链表中没有待删除的节点
System.out.println("当前链表中不存在值为" + element + "的节点");
}
/**
* 删除所有值为element的元素:元素的删除还是比较难的,尤其是删除所有元素,情况比较复杂
* 这里要注意指针的移动和边界条件的出现
*/
@Override
public void removeAllValue(E element) {
//边界条件
if(head == null){
return;
}
//如果要删除的元素值在头结点且出现连续待删除的节点
while (head !=null && head.val.equals(element)){
head = head.next;
size--;
}
//---------经过上面的删除头结点操作,如果此时整个链表已经不存在全部删除了
if(head == null){
return;
}
//正常位置的删除
Node prev = head;
while (prev.next != null){
if(prev.next.val.equals(element)){
//此时的prev就是被删除节点的前驱
prev.next= prev.next.next;
size--;
}else{
//----------只有当后继节点不是被删除的节点的时候才能移动prev引用
prev=prev.next;
}
}
}
/**
* 在索引为Index的位置插入元素elemnet
*/
@Override
public E set(int index, E elemnet) {
//用一个节点表示head,不断移动
Node node = head;
//遍历集合,走到索引为Index的位置
for (int i = 0; i < index; i++) {
node = node.next;
}
//此时node节点就是要被设置新的值的节点
//用一个变量接收旧值
E oldVal = node.val;
//设置新的值
node.val = elemnet;
//返回旧值
return oldVal;
}
/**
* 方法:索引的合法性判断
*/
private boolean indexCheck(int index) {
if(index<0 || index>size){
return false;
}else{
return true;
}
}
/**
* 获取索引为index位置下的元素
*/
@Override
public E get(int index) {
//用一个节点表示head,不断移动
Node node = head;
//遍历集合,走到索引为Index的位置
for (int i = 0; i < index; i++) {
node = node.next;
}
//此时node节点就是要被获取值的节点
E getVal = node.val;
return getVal;
}
/**
*判断是否包含该元素
*/
@Override
public boolean contains(E element) {
Node node = head;
while (node.next != null){
if(node.val.equals(element)){
return true;
}
node = node.next;
}
return false;
}
/**
* 返回元素的索引
*/
@Override
public int indexOf(E element) {
//遍历链表,判断链表中的每个节点的元素值是否与传入的element值相等
Node node = head;//用node代替head
for (int i = 0; i < size; i++) {
if(node.val.equals(element)){
return i;
}
node = node.next;//不断移动到下一个节点
}
throw new IllegalArgumentException("没有找到该元素");
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
/**
* 方法1:while循环方式
*/
//链表中的最后一个元素的next地址为空
//理论上:head是头结点,但是不能一直用head.next指向下一个节点,这样遍历完成,head就到了最后的节点,节点就没了
//因此要用一个临时变量存储head
Node temp = head;
while (temp!=null){//说明该temp还没有遍历完节点,当前的节点还有值
sb.append(temp.val);//将当前节点的值val添加到字符串容器中
sb.append("——>");
if(temp.next==null){
sb.append("null");
}
temp= temp.next;//让temp指向下一个节点
}
/**
* 方法2: for循环方式
*/
// for (Node i = head; i !=null ; i=i.next) {
// sb.append(i.val);//将当前节点的值val添加到字符串容器中
// sb.append("——>");
// if(i.next==null){
// sb.append("null");
// }
// }
return sb.toString();
}
}
(2)SingleLinkedListTest测试类
public class SingleLinkedListTest {
public static void main(String[] args) {
SingleLinkedList<Integer> list = new SingleLinkedList<>();
// 尾插
list.add(3);
list.add(3);
list.add(3);
list.add(4);
System.out.println(list);
// 头插
list.addFirst(8);
System.out.println(list);
//在索引位置添加值为element的节点
list.add(3,9);
System.out.println(list);
//修改索引为4的节点值为0
list.set(4,0);
System.out.println(list);
//获取索引为4的节点的值
System.out.println(list.get(4));
//删除索引为2的值
System.out.println(list.removeByIndex(5));
System.out.println(list);
//删除第一个值为3的节点
list.removeByValue(3);
System.out.println(list);
//删除所有值为3的节点
list.removeAllValue(3);
System.out.println(list);
//判断是否包含某个元素
System.out.println(list.contains(8));
//查询元素的索引值
System.out.println(list.indexOf(0));
System.out.println(list.indexOf(67));
}
}
🌟2、带头单链表
通过上述无头单链表的代码可以发现,单链表的核心操作都在找前驱。但是头结点没有前驱,因此在各项操作之前都在处理头结点的情况。而我们这里说的带头单链表就是:链表的头部节点不存储具体的元素值,只是作为链表的头来使用,所有存储具体值的节点都在这个头结点的后面。
(1)SLinkedWithHead类实现SeqList接口:在这里只实现了部分方法。
public class SLinkedWithHead<E> implements SeqList<E> {
//创建单链表的虚拟头结点,但是不存储具体元素
private Node dummyHead = new Node(null);
private int size;
private class Node{
E val;
Node next;
//构造方法
Node(E val) {
this.val = val;
}
public Node(E val, Node next) {
this.val = val;
this.next = next;
}
}
/**
* 头插
*/
public void addFirst(E element) {
add(0,element);
}
/**
* 尾插
*/
@Override
public void add(E element) {
add(size,element);
}
/**
* 正常位置的插入
*/
@Override
public void add(int index, E element) {
//判断索引的合法性
if(!indexCheck(index)){
throw new IllegalArgumentException("add index illegal!");
}
//取临时的值代替虚拟头结点
Node prev = dummyHead;
for (int i = 0; i < index; i++) {
prev = prev.next;
}
//此时的Prev就是索引位置的前驱
//产生新的值为element的节点
Node node = new Node(element);
//更改指向
node.next = prev.next;
prev.next = node;
//更新节点数
size++;
}
/**
* 检查索引的合法性
*/
private boolean indexCheck(int index) {
if(index < 0 || index > size){
return false;
}
return true;
}
@Override
public void removeByValue(E element) {
}
/**
* 删除所有值为element的元素
*/
@Override
public void removeAllValue(E element) {
//用临时节点代替head进行操作
Node prev = dummyHead;
while (prev.next !=null){
if(prev.next.val.equals(element)){
prev.next = prev.next.next;
size--;
}else{
prev=prev.next;//这里的操作同 单链表一样,只有下一个节点的值和element不相等的时候,才移动指针。否则会造成元素的漏删
}
}
}
/**
* 重写toString方法
*/
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
for (Node x = dummyHead.next; x!=null ; x=x.next) {
sb.append(x.val).append("——>");
if(x.next == null){
sb.append("Null");
}
}
return sb.toString();
}
}
(2)SLinkedWithHeadTest测试类
public class SLinkedWithHeadTest {
public static void main(String[] args) {
SLinkedWithHead<Integer> list = new SLinkedWithHead<>();
//尾插
list.add(3);
list.add(3);
list.add(4);
list.add(3);
list.add(6);
list.add(3);
//头插
list.addFirst(0);
//在中间位置插入元素
list.add(1,11);
list.add(2,22);
System.out.println(list);
//删除所有值为3的节点
list.removeAllValue(3);
System.out.println(list);
}
}
🌟3、双链表
(1)DoubleLinkedList类实现SelList接口
public class DoubleLinkedList<E> implements SeqList<E> {
private int size;//节点个数
private DoubleNode head;//头节点
private DoubleNode tail;//尾结点
public class DoubleNode{
DoubleNode prev;//前驱
DoubleNode next;//后继
E val;//值
public DoubleNode(E val) {
this.val = val;
}
public DoubleNode(DoubleNode prev, DoubleNode next, E val) {
this.prev = prev;
this.next = next;
this.val = val;
}
}
/**
* 增:尾插
* 思想:判断节点是否存在:
* 如果头结点为null,则直接返回null;
* 如果有节点存在:产生新节点——>新节点的prev指向尾结点的tail,将尾结点的next指向新节点——>更新tail的位置——>size++
*/
@Override
public void add(E element) {
//产生新节点
DoubleNode node = new DoubleNode(element);
//边界条件:如果头结点为空,没有节点,则该插入的节点就是新的头结点
if(head == null){
head = node;
}else{//如果存在头结点,则正常尾插
//更改指向
node.prev = tail;
tail.next = node;
}
//更新尾结点和节点数
tail = node;
size++;
}
/** 头插:特殊情况:当不存在节点时,此时的头插就是将新产生的节点作为尾结点
*/
public void addFirst(E element){
DoubleNode node = new DoubleNode(element);
//当原链表中不存在节点时:-------这里理解一下if的边界条件
if(tail == null){
tail = node;
}else{//原链表中有节点存在时
node.next = head;
head.prev = node;
}
head = node;
size++;
}
/**
* 增:根据索引插入元素
*/
@Override
public void add(int index, E element) {
//索引的合法性判断
if(!indexCheck(index)){
throw new IllegalArgumentException("add index illegal");
}
//边界条件的判断:如果是在索引为0的位置进行元素的插入,那就直接调用头插方法
if(index == 0){
addFirst(element);
return;
}//如果是在索引为size的位置插入元素,则直接调用尾插方法:size表示当前列表中所有节点的个数
if(index == size){
add(element);
return;
}
DoubleNode prev = NodeLocation(index-1);
DoubleNode next = prev.next;
DoubleNode node = new DoubleNode(element);
//更改节点指向:左半部分(这里两部分的顺序不影响结果)
node.prev = prev;
prev.next = node;
//右半部分
next.prev = node;
node.next = next;
//更新节点数
size++;
}
/**
* 内部使用的工具方法:根据索引判判断与中间位置的前后关系,决定到底是从前向后寻找节点还是从后向前寻找节点
*/
private DoubleNode NodeLocation(int index){
DoubleNode result = null;
if(index < (size>>1)){
result = head;
for (int i = 0; i < index; i++) {
result = result.next;
}
}else{
result = tail;
for (int i = size-1; i > index ; i--) {
result = result.prev;
}
}
return result;
}
/**
* 删:通过索引删除元素
* 思想:每个被删除的节点都有一个前节点和后节点,此时我们先找到被删除的节点,再找到被删除节点的前后节点。
* 然后更改节点的指向。
* 要注意的是:前后节点有两种情况,也就是存在和不存在的情况,排列组合也就是4种情况:
* (1)前节点不存在,后节点存在:也就是头删——>要更新头结点的位置
* (2)前节点存在,后节点不存在,也就是尾删——>要更新尾结点的位置
* (3)前后节点都存在,在中间位置的删除
* (4)前后节点都不存在,说明是只存在一个节点或者是没有节点存在
*/
@Override
public E removeByIndex(int index) {
//判断索引的合法性
if(!indexCheck(index)){
throw new IllegalArgumentException("remove index illegal");
}
//通过NodeLocation函数,传入索引值找到被删除的节点node
DoubleNode node = NodeLocation(index);
//删除节点node
removeNodeFun(node);
return node.val;//返回被删除节点的值
}
//方法:findRelativeNode的作用是先找到删除节点的前后节点,然后排列组合4种情况删除节点
private void removeNodeFun(DoubleNode node) {
DoubleNode prev = node.prev;//前节点
DoubleNode next = node.next;//后节点
//先处理被删除节点的左半部分
if(node.prev == null){//前节点不存在
head = next;//更新头结点
}else{//前节点存在
node.prev = null;
prev.next = next;
}
//再处理被删除节点的右半部分
if(node.next == null){//后节点不存在
tail = prev;//更新尾结点
}else{//后节点存在
node.next = null;
next.prev = prev;
}
//更新节点数
size--;
}
/**
* 删:删除链表中出现的第一个元素
* 从头结点开始不断遍历链表,找到被删除的节点删除并结束方法
*/
@Override
public void removeByValue(E element) {
//让node接收head的值
DoubleNode node = head;
//遍历整个链表,走到与被删除的节点值相等的位置
for (int i = 0; i < size; i++) {
if(node.val.equals(element)){
//使用removeNodeFun函数删除node节点
removeNodeFun(node);
return;
}
node = node.next;//不断移动node的位置
}
}
/**
* 删除出现的所有为element的元素:刚开始自己写的时候直接去掉上面的return,觉得就可以了,但是报错空指针异常
* 后来发现是因为遍历的长度为size,也就是链表的长度,但是每一次找到要删除的元素值后,链表中的节点个数就会发生变化
* 因此这里不能用size,应该用一个临时变量 来接收size的值作为固定长度
*/
@Override
public void removeAllValue(E element) {
//让node接收head的值
DoubleNode node = head;
//取链表的长度
int length = size;
//遍历整个链表,走到与被删除的节点值相等的位置
for (int i = 0; i < length; i++) {
//这里要注意:之前自己写的时候,DoubleNode next = node.next;和 node = next;写为一句凡在for循环里的最后,但是会报空指针异常
//个人理解:因为出现多个要被删除的值,这个时候要先获取每个节点的下一个节点进行接收,否则如果这个节点被删除了,下一个节点就找不到了,指向的是null
DoubleNode next = node.next;
if(node.val.equals(element)){
//使用removeNodeFun函数删除node节点
removeNodeFun(node);
}
node = next;//不断移动node的位置
}
}
@Override
public E set(int index, E element) {
//判断索引的合法性
if(!indexCheck(index)){
throw new IllegalArgumentException("set index illegal");
}
//从头结点开始从前向后走到index对应的元素:但是注意不能直接使用head,head不断后移会导致链表发生变化最后丢失。所以这里用一个新的节点x存储head的位置
DoubleNode x = head;
for (int i = 0; i < index; i++) {
x = x.next;
}
//此时x就走到了待修改位置,也就是索引位置
E oldVal = x.val;//获取原链表的旧值
x.val = element;//设置新值
return oldVal;//返回旧值
}
/**
* 查:获取Index的元素
*/
@Override
public E get(int index) {
//判断索引的合法性
if(!indexCheck(index)){
throw new IllegalArgumentException("get index illegal");
}
//直接返回索引位置下的元素值
return NodeLocation(index).val;
}
private boolean indexCheck(int index) {
if(index<0 || index>size){
return false;
}else{
return true;
}
}
/**
* contains:判断该元素是否被包含在链表中,返回boolean类型的值
*/
@Override
public boolean contains(E element) {
//遍历链表,判断链表中的每个节点的元素值是否与传入的element值相等
DoubleNode node = head;//用node代替head
for (int i = 0; i < size; i++) {
if(node.val.equals(element)){
return true;
}
node = node.next;//不断移动到下一个节点
}
return false;
}
/**
* indexOf:返回查找元素element的索引值
* 如果是多个重复元素,返回第一个查找到元素值的索引
*/
@Override
public int indexOf(E element) {
//遍历链表,判断链表中的每个节点的元素值是否与传入的element值相等
DoubleNode node = head;//用node代替head
for (int i = 0; i < size; i++) {
if(node.val.equals(element)){
return i;
}
node = node.next;//不断移动到下一个节点
}
throw new IllegalArgumentException("没有找到该元素");
}
/**
* 重写toString方法
*/
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
for (DoubleNode x = head; x != null ; x=x.next) {
sb.append(x.val).append("——>");
if(x == tail){
sb.append("Null\n");
}
}
return sb.toString();
}
}
(2)DoubleLinkedListTest
public class DoubleLinkedListTest {
public static void main(String[] args) {
DoubleLinkedList<Integer> list = new DoubleLinkedList<>();
//尾插
list.add(1);
list.add(3);
list.add(3);
list.add(3);
list.add(3);
list.add(6);
list.add(7);
// System.out.println(list);
//头插
// list.addFirst(9);
// System.out.println(list);
//在索引位置插入元素
// list.add(6,9);
// System.out.println(list);
//查询index=3位置下的元素值
// System.out.println(list.get(4));
//改
// System.out.println(list.set(0,9));
//按索引删除元素
// System.out.println(list.removeByIndex(6));
// System.out.println(list);
//删除出现的第一个元素值为3的节点
// list.removeByValue(3);
// System.out.println(list);
//删除所有出现的元素值为3的节点
// list.removeAllValue(3);
// System.out.println(list);
//判断元素6在是否存在于链表中
// System.out.println(list.contains(6));
//返回元素为6的节点所在的索引值
// System.out.println(list.indexOf(3));
}
}