目录
前言:
1.链表的概念以及分类
1.1链表的概念
1.2分类
1.2.1单向和双向
1.2.2循环和非循环
1.2.3带头和不带头
2.无头单链表的模拟实现
3.双向链表的模拟实现
4.LinkedList的简单介绍
5.LinkedList的遍历
5.1直接打印
5.2for-each遍历
5.3迭代器遍历
6.LinkedList与ArrayList的区别
结束语:
前言:
我们之前学习了线性结构中的顺序存储,那么在之前也与大家一起实现了ArrayList的基本的方法,我们会发现这种结构的优点是当我们在给定下标查询的时候速度会非常的快,时间复杂度可以达到O(1),但是当我们在插入元素、删除元素和扩容的时候你会发现时间会很慢,我们必须通过挪动元素才可以达到我们想要的效果,在扩容的时候也会非常的浪费空间,所以这种顺序表适合随机查找某个元素,那么为了解决我们上述的一些问题,接下来我们就需要学习另一种线性结构——链式结构。
1.链表的概念以及分类
1.1链表的概念
链表是一种物理结构上非连续存储结构,数据元素以链式存储的方式存储在链表中,是一种物理上非连续,逻辑上通过链表中的引用链接次序实现的。
一个结点存在两个域:数据域和地址域。
数据域用来存储当前结点的数据,地址域用来存储下一个结点的地址,这样我们就可以实现逻辑上连续了。
1.2分类
链表的分类一共有八种:带头和不带头、循环和非循环、单向和双向,那么接下来我们来分别看一下都是怎么构成的。
1.2.1单向和双向
单向:
双向:
1.2.2循环和非循环
非循环:
循环:
1.2.3带头和不带头
带头:
不带头:
上面的这四类八种组合中接下来我们主要学习就是单向不带头非循环和双向不带头非循环。
2.无头单链表的模拟实现
主要核心代码如下所示:
public class MySingleList {
class Node{
public int val;//存储数据
public Node next;//存储下一个结点的地址
public Node(int val) {
this.val = val;
}
}
public Node head = null;//头结点
//接下来我们直接手动创建一个链表
public void createList() {
Node node1 = new Node(1);
Node node2 = new Node(2);
Node node3 = new Node(3);
Node node4 = new Node(4);
//关联上面的四个结点
node1.next = node2;
node2.next = node3;
node3.next = node4;
//让头结点引用结点
head = node1;
}
/**
下面我们来实现一些方法
*/
//遍历链表
public void display() {
Node cur = head;
while (cur != null) {
System.out.print(cur.val + " ");
cur = cur.next;
}
}
//查找是否包含关键字key在链表中
public boolean contains(int key) {
Node cur = head;
while (cur != null) {
if (cur.val == key) {
return true;
}
cur = cur.next;
}
return false;
}
//得链表的长度
public int size() {
int count = 0;
Node cur = head;
while (cur != null) {
count++;
cur = cur.next;
}
return count;
}
//链表的插入只是修改指向
//头插法
//时间复杂度O(1)
public void addFirst(int data){
//先得到一个结点
Node node = new Node(data);
//先绑定后面
node.next = head;
head = node;
}
//尾插法
//时间复杂度O(N)
public void addLast(int data) {
//先得到一个结点
Node node = new Node(data);
if (head == null) {
head = node;
return;
}
//找到最后一个结点
Node cur = head;
while (cur.next != null) {
cur = cur.next;
}
//绑定
cur.next = node;
}
//任意位置插入
public void addIndex(int index, int data) throws IndexOutOfBoundsException{
Node node = new Node(data);
//查下标的合法性
checkIndex(index);
if (index == 0) {
addFirst(data);
return;
}
if (index == size()) {
addLast(data);
return;
}
//先要找到index - 1的位置
Node cur = findIndexSubOne(index);
//先绑定后面的
node.next = cur.next;
cur.next = node;
}
//找到index - 1位置结点的位置
private Node findIndexSubOne(int index) throws IndexOutOfBoundsException{
Node cur = head;
int count = 0;
while (count != index - 1) {
cur = cur.next;
count++;
}
return cur;
}
//检查下标的合法性
private void checkIndex(int index) throws IndexOutOfBoundsException{
if (index < 0 || index > size()) {
throw new IndexOutOfException("该下标不合法");
}
}
//删除第一次出现关键字为key的结点
//时间复杂度O(N)
public void remove(int key) {
if (head.val == key) {
head = head.next;
return;
}
//先找到前key个结点
Node cur = searchPrev(key);
if (cur == null) {
return;
}
cur.next = cur.next.next;
}
//找到key的前一个结点
private Node searchPrev(int key) {
if (head == null) {
return null;
}
Node cur = head;
while (cur.next != null) {
if (cur.next.val == key){
return cur;
}
cur = cur.next;
}
return null;
}
//删除所有key的结点
public void removeAll(int key) {
if (head == null) {
return;
}
Node prev = head;
Node cur = head.next;
while (cur != null) {
if (cur.val == key) {
prev.next = cur.next;
cur = cur.next;
}else {
cur = cur.next;
prev = prev.next;
}
}
//处理头部是关键字的情况
if (head.val == key) {
head = head.next;
}
}
//清除
public void clear() {
head = null;
}
}
异常代码如下所示:
public class IndexOutOfException extends RuntimeException{
public IndexOutOfException() {
}
public IndexOutOfException(String mag) {
super(mag);
}
}
测试代码如下所示:
public class Test {
public static void main(String[] args) {
MySingleList mySingleList = new MySingleList();
mySingleList.createList();
mySingleList.display();
System.out.println();
System.out.println("=========1.查找是否包含关键字测试============");
System.out.println(mySingleList.contains(2));
System.out.println("=========2.长度测试============");
System.out.println(mySingleList.size());
System.out.println("=========3.头插发测试============");
mySingleList.addFirst(88);
mySingleList.display();
System.out.println();
System.out.println("=========4.尾插发测试============");
mySingleList.addLast(88);
mySingleList.display();
System.out.println();
System.out.println("=========5.任意位置插入测试============");
try {
mySingleList.addIndex(0,1);
}catch (IndexOutOfBoundsException e) {
e.printStackTrace();
}
mySingleList.display();
System.out.println();
System.out.println("=========6.删除第一次出现的关键字测试============");
mySingleList.remove(1);
mySingleList.display();
System.out.println();
System.out.println("=========7.删除出现的所有关键字测试============");
mySingleList.removeAll(88);
mySingleList.display();
System.out.println();
System.out.println("=========8.清除测试============");
mySingleList.clear();
mySingleList.display();
}
}
结果如下所示:
3.双向链表的模拟实现
核心代码:
public class MyLinkedList {
static class ListNode{
public int val;
public ListNode prev;//前驱
public ListNode next;//后继
public ListNode(int val) {
this.val = val;
}
}
public ListNode head;
public ListNode last;
//打印
public void display() {
ListNode cur = head;
while (cur != null) {
System.out.print(cur.val + " ");
cur = cur.next;
}
}
//求长度
public int size() {
ListNode cur = head;
int count = 0;
while (cur != null) {
cur = cur.next;
count++;
}
return count;
}
//判断是否包含
public boolean contains(int key) {
ListNode cur = head;
if (head.val == key) {
return true;
}
while (cur != null) {
if (cur.val == key) {
return true;
}
cur = cur.next;
}
return false;
}
//头插
//时间复杂度O(1)
public void addFirst(int data) {
ListNode node = new ListNode(data);
if (head == null) {
head = node;
last = node;
}else {
node.next = head;//先绑定前面的
head.prev = node;//修改刚刚head的前驱
head = node;//修改头部
}
}
//尾插法
//时间复杂度O(1)
public void addLast(int data) {
ListNode node = new ListNode(data);
if (head == null) {
head = node.next;
last = node.next;
}else {
last.next = node;
node.prev = last;
last = node;
}
}
//任意位置插入
public void addIndex(int index,int data) {
//处理合法性
checkIndex(index);
if (index == 0) {
addFirst(data);
return;
}
if (index == size()) {
addLast(data);
return;
}
ListNode node = new ListNode(data);
//找到index - 1的位置
ListNode cur = findIndex(index);
//注意下面修改地址的顺序
node.next = cur;
cur.prev.next = node;
node.prev = cur.prev;
cur.prev = node;
}
private void checkIndex(int index) {
if (index < 0 || index > size()) {
throw new IndexOutOfException("下标不合法!");
}
}
//找到index的位置
private ListNode findIndex(int index) {
ListNode cur = head;
while (index != 0) {
cur = cur.next;
index--;
}
return cur;
}
//删除
public void remove(int key) {
ListNode cur = head;
while (cur != null) {
if (cur.val == key) {
//删除的是头结点
if (cur == head) {
head = head.next;
//只有一个结点的时候
if (head != null) {
head.prev = null;
}
}else {
//中间或者是尾部
cur.prev.next = cur.next;
if (cur.next != null) {
//不是尾巴结点
cur.next.prev = cur.prev;
}else {
//是尾部结点
last = last.prev;
}
}
return;
}
cur = cur.next;
}
}
//删除所有
public void removeAll(int key) {
ListNode cur = head;
while (cur != null) {
if (cur.val == key) {
//删除的是头结点
if (cur == head) {
head = head.next;
//只有一个结点的时候
if (head != null) {
head.prev = null;
}
}else {
//中间或者是尾部
cur.prev.next = cur.next;
if (cur.next != null) {
//不是尾巴结点
cur.next.prev = cur.prev;
}else {
//是尾部结点
last = last.prev;
}
}
}
cur = cur.next;
}
}
//清空
public void clear() {
//遍历双向链表的每一个结点,把里面的后继和前驱都置为空
ListNode cur = head;
while (cur != null) {
ListNode curNext = cur.next;
cur.prev = null;
cur.next = null;
cur = curNext;
}
head = null;
last = null;
}
}
测试代码:
public class Test {
public static void main(String[] args) {
MyLinkedList myLinkedList = new MyLinkedList();
System.out.println("===========1.头插法测试==========");
myLinkedList.addFirst(1);
myLinkedList.addFirst(1);
myLinkedList.addFirst(1);
myLinkedList.addFirst(1);
myLinkedList.addFirst(5);
myLinkedList.display();
System.out.println();
System.out.println("===========2.尾插法测试==========");
myLinkedList.addLast(11);
myLinkedList.addLast(12);
myLinkedList.addLast(13);
myLinkedList.addLast(14);
myLinkedList.addLast(15);
myLinkedList.display();
System.out.println();
System.out.println("===========3.任意位置插入测试==========");
try {
myLinkedList.addIndex(2,99);
}catch (IndexOutOfException e){
e.printStackTrace();
}
myLinkedList.display();
System.out.println();
System.out.println("===========4.删除测试==========");
myLinkedList.remove(99);
myLinkedList.display();
System.out.println();
System.out.println("===========4.删除所有测试==========");
myLinkedList.removeAll(1);
myLinkedList.display();
System.out.println();
System.out.println("===========5.清除测试==========");
myLinkedList.clear();
myLinkedList.display();
}
}
异常处理代码:
public class IndexOutOfException extends RuntimeException{
public IndexOutOfException() {
}
public IndexOutOfException(String mag) {
super(mag);
}
}
结果截图:
4.LinkedList的简单介绍
LinkedList是一个双向链表,也就是在一个结点中包含三个域(数值域、前驱、后继)如下所示:
使用如下所示:
import java.util.LinkedList;
public class Test6 {
public static void main(String[] args) {
LinkedList<Integer> linkedList = new LinkedList<>();
linkedList.add(1);
linkedList.add(2);
linkedList.add(3);
linkedList.add(4);
System.out.println(linkedList);
}
}
结果如下所示:
5.LinkedList的遍历
5.1直接打印
代码如下所示:
import java.util.LinkedList;
public class Test2 {
public static void main(String[] args) {
LinkedList<String> linkedList = new LinkedList<>();
linkedList.add("hello");
linkedList.add("world");
linkedList.add("!!!");
System.out.println(linkedList);
}
}
结果截图如下所示:
5.2for-each遍历
代码如下所示:
import java.util.LinkedList;
public class Test3 {
public static void main(String[] args) {
LinkedList<String> linkedList = new LinkedList<>();
linkedList.add("hello");
linkedList.add("world");
linkedList.add("!!!");
for (String x:linkedList) {
System.out.print(x + " ");
}
}
}
结果截图如下所示:
5.3迭代器遍历
正向打印:
代码如下所示:
import java.util.LinkedList;
import java.util.ListIterator;
public class Test4 {
public static void main(String[] args) {
LinkedList<String> linkedList = new LinkedList<>();
linkedList.add("hello");
linkedList.add("world");
linkedList.add("!!!");
//正向打印
ListIterator<String> listIterator = linkedList.listIterator();
while (listIterator.hasNext()) {
System.out.print(listIterator.next() + " ");
}
}
}
结果截图如下所示:
反向打印:
代码如下所示:
import java.util.LinkedList;
import java.util.ListIterator;
public class Test5 {
public static void main(String[] args) {
LinkedList<String> linkedList = new LinkedList<>();
linkedList.add("hello");
linkedList.add("world");
linkedList.add("!!!");
//反向遍历
ListIterator<String> listIterator = linkedList.listIterator(linkedList.size());
while (listIterator.hasPrevious()) {
System.out.print(listIterator.previous() +" ");
}
}
}
结果截图如下所示:
6.LinkedList与ArrayList的区别
顺序表在物理上和逻辑上都是连续的,但是链表只是在逻辑上是连续的,在物地址上的不连续的。
随机访问上ArrayList比LinkedList要快很多,ArrayList随机访问的时间复杂度是O(1),LinkedList随机访问的时间复杂度是O(n)。
在插入的时候ArrayList中会有扩容的概念,而LinkedList中是没有这个概念的。
应用场景:ArrayList中适合高效存储和数据的频繁访问,LinkedList适合任意位置的插入和频繁的删除。
结束语:
好了这节小编带着大家简单的认识了什么是链表以及自己模拟实现了一个链表,那么下一次小编将会和大家分享一些链的练习题巩固一下我们这节的知识点,大家记得查看哦!大家继续跟紧小编的步伐一起往下走吧!!!希望对大家有所帮助,想要学习的同学记得关注小编和小编一起学习吧!如果文章中有任何错误也欢迎各位大佬及时为小编指点迷津(在此小编先谢过各位大佬啦!)