一、链表的定义
链表又叫做线性表的链式存储,有一个个节点组成,类似生活中火车车厢一节一节串在一起。链表是一种物理存储结构上非连续的存储结构,数据元素的逻辑顺序是通过链表的引用链接次序实现的。
链表中的每个结点包含两部分:一个叫数据域:存储数据元素信息;另一个叫指针域:存储下一个结点的存储地址。
二、链表的种类
链表根据是否带有头结点,不带头结点,单向或者双向,循环或者非循环可以组成8种情况:
带头 双向 循环链表 不带头 双向 循环链表
带头 双向 非循环链表 不带头 双向 非循环链表
带头 单向 循环链表 不带头 单向 循环链表
带头 单向 非循环链表 不带头 单向 非循环链表
其中双向是带有两个指针域,一个指针域是指向下个结点的存储地址,另一个指针域指向上一个结点的存储地址。
重点掌握:带头单向非循环链表和不带头双向非循环链表
三、不带头单向非循环链表的实现
(1)创建链表
static class ListNode { //静态内部类
public int value;
public ListNode next; //因为next是指向结点的,所以是ListNode类型
public ListNode(int value) { //实例化结点的时候不知道next指向谁,所以不用next构造
this.value = value;
}
}
//链表的属性,链表的头结点
//不能创建在内部类里,否则成为新结点的头结点
public ListNode head; //null
public void creatList() { //方法执行完后,里面的局部变量node1...都会被回收掉,链表还存在
ListNode node1 = new ListNode(12);
ListNode node2 = new ListNode(23);
ListNode node3 = new ListNode(34);
ListNode node4 = new ListNode(45);
node1.next = node2;
node2.next = node3;
node3.next = node4;
this.head = node1;
}
(2)遍历打印链表元素
重点:1. 怎么从第一个结点走到下个结点-------head = head.next
2. 什么时候算把所有结点遍历完成 --------head==null
public void display() {
ListNode cur = head; //定义一个cur代替head去遍历
while (cur != null) { //指向空就退出循环
System.out.println(cur.value + " ");
cur = cur.next; //从当前结点走到下个结点
}
}
(3)查看关键字key是否在单链表中
public boolean contains(int key) {
ListNode cur = head; //定义一个cur代替head去遍历
while (cur != null) { //循环执行完后cur指向尾结..点的后一个位置,也就是null
if (cur.value == key) {
System.out.println("元素存在");
return true;
}
cur = cur.next;
}
return false;
}
(4)求当前链表有多少个结点
public int size() {
int count = 0;
ListNode cur = head; //定义一个cur代替head去遍历
while (cur != null) {
count++;
cur = cur.next;
}
return count;
}
(5)头插法
public void addFirst(int data) {
ListNode node = new ListNode(data); //创建一个新结点
node.next = this.head; //先让新节点的next指向head,然后head指针走向node
this.head = node;
}
(6)尾插法
public void addLast(int data) {
ListNode node = new ListNode(data); //创建一个新结点
if (head == null) {
head = node; //分两种情况,如果链表是空的
} else {
ListNode cur = head; //定义一个cur代替head去遍历
while (cur.next != null) { //cur.next指向空的时候,就说明是最后一个结点
cur = cur.next;
}
cur.next = node;
}
}
(6)链表的插入
1.先找到cur的位置 : cur先走到Index - 1步
public void addIndex(int index, int data) throws indexException {
//判断index是否合法
if (index < 0 || index > size()) {
throw new indexException("index不合法: " + index);
}
ListNode node = new ListNode(data);
if (head == null) { //链表为空
head = node;
return;
}
//如果索引为0,直接头插法
if (index == 0) {
addFirst(data);
return;
}
//如果索引为链表长度,直接尾插法
if (index == size()) {
addLast(data);
return;
}
//中间插入
ListNode cur = searchPrevIndex(index); //cur指向index前一个位置
node.next = cur.next; //先把要插入的结点node的next指向cur的next,先绑定后面
cur.next = node; //cur的next再指向node,不然会丢失
}
(7) 查看关键字key是否在链表中
public boolean contains(int key) {
ListNode cur = head; //定义一个cur代替head去遍历
while (cur != null) { //循环执行完后cur指向尾结..点的后一个位置,也就是null
if (cur.value == key) {
System.out.println("元素存在");
return true;
}
cur = cur.next;
}
return false;
}
(8) 删除key结点
因为是单向链表,cur不能走到要删除结点的位置,否则找不到删除结点的前驱,因为链表的删除操作是让删除结点的前驱直接指向删除结点的后继,所以cur要走到要删除结点的前驱即可.
public void remove(int key) {
//链表为空
if (head == null) {
return;
}
//删除的关键字是头结点
if (head.value == key) {
head = head.next; //直接指向头结点的下一位
return;
}
//删除操作
ListNode cur = findPrevKey(key);
if (cur == null) {
System.out.println("没有你要删除的数字");
return;
} else {
cur.next = cur.next.next; //直接指向要删除的下下个结点
}
}
private ListNode findPrevKey(int key) { //找322去 到要删除结点的前驱才能
ListNode cur = head; //定义一个cur代替head去遍历
while (cur.next != null) {
if (cur.next.value == key) {
return cur;
} else {
cur = cur.next;
}
}
return null;
}
(9) 删除所有值为key的节点
public void removeAllKey(int key) {
if (head == null) {
return;
}
while (head.value == key) { //会出现重复要删除的头结点,所以要用循环判断
head = head.next;
return;
}
ListNode cur = head.next; //定义一个cur代替head去遍历
ListNode prev = head; //代表要删除结点的前驱
while (cur != null) {
if (cur.value == key) {
prev.next = cur.next; //这一步cur的前驱直接跳过cur结点,指向cur的后继
cur = cur.next;
} else {
prev = cur;
cur = cur.next;
}
}
}
(10) 求链表的长度
public int size() {
int count = 0;
ListNode cur = head; //定义一个cur代替head去遍历
while (cur != null) {
count++;
cur = cur.next;
}
return count;
}
(11) 清空链表
当一个对象没有人引用的时候就会被回收掉
//清空
@Override
public void clear() {
head = null; //当一个对象没人引用的时候就会被回收
}
四、链表的优缺点
优点:
1、对于频繁插入和删除数据的操作,链表的效率很高
2、单链表不需要分配存储空间,只要有就可以分配,元素个数也不受限制
缺点:
1、在查找数据元素上,时间复杂度为O(N)
2、链表不是一种随机存储结构,不能随机存取元素。