在之前的文章中☞Java实现链表之单链表,写过一遍单链表,针对链表的插入、删除操作,只需要对插入第一个结点和删除最后一个结点的情况进行特殊处理。这样代码实现就会很繁琐,不简洁。如果引入哨兵结点,不管链表是不是空,head指针都会一直指向这个哨兵结点。
哨兵结点一直存在,其不储存数据,只储存下一个结点的地址,所以插入第一个结点和删除最后一个结点都可以统一为相同的代码实现。把有哨兵结点的链表叫做带头链表,没有哨兵结点的叫做不带头链表。
今天用带头链表实现一个单链表。
这套代码封装了 插入、追加、根据下标查找节点、根据下标查找节点的元素、根据元素查找在链表中的下标、根据下标删除节点 的操作。
package com.current;
import org.apache.commons.lang3.StringUtils;
/**
* @author Leo
* @ClassName LeadingLinkedList
* @DATE 2020/6/20 1:13 下午
* @Description 带头链表
*/
public class LeadingLinkedList<T> {
/*哨兵结点*/
public Node<T> sentry = new Node();
private int length;
private LeadingLinkedList<String> LeadingLinkedList;
public LeadingLinkedList() {
this.length = 0;
}
public int getLength() {
return length;
}
private static class Node<T> {
private T obj;
private Node<T> next;
public Node() {
}
public Node(T obj) {
if (obj == null) {
return;
}
this.obj = obj;
}
public T getObj() {
return obj;
}
public void setObj(T obj) {
this.obj = obj;
}
public Node<T> getNext() {
return next;
}
public void setNext(Node<T> next) {
this.next = next;
}
}
/**
* 功能描述 : 根据index位置插入
* @author Leo
* @date 2020/6/20 2:23 下午
* @param obj
* @param index
* @throw
* @return void
*/
public void insert(T obj, int index) {
verifyByIndex(index);
Node node = new Node(obj);
Node preNode ;
if (index == 0) {
preNode = this.sentry;
} else {
preNode = this.findIndexByNode(index - 1);
}
Node tempNode = preNode.getNext();
node.setNext(tempNode);
preNode.setNext(node);
}
/**
* 功能描述 : 追加
* @author Leo
* @date 2020/6/20 1:46 下午
* @param obj
* @throw
* @return void
*/
public void append(T obj) {
Node<T> node = new Node(obj);
if (this.length <= 0) {
this.sentry.setNext(node);
} else {
Node endNode = this.findIndexByNode( this.length - 1);
endNode.setNext(node);
}
this.length++;
}
/**
* 功能描述 : 根据index位置查找结点
* @author Leo
* @date 2020/6/20 2:03 下午
* @param index
* @throw
* @return com.current.LeadingLinkedList.Node
*/
public Node findIndexByNode(int index) {
verifyByIndex(index);
int currentIndex = 0;
Node currentNode = this.sentry.getNext();
while (currentNode.getNext() != null) {
if (currentIndex == index) {
break;
}
currentNode = currentNode.getNext();
currentIndex++;
}
return currentNode;
}
/**
* 功能描述 : 根据index位置查找结点中的元素
* @author Leo
* @date 2020/6/20 2:03 下午
* @param index
* @throw
* @return T
*/
public T find(int index) {
verifyByIndex(index);
return (T) findIndexByNode(index).getObj();
}
/**
* 功能描述 : 根据对象返回结点位置 -1 表示查找的对象在链表中不存在
* @param object
* @return int
* @author Leo
* @date 2020/6/20 3:06 下午
* @throw
*/
public int findNodeIndexByObject(T object) {
int currentIndex = -1;
Node currentNode = this.sentry;
while (currentNode.getNext() != null) {
currentIndex++;
if (currentNode.getNext().getObj() == object) {
break;
}
currentNode = currentNode.getNext();
}
return currentIndex;
}
/**
* 功能描述 : 根据index位置删除
* @author Leo
* @date 2020/6/20 2:22 下午
* @param index
* @throw
* @return void
*/
public void delete(int index) {
verifyByIndex(index);
Node preNode;
if (index == 0) {
preNode = this.sentry;
} else {
preNode= this.findIndexByNode(index - 1);
}
preNode.setNext(preNode.getNext().getNext());
}
/**
* 功能描述 : 验证index位置是否合法
* @author Leo
* @date 2020/6/20 2:22 下午
* @param index
* @throw
* @return void
*/
private void verifyByIndex(int index) {
if (index >= this.length || index < 0) {
throw new IllegalArgumentException("index is error!");
}
}
/**
* 功能描述 : toString()
* @author Leo
* @date 2020/6/20 2:25 下午
* @param
* @throw
* @return java.lang.String
*/
@Override
public String toString() {
StringBuffer LeadingLinkedListStr = new StringBuffer("LeadingLinkedList={ ");
String LeadingLinkedListContentStr = StringUtils.EMPTY;
if (this.length > 0) {
StringBuffer LeadingLinkedListContent = new StringBuffer();
Node currentNode = this.sentry;
while (currentNode.getNext() != null) {
LeadingLinkedListContent.append("," + currentNode.getNext().getObj());
currentNode = currentNode.getNext();
}
LeadingLinkedListContentStr = LeadingLinkedListContent.substring(1);
}
return LeadingLinkedListStr.append(LeadingLinkedListContentStr).append(" }").toString();
}
}
可以明显感觉到,处理链表结构的逻辑确实非常方便,代码量也很少。
单链表的常见操作有:
链表翻转,就是将其节点顺序倒序排列一遍并返回,就是跟StringUtils.reverse(str)一个意思。
没有深入学习之前,我的代码是这样的:
public static LeadingLinkedList reversalDeleteAndInsert(LeadingLinkedList leadingLinkedList) {
if (leadingLinkedList.getLength() == 0) {
return leadingLinkedList;
}
long start = System.currentTimeMillis();
int first = 0;
int end = leadingLinkedList.getLength()-1;
while (first <= end) {
Object firstObj = leadingLinkedList.findIndexByNode(first).getObj();
Object endObj = leadingLinkedList.findIndexByNode(end).getObj();
//删除 插入
leadingLinkedList.delete(end);
leadingLinkedList.insert(firstObj,end);
leadingLinkedList.delete(first);
leadingLinkedList.insert(endObj,first);
first++;
end--;
}
System.out.println("翻转用时: " + (System.currentTimeMillis() - start));
return leadingLinkedList;
}
先根据指针位置查找节点,然后删除节点,最后在指针的位置插入节点。
链表单独的删除和插入的时间复杂度为O(1), 删除和插入的操作如果算上查询的时间复杂度为O(n), 随机访问的时间复杂度是O(n),不得不说我这段非常low的代码完全没有发挥链表的优势,反而将链表的劣势发挥至了极致。
普遍将这种方式称为“原地翻转”。
public static LeadingLinkedList reversal(LeadingLinkedList leadingLinkedList) {
if (leadingLinkedList.getLength() == 0) {
return leadingLinkedList;
}
long start = System.currentTimeMillis();
Node sentry = leadingLinkedList.sentry;
Node prevNode = sentry.getNext();
Node curNode = prevNode.getNext();
while (curNode != null) {
prevNode.setNext(curNode.getNext());
curNode.setNext(sentry.getNext());
sentry.setNext(curNode);
curNode = prevNode.getNext();
}
System.out.println("翻转用时: " + (System.currentTimeMillis() - start));
return leadingLinkedList;
}
第一步,将curNode的next指针赋值给prevNode的next指针。
prevNode.setNext(curNode.getNext());
第二步,将curNode的next指向prevNode节点,prevNode节点可以通过哨兵节点找到。
curNode.setNext(sentry.getNext());
第三步,将哨兵节点的next指向curNode节点,即可完成改变位置。
sentry.setNext(curNode);
第四步,需要将curNode指针指向prevNode的next节点。
curNode = prevNode.getNext();
如果curNode != null,则while继续执行。
第二次循环,执行上面四步结果如下:
public static void main(String[] args){
LeadingLinkedList<String> leadingLinkedList = new LeadingLinkedList<String>();
for (int i = 0; i < 10000; i++) {
leadingLinkedList.append(String.valueOf(i));
}
System.out.println("链表初始化...");
System.out.println("链表长度: "+leadingLinkedList.getLength()+";当前链表数据: "+leadingLinkedList.toString());
System.out.println("原地翻转...");
leadingLinkedList = LeadingLinkedList.reversal(leadingLinkedList);
System.out.println("链表长度: "+leadingLinkedList.getLength()+";当前链表数据: "+leadingLinkedList.toString());
System.out.println("low翻转...");
leadingLinkedList = LeadingLinkedList.reversalDeleteAndInsert(leadingLinkedList);
System.out.println("链表长度: "+leadingLinkedList.getLength()+";当前链表数据: "+leadingLinkedList.toString());
}