1 基本概念
- 链表是一系列存储数据元素的单元通过指针串接起来形成的,因此每个单元至少有两个域,一个域用于数据元素的存储,另一个或两个域是指向其他单元的指针。这里具有一个数据域和多个指针域的存储单元通常称为结点(node)。
- 链表分为带头结点的链表和不带头结点的链表,根据实际需要来确定。
- 指向链表中第一个结点的指针称为头指针,头指针是链表必须的元素;
- 链表数据结构中主要包含单向链表、双向链表及循环链表。
2 概念辨析:头结点,头指针
-
通常使用“头指针”来标识一个链表,头指针始终指向链表的第一个结点。如单链表L,头指针为NULL的时表示一个空链表。下图为一个不带头结点的单链表,头指针指向链表第一个结点,但结点1并不是头结点;
-
在单链表的第一个结点之前附加一个结点,称为头结点。头结点的Data域可以不存储任何信息,也可以记录表长等相关信息。如下图,就是一个含有头结点的链表,此时头指针指向头结点;
- 无论是否有头结点,头指针始终指向链表的第一个结点。如果有头结点,头指针就指向头结点。
3 单链表
单链表只有一个指针域,在整个结点中数据域用来存储数据元素,指针域用于指向下一个具有相同结构的结点,如下图所示。
与数组类似,单向链表中的节点也具有一个线性次序。如下图所示,如果节点 1 的 next 引用指向节点2,则结点1就是结点2的直接前驱,结点2是结点1的直接后继。即只能通过前驱节点找到后继节点,而无法从后继节点找到前驱节点。
特点:
- 数据元素的存储对应的是不连续的存储空间,每个存储结点对应一个需要存储的数据元素。每个结点是由数据域和指针域组成。 元素之间的逻辑关系通过存储节点之间的链接关系反映出来。
- 逻辑上相邻的节点物理上不必相邻。
优点:
- 插入、删除灵活 。不必移动节点,只要改变节点中的指针,但是需要先定位到结点上。
- 有元素才会分配结点空间,不会有闲置的结点。
缺点:
- 比顺序存储结构的存储密度小 。每个节点都由数据域和指针域组成,所以相同空间内假设全存满的话顺序比链式存储更多。
- 查找结点时链式存储要比顺序存储慢。每个节点地址不连续、无规律,导致按照索引查询效率低下。
单链表的Java实现
package com.victor.linkedlist;
import java.util.Scanner;
public class SingleLinkedListDemo {
public static void main(String[] args) {
SingleLinkedList sll = new SingleLinkedList();
char key = ' '; //接收用户输入
Scanner scanner = new Scanner(System.in);
boolean loop = true;
//输出一个菜单栏
while(loop){
System.out.println("s(show): 打印链表");
System.out.println("a(add): 从尾部添加结点");
System.out.println("g(get): 删除尾结点");
System.out.println("l(head): 输出链表长度");
System.out.println("e(exit): 退出程序");
key = scanner.next().charAt(0);
switch (key) {
case 's':
sll.showLinkedList();
break;
case 'a': //从尾部添加结点
System.out.println("请输入一个整数");
int value = scanner.nextInt();
sll.addFromTail(value); //最好判断一下value是不是整数
break;
case 'g': //删除链表尾结点
try {
int res = sll.getListNode();;
System.out.printf("删除的结点值为%d
", res);
} catch (Exception e) {
System.out.println(e.getMessage());
}
break;
case 'l': //输出链表长度
System.out.printf("链表长度为%d
", sll.getLength());
break;
case 'e': //退出
scanner.close();
loop = false;
break;
default:
break;
}
}
System.out.println("程序退出");
}
}
//定义结点类
class ListNode{
private int data;
private ListNode next = null;
//构造方法
public ListNode(int data) {
this.data = data;
}
//返回data值
public int getData() {
return this.data;
}
//设置data值
public void setData(int data) {
this.data = data;
}
//返回下一个结点地址
public ListNode getNext() {
return this.next;
}
//设置下一个结点地址
public void setNext(ListNode next) {
this.next = next;
}
//重写toString方法
@Override
public String toString() {
return "ListNode [data=" + data + "]";
}
}
//定义单链表类
class SingleLinkedList{
//头结点
private ListNode head;
//构造方法
public SingleLinkedList() {
head = new ListNode(-1);
}
//头插法添加结点
public void addFromHead(int data) {
ListNode ListNode = new ListNode(data); //新建结点
ListNode curr = head.getNext();
head.setNext(ListNode);
ListNode.setNext(curr);
}
//尾插法添加结点
public void addFromTail(int data) {
ListNode ListNode = new ListNode(data); //新建结点
ListNode curr = head;
while(curr.getNext() != null) {
curr = curr.getNext();
}
curr.setNext(ListNode);
}
//删除链表尾结点
public int getListNode() {
if (head.getNext() == null) {
throw new RuntimeException("链表为空链表");
}
ListNode curr = head;
ListNode prev = head;
while(curr.getNext() != null) {
prev = curr;
curr = curr.getNext();
}
prev.setNext(null);
return curr.getData();
}
//求链表长度
public int getLength() {
int length = 0;
ListNode curr = head;
while(curr.getNext() != null) {
length++;
curr = curr.getNext();
}
return length;
}
//打印链表
public void showLinkedList() {
ListNode curr = head.getNext();
while(curr != null) {
System.out.println(curr);
curr = curr.getNext();
}
}
}
reference
深刻理解:带头结点和不带头结点的区别 使用头结点的优势https://blog.csdn.net/qq_24118527/article/details/81317410
链表详解(易懂)https://blog.csdn.net/SlimShadyKe/article/details/89503062
韩顺平数据结构
大话数据结构