前言
本文为 数据结构基础【链表】 相关知识,下边将对链表概念
,单链表
,双链表
,循环链表
,Java中链表的使用
等进行详尽介绍~
📌博主主页:´Code_Wang的主页
👉Java全栈学习路线可参考:【Java全栈学习路线】最全的Java学习路线及知识清单,Java自学方向指引,内含最全Java全栈学习技术清单~
👉算法刷题路线可参考:算法刷题路线总结与相关资料分享,内含最详尽的算法刷题路线指南及相关资料分享~
目录
一、链表介绍
- 在内存空间中,数组和链表都是基本的数据结构,都是【表】,或者叫【线性表】。
- 线性表是一个线性结构,它是一个含有n≥0个结点的有限序列,对于其中的结点,有且仅有一个开始结点没有前驱但有一个后继结点,有且仅有一个终端结点没有后继但有一个前驱结点,其它的结点都有且仅有一个前驱和一个后继结点,说人话,就是有头有尾一条线。
还不明白就再来一张图:
- 链表是一种动态数据结构,他的特点是用一组任意的存储单元(可以是连续的,也可以是不连续的)存放 数据元素。
- 链表中每一个元素成为 “结点” , 每一个结点都是由数据域和指针域组成的, 每个结点中的指针域指向下一 个结点。
- Head 是 “头指针” , 表示链表的开始, 用来指向第一个结点, 而最后一个指针的指针域为 NULL( 空地址 ) ,表示链表的结束。
- 可以看出链表结构必须利用指针才能实现,即一个结点中必须包含一个指针变量,用来存放下一个结点的 地址。
- 实际上,链表中的每个结点可以用若干个数据和若干个指针。
二、单链表
单链表特点:
- 长度可变,扩展性好
- 内存利用高(可以不连续)
- 时间性能:查找O(n)、插入和删除O(1)
- 空间性能:不需要分配存储空间,只要有就可以分配,元素个数不受限制
单链表的逻辑结构:
单向链表的节点的抽象:
其节点由两部分构成:
- data域:数据域,用来存储元素数据
- next域:用于指向下一节点
class Node<E> {
E item;
Node<E> next;
//构造函数
Node(E element) {
this.item = element;
this.next = null;
}
}
三、双链表
单向链表的缺点分析:
- 单向链表,查找的方向只能是一个方向,而双向链表可以向前或者向后查找。
- 单向链表不能自我删除,需要靠辅助节点 ,而双向链表,则可以自我删除,所以前面我们单链表删除时节点,总是找到temp,temp是待删除节点的前一个节点
为了能够实现双向查找的和自我删除的功能,引出了双向链表的概念。
双向链表的逻辑结构:
双向链表的节点的抽象:
其节点由三部分构成:
- data域:数据域,用来存储元素数据
- next域:用于指向后一节点
- front域:用于指向前一节点
class Node<E> {
E item;
Node<E> next;
Node<E> prev;
//构造函数
Node(E element) {
this.item = element;
this.next = null;
this.prev = null;
}
}
四、循环链表
链表的最后一个节点指向第一个节点,整体构成一个链环。
将单链表中的终端结点的指针端由空指针改为指向头结点,就使整个单链表形成一个环,这种头尾相接的单链表称为单循环链表,简称 循环链表(circular linked list),循环链表不一定需要头结点。
五、封装一个超级链表
写链表首先要封装一个保存数据和引用的节点,俗称node
public class Node {
private Integer data;
private Node next;
public Integer getData() {
return data;
}
public void setData(Integer data) {
this.data = data;
}
public Node getNext() {
return next;
}
public void setNext(Node next) {
this.next = next;
}
}
封装一个超级链表
public class SuperLinked {
// 链表的长度
private int size;
// 维护一个头节点
private Node first;
// 维护一个尾节点
private Node last;
// 无参构造器
public SuperLinked(){
}
//添加元素至链表尾部
public boolean add(Integer data){
Node node = new Node(data,null);
if (first == null){
first = node;
} else {
last.setNext(node);
}
last = node;
size++;
return true;
}
//在指定下标添加元素
public boolean add(int index,Integer data){
Node node = getNode(index);
Node newNode = new Node(data,null);
if (node != null){
newNode.setNext(node.getNext());
node.setNext(newNode);
} else {
first = newNode;
last = newNode;
}
size++;
return true;
}
// 删除头元素
public boolean remove(){
if (size < 0){
return false;
}
if (first != null ){
first = first.getNext();
size--;
}
return true;
}
// 删除指定元素
public boolean remove(int index){
if (size < 0){
return false;
}
if(size == 1){
first = null;
last = null;
} else {
Node node = getNode(index-1);
node.setNext(node.getNext().getNext());
}
size--;
return true;
}
// 修改指定下标的元素
public boolean set(int index,Integer data){
// 找到第index个
Node node = getNode(index);
node.setData(data);
return true;
}
// 获取指定下标的元素
public Integer get(int index){
return getNode(index).getData();
}
//查看当前有多少个数字
public int size(){
return size;
}
//添加元素
private Node getNode(int index){
// 边界判断
if(index <= 0){
index = 0;
}
if(index >= size-1){
index = size-1;
}
// 找到第index个
Node cursor = first;
for (int i = 0; i < index; i++) {
cursor = cursor.getNext();
}
return cursor;
}
public static void main(String[] args) {
SuperLinked linked = new SuperLinked();
linked.add(1);
linked.add(2);
linked.add(4);
linked.add(6);
linked.add(3);
linked.add(2);
linked.add(7);
linked.add(6);
linked.remove();
linked.remove(2);
linked.set(0,3);
for (int i = 0; i < linked.size(); i++) {
System.out.println(linked.get(i));
}
}
}
六、Java中链表的使用
- ArrayList和LinkedList都实现了链表,两者的区别在于与 ArrayList 相比,LinkedList的增加和删除的操作效率更高,而查找和修改的操作效率较低。
- 链表用LinkedList较多。
1️⃣Java中LinkedList的操作
LinkedList 类位于 java.util 包中,使用前需要引入它,语法格式如下:
// 引入 LinkedList 类
import java.util.LinkedList;
LinkedList<E> list = new LinkedList<E>(); // 普通创建方法
// 或者
LinkedList<E> list = new LinkedList(Collection<? extends E> c); // 使用集合创建链表
创建一个简单的链表实例:
import java.util.LinkedList;
public class RunoobTest {
public static void main(String[] args) {
LinkedList<String> sites = new LinkedList<String>();
sites.add("Google");
sites.add("Runoob");
sites.add("Taobao");
sites.add("Weibo");
System.out.println(sites);
}
}
以上链表实例的执行输出结果为:
// 打印结果:
[Google, Runoob, Taobao, Weibo]
LinkedList的其他操作:
方法 | 描述 |
---|---|
addFirst() | 在表头增加一个元素 |
addLast() | 在表尾增加一个元素 |
removeFirst() | 从表头移除一个元素 |
removeLast() | 从表尾移除一个元素 |
getFirst() | 获得表头的元素 |
getLast() | 获得表尾的元素 |
2️⃣Java实现链表
java中ListNode链表就是用Java自定义实现的链表结构。
基本结构:
class ListNode { //类名 :Java类就是一种自定义的数据结构
int val; //数据 :节点数据
ListNode next; //对象 :引用下一个节点对象。在Java中没有指针的概念,Java中的引用和C语言的指针类似
}
添加构造方法方便初始化:
class ListNode { //类名 :Java类就是一种自定义的数据结构
int val; //数据 :节点数据
ListNode next; //对象 :引用下一个节点对象。在Java中没有指针的概念,Java中的引用和C语言的指针类似
ListNode(int val){ //构造方法 :构造方法和类名相同
this.val=val; //把接收的参数赋值给当前类的val变量
}
}
范型写法,使用范型可以兼容不同的数据类型:
class ListNode<E>{ //类名 :Java类就是一种自定义的数据结构
E val; //数据 :节点数据
ListNode<E> next; //对象 :引用下一个节点对象。在Java中没有指针的概念,Java中的引用和C语言的指针类似
ListNode(E val){ //构造方法 :构造方法和类名相同
this.val=val; //把接收的参数赋值给当前类的val变量
}
}
创建链表及遍历链表:
class ListNode { //类名 :Java类就是一种自定义的数据结构
int val; //数据 :节点数据
ListNode next; //对象 :引用下一个节点对象。在Java中没有指针的概念,Java中的引用和C语言的指针类似
ListNode(int val){ //构造方法 :构造方法和类名相同
this.val=val; //把接收的参数赋值给当前类的val变量
}
}
class Test{
public static void main(String[] args){
ListNode nodeSta = new ListNode(0); //创建首节点
ListNode nextNode; //声明一个变量用来在移动过程中指向当前节点
nextNode=nodeSta; //指向首节点
//创建链表
for(int i=1;i<10;i++){
ListNode node = new ListNode(i); //生成新的节点
nextNode.next=node; //把心节点连起来
nextNode=nextNode.next; //当前节点往后移动
} //当for循环完成之后 nextNode指向最后一个节点,
nextNode=nodeSta; //重新赋值让它指向首节点
print(nextNode); //打印输出
}
//打印输出方法
static void print(ListNode listNoed){
//创建链表节点
while(listNoed!=null){
System.out.println("节点:"+listNoed.val);
listNoed=listNoed.next;
}
System.out.println();
}
}
插入节点:
class ListNode { //类名 :Java类就是一种自定义的数据结构
int val; //数据 :节点数据
ListNode next; //对象 :引用下一个节点对象。在Java中没有指针的概念,Java中的引用和C语言的指针类似
ListNode(int val){ //构造方法 :构造方法和类名相同
this.val=val; //把接收的参数赋值给当前类的val变量
}
}
class Test{
public static void main(String[] args){
ListNode nodeSta = new ListNode(0); //创建首节点
ListNode nextNode; //声明一个变量用来在移动过程中指向当前节点
nextNode=nodeSta; //指向首节点
//创建链表
for(int i=1;i<10;i++){
ListNode node = new ListNode(i); //生成新的节点
nextNode.next=node; //把心节点连起来
nextNode=nextNode.next; //当前节点往后移动
} //当for循环完成之后 nextNode指向最后一个节点,
nextNode=nodeSta; //重新赋值让它指向首节点
print(nextNode); //打印输出
//插入节点
while(nextNode!=null){
if(nextNode.val==5){
ListNode newnode = new ListNode(99); //生成新的节点
ListNode node=nextNode.next; //先保存下一个节点
nextNode.next=newnode; //插入新节点
newnode.next=node; //新节点的下一个节点指向 之前保存的节点
}
nextNode=nextNode.next;
}//循环完成之后 nextNode指向最后一个节点
nextNode=nodeSta; //重新赋值让它指向首节点
print(nextNode); //打印输出
}
static void print(ListNode listNoed){
//创建链表节点
while(listNoed!=null){
System.out.println("节点:"+listNoed.val);
listNoed=listNoed.next;
}
System.out.println();
}
}
替换节点:
class ListNode { //类名 :Java类就是一种自定义的数据结构
int val; //数据 :节点数据
ListNode next; //对象 :引用下一个节点对象。在Java中没有指针的概念,Java中的引用和C语言的指针类似
ListNode(int val){ //构造方法 :构造方法和类名相同
this.val=val; //把接收的参数赋值给当前类的val变量
}
}
class Test{
public static void main(String[] args){
ListNode nodeSta = new ListNode(0); //创建首节点
ListNode nextNode; //声明一个变量用来在移动过程中指向当前节点
nextNode=nodeSta; //指向首节点
//创建链表
for(int i=1;i<10;i++){
ListNode node = new ListNode(i); //生成新的节点
nextNode.next=node; //把心节点连起来
nextNode=nextNode.next; //当前节点往后移动
} //当for循环完成之后 nextNode指向最后一个节点,
nextNode=nodeSta; //重新赋值让它指向首节点
print(nextNode); //打印输出
//替换节点
while(nextNode!=null){
if(nextNode.val==4){
ListNode newnode = new ListNode(99); //生成新的节点
ListNode node=nextNode.next.next; //先保存要替换节点的下一个节点
nextNode.next.next=null; //被替换节点 指向为空 ,等待java垃圾回收
nextNode.next=newnode; //插入新节点
newnode.next=node; //新节点的下一个节点指向 之前保存的节点
}
nextNode=nextNode.next;
}//循环完成之后 nextNode指向最后一个节点
nextNode=nodeSta; //重新赋值让它指向首节点
print(nextNode); //打印输出
}
//打印输出方法
static void print(ListNode listNoed){
//创建链表节点
while(listNoed!=null){
System.out.println("节点:"+listNoed.val);
listNoed=listNoed.next;
}
System.out.println();
}
}
删除节点:
class ListNode { //类名 :Java类就是一种自定义的数据结构
int val; //数据 :节点数据
ListNode next; //对象 :引用下一个节点对象。在Java中没有指针的概念,Java中的引用和C语言的指针类似
ListNode(int val){ //构造方法 :构造方法和类名相同
this.val=val; //把接收的参数赋值给当前类的val变量
}
}
class Test{
public static void main(String[] args){
ListNode nodeSta = new ListNode(0); //创建首节点
ListNode nextNode; //声明一个变量用来在移动过程中指向当前节点
nextNode=nodeSta; //指向首节点
//创建链表
for(int i=1;i<10;i++){
ListNode node = new ListNode(i); //生成新的节点
nextNode.next=node; //把心节点连起来
nextNode=nextNode.next; //当前节点往后移动
} //当for循环完成之后 nextNode指向最后一个节点,
nextNode=nodeSta; //重新赋值让它指向首节点
print(nextNode); //打印输出
//删除节点
while(nextNode!=null){
if(nextNode.val==5){
ListNode listNode=nextNode.next.next; //保存要删除节点的下一个节点
nextNode.next.next=null; //被删除节点 指向为空 ,等待java垃圾回收
nextNode.next=listNode; //指向要删除节点的下一个节点
}
nextNode=nextNode.next;
}//循环完成之后 nextNode指向最后一个节点
nextNode=nodeSta; //重新赋值让它指向首节点
print(nextNode); //打印输出
}
//打印输出方法
static void print(ListNode listNoed){
//创建链表节点
while(listNoed!=null){
System.out.println("节点:"+listNoed.val);
listNoed=listNoed.next;
}
System.out.println();
}
}
在对节点进行替换或删除的时候,被替换或被删节点的next引用需不需要设置为null?
答案是: 不需要,因为一个对象被回收的前提是因为没有任何地方持有这个对象的引用(引用计数器为0)也就是说它不在被引用,那么那么它将被回收,至于它引用什么对象无关紧要,因为对于它所引用的对象来说依然是看引用计数器是否为0。
后记
👉Java全栈学习路线可参考:【Java全栈学习路线】最全的Java学习路线及知识清单,Java自学方向指引,内含最全Java全栈学习技术清单~
👉算法刷题路线可参考:算法刷题路线总结与相关资料分享,内含最详尽的算法刷题路线指南及相关资料分享~