1. 双向循环链表的定义:双向链表也叫双链表,是链表的一种,它的每个数据结点中都有两个指针,分别指向直接后继和直接前驱。所以,从双向链表中的任意一个结点开始,都可以很方便地访问它的前驱结点和后继结点。一般我们都构造双向循环链表。如图:
2. 双向循环链表不只可以实现线性表List,也可以实现队列,双端队列,栈这些数据结构。因此我们在实现双端循环链表时,使其实现List,Queue,DoubleQueue,Stack这些接口。
//双向循环链表 即可以实现列表List的方法 也可以实现双端循环队列DoubleLoopQueue 队列Queue 栈Stack的方法
public class LinkedDoubleEndCircularList<E> implements List<E>, DoubleLoopQueue<E>, Stack<E> {
/*将数据封装成节点 节点包括三部分 数据域 前驱指针域 和 后继指针域
数据域中存放的是数据内容
前驱指针域中存放的是当前节点的上一跳节点的地址 即前驱指针域指向当前节点的上一跳节点
后继指针域中存放的是当前节点的下一跳节点的地址 即后继指针域指向当前节点的下一跳节点
因为是双向循环链表 因此尾节点的下一跳始终指向头节点 头节点的上一跳也始终指向尾节点
*/
class Node{ //封装数据域和指针域的类
E data; //表示节点的数据域
Node next; //表示节点的后继指针域
Node pre; //表示节点的前驱指针域
public Node(){ //节点的无参构造
data = null;
next = null;
pre = null;
}
public Node(E data){ //节点的带参构造 参数为节点的数据
this.data = data;
next = null;
pre = null;
}
public Node(E data, Node next, Node pre){ //节点的带参构造 参数为节点的数据 当前节点的下一跳节点和上一跳节点
this.data = data;
this.next = next;
this.pre = pre;
}
//格式化节点输出时的格式 即输出数据内容即可
@Override
public String toString() {
return data.toString();
}
}
//表示头指针 指向链表中的头节点
private Node head;
//表示尾指针 指向链表中的尾节点
private Node tail;
//表示链表中有效元素的个数
private int size;
public LinkedDoubleEndCircularList(){ //双向链表的无参构造 为head tail size初始化
head = null;
tail = null;
size = 0;
}
public LinkedDoubleEndCircularList(E[] arr){ //双向链表的带参构造 参数为一个数组 将数组中的元素依次添加到链表中
if(arr == null){
throw new IllegalArgumentException("arr can not be null");
}
for(int i = 0; i < arr.length; i++){
add(arr[i]);
}
}
//向链表的表尾追加元素
@Override
public void add(E element) {
add(size, element);
}
//向链表的指定位置添加元素
@Override
public void add(int index, E element) {
if(index < 0 || index > size){ //判断索引是否存在
throw new IllegalArgumentException("add index out of range");
}
Node node = new Node(element); //创建要添加的节点对象
if(size == 0){ //表空时添加元素
/*
表空时添加元素:
1.使头指针 和 尾指针都指向要添加的节点node
2.使头节点的上一跳指向尾节点
3.使尾节点的下一跳指向头节点
*/
head = node;
tail = node;
head.pre = tail;
tail.next = head;
}else if(index == 0){ //向表头添加元素
/*
向表头添加元素:
1.将当前头节点的上一跳赋值给要添加节点的上一跳
2.使要添加节点的下一跳指向当前头节点
3.使当前头节点的上一跳指向要添加节点
4.使头指针指向要添加节点
5.使尾节点的下一跳指向要添加节点
*/
node.pre = head.pre;
node.next = head;
head.pre = node;
head = node;
tail.next = head;
}else if(index == size){ //向表尾添加元素
/*
向表尾添加元素:
1.将当前尾节点的下一跳赋值给要添加节点的下一跳
2.使要添加节点的上一跳指向当前尾节点
3.使当前尾节点的下一跳指向要添加节点
4.使尾指针指向要添加节点
5.使头节点的上一跳指向要添加节点
*/
node.next = tail.next;
node.pre = tail;
tail.next = node;
tail = node;
head.pre= tail;
}else{ //向表中间添加元素
/*
向表中间添加元素:
1.先判断要添加位置的索引离头节点近还是尾节点近
2.定义两个节点 一个为要添加节点的前驱 一个为要添加节点的后继
3.使前驱节点的下一跳指向要添加节点 要添加节点的上一跳指向前驱节点
4.使后继节点的上一跳指向要添加节点 要添加节点的下一跳指向后继节点
*/
Node p, q; //节点p表示要添加节点的前驱节点 节点q表示要添加节点的后继节点
if(index <= size / 2){
p = head;
for(int i = 0; i < index - 1; i++){
p = p.next;
}
q = p.next;
p.next = node;
node.pre = p;
node.next = q;
q.pre = node;
}else{
p = tail;
for(int i = size - 1; i > index; i--){
p = p.pre;
}
q = p.pre;
q.next = node;
node.pre = q;
node.next = p;
p.pre = node;
}
}
size++;
}
//删除链表中的指定元素
@Override
public void remove(E element) {
int index = indexOf(element);
remove(index);
}
//删除链表中的指定位置的元素 并返回要删除元素的内容
@Override
public E remove(int index) {
if(index < 0 || index > size){ //判断索引是否存在
throw new IllegalArgumentException("add index out of range");
}
E ele = null;
if(size == 1){ //当表中只有一个元素时删除
/*
当表中只有一个元素时删除: 先获取要删除节点的数据 再使头指针 和 尾指针都指向空即可
*/
ele = head.data;
head = null;
tail = null;
}else if(index == 0){ //删除表头元素
/*
删除表头元素:
1.获取头节点的数据
2.将表头结点的下一跳置null
3.将表头结点的上一跳赋值给要删除节点的后继节点的上一跳
4.使头节点的上一跳置null
5.更新头指针 使其指向要删除节点的后继节点
6.重新使尾节点的下一跳指向新的头节点
*/
Node p = head.next;
ele = head.data;
head.next = null;
p.pre = head.pre;
head.pre = null;
head = p;
tail.next = head;
}else if(index == size - 1){ //删除表尾元素
/*
删除表尾元素:
1.获取尾节点的数据
2.将表尾结点的上一跳置null
3.将表尾结点的下一跳赋值给要删除节点的前驱节点的下一跳
4.使尾节点的下一跳置null
5.更新尾指针 使其指向要删除节点的前驱节点
6.重新使头节点的上一跳指向新的尾节点
*/
Node p = tail.pre;
ele = tail.data;
tail.pre = null;
p.next = tail.next;
tail.next = null;
tail = p;
head.pre = tail;
}else{ //删除表中间元素
/*
删除表中间元素:
1.先判断要删除节点的索引离头节点近还是尾节点近
2.定义三个节点 一个为要删除节点的前驱 一个为要删除节点的后继 一个为要删除的节点
3.使前驱节点的下一跳指向后继节点
4.使后继节点的上一跳指向前驱节点
5.使要删除节点的下一跳置null
6.使要删除节点的上一跳置null
*/
Node p, q, r; //p节点表示删除节点的前驱 q节点表示要删除的节点 r节点表示要删除节点的后继
if(index <= size / 2){
p = head;
for(int i = 0; i < index - 1; i++){
p = p.next;
}
q = p.next;
r = q.next;
p.next = r;
r.pre = p;
q.next = null;
q.pre = null;
}else{
p = tail;
for(int i = size - 1; i > index + 1; i--){
p = p.pre;
}
q = p.pre;
r = q.pre;
r.next = p;
p.pre = r;
q.next = null;
q.pre = null;
}
}
size--;
return ele;
}
//获取指定索引处的节点的值
@Override
public E get(int index) {
if(index < 0 || index >= size){ //判断索引是否存在
throw new IllegalArgumentException("get index out of range");
}
if(index == 0){ //获取表头元素 即头指针指向的节点的值
return head.data;
}else if(index == size - 1){ //获取表尾元素 即尾指针指向的节点的值
return tail.data;
}else{ //获取表中间某一索引的值
/*
获取表中间某一索引的值:定义一个新节点p使其指向头节点 从表头开始遍历 遍历到索引位置 每次更新p节点
最后p节点就是要获取节点 返回该节点的值即可
*/
Node p = head;
for(int i = 0; i < index; i++){
p = p.next;
}
return p.data;
}
}
//修改指定位置的节点的值 并返回要修改节点的值
@Override
public E set(int index, E element) {
if(index < 0 || index >= size){ //判断索引是否存在
throw new IllegalArgumentException("get index out of range");
}
E ele;
if(index == 0){ //修改表头节点的值 即先获取表头结点的值 再将表头结点的值修改为指定的值element即可
ele = head.data;
head.data = element;
}else if(index == size - 1){ //修改表尾节点的值 即先获取表尾结点的值 再将表尾结点的值修改为指定的值element即可
ele = tail.data;
tail.data = element;
}else{ //修改表中间位置的节点的值
/*
修改表中间位置的节点的值:定义一个新节点p使其指向头节点
从头节点开始遍历 遍历到索引位置的前驱位置 每次更新p节点为p节点的下一个节点
遍历结束后 p节点即指定要修改的索引处的节点 获取p节点的值
修改p节点的值为element
*/
Node p = head;
for(int i = 0; i < index; i++){
p = p.next;
}
ele = p.data;
p.data = element;
}
return ele;
}
//获取链表有效元素的个数
@Override
public int size() {
return size;
}
//获取指定元素第一次在链表出现的索引
@Override
public int indexOf(E element) {
/*
定义一个新节点p使其指向头节点
遍历链表 直到p节点的数据与指定数据相等即可
每次更新p节点为p节点的下一个节点 索引index的值
判断 如果p节点再次指向头节点 则表示已经遍历了一圈了 都没有找到指定元素 则返回-1
*/
Node p = head;
int index = 0;
while (!p.data.equals(element)){
p = p.next;
index++;
if(p == head){
return -1;
}
}
return index;
}
//判断链表中是否包含指定元素
@Override
public boolean contains(E element) {
return indexOf(element) != -1;
}
//判断链表是否为空
@Override
public boolean isEmpty() {
return size == 0 && (head == null && tail == null);
}
//清空链表中的所有元素
@Override
public void clear() {
head = null;
tail = null;
size =0;
}
//对链表进行排序 插入排序的思想 时间复杂度为O(n^2)
@Override
public void sort(Comparator<E> c) {
if(c == null){ //判断比较器是否为空
throw new IllegalArgumentException("comparator can not be null");
}
if(size == 0 || size == 1){ //如果链表中有效元素的个数等于0或1 就不需要在对链表进行排序
return;
}
Node nodeA, nodeB, nodeC; //定义三个节点
for(nodeA = head.next; nodeA != head; nodeA = nodeA.next){ //外层循环 更新nodeA节点
E e = nodeA.data; //获取nodeA节点的数据
/*
内层循环: 更新nodeB 和 nodeC节点
nodeB节点(相当于j)开始指向nodeA节点 nodeC(相当于j-1)节点开始指向nodeB节点的前驱节点
当nodeC节点不等于尾节点 且 nodeC节点的数据大于nodeA节点的数据 则更新nodeB节点 和 nodeC节点分别为当前节点的前驱节点
满足条件时 将nodeC节点的值赋值给nodeB节点的数据
否则 将nodeA节点的数据赋值给nodeB节点的数据
*/
for (nodeB = nodeA, nodeC = nodeB.pre; nodeC != tail && c.compare(nodeC.data, e) > 0; nodeB = nodeB.pre, nodeC = nodeC.pre){
nodeB.data = nodeC.data;
}
nodeB.data = e;
}
}
//获取链表中指定索引范围的子链表 [fromIndex, toIndex]
/*
先定义一个节点A 使其指向头节点 遍历链表 更新节点A 使其到达fromIndex位置处
再定义一个节点B 也使其指向头节点 遍历链表 更新节点B 使其到达toIndex位置处
定义一个新节点使其指向节点A 从fromIndex位置遍历到toIndex位置 最后使其指向节点B
将[fromIndex, toIndex]范围内的节点重新依次加入到新的子链表中
该获取子链表的方法时间复杂度为O(n)
*/
@Override
public List<E> subList(int fromIndex, int toIndex) {
if(fromIndex < 0 || fromIndex > toIndex || toIndex >= size){ //判断两个索引是否存在
throw new IllegalArgumentException("must 0 <= fromIndex <= toIndex <= size - 1");
}
Node nodeA = head;
for(int i = 0; i < fromIndex; i++){
nodeA = nodeA.next;
}
Node nodeB = head;
for(int i = 0; i < toIndex; i++){
nodeB = nodeB.next;
}
Node p = nodeA;
LinkedDoubleEndCircularList<E> linkedList = new LinkedDoubleEndCircularList<>();
while (true){
linkedList.add(p.data);
if(p == nodeB){
break;
}
p = p.next;
}
return linkedList;
}
//格式化链表输出是的格式
@Override
public String toString() {
StringBuilder str = new StringBuilder();
str.append("LinkedDoubleEndCircularList: " + size + " [");
if(isEmpty()){
str.append(']');
}
Node p = head;
for(int i = 0; i < size; i++){
str.append(p.data);
if(p == tail){
str.append(']');
}else{
str.append(',');
str.append(' ');
}
p = p.next;
}
return str.toString();
}
//获取当前这个数据结构/容器 的 迭代器
//通过迭代器对象 更方便挨个取出每一个元素
//同时 实现了Iterable 可以让当前的数据结构/容器 被foreach循环遍历
@Override
public Iterator<E> iterator() {
return new LinkedDoubleEndCircularListIterator();
}
class LinkedDoubleEndCircularListIterator implements Iterator<E> {
private Node cur = head; //定义一个游标节点
private boolean flag = true; //表示是否在第一圈内 在第一圈是true 遍历到第二圈是false
@Override
public boolean hasNext() { //判断是否有下一个节点
return flag; //flag == true时 表示有下一个节点
}
@Override
public E next() { //获取下一个节点的值
E ele = cur.data; //先获取节点cur的值
cur = cur.next; //再更新cur节点为当前cur节点的下一个节点
if(cur == head){ //判断cur游标节点是否重新指向头节点 如果是则表示已经遍历一圈了 反之还在第一圈
flag = false;
}
return ele;
}
}
//实现双端循环队列的方法
//向双端循环队列的队首添加元素
@Override
public void addFirst(E element) {
add(0, element);
}
//向双端循环队列的队尾添加元素
@Override
public void addLast(E element) {
add(size, element);
}
//删除双端循环队列的队首元素
@Override
public E removeFirst() {
return remove(0);
}
//删除双端循环队列的队尾元素
@Override
public E reomveLast() {
return remove(size - 1);
}
//获取双端循环队列的队首元素
@Override
public E getFirst() {
return get(0);
}
//获取双端循环队列的队尾元素
@Override
public E getLast() {
return get(size - 1);
}
//实现队列的方法
//向队尾添加元素
@Override
public void offer(E element) {
addLast(element);
}
//删除队首元素
@Override
public E poll() {
return removeFirst();
}
//查看队首元素
@Override
public E element() {
return getFirst();
}
//实现栈的方法
//入栈 进栈一个元素 在线性表的表尾添加一个元素
@Override
public void push(E element) {
addLast(element);
}
//出栈 弹出一个元素 在线性表的表尾删除一个元素
@Override
public E pop() {
return reomveLast();
}
//查看当前栈顶元素 并不是移除 查看线性表中最后一个元素
@Override
public E peek() {
return getLast();
}
}