链式存储
顺序存储的不足
插入和删除时需要移动大量元素。
链式存储
为了表示每个数据元素ai 与其直接后继数据元素ai+1之间的逻辑关系,对数据元素ai来说**,除了存储其本身的信息之外,还需存储一个指示其直接后继的信息(即直接后继的存储位置)。我们把存储数据元素信息的域称为数据域**,把存储直接后继位置的域称为指针域。指针域中存储的信息称做指针或链。结点由存放数据元素的数据域和存放后继结点地址的指针域组成
n个结点(a的存储映像)链结成-一个链表, 即为线性表(a1, a2, .. an)的链式存储结构,因为此链表的每个结点中只包含一个指针域, 所以叫做单链表。
头指针:链表中第一个结点的存储位置
头指针和头结点的异同
头指针
- 头指针是指链表指向第一个结点的指针,若链表有头结点,则指向头结点的指针。
- 头指针具有标识作用,所以常用头指针冠以链表的名字。
- 无论链表是否为空,头指针均不为空。头指针是链表的必要元素
头结点
- 头结点是为了操作的统一和方便而创立的,放在第一元素的结点之前,数据域一般无意义。
- 有了头结点,对在第一元素结点前插入结点和删除第一结点,其操作与其他结点的操作统一
- 头结点不一定是链表的必须要素
单链表
存储结构
static class Node {
Integer data;
Node next;
public Node() { }
}
单链表的读取
获取链表第i个数据的算法思路
- 声明一个指针p指向链表的第一个结点,初始化j从1开始。
- 当j<i时,就遍历链表,让p的指针向后移动,不断指向下一结点,j累加1.
- 若到链表末尾p为空,说明第i个结点不存在
- 否则查找成功,返回结点p的数据
代码
static Integer getElem(Node head, int i) throws Exception {
int j = 1;
Node p = head.next; // 声明p并指向链表的第一个结点
while(p != null && (j < i)) { // p不为空切计数器j还没有等于i时,循环继续
p = p.next;
++j;
}
if(p == null || j>i) {
throw new Exception("第i个结点不存在");
}
return p.data;
}
单链表的插入
图示
思路
- 声明一个结点p指向链表头结点,初始化计数器j从1开始
- 当j<i时,就遍历链表,让p的指针向后移动,不断指向下一结点,j累加1
- 若看到链表末尾p为空,则说明第i个结点不存在。
- 否则查找完成,在系统中生成一个空结点s。
- 将数据元素e赋值给s.data
- 单链表的插入标准语句 s.next = p.next, p.next = s
代码
static void listInsert(Node head, int i, Integer elementData) throws Exception {
Node p = head;
int j = 1;
while(p != null && j<i) { // 寻找第i-1个结点
p = p.next;
++j;
}
if(p == null || j > i) {
throw new Exception("第i个结点不存在");
}
Node s = new Node();
s.data = elementData;
s.next = p.next;
p.next = s;
}
单链表的删除
图示
思路
- 声明一个结点p指向链表头指针,初始化计数器j从1开始
- 当j<i时,就遍历链表,让p的指针向后移动,不断指向下一结点,j累加1
- 若看到链表末尾p为空,则说明第i个结点不存在。
- 否则查找完成,将预删除的结点p.next赋值给q。
- 单链表的删除标准语句 p.next = q.next
- 将q结点中的数据赋值给e,返回
代码
static Integer listDelete(Node head, int i) throws Exception {
Node p = head;
int j = 1;
while(p.next != null && j<i) { // 寻找第i-1个结点
p = p.next;
++j;
}
if(p.next == null || j > i) {
throw new Exception("第i个结点不存在");
}
Node q = p.next;
p.next = q.next;
return q.data;
}
单链表的整表创建
思路
- 声明一指针p和计数器变量i
- 初始化以空链表L
- 让L的头结点的指针指向NULL,即简历一个带头结点的单链表
- 循环 (1)生成以新结点赋值给p。(2)随机生成一数字赋值给p的数据域p.data。(3)将p插入到头结点与前一新结点之间
代码
static void createListHead(Node head, int n) { // 新建头结点
Node tail = head;
for (int i=0; i<n; i++) {
Node p = new Node();
p.data = new Random().nextInt(100);
// 将表尾结点的指针指向新创建的结点
tail.next = p;
// 将新创建的结点当做表尾结点
tail = p;
}
tail.next = null; // 当前链表结束
}
单链表的整表删除
思路:
- 声明一结点p和q。
- 将第一个结点赋值给p
- 循环(1)将下一结点赋值给p。(2)释放p。(3)将q赋值给p
代码
static void clearList (Node head) {
Node p = head.next;
Node q;
while(p != null) {
q = p.next;
p.next = null;
p = q;
}
head.next = null;
}
测试
代码
public static void main(String[] args) throws Exception { Node head = new Node(); createListHead(head, 10); System.out.println("创建"); printList(head); System.out.println("获取元素(3)"); System.out.println(getElem(head,3)); System.out.println("插入前"); printList(head); listInsert(head, 4, 13); System.out.println("插入后(i=4, elementData = 13)"); printList(head); System.out.println("删除元素(5):" + listDelete(head, 5)); System.out.println("删除后"); printList(head); System.out.println("清空"); clearList(head); System.out.println("清空后"); printList(head);}
结果
单链表结构和顺序存储结构优缺点
存储分配方式
- 顺序存储结构用一段连续的存储单元一次存储线性表的数据元素
- 单链表采用链式存储结构,用一组任意的存储单元存放线性表的元素
时间性能
- 查找:顺序存储结构O(1)。单链表O(n)
- 插入和删除(1) 顺序存储结构需要平均一定表长一半元素,时间O(n)。(2)单链表在找出某位置得到指针后,插入和删除时间为O(1)
- 空间性能(1)顺序存储结构需要预分配存储空间,分大了,浪费,小了易发生上溢(2)单链表不需要分配存储空间,只要有就可以分配,元素个数也不受限制
双向链表
双向链表是在单链表的每个结点上,再设置一个指向其前驱结点的指针域
存储结构
static class Node {
Integer data;
Node next;
Node prior;
public Node() { }
}