双向链表
- 使用双向链表可以提升链表的综合性能;其结构如下图所示:
- size表示链表中已经存储元素的个数;
- first指针指向链表的首节点,last指针指向链表的尾节点;
- Node是节点模型,element是数据域存储数据元素,prev是指针域,指向当前节点的前面一个节点,next是指针域,指向当前节点的后面一个节点。
双向链表的接口设计与动态数组,单向链表的类似
代码实现如下:
package com.example.linkedlist.list;
/**
* 双向链表
*/
public class YYLinkedList<E> extends AbstrctList<E> {
/**
* 首节点
*/
private Node<E> first;
/**
* 尾节点
*/
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() {
StringBuffer sb = new StringBuffer();
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();
}
}
@Override
public void insertObjectAtIndex(int index, E element) {
rangeCheckForInsert(index);
//首先根据index找到当前节点
if (index == size){//往最后面插入元素
Node<E> oldLast = last;
last = new Node<>(element,oldLast,null);
if (oldLast == null){//这是链表添加的第一个元素
first = last;
}else {
oldLast.next = last;
}
}else{
//往首节点插入元素
Node<E> node = nodeOfIndex(index);
Node<E> newNode = new Node<>(element,node.prev,node);
if (node.prev == null){
node.prev = newNode;
first = newNode;
}else {//往中间插入元素
newNode.prev.next = newNode;
node.prev = newNode;
}
}
size++;
}
@Override
public E removeObjectAtIndex(int index) {
rangeCheck(index);
Node<E> node = nodeOfIndex(index);
if (node.prev == null){ //index = 0
first = node.next;
}else {
node.prev.next = node.next;
}
if (node.next == null){//index = size - 1
last = node.prev;
}else {
node.next.prev = node.prev;
}
size--;
return node.element;
}
@Override
public void clear() {
first = null;
last = null;
size = 0;
}
@Override
public E setObjectAtIndex(int index, E element) {
Node<E> node = nodeOfIndex(index);
E old = node.element;
node.element = element;
return old;
}
@Override
public E objectAtIndex(int index) {
return nodeOfIndex(index).element;
}
@Override
public int indexOfObject(E element) {
if (element == null){
Node<E> node = first;
for (int i = 0;i < size;i++){
node = node.next;
if (node.element == null) return I;
}
}else {
Node<E> node = first;
for (int i = 0;i < size;i++){
node = node.next;
if (node.element.equals(element)) return I;
}
}
return ELEMENT_NOT_FOUND;
}
/**
* 根据index索引 返回节点
* @param index
* @return
*/
private Node<E> nodeOfIndex(int index){
rangeCheck(index);
//当index小于size的一半,以first首节点为起点,进行查找
if (index < (size >> 1)){
Node<E> node = first;
for (int i = 0;i < index;i++){
node = node.next;
}
return node;
}else {//当index小于size的一半,以last尾节点为起点,进行查找
Node<E> node = last;
for (int i = size - 1;i > index;i--){
node = node.prev;
}
return node;
}
}
@Override
public String toString() {
StringBuffer string = new StringBuffer();
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 void insertObjectAtIndex(int index, E element)
:往链表指定index位置插入元素element;
@Override
public void insertObjectAtIndex(int index, E element) {
rangeCheckForInsert(index);
//首先根据index找到当前节点
if (index == size){//往最后面插入元素
Node<E> oldLast = last;
last = new Node<>(element,oldLast,null);
if (oldLast == null){//这是链表添加的第一个元素
first = last;
}else {
oldLast.next = last;
}
}else{
//往首节点插入元素
Node<E> node = nodeOfIndex(index);
Node<E> newNode = new Node<>(element,node.prev,node);
if (node.prev == null){
node.prev = newNode;
first = newNode;
}else {//往中间插入元素
newNode.prev.next = newNode;
node.prev = newNode;
}
}
size++;
}
代码逻辑中主要分为四种情形:
- 1> 往空链表中添加元素,原理如下图所示:
- 2> 往链表的尾节点添加元素,原理如下图所示:
- 3> 往链表的首节点添加元素,原理如下图所示:
- 4> 往链表中间节点index=2添加元素,原理如下图所示:
-
public E removeObjectAtIndex(int index)
:移除指定index位置的元素
@Override
public E removeObjectAtIndex(int index) {
rangeCheck(index);
Node<E> node = nodeOfIndex(index);
if (node.prev == null){ //index = 0
first = node.next;
}else {
node.prev.next = node.next;
}
if (node.next == null){//index = size - 1
last = node.prev;
}else {
node.next.prev = node.prev;
}
size--;
return node.element;
}
代码逻辑中主要分为四种情况:
- 1> 当链表只有一个节点元素时删除节点,原理如下图所示:
- 2> 移除首节点元素,原理如下图所示:
- 3> 当删除链表的尾节点,原理如下图所示:
- 4> 当删除链表的中间节点index=2,原理如下图所示:
-
public void clear()
:清空链表,其实现原理如下图所示:
@Override
public void clear() {
first = null;
last = null;
size = 0;
}
首先介绍一下 gc root对象;
被栈指针指向的对象称之为gc root对象;只有当栈指针销毁,对象才会销毁;
如果对象没有被栈指针指向,而是堆指针引用,此对象会释放销毁;
所以中间节点的相互引用都属于堆指针引用,不会形成循环引用,节点都会回收。
private Node<E> nodeOfIndex(int index)
:根据index获取节点
private Node<E> nodeOfIndex(int index){
rangeCheck(index);
//当index小于size的一半,以first首节点为起点,进行查找
if (index < (size >> 1)){
Node<E> node = first;
for (int i = 0;i < index;i++){
node = node.next;
}
return node;
}else {//当index大于size的一半,以last尾节点为起点,进行查找
Node<E> node = last;
for (int i = size - 1;i > index;i--){
node = node.prev;
}
return node;
}
}
为了提高查找效率,查找逻辑如下:
- 当index小于size的一半,以first首节点为起点,进行查找;
- 当index大于size的一半,以last尾节点为起点,进行查找。
测试代码如下:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
YYLinkedList linkedList = new YYLinkedList();
linkedList.addObject(11);
linkedList.addObject(22);
linkedList.addObject(33);
linkedList.addObject(44);
linkedList.addObject(55);
System.out.println(linkedList);
linkedList.insertObjectAtIndex(1,88);
System.out.println(linkedList);
linkedList.removeObjectAtIndex(1);
System.out.println(linkedList);
}
调试结果如下:
- 打印中将当前节点的prev节点与next节点的数据元素都打印出来了。
单向链表与双向链表的对比
针对删除的操作数量
- 单向链表:平均复杂度 = 1+2+3+....+n = n/2+n^2/2,再除以n最后为:1/2+n/2
- 双向链表:平均复杂度 = (1+2+3+....+n/2)*2=n/2+n^2/4,再除以n最后为:1/2+n/4
- 可以看出复杂度相同,但双向链表操作数量缩减了一半。
双向链表与动态数组的对比
- 动态数组:开辟,销毁内存空间的次数相对较少,但可能造成内存空间的浪费(可以通过缩容来解决)
- 双向链表:开辟,销毁内存空间的次数相对较多,但不会造成内存空间的浪费。
- 如果频繁的在尾部进行添加,删除操作,动态数组,双向链表均可选择;
- 如果频繁的在头部进行添加,删除操作,建议使用双向链表;
- 如果频繁的在任意位置进行添加,删除操作,建议使用使用双向链表;
- 如果频繁的进行查询操作(随机查询),建议选择使用动态数组,通过地址直接查询。
- 在哈希表中会使用到单向链表。
双向循环链表
- 双向链表尾节点的next指针指向首节点,首节点的prev指针指向尾节点的链表属于双向循环链表;其结构如下图所示:
代码实现如下:
/**
* 双向循环链表
*/
public class YYCircleLinkedList<E> extends AbstrctList<E> {
/**
* 首节点
*/
private Node<E> first;
/**
* 尾节点
*/
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() {
StringBuffer sb = new StringBuffer();
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();
}
@Override
protected void finalize() throws Throwable {
super.finalize();
System.out.println("Node<E> -- finalize");
}
}
@Override
public void insertObjectAtIndex(int index, E element) {
rangeCheckForInsert(index);
//首先根据index找到当前节点
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{
//往首节点插入元素
Node<E> node = nodeOfIndex(index);
if (index == 0){
Node<E> newNode = new Node<>(element,last,node);
node.prev = newNode;
first = newNode;
last.next = newNode;
}else {//往中间插入元素
Node<E> newNode = new Node<>(element,node.prev,node);
newNode.prev.next = newNode;
node.prev = newNode;
}
}
size++;
}
@Override
public E removeObjectAtIndex(int index) {
rangeCheck(index);
Node<E> node = first;
if (size == 1){
first = null;
last = null;
}else {
node = nodeOfIndex(index);
node.prev.next = node.next;
node.next.prev = node.prev;
if (index == 0){
first = node.next;
}
if (index == size - 1){
last = node.prev;
}
}
size--;
return node.element;
}
@Override
public void clear() {
first = null;
last = null;
size = 0;
}
@Override
public E setObjectAtIndex(int index, E element) {
Node<E> node = nodeOfIndex(index);
E old = node.element;
node.element = element;
return old;
}
@Override
public E objectAtIndex(int index) {
return nodeOfIndex(index).element;
}
@Override
public int indexOfObject(E element) {
if (element == null){
Node<E> node = first;
for (int i = 0;i < size;i++){
node = node.next;
if (node.element == null) return I;
}
}else {
Node<E> node = first;
for (int i = 0;i < size;i++){
node = node.next;
if (node.element.equals(element)) return I;
}
}
return ELEMENT_NOT_FOUND;
}
/**
* 根据index索引 返回节点
* @param index
* @return
*/
private Node<E> nodeOfIndex(int index){
rangeCheck(index);
//当index小于size的一半,以first首节点为起点,进行查找
if (index < (size >> 1)){
Node<E> node = first;
for (int i = 0;i < index;i++){
node = node.next;
}
return node;
}else {//当index小于size的一半,以last尾节点为起点,进行查找
Node<E> node = last;
for (int i = size - 1;i > index;i--){
node = node.prev;
}
return node;
}
}
@Override
public String toString() {
StringBuffer string = new StringBuffer();
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 void insertObjectAtIndex(int index, E element)
:往链表指定index位置插入元素element;
@Override
public void insertObjectAtIndex(int index, E element) {
rangeCheckForInsert(index);
//首先根据index找到当前节点
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{
//往首节点插入元素
Node<E> node = nodeOfIndex(index);
if (index == 0){
Node<E> newNode = new Node<>(element,last,node);
node.prev = newNode;
first = newNode;
last.next = newNode;
}else {//往中间插入元素
Node<E> newNode = new Node<>(element,node.prev,node);
newNode.prev.next = newNode;
node.prev = newNode;
}
}
size++;
}
代码逻辑主要分为四种情况:
- 1> 往空链表中添加元素,原理如下图所示:
- 2> 往链表的尾节点添加元素,原理如下图所示::
- 3> 往链表的首节点添加元素,原理如下图所示:
- 4> 往链表中间节点index=2添加元素,原理如下图所示:
-
public E removeObjectAtIndex(int index)
:移除指定index位置的元素
@Override
public E removeObjectAtIndex(int index) {
rangeCheck(index);
Node<E> node = first;
if (size == 1){
first = null;
last = null;
}else {
node = nodeOfIndex(index);
node.prev.next = node.next;
node.next.prev = node.prev;
if (index == 0){
first = node.next;
}
if (index == size - 1){
last = node.prev;
}
}
size--;
return node.element;
}
代码逻辑分为四种情况:
- 1> 当链表只有一个节点元素时删除节点,原理如下图所示:
- 2> 当删除链表的尾节点,原理如下图所示:
- 3> 当删除链表的首节点,原理如下图所示:
- 4> 当删除链表的中间节点index=2,原理如下图所示:
链表的添加与删除逻辑都可以抽象成下面的一般逻辑;
添加元素
- 1> 往空链表中添加元素;
- 2> 往链表的尾节点添加元素;
- 3> 往链表的首节点添加元素;
- 4> 往链表中间节点index=x添加元素;
删除元素
- 1> 当链表只有一个节点元素时删除节点;
- 2> 当删除链表的尾节点;
- 3> 当删除链表的首节点;
- 4> 当删除链表的中间节点index=x;
约瑟夫问题
- 约瑟夫问题是个有名的问题:N个人围成一圈,从第一个开始报数,第M个将被杀掉,最后剩下一个,其余人都将被杀掉。例如N=6,M=5,被杀掉的顺序是:5,4,6,2,3;类似于丢手绢游戏。
在双向循环链表的基础上实现,代码如下所示:
/**
* 双向循环链表 -- 解决约瑟夫问题(扩展)
*/
public class YYCircleLinkedList2<E> extends AbstrctList<E> {
/**
* 首节点
*/
private Node<E> first;
/**
* 尾节点
*/
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() {
StringBuffer sb = new StringBuffer();
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();
}
@Override
protected void finalize() throws Throwable {
super.finalize();
System.out.println("Node<E> -- finalize");
}
}
public void reset(){
current = first;
}
public E next(){
if (current == null) return null;
current = current.next;
return current.element;
}
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;
}
@Override
public void insertObjectAtIndex(int index, E element) {
rangeCheckForInsert(index);
//首先根据index找到当前节点
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{
//往首节点插入元素
Node<E> node = nodeOfIndex(index);
if (index == 0){
Node<E> newNode = new Node<>(element,last,node);
node.prev = newNode;
first = newNode;
last.next = newNode;
}else {//往中间插入元素
Node<E> newNode = new Node<>(element,node.prev,node);
newNode.prev.next = newNode;
node.prev = newNode;
}
}
size++;
}
@Override
public E removeObjectAtIndex(int index) {
rangeCheck(index);
return remove(nodeOfIndex(index));
}
private E remove(Node<E> node){
if (size == 1){
first = null;
last = null;
}else {
node.prev.next = node.next;
node.next.prev = node.prev;
if (node == first){//删除首节点 index = 0
first = node.next;
}
if (node == last){//删除尾节点 index = size - 1
last = node.prev;
}
}
size--;
return node.element;
}
@Override
public void clear() {
first = null;
last = null;
size = 0;
}
@Override
public E setObjectAtIndex(int index, E element) {
Node<E> node = nodeOfIndex(index);
E old = node.element;
node.element = element;
return old;
}
@Override
public E objectAtIndex(int index) {
return nodeOfIndex(index).element;
}
@Override
public int indexOfObject(E element) {
if (element == null){
Node<E> node = first;
for (int i = 0;i < size;i++){
node = node.next;
if (node.element == null) return I;
}
}else {
Node<E> node = first;
for (int i = 0;i < size;i++){
node = node.next;
if (node.element.equals(element)) return I;
}
}
return ELEMENT_NOT_FOUND;
}
/**
* 根据index索引 返回节点
* @param index
* @return
*/
private Node<E> nodeOfIndex(int index){
rangeCheck(index);
//当index小于size的一半,以first首节点为起点,进行查找
if (index < (size >> 1)){
Node<E> node = first;
for (int i = 0;i < index;i++){
node = node.next;
}
return node;
}else {//当index小于size的一半,以last尾节点为起点,进行查找
Node<E> node = last;
for (int i = size - 1;i > index;i--){
node = node.prev;
}
return node;
}
}
@Override
public String toString() {
StringBuffer string = new StringBuffer();
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();
}
}
新增了一个属性与三个方法;
- current:记录当前节点;
- reset:重置当前节点为首节点;
- next:将当前节点移动到下一个节点;
- remove:删除当前节点,并返回当前节点的数据元素。
代码测试:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
YYCircleLinkedList2 list = new YYCircleLinkedList2();
for (int i = 1; i <= 8;i++){
list.addObject(i);
}
System.out.println(list);
//指向头节点
list.reset();
while (!list.isEmpty()){
list.next();
list.next();
System.out.println(list.remove());
}
}
测试结果: