循环链表
单向循环链表
单向循环链表的结构
尾结点的next,指向头结点
当只有一个结点
接口设计
相较于单向链表,单向循环链表需要重写插入结点、删除结点两个方法
- 插入节点,需要特别关注插入头节点的情况。此时需要拿到尾节点,然后将其next指向新节点。
public class SingleCircleLinkedList<E> extends AbstractList<E> {
......
/**
* 往index位置添加元素 --- 改造
* @param index
* @param element
*/
public void add(int index, E element){
//边界判断
rangeCheckForAdd(index);
if (index == 0){// 给空链表添加第一个元素的情况
//创建新节点
Node<E> newFirst = new Node<>(element,first);
//拿到最后一个节点
Node<E> last = (size==0) ? newFirst : node(size - 1 );
//尾指向头
last.next = newFirst;
//头指向
first = newFirst;
}else {
//index上一个节点
Node<E> prev = node(index - 1);
//创建新节点,prev节点指向这个新节点
prev.next = new Node<>(element, prev.next);
}
size++;
}
......
}
- 如果删除的是头节点,删除后需让last指向新的头节点
- 当只有一个节点并删除时, first指向null
public class SingleCircleLinkedList<E> extends AbstractList<E> {
......
/**
* 删除index位置对应的元素 --- 改造
* @param index
* @return
*/
public E remove(int index){
//边界判断
rangeCheck(index);
//记录节点
Node<E> node = first;
if (index == 0){ // 删除第一个元素是特殊情况
// 只有一个节点
if (size == 1){
first = null;
}else {
//拿到最后一个节点
Node<E> last = node(size - 1);
first = first.next;
last.next = first;
}
}else {
//index上一个节点
Node<E> prev = node(index - 1);
//记录删除的节点
node = prev.next;
//node节点下一个节点,prev节点指向这个节点
prev.next = node.next;
}
size --;
return node.element;
}
......
}
完整代码
/**
* 单向循环链表的实现
* @param <E> 泛型
*/
public class SingleCircleLinkedList<E> extends AbstractList<E> {
/**
* 元素所在的结点
*/
private Node<E> first;
/**
* 内部类 结点
* @param <E>
*/
private static class Node<E>{
E element;//元素
Node<E> next;//下个元素的地址
public Node(E element, Node<E> next) {
this.element = element;
this.next = next;
}
}
/**
* 返回index位置对应的元素
* @param index
* @return
*/
public E get(int index){
return node(index).element;
}
/**
* 设置index位置的元素
* @param index
* @param element
* @return
*/
public E set(int index, E element){
Node<E> node = node(index);
E old = node.element;
node.element = element;
return old;
}
/**
* 往index位置添加元素 --- 改造
* @param index
* @param element
*/
public void add(int index, E element){
//边界判断
rangeCheckForAdd(index);
if (index == 0){// 给空链表添加第一个元素的情况
//创建新节点
Node<E> newFirst = new Node<>(element,first);
//拿到最后一个节点
Node<E> last = (size==0) ? newFirst : node(size - 1 );
//尾指向头
last.next = newFirst;
//头指向
first = newFirst;
}else {
//index上一个节点
Node<E> prev = node(index - 1);
//创建新节点,prev节点指向这个新节点
prev.next = new Node<>(element, prev.next);
}
size++;
}
/**
* 删除index位置对应的元素 --- 改造
* @param index
* @return
*/
public E remove(int index){
//边界判断
rangeCheck(index);
//记录节点
Node<E> node = first;
if (index == 0){ // 删除第一个元素是特殊情况
// 只有一个节点
if (size == 1){
first = null;
}else {
//拿到最后一个节点
Node<E> last = node(size - 1);
first = first.next;
last.next = first;
}
}else {
//index上一个节点
Node<E> prev = node(index - 1);
//记录删除的节点
node = prev.next;
//node节点下一个节点,prev节点指向这个节点
prev.next = node.next;
}
size --;
return node.element;
}
/**
* 查看元素的位置
* @param element
* @return
*/
public int indexOf(E element){
// 取出头结点
Node<E> node = first;
if(element ==null){// 当element为null时的处理
// 遍历节点, 找到存储为null的节点, 返回索引
for (int i = 0; i < size; i++) {
if (node.element == null) return i;
node = node.next;
}
}else {
// 遍历节点, 找到存储的元素与指定元素相等的节点, 返回索引
for (int i = 0; i < size; i++) {
if (element.equals(node.element)) return i;
node = node.next;
}
}
return ELEMENT_NOT_FOUND;
}
/**
* 清除所有元素
*/
public void clear(){
size = 0;
first = null;
}
/**
* 获取index位置对应的节点对象
* @param index
* @return
*/
private Node<E> node(int index){
rangeCheck(index);
Node<E> node = first;
for (int i = 0; i < index; i++) {
node = node.next;
}
return node;
}
/**
* 打印
* @return
*/
@Override
public String toString() {
//Size = ? , [1, 2, 3]
StringBuilder string = new StringBuilder();
string.append("size=").append(size).append(", [");
Node<E> node = first;
for (int i = 0; i < size; i++) {
if (i != 0){
string.append(", ");
}
string.append(node.element);
node = node.next;
}
string.append("]");
return string.toString();
}
}
双向循环链表
双向循环链表的结构
- 头节点的prev指向尾节点。
- 尾节点的next指向头节点。
当只有一个节点
接口设计
相较于双向链表,双向循环链表需要重写插入节点、删除节点两个方法
- 需特殊处理添加第一个元素和添加到尾节点两种特殊情况
public class DulCircleLinkedList<E> extends AbstractList<E> {
......
/**
* 往index位置添加元素 --- 改造
* @param index
* @param element
*/
public void add(int index, E element){
//边界判断
rangeCheckForAdd(index);
// size == 0
// index == 0
if (index == size){//往最后添加元素
Node<E> oldLast = last;
last = new Node<>(element,oldLast,first);
if (oldLast == null){//这是链表的第一个元素
first = last;
//指向第一个节点
first.next = first;
first.prev = first;
}else {
oldLast.next = last;
first.prev = last;
}
}else {// 正常添加元素
//插入位置的原结点,即为新节点的next节点
Node<E> next = node(index);
//新添加结点的上一个节点,即为该位置原节点的上一个节点
Node<E> prev = next.prev;
//创建新结点
Node<E> node = new Node<>(element,prev,next);
//原结点上一个结点的next,即为新添加节点
prev.next = node;
//原结点的上一个结点,为新添加结点
next.prev = node;
if (next == first){//index == 0 即添加的是第一个
first = node;
}
}
size++;
}
......
}
- 删除节点,就是在环上去掉某一个节点,然后根据删除的节点是首节点或者尾节点来处理first和last
- 需要特殊处理只有一个节点的删除操作
public class DulCircleLinkedList<E> extends AbstractList<E> {
......
/**
* 删除index位置对应的元素 --- 改造
* @param index
* @return
*/
public E remove(int index){
//边界判断
rangeCheck(index);
Node<E> node = first;
if (size == 1){
first = null;
last = null;
}else {
//删除的结点
node = node(index);
//删除结点的上一个结点
Node<E> prev = node.prev;
//删除结点的下一个结点
Node<E> next = node.next;
prev.next = next;
next.prev = prev;
//删除结点, 只需去掉对这个结点的引用
if (node == first){//index == 0 即删除的是第一个
first = next;
}
if (next == last){//index == size - 1 即删除的是最后一个
last = prev;
}
}
size --;
return node.element;
}
......
}
完整代码
/**
* 双向循环链表的实现
* @param <E> 泛型
*/
public class DulCircleLinkedList<E> extends AbstractList<E> {
/**
* 元素所在的结点
*/
private Node<E> first;
//增加last节点
private Node<E> last;
/**
* 内部类 结点
* @param <E>
*/
private static class Node<E>{
E element;//元素
Node<E> prev;//增加上一个元素地址
Node<E> next;//下个元素的地址
public Node(E element, Node<E> prev,Node<E> next) {
this.element = element;
this.prev = prev;
this.next = next;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
if (prev !=null){
sb.append(prev.element);
}else {
sb.append("null");
}
sb.append("_").append(element).append("_");
if (next !=null){
sb.append(next.element);
}else {
sb.append("null");
}
return sb.toString();
}
}
/**
* 返回index位置对应的元素
* @param index
* @return
*/
public E get(int index){
return node(index).element;
}
/**
* 设置index位置的元素
* @param index
* @param element
* @return
*/
public E set(int index, E element){
Node<E> node = node(index);
E old = node.element;
node.element = element;
return old;
}
/**
* 往index位置添加元素 --- 改造
* @param index
* @param element
*/
public void add(int index, E element){
//边界判断
rangeCheckForAdd(index);
// size == 0
// index == 0
if (index == size){//往最后添加元素
Node<E> oldLast = last;
last = new Node<>(element,oldLast,first);
if (oldLast == null){//这是链表的第一个元素
first = last;
//指向第一个节点
first.next = first;
first.prev = first;
}else {
oldLast.next = last;
first.prev = last;
}
}else {// 正常添加元素
//插入位置的原结点,即为新节点的next节点
Node<E> next = node(index);
//新添加结点的上一个节点,即为该位置原节点的上一个节点
Node<E> prev = next.prev;
//创建新结点
Node<E> node = new Node<>(element,prev,next);
//原结点上一个结点的next,即为新添加节点
prev.next = node;
//原结点的上一个结点,为新添加结点
next.prev = node;
if (next == first){//index == 0 即添加的是第一个
first = node;
}
}
size++;
}
/**
* 删除index位置对应的元素 --- 改造
* @param index
* @return
*/
public E remove(int index){
//边界判断
rangeCheck(index);
Node<E> node = first;
if (size == 1){
first = null;
last = null;
}else {
//删除的结点
node = node(index);
//删除结点的上一个结点
Node<E> prev = node.prev;
//删除结点的下一个结点
Node<E> next = node.next;
prev.next = next;
next.prev = prev;
//删除结点, 只需去掉对这个结点的引用
if (node == first){//index == 0 即删除的是第一个
first = next;
}
if (next == last){//index == size - 1 即删除的是最后一个
last = prev;
}
}
size --;
return node.element;
}
/**
* 查看元素的位置
* @param element
* @return
*/
public int indexOf(E element){
// 取出头结点
Node<E> node = first;
if(element ==null){// 当element为null时的处理
// 遍历节点, 找到存储为null的节点, 返回索引
for (int i = 0; i < size; i++) {
if (node.element == null) return i;
node = node.next;
}
}else {
// 遍历节点, 找到存储的元素与指定元素相等的节点, 返回索引
for (int i = 0; i < size; i++) {
if (element.equals(node.element)) return i;
node = node.next;
}
}
return ELEMENT_NOT_FOUND;
}
/**
* 清除所有元素
*/
public void clear(){
size = 0;
first = null;
last = null;
}
/**
* 获取index位置对应的节点对象
* @param index
* @return
*/
private Node<E> node(int index){
rangeCheck(index);
//往后
if (index < (size >> 1)){
Node<E> node = first;
for (int i = 0; i < index; i++) {
node = node.next;
}
return node;
}else {//往前
Node<E> node = last;
for (int i = size - 1; i > index; i--) {
node = node.prev;
}
return node;
}
}
/**
* 打印
* @return
*/
@Override
public String toString() {
//Size = ? , [1, 2, 3]
StringBuilder string = new StringBuilder();
string.append("size=").append(size).append(", [");
Node<E> node = first;
for (int i = 0; i < size; i++) {
if (i != 0){
string.append(", ");
}
string.append(node);
node = node.next;
}
string.append("]");
return string.toString();
}
}
如何发挥循环链表的最大威力?
约瑟夫问题
可以考虑增设1个成员变量、3个方法:
- current :用于指向某个节点
- void reset() :让 current 指向头结点 first
- E next():让 current 往后走一步,也就是 current = current.next
- E remove() :删除 current 指向的节点,删除成功后让 current 指向下一个节点
在双向循环链表的基础上增强
/**
* 双向循环链表的实现 --- 增强
* @param <E> 泛型
*/
public class DulCircleLinkedListPlus<E> extends AbstractList<E> {
/**
* 元素所在的结点
*/
private Node<E> first;
//增加last节点
private Node<E> last;
//用来指向某节点
private Node<E> current;
/**
* 内部类 结点
* @param <E>
*/
private static class Node<E>{
E element;//元素
Node<E> prev;//增加上一个元素地址
Node<E> next;//下个元素的地址
public Node(E element, Node<E> prev,Node<E> next) {
this.element = element;
this.prev = prev;
this.next = next;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
if (prev !=null){
sb.append(prev.element);
}else {
sb.append("null");
}
sb.append("_").append(element).append("_");
if (next !=null){
sb.append(next.element);
}else {
sb.append("null");
}
return sb.toString();
}
}
/**
* 返回index位置对应的元素
* @param index
* @return
*/
public E get(int index){
return node(index).element;
}
/**
* 设置index位置的元素
* @param index
* @param element
* @return
*/
public E set(int index, E element){
Node<E> node = node(index);
E old = node.element;
node.element = element;
return old;
}
/**
* 往index位置添加元素 --- 改造
* @param index
* @param element
*/
public void add(int index, E element){
//边界判断
rangeCheckForAdd(index);
// size == 0
// index == 0
if (index == size){//往最后添加元素
Node<E> oldLast = last;
last = new Node<>(element,oldLast,first);
if (oldLast == null){//这是链表的第一个元素
first = last;
//指向第一个节点
first.next = first;
first.prev = first;
}else {
oldLast.next = last;
first.prev = last;
}
}else {// 正常添加元素
//插入位置的原结点,即为新节点的next节点
Node<E> next = node(index);
//新添加结点的上一个节点,即为该位置原节点的上一个节点
Node<E> prev = next.prev;
//创建新结点
Node<E> node = new Node<>(element,prev,next);
//原结点上一个结点的next,即为新添加节点
prev.next = node;
//原结点的上一个结点,为新添加结点
next.prev = node;
if (next == first){//index == 0 即添加的是第一个
first = node;
}
}
size++;
}
/**
* 删除index位置对应的元素 --- 改造
* @param index
* @return
*/
public E remove(int index){
//边界判断
rangeCheck(index);
return remove(node(index));
}
/**
* 根据节点删除 --- 新增
* @param node
* @return
*/
private E remove(Node<E> node){
if (size == 1){
first = null;
last = null;
}else {
//删除结点的上一个结点
Node<E> prev = node.prev;
//删除结点的下一个结点
Node<E> next = node.next;
prev.next = next;
next.prev = prev;
//删除结点, 只需去掉对这个结点的引用
if (node == first){//index == 0 即删除的是第一个
first = next;
}
if (next == last){//index == size - 1 即删除的是最后一个
last = prev;
}
}
size --;
return node.element;
}
/**
* 查看元素的位置
* @param element
* @return
*/
public int indexOf(E element){
// 取出头结点
Node<E> node = first;
if(element ==null){// 当element为null时的处理
// 遍历节点, 找到存储为null的节点, 返回索引
for (int i = 0; i < size; i++) {
if (node.element == null) return i;
node = node.next;
}
}else {
// 遍历节点, 找到存储的元素与指定元素相等的节点, 返回索引
for (int i = 0; i < size; i++) {
if (element.equals(node.element)) return i;
node = node.next;
}
}
return ELEMENT_NOT_FOUND;
}
/**
* 清除所有元素
*/
public void clear(){
size = 0;
first = null;
last = null;
}
/**
* 获取index位置对应的节点对象
* @param index
* @return
*/
private Node<E> node(int index){
rangeCheck(index);
//往后
if (index < (size >> 1)){
Node<E> node = first;
for (int i = 0; i < index; i++) {
node = node.next;
}
return node;
}else {//往前
Node<E> node = last;
for (int i = size - 1; i > index; i--) {
node = node.prev;
}
return node;
}
}
/**
* 重置 指向头节点 --- 新增
*/
public void reset(){
current = first;
}
/**
* current的下一个节点 --- 新增
* @return
*/
public E next(){
if (current == null) return null;
current = current.next;
return current.element;
}
/**
* 删除current指向的节点,删除成功后让current指向下一个节点 --- 新增
* @return
*/
public E remove(){
if (current == null) return null;
Node<E> next= current.next;
E element = remove(current);
if (size == 0){
current = null;
}else {
current = next;
}
return element;
}
/**
* 打印
* @return
*/
@Override
public String toString() {
//Size = ? , [1, 2, 3]
StringBuilder string = new StringBuilder();
string.append("size=").append(size).append(", [");
Node<E> node = first;
for (int i = 0; i < size; i++) {
if (i != 0){
string.append(", ");
}
string.append(node);
node = node.next;
}
string.append("]");
return string.toString();
}
}
解决约瑟夫问题
public class Test {
public static void main(String[] args) {
josephus();
}
public static void josephus(){
DulCircleLinkedListPlus<Integer> list = new DulCircleLinkedListPlus<>();
for(int i = 1; i <= 8; i++){
list.add(i);
}
list.reset(); // current->1
while(!list.isEmpty()){
list.next();
list.next();
System.out.println(list.remove());
}
}
}
静态链表
前面所学习的链表,是依赖于指针(引用)实现的,有些编程语言是没有指针的,比如早期的 BASIC、FORTRAN 语言,没有指针的情况下,如何实现链表?
- 可以通过数组来模拟链表,称为静态链表
- 数组的每个元素存放 2 个数据:值、下个元素的索引
- 数组 0 位置存放的是头结点信息
可以表示如下结构
思考:如果数组的每个元素只能存放 1 个数据呢?
- 那就使用 2 个数组,1 个数组存放索引关系,1 个数组存放值