目录
文章目录
Java数据结构之线性表
链表结构
链式存储结构是基于指针实现,我们把一个数据元素和一个指针成为节点
链式存储结构是用指针把相互直接关联的结点(即直接前驱节点或直接后继节点)链接起来。 链式存储结构的线性表成为链表
链表类型
根据链表的构造方式的不同可以分为:
1. 单向链表
2. 循环链表
3. 双向链表
线性表的抽象数据类型
1、线性表的置空操作 | clear() |
2、线性表判空操作: | isEmpty() |
3、求线性表的长度: | size( ) |
4、取元素操作: | get( i ) |
5、插入操作: | insert( i, x ) |
6、删除操作: | delete( i) |
单向链表
1、基本概念
单向链表(单链表)是链表的一种,其特点是链表的链接方向是单向的,对链表的访问要通过顺序读取从头部开始;链表是使用指针进行构造的列表;又称为结点列表,因为链表是由一个个结点组装起来的;其中每个结点都有指针成员变量指向列表中的下一个结点;
链表是由结点构成,head指针指向第一个成为表头结点,而终止于最后一个指向NULL的指针。
2、图形理解
- 头结点是为了操作的统一与方便而设立的,放在第一个元素结点之前,其数据域一般无意义(当然有些情况下也可存放链表的长度、用做监视哨等等)。
- 有了头结点后,对在第一个元素结点前插入结点和删除第一个结点,其操作与对其它结点的操作统一了。
- 首元结点也就是第一个元素的结点,它是头结点后边的第一个结点。
- 头结点不是链表所必需的。
- 在线性表的链式存储结构中,头指针是指链表指向第一个结点的指针,若链表有头结点,则头指针就是指向链表头结点的指针。
- 头指针具有标识作用,故常用头指针冠以链表的名字。
- 无论链表是否为空,头指针均不为空。头指针是链表的必要元素。
- 单链表也可以没有头结点。
3、链表特性
链表的链接方向是单向的
4、优缺点(对比线性表)
优点
结点创建非常方便,普通的线性内存通常在创建的时候就需要设定数据的大小
结点的删除非常方便,不需要像线性结构那样移动剩下的数据
结点的访问方便,可以通过循环或者递归的方法访问到任意数据,但是平均的访问效率低于线性表
缺点
只能从头到尾遍历。只能找到后继,无法找到前驱,也就是只能前进
优缺点总结
适用于节点的增加删除
5、代码展示
5.1、基础代码
/** 保存该链表的头结点 */
private Node head;
/** 保存该链表的尾结点 */
private Node tail;
/** 保存该链表中已包含的节点数 */
private int size;
/** 内部类 */
private class Node {
// 保存节点的数据
private T date;
// 指向下个节点的引用
private Node next;
// 无参构造器
public Node() {
}
// 初始化构造器
public Node(T date) {
this.date = date;
}
public Node(T date, Node next) {
this.date = date;
this.next = next;
}
}
5.2、插入数据
a、链表为空时
/**
* <p>以指定数据元素来创建链表,Ps.(当前链表为空)</p>
*
* @param element 数据
* @author Kallen
* @since 2020/12/22 13:52
*/
public void OneLink(T element) {
head = new Node(element);
// 只有一个节点,head、tail 都指向该节点
tail = head;
size++;
}
b、根据索引添加
/**
* <p>根据索引添加元素</p>
*
* @param element 数据
* @param index 索引
* @author Kallen
* @since 2020/12/22 14:22
*/
public void insert(T element, int index) {
// 索引小于0或大于当前链表最大索引长度
if (index < 0 || index > size -1) {
throw new IndexOutOfBoundsException("索引越界");
}
// 是否为空链表
if (head == null) {
OneLink(element);
}else {
// index是否为0
if (index == 0) {
addAtHead(element);
}else {
// 获取插入点的前一个节点
Node prev = getNodeByIndex(index - 1);
// 获取新节点
Node newNode = new Node(element);
// prev的next指向新节点
prev.next = newNode;
// 新节点的next指向原prev的next节点
newNode.next = prev.next;
size++;
}
}
}
c、尾插法
/**
* <p>采用尾插法插入新节点</p>
*
* @param element 数据
* @author Kallen
* @since 2020/12/22 14:28
*/
public void add(T element) {
// 该链表是否为空表
if (head == null) {
OneLink(element);
} else {
// 新建Node结点
Node newNode = new Node(element);
// 当前"尾节点"的"指针"指向"新节点"
tail.next = newNode;
// 以"新节点"作为"尾结点"
tail = newNode;
size++;
}
}
d、头插法
/**
* <p>采用头插法插入新节点</p>
*
* @param element 元素
* @author Kallen
* @since 2020/12/22 14:16
*/
public void addAtHead(T element) {
// 创建新节点,让新节点的next指向原head,并以新节点作为head
head = new Node(element, head);
// 如果插入之前是空链表
if (tail == null) {
tail = head;
}
size++;
}
5.3、查询数据
a、根据索引查询
/**
* <p>根据指定索引获取元素</p>
*
* @param index 索引
* @return {@link T} 数据
* @author Kallen
* @since 2020/12/22 13:55
*/
public T get(int index) {
return getNodeByIndex(index).date;
}
b、根据指定元素查找索引
/**
* <p>根据指定元素查找索引</p>
*
* @param element 元素
* @return {@link int} 索引
* @author Kallen
* @since 2020/12/22 14:06
*/
public int locate(T element) {
// 从头结点开始搜索
Node current = head;
for (int i = 0; i < size && current != null; i++, current = current.next) {
if (current.date.equals(element)) {
return i;
}
}
return -1;
}
c、获取当前链表长度
/**
* <p>获取链表长度</p>
*
* @return {@link int} 链表长度
* @author Kallen
* @since 2020/12/23 13:34
*/
public int size() {
return size;
}
5.4、删除数据
a、根据索引删除
/**
* <p>根据索引删除元素</p>
*
* @param value 索引
* @author Kallen
* @since 2020/12/22 14:34
*/
public void delete(int value) {
if (value < 0 || value > size -1) {
throw new IndexOutOfBoundsException("索引越界");
}
// 是否为head
if (value == 0) {
head = head.next;
}else {
// 获取指定删除节点的前一个节点
Node prev = getNodeByIndex(value - 1);
// prev的next节点指向要删除节点的next
prev.next = prev.next.next;
}
size--;
}
b、删除最后一个元素
/**
* <p>删除最后一个元素</p>
*
* @author Kallen
* @since 2020/12/22 14:43
*/
public void remove() {
delete(size -1);
}
5.5、判断是否为空
/**
* <p>是否为空</p>
*
* @author Kallen
* @since 2020/12/22 14:45
*/
public boolean isEmpty() {
return size == 0;
}
5.6、清空链表
/**
* <p>清空链表</p>
*
* @author Kallen
* @since 2020/12/22 14:47
*/
public void clear() {
// head、tail、size设置为空
head = null;
tail = null;
size = 0;
}
5.7、私有方法
/**
* <p>根据指定索引获取结点</p>
*
* @param index 索引
* @return {@link OneLink<T>.Node} 结点
* @author Kallen
* @since 2020/12/28 14:33
*/
private Node getNodeByIndex(int index) {
// 判断指定位置是否合法
if (index < 0 || index > size - 1) {
throw new IndexOutOfBoundsException("索引越界");
}
// 从head节点开始
Node current = head;
for (int i = 0; i < size && current != null; i++, current = current.next) {
if (i == index) {
return current;
}
}
return null;
}
你知道越多,不知道的越多,感谢各位人才的:点赞、收藏和评论,我们下期见!