目录
双向链表,它由多个结点组成,每个结点都由一个数据域和两个指针域组成,数据域用来存储数据,其中一个指针域用来指向其后继结点,另一个指针域用来指向前驱结点。链表的头结点的数据域不存储数据,指向前驱结点的指针域值为null,指向后继结点的指针域指向第一个真正存储数据的结点。
双链表的实现
结点API设计
类名 | Node |
构造方法 | Node(T t,Node pre,Node next) |
成员变量 | T item :存储数据 Node pre:指向上一个结点 Node next:指向下一个结点 |
双向链表API设计
类名 | LoopLinkList |
构造方法 | LoopLinkList():创建对象 |
成员方法 | 1.public void clear():清空线性表 2.public boolean isEmpty():判断线性表是否为空 3.public int length():获取线性表中元素的个数 4.public T get(int i)获取第i个元素的值 5.public void insert(T t):在线性表中添加一个元素 6.public void insert(int i,T t):在第i个元素之前插入一个值为t的元素 7.public T remove(int i):删除第i个数据元素 8.public int indexOf(T t):返回线性表中首次出现该元素的索引值 10.public T getLast():获取最后一个元素 |
成员内部类 | private class Node:结点类 |
成员变量 | 1.private Node first:记录首结点 2.private Node last:记录尾结点 3.private int N:记录链表的长度 |
public class LoopLinkList<T> implements Iterable<T>{
public static void main(String[] args) {
// 创建双向链表对象
LoopLinkList<String> sl= new LoopLinkList<>();
// 测试插入
sl.insert("龍弟");
sl.insert("龍龍");
sl.insert("龍哥");
sl.insert(1,"龍帝");
for(String s: sl){
System.out.println(s);
}
System.out.println("-----------------");
// 测试获取
String getResult = sl.get(1);
System.out.println("获取索引1处的结果为:"+getResult);
// 测试删除
String removeResult = sl.remove(0);
System.out.println("删除的元素:" + removeResult);
System.out.println("---------------------");
System.out.println("第一个元素是"+sl.getFirst());
System.out.println("最后一个元素是:" + sl.getLast());
// 测试清空
sl.clear();
System.out.println("清空后的线性表中的元素个数为:"+sl.length());
}
//首结点
private Node head;
// 最后一个结点
private Node last;
//链表的长度
private int N;
// 结点类
private class Node {
public Node(T item, Node pre, Node next) {
this.item = item;
this.pre = pre;
this.next = next;
}
//存储数据
public T item;
// 指向上一个结点
public Node pre;
// 指向下一个结点
public Node next;
}
public LoopLinkList() {
//初始化头节点和尾结点
this.head = new Node(null, null, null);
this.last = null;
// 初始化元素结点
this.N = 0;
}
// 清空链表
public void clear() {
this.head.next = null;
this.head.item = null;
this.last = null;
this.N = 0;
}
// 获取链表长度
public int length() {
return N;
}
// 判断链表是否为空
public boolean isEmpty() {
return N == 0;
}
// 获取第一个元素
public T getFirst() {
if (isEmpty()) {
return null;
}
return head.next.item;
}
// 获取最后一个元素
public T getLast() {
if (isEmpty()) {
return null;
}
return last.item;
}
// 插入元素t
public void insert(T t) {
// 如果链表为空
if (isEmpty()) {
// 创建新的节点
Node newNode = new Node(t, head, null);
// 让新结点成为尾结点
last = newNode;
// 让头结点指向尾结点
head.next = last;
} else {
// 如果链表不为空
Node oldLast = last;
// 创建新的结点
Node newNode = new Node(t, oldLast, null);
// 让当前的尾节点 指向新结点
oldLast.next = newNode;
// 让新结点成为尾结点
last = newNode;
}
N++;
}
//向指定位置i处插入元素t
public void insert(int i, T t) {
//找到位置i的前一个结点
Node pre = head;
for (int index = 0; index < i; index++ ){
pre = pre.next;
}
//当前结点
Node curr = pre.next;
//构建新节点
Node newNode = new Node(t, pre, curr);
pre.next = newNode;
curr.pre = newNode;
//长度+1
N++;
}
// 获取指定位i处的元素
public T get(int i){
Node n = head.next;
for(int index = 0; index < i; index++){
n = n.next;
}
return n.item;
}
// 找到元素t在链表中第一次出现的位置
public int indexOf(T t){
Node n = head;
for(int index = 0; index<N; index++){
n = n.next;
if(n.next.equals(t)){
return index;
}
}
return -1;
}
// 删除位置i处的元素,并返回该元素
public T remove(int i){
//寻找i位置的前一个元素
Node pre = head;
for(int index = 0; index < i; index++){
pre = pre.next;
}
//i位置的元素
Node curr = pre.next;
//i位置的下一个元素
Node nextNode = curr.next;
pre.next = nextNode;
nextNode.pre = pre;
// 长度减一
N--;
return curr.item;
}
@Override
public Iterator<T> iterator() {
return new TIterator();
}
private class TIterator implements Iterator{
private Node n;
public TIterator(){
this.n = head;
}
@Override
public boolean hasNext() {
return n.next != null;
}
@Override
public Object next() {
n = n.next;
return n.item;
}
}
}
java中LinkedList实现
java中LinkedList集合也是使用双向链表实现,并提供了增删改查等相关方法
1.底层是否用双向链表实现;
2.结点类是否有三个域
链表的复杂度分析
get(int i):每一次查询,都需要从链表的头部开始,依次向后查找,随着数据元素N的增多,比较的元素越多,时间复杂度为O(n)
insert(int i,T t);每一次插入,需要先找到i位置的前一个元素,然后完成插入操作,随着数据元素N的增多,查找的元素越多,时间复杂度为O(n);
remove(int i):每一次移除,需要先找到i位置的前一个元素,然后完成插入操作,随着数据元素N的增多,查找的元素越多,时间复杂度为O(n)
相比较顺序表,链表插入和删除的时间复杂度虽然一样,但仍然有很大的优势,因为链表的物理地址是不连续的,它不需要预先指定存储空间大小,或者在存储过程中涉及到扩容等操作..同时它并没有涉及的元素的交换。
相比较顺序表,链表的查询操作性能会比较低。因此,如果我们的程序中查询操作比较多,建议使用顺序表;增删操作比较多,建议使用链表。
循环链表
循环链表,也就是链表整体要形成一个圆环状。在单向链表中,最后一个节点的指针为null ,不指向任何结点,因为没有下一个元素了。要实现循环链表,我们只需要让单向链表的最后一个节点的指针指向头结点即可。
public static void main(String[] args) {
//构建结点
Node<Integer> first=new Node<Integer>(5,null);
Node<Integer> second=new Node<Integer>(6,null);
Node<Integer> third=new Node<Integer>(7,null);
Node<Integer> fourth=new Node<Integer>(7,null);
Node<Integer> fifth=new Node<Integer>(7,null);
//生成单链表
first.next=second;
second.next=third;
third.next=fourth;
fourth.next=fifth;
//构成循环链表,让最后一个结点指向第一个结点
fifth.next=first;
}