链表
什么是链表
-
链表是物理存储单元上非连续的、非顺序的存储结构,它是由一个个节点,通过指针来联系起来的,其中每个节点包括数据域(data域)和指针域(next域)。
-
数据域用来存储数据,指针域指向该节点的下一个节点
-
链表的第一个节点叫头结点,最后一个节点叫尾节点,尾节点的特征是next域为null值。
-
链表可分为带头结点的链表和不带头节点的链表,可根据实际需求选择
特点:
数据元素的存储对应的是不连续的存储空间,每个存储结点对应一个需要存储的数据元素。每个结点是由数据域和指针域组成。 元素之间的逻辑关系通过存储节点之间的链接关系反映出来。
逻辑上相邻的节点物理上不必相邻。
缺点:
1、比顺序存储结构的存储密度小 (每个节点都由数据域和指针域组成,所以相同空间内假设全存满的话顺序比链式存储更多)。
2、查找结点时链式存储要比顺序存储慢(每个节点地址不连续、无规律,导致按照索引查询效率低下)。
优点:
1、插入、删除灵活 (不必移动节点,只要改变节点中的指针,但是需要先定位到元素上)。
2、有元素才会分配结点空间,不会有闲置的结点。
单向链表
单向链表的节点只有一个数据域和一个指针域,数据域用来存储该节点需要存储的数据,指针域指向该节点的下一个节点
代码实现
//单向链表类
public class SingleLinkedList<T>{
//节点类
private class Node{
T val; //该节点存储的数据
Node next; //指向下一个节点
public Node(){
}
public Node(T val){
this.val = val;
}
@Override
public String toString() {
return "Node{" +
"val=" + val +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Node node = (Node) o;
return val.equals(node.val);
}
}
//创建头结点 不存放任何数据
Node head = new Node();
//增
//尾插法
public void addLast(T val){
//通过辅助变量来遍历链表
Node temp = head;
//找到链表的尾节点
while(temp.next != null){
temp = temp.next;
}
//添加到尾节点后
temp.next = new Node(val);
}
//头插法
public void addHead(T val){
Node node = new Node(val);
//将当前要添加的节点的next域指向head节点的下一个节点
node.next = head.next;
//head节点的next域指向当前要添加的节点
head.next = node;
}
//删除指定下标元素
public boolean delete(int index){
//index校验
if (index >= getLength() || index < 0){
return false;
}
//下标从0开始,将辅助节点temp移动到指定下标处的前一个元素
Node temp = head;
for (int i = 0; i < index; i++){
temp = temp.next;
}
//将temp的next域指向要删除的节点的下一个节点
temp.next = temp.next.next;
return true;
}
//获取链表中有效节点个数
public int getLength(){
if (head.next == null){
return 0;
}
Node temp = head.next;
int count = 1;
while ((temp = temp.next) != null){
++count;
}
return count;
}
//改
public T alter(int index,T val){
//校验
if (index >= getLength() || index < 0){
return null;
}
//将temp移动到指定下标处,下标从0开始
Node temp = head.next;
T oldVal;
for (int i = 0; i < index; i++){
temp = temp.next;
}
//修改当前节点的数据域存储的数据,并将原来的数据返回
oldVal = temp.val;
temp.val = val;
return oldVal;
}
//查
public int select(T val){
Node temp = head;
int index = 0;
Node node = new Node(val);
//遍历整个链表,找到数据域中的数据和val相同时,返回下标,否则返回-1表示没有该节点
while ((temp = temp.next) != null){
if (temp.equals(node)){
return index;
}
index++;
}
return -1;
}
//显示链表中的元素
public void show(){
Node temp = head;
while ((temp = temp.next) != null){
System.out.println(temp);
}
}
}
缺点
- 单向链表只能通过一个方向来遍历链表,无法反向遍历
- 删除节点时需要依靠被删除的节点的前一个节点
双向链表
双向链表的节点由一个数据域和两个指针域组成,一个指针域指向该节点的前一个节点(pre),另一个指针域指向该节点的下一个节点(next)
代码实现
//双向链表类
public class TwinLinkedList<T>{
private class Node{
T val;
Node pre;
Node next;
public Node() {
}
public Node(T val) {
this.val = val;
}
@Override
public String toString() {
return "Node{" +
"val=" + val +
'}';
}
}
//头结点
private Node head;
//尾节点
private Node tail;
//链表中节点个数
private int size = 0;
public TwinLinkedList(){
}
//增
public void add(T val){
//头结点初始化
if (head == null){
head = new Node(val);
size++;
return;
}
//尾节点初始化
if (tail == null){
tail = new Node(val);
head.next = tail;
tail.pre = head;
size++;
return;
}
//将要添加的节点添加到尾节点的下一个
tail.next = new Node(val);
//要添加的节点的pre指向尾节点
tail.next.pre = tail;
//尾节点变成新添加的节点
tail = tail.next;
size++;
}
//删
public boolean delete(int index){
if (head == null || index >= size){
return false;
}
Node temp = head;
for (int i = 0; i < index; i++){
temp = temp.next;
}
size--;
//将temp的前一个节点的next指向temp的后一个节点
temp.pre.next = temp.next;
if (temp.next == null){
return true;
}
//temp的后一个节点的pre指向temp的前一个节点
temp.next.pre = temp.pre;
return true;
}
//改
public T alter(int index,T val){
if (head == null || index < 0 || index >= size){
return null;
}
Node temp = head;
T oldVal = null;
for (int i = 0; i < index; i++){
temp = temp.next;
}
oldVal = temp.val;
temp.val = val;
return oldVal;
}
//查
public int select(T val){
if (head == null){
return -1;
}
Node temp = head;
int index = 0;
while (temp != null){
if (val.equals(temp.val)){
return index;
}
index++;
temp = temp.next;
}
return -1;
}
//打印链表中的节点
public void print(){
Node temp = head;
while (temp != null){
System.out.println(temp);
temp = temp.next;
}
}
public int getSize() {
return size;
}
}
环形链表
头结点和尾节点连接在一起的链表,在单链表和双链表中都可以实现
代码实现
public class AnnularLinedList<T>{
//节点类
private class Node{
T val;
Node next;
public Node(T val) {
this.val = val;
}
@Override
public String toString() {
return "Node{" +
"val=" + val +
'}';
}
}
private Node first; //头结点
private Node temp; //下一个节点的添加位置
public void add(T val){
//初始化头结点
if (first == null){
first = new Node(val);
//形成环链
first.next = first;
temp = first.next;
return;
}
temp.next = new Node(val);
temp.next.next = first;
temp = temp.next;
}
}