目录
复习回顾(预先了解):List(线性表)的介绍及自我实现
一.定义
1.概念
2.结点
(1).元素(val)
(2)线索(next)
(3).头结点(head)
3.关系
二.LinkedList的使用
1.LinkedList的构造
2.LinkedList的其他常用方法的介绍
(1).链表的遍历
(2).统计链表中的元素个数
(3).查找链表中某个元素所在的结点
(4).查找链表中所有和某元素相等的结点
(5).找到链表中第n个结点
补充:找到链表中的第n个结点但不保证链表长度
(6).头插法
a.头插元素
b.头插结点
7.尾插法
a.尾插元素
b.尾插结点
8.删除
a.头删法
b.尾删法
四.自定义实现List和LinkedList
1.合法的链表需要满足什么条件
2.MyList 接口的实现
3.MyNode类的实现
4.MyLinkedList类的实现
4. MyLinkedList的检查
链表是一种物理存储结构上非连续存储结构,数据元素的逻辑顺序是通过链表中的引用链接次序实现的。
若想了解更多,请查阅:LinkedList的官方文档
结点(Node):装元素的一种结构,至少包括元素本身和指向下一个结点的线索。
public class Node {
public int val; //元素
public Node next; //引用
//当next=null时表示不存在下一个结点,即当前结点就是链表的最后一个结点
}
类型不定,逻辑上有序的元素序列。
引用(结点类型的引用)。
一般,我们用头结点表示一条链表。
Node head=null; //头结点
当头结点head=null时,表示头结点不存在,链表中一个结点都没有,一个元素也没有,即表示一条空(empty)链表。
- 元素和结点:一一对应。
- 链表和结点:一条链表是由多个结点(0个也行)组成的 。
方法 | 解释 |
LinkedList() | 无参构造 |
public LinkedList(Collection extends E> c) | 使用其他集合容器中元素构造List |
方法 | 解释 |
boolean add(E e) | 尾插 e |
void add(int index, E element) | 将 e 插入到 index 位置 |
boolean addAll(Collection extends E> c) | 尾插 c 中的元素 |
E remove(int index) | 删除 index 位置元素 |
boolean remove(Object o) | 删除遇到的第一个 o |
E get(int index) | 获取下标 index 位置元素 |
E set(int index, E element) | 将下标 index 位置元素设置为 element |
void clear() | 清空 |
boolean contains(Object o) | 判断 o 是否在线性表中 |
int indexOf(Object o) | 返回第一个 o 所在下标 |
int lastIndexOf(Object o) | 返回最后一个 o 的下标 |
List |
截取部分 list |
PS:以下操作需要提前创建好链表。
// 链表的遍历操作
public static void loopLinkedList(Node head) {
// 参数 head,是我们手上的唯一线索,如何完成链表的遍历?
// 需要循环完成,拿什么作为遍历时的 内容 进行
Node cur = head; // 让 cur 指向 head 指向的对象(链表的头结点对象)
while (cur != null) {
cur = cur.next;
}
// cur 会经历链表中的每个结点:按照从前往后的顺序,每个结点只会经过一次
}
private static int countLinkedList(Node head){
int count=0;
for (Node cur=head;cur!=null;cur=cur.next){
count++;
}
return count;
}
private static Node findNode(Node head,int target){
Node cur=head;
while(cur!=null){
if(target==cur.val){
return cur;
}
cur=cur.next;
}
return null;
}
private static List findAllNode(Node head,int target){
List ans=new ArrayList<>();
Node cur=head;
while(cur.next!=null){
if(cur.val==target){
ans.add(cur);
}
cur=cur.next;
}
return ans;
}
private static Node findNodeLocation(Node head,int n){
Node cur=head;
for (int i=0;i
private static Node findNodeLocation1(Node head,int n){
Node cur=head;
for (int i=0;i
//返回值类型是Node,返回链表新的头结点
private static Node headAddElement(Node head,int e){
Node node=new Node(e);
node.next=head;
return node;
}
private static Node headAddNode(Node head,Node node){
node.next=head;
return node;
}
//方法返回值Node,原因是尾插也有可能修改头结点(当原来的链表是空链表时)
private static Node lastAddElement(Node head,int e){
Node node=new Node(e);
if(head==null){
node.next=null; //可以省略
return node;
}
Node last=head;
while(last.next!=null){
last=last.next;
}
node.next=null; //可以省略
last.next=node;
return head;
}
private static Node lastAddNode(Node head,Node node){
if(head==null){
node.next=null; //可以省略
return node;
}
Node last=head;
while(last.next!=null){
last=last.next;
}
node.next=null; //可以省略
last.next=node;
return head;
}
private static Node headRemove(Node head){
if(head==null){
throw new RuntimeException("链表是空的(empty),没法删除结点");
}
}
private static Node lastRemove(Node head){
if(head==null){
//空链表,异常
throw new RuntimeException("空链表!");
}
if(head.next==null){
//没有第二个结点,说明只有一个结点
return null;
}
Node lastOfLast=head;
while (lastOfLast.next.next!=null){
lastOfLast=lastOfLast.next;
}
lastOfLast.next=null;
return head;
}
自定义实现线性表(接口MyList)和链表表(类MyLinkedList)。
- 1.合法的线性表 。
- 2.当head!=null则last!=null;反之亦然,当head==null则last==null 。
- 3.当head!=null,size>0 ;同理,当head==null时,size==0 。
- 4.size的值应该==通过遍历数出来结点个数 。
- 5.除了head和last之外,所有其他结点(node),node.prev!=null&&node.next!=null 。
- 6.head!=null时,则head.prev==null,但head.next不确定 ;last!=null时,则last.next==null,但last.prev不确定 。
- 7.除了head和last之外,所有其他结点(node),node.prev.next==node&&node.next.prev==node
- 8.当head.next!=null时,head.next.prev==head ;当last.prev!=null时,last.prev.next==last 。
- 9.size==1,head==last&&head!=null。
后续检查自定义链表是否正确需要根据这些要求进行检测。
/*
1.元素类型 Long
2.线性表
3.无元素的位置 null
*/
public interface MyList {
/**
* 返回线性表中的元素个数
* @return
*/
int size();
/**
* 将 e 尾插到线性表中,一定返回 true
* @param e
* @return
*/
boolean add(Long e);
/**
* 将 e 插入到线性表的 index 位置,从 [index, size()) 向后移
* index 的合法下标 [0, size()]
* 如果下标不合法:抛出一个 ArrayIndexOutOfBoundsException
* @param index
* @param e
*/
void add(int index,Long e);
/**
* 删除 index 位置的元素
* index 的合法下标:[0, size())
* 如果下标不合法:抛出一个 ArrayIndexOutOfBoundsException
* @param index
* @return 从线性表中删除掉的元素
*/
Long remove(int index);
/**
* 从前到后,删除第一个遇到的 e( equals() == true)
* @param e
* @return 删除成功:true,没有该元素:false
*/
boolean remove(Long e);
/**
* 直接返回 index 位置的元素
* index: [0, size())
* @param index
* @return
*/
Long get(int index);
/**
* 使用 e 替换 index 位置的元素
* @param index [0, size())
* @param e
* @return 原来 index 位置的元素
*/
Long set(int index,Long e);
/**
* 返回第一次遇到 e 的下标(equals() == true)
* @param e
* @return 如果没有找到,返回 -1
*/
int indexOf(Long e);
/**
* 从后往前,返回第一次遇到 e 的下标(equals() == true)
* @param e
* @return 如果没有找到,返回 -1
*/
int lastIndexOf(Long e);
/**
* 线性表中是否包含 e(equals)
* @param e
* @return
*/
boolean contains(Long e);
/**
* 清空数组
*/
void clear();
/**
* 判断数组是否为空
* @return
*/
boolean isEmpty();
}
public class MyNode {
public Long val;
MyNode prev;
MyNode next;
public MyNode(Long val){
this.val=val;
this.prev=null;
this.next=null;
}
}
public class MyLinkedList implements MyList{
//维护3个属性
//1.链表的头结点
private MyNode head;
//2.链表的尾结点
private MyNode last;
//3.链表中的元素个数
private int size; //不维护也可以通过遍历数出来,但维护好这个属性,可以让size的时间复杂度O(n)->O(1)
//无参构造,空链表
public MyLinkedList(){
this.head=this.last=null;
this.size=0;
}
@Override
public int size() {
return size;
}
//链表的尾插
//时间复杂度:O(1)
@Override
public boolean add(Long e) {
// 1) 先把元素装到结点中
MyNode node=new MyNode(e);
node.next=null; // 这步可以省略
// 2) 分情况讨论
if(size>0){
// 2.1) 链表中有尾结点:last != null 等价于 head != null 等价于 size > 0
// 3) 找到尾结点 this.last
this.last.next=node;
node.prev=this.last;
this.last=node;
}else{
// 2.2) 链表中没有尾结点
node.prev=null;
this.head=this.last=node;
}
this.size++;
return true;
}
//链表的头插
//时间复杂度:O(1)
private boolean addFirst(Long e){
// 1) 先把元素装到结点中
MyNode node=new MyNode(e);
node.prev=null; // 这步可以省略
if(size>0){
this.head.prev=node;
node.next=this.head;
this.head=node;
}else {
node.next=null;
this.head=this.last=node;
}
this.size++;
return true;
}
//根据下标添加元素
//时间复杂度:O(n)
@Override
public void add(int index, Long e) {
if(index<0||index>size){
throw new ArrayIndexOutOfBoundsException("下标越界");
}
if(size==0){
add(e);
//addFirst(e);
return;
}
if(size==1){
if(index==0){
addFirst(e);
}
else {
add(e);
}
return;
}
if(index==0){
addFirst(e);
return;
}else if(index==size){
add(e);
return;
}
//先找到前驱节点
MyNode prevNode=this.head;
for (int i = 0; i < index-1; i++) {
prevNode=prevNode.next;
}
MyNode curNode=prevNode.next;
MyNode node=new MyNode(e);
prevNode.next=node;
curNode.prev=node;
node.prev=prevNode;
node.next=curNode;
size++;
}
//根据下标删除
//时间复杂度:O(n)
@Override
public Long remove(int index) {
if(index<0||index>=size){
//包含了size==0的情况
throw new ArrayIndexOutOfBoundsException("下标越界");
}
if(size==1){
Long e=this.head.val;
this.head=this.last=null;
this.size=0;
return e;
}
if(index==0){
Long e=this.head.val;
this.head=this.head.next;
this.head.prev=null;
size--;
return e;
}
if(index==size-1){
Long e=this.last.val;
this.last=this.last.prev;
this.last.next=null;
size--;
return e;
}
MyNode curNode=this.head;
for (int i=0;i=1
this.head=this.head.next;
if(this.head!=null){ //size>1
this.head.prev=null;
}else { //size=1
this.last=null;
}
size--;
return true;
}
if(i==size-1){ //size>1
this.last=this.last.prev;
this.last.next=null;
size--;
return true;
}
MyNode prevNode=cur.prev;
MyNode nextNode=cur.next;
prevNode.next=nextNode;
nextNode.prev=prevNode;
size--;
return true;
}
cur=cur.next;
}
return false;
}
//时间复杂度:O(n)
@Override
public Long get(int index) {
if(index<0||index>=size){
throw new ArrayIndexOutOfBoundsException("下标越界");
}
MyNode cur=this.head;
for (int i = 0; i < index; i++) {
cur=cur.next;
}
return cur.val;
}
//时间复杂度:O(n)
@Override
public Long set(int index, Long e) {
if(index<0||index>=size){
throw new ArrayIndexOutOfBoundsException("下标越界");
}
MyNode cur=this.head;
for (int i = 0; i < index; i++) {
cur=cur.next;
}
Long oldValue=cur.val;
cur.val=e;
return oldValue;
}
@Override
public int indexOf(Long e) {
MyNode cur=this.head;
int i=0;
while (cur!=null){
if(cur.val.equals(e)){
return i;
}
i++;
cur=cur.next;
}
return -1;
}
@Override
public int lastIndexOf(Long e) {
int i=size-1;
MyNode cur=this.last;
while (cur!=null){
if(cur.val.equals(e)){
return i;
}
i--;
cur=cur.prev;
}
return -1;
}
@Override
public boolean contains(Long e) {
return indexOf(e)!=-1;
}
@Override
public void clear() {
this.head=this.last=null;
this.size=0;
}
@Override
public boolean isEmpty() {
return this.size==0;
}
}
我们可以运用如下方法对我们自己编写的链表进行检查,这些检查序号对应着合法的链表需要满足什么条件中的要求:
private static void assertTrue(boolean condition,String message){
if(!condition){
throw new RuntimeException(message);
}
}
private static void test2(MyLinkedList list){
if (list.head == null) {
assertTrue(list.last == null, "head 为 null 时,last 必须是 null");
} else {
assertTrue(list.last != null, "head 不为 null 时,last 必须不为 null");
}
}
private static void test3(MyLinkedList list){
assertTrue(list.size >= 0, "size 必须 >= 0");
if (list.head == null) {
assertTrue(list.size == 0, "head 为 null 时,size 必须是 0");
} else {
assertTrue(list.size > 0, "head 不为 null 时,size 必须大于 0");
}
}
private static int countNode(MyLinkedList list){
int size=0;
for (MyNode cur = list.head; cur !=null; cur=cur.next) {
size++;
}
return size;
}
private static void test4(MyLinkedList list){
assertTrue(list.size == countNode(list), "记录的 size 应该和遍历出的 size 相等");
}
private static void test5(MyLinkedList list){
if (list.size > 1) {
MyNode cur = list.head.next;
while (cur != list.last) {
assertTrue(cur.prev != null, "非头尾结点的 prev 不能是 null");
assertTrue(cur.next != null, "非头尾结点的 next 不能是 null");
}
cur=cur.next;
}
}
private static void test6(MyLinkedList list){
if (list.head != null) {
assertTrue(list.head.prev == null, "头结点的 prev 一定是 null");
assertTrue(list.last.next == null, "尾结点的 next 一定是 null");
if (list.size == 1) {
assertTrue(list.head.next == null, "size 为 1 时,头节点的 next 一定是 null");
assertTrue(list.last.prev == null, "size 为 1 时,为节点的 prev 一定是 null");
} else {
assertTrue(list.head.next != null, "size > 1 时,头节点的 next 一定不是 null");
assertTrue(list.last.prev != null, "size > 1 时,尾节点的 prev 一定不是 null");
}
}
}
private static void test7(MyLinkedList list){
if (list.size > 1) {
MyNode cur = list.head.next; // 跳过头节点
while (cur != list.last) { // 跳过尾结点
assertTrue(cur.prev.next == cur, "非头尾结点的 前驱的 后继是自己");
assertTrue(cur.next.prev == cur, "非头尾结点的 后继的 前驱是自己");
}
cur=cur.next;
}
}
private static void test8(MyLinkedList list){
if (list.size > 1) {
assertTrue(list.head.next.prev == list.head, "当 head.next != null 时,head.next.prev == head");
assertTrue(list.last.prev.next == list.last, "last.prev != null 时,last.prev.next == last");
}
}
//断言是一个合法的链表
private static void assertLinkedList(MyLinkedList list){
test2(list);
test3(list);
test4(list);
test5(list);
test6(list);
test7(list);
test8(list);
}
//断言链表的某位置一定是某个值
private static void assertValue(MyLinkedList list,int index,long e){
MyNode cur=list.head;
for (int i = 0; i < index; i++) {
cur=cur.next;
}
assertTrue(cur.val==e,"该位置的值必须是这个值");
}
如有建议或想法,欢迎一起讨论学习~