用java实现单链表之链表翻转

用java实现单链表之链表翻转

  • 1. 带头链表
    • 1.1 代码
  • 2. 链表翻转
    • 2.1 没有深入学习数据结构和算法时的代码
    • 2.2 学习数据结构和算法后的代码
    • 2.3 测试
  • 3. 最后

1. 带头链表

  在之前的文章中☞Java实现链表之单链表,写过一遍单链表,针对链表的插入、删除操作,只需要对插入第一个结点和删除最后一个结点的情况进行特殊处理。这样代码实现就会很繁琐,不简洁。如果引入哨兵结点,不管链表是不是空,head指针都会一直指向这个哨兵结点。
  哨兵结点一直存在,其不储存数据,只储存下一个结点的地址,所以插入第一个结点和删除最后一个结点都可以统一为相同的代码实现。把有哨兵结点的链表叫做带头链表,没有哨兵结点的叫做不带头链表。

  今天用带头链表实现一个单链表。

1.1 代码

  这套代码封装了 插入追加根据下标查找节点根据下标查找节点的元素根据元素查找在链表中的下标根据下标删除节点 的操作。

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();
    }
    
}


  可以明显感觉到,处理链表结构的逻辑确实非常方便,代码量也很少。

2. 链表翻转

  单链表的常见操作有:

  • 单链表翻转
  • 链表中环的检测
  • 两个有序链表合并
  • 删除链表倒数第n个结点
  • 求链表的中间结点

链表翻转,就是将其节点顺序倒序排列一遍并返回,就是跟StringUtils.reverse(str)一个意思。
用java实现单链表之链表翻转_第1张图片

2.1 没有深入学习数据结构和算法时的代码

  没有深入学习之前,我的代码是这样的:

	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的代码完全没有发挥链表的优势,反而将链表的劣势发挥至了极致。
          用java实现单链表之链表翻转_第2张图片

2.2 学习数据结构和算法后的代码

  普遍将这种方式称为“原地翻转”。

    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;
    }
  • 实现思路
  1. 根据哨兵节点找到第一个结点作为prevNode指针,将prevNode的下一个节点作为curNode指针。
    用java实现单链表之链表翻转_第3张图片
  2. 将prevNode与curNode节点互换位置。这一步也是相对较为难理解的,prevNode和curNode两个节点互换位置,只需要改变prevNode和curNode两个节点的next指针,再改变prevNode结点的前一个结点,即哨兵节点的next指针就好了。

    第一步,将curNode的next指针赋值给prevNode的next指针。

prevNode.setNext(curNode.getNext());

用java实现单链表之链表翻转_第4张图片
    第二步,将curNode的next指向prevNode节点,prevNode节点可以通过哨兵节点找到。

curNode.setNext(sentry.getNext());

用java实现单链表之链表翻转_第5张图片

    第三步,将哨兵节点的next指向curNode节点,即可完成改变位置。

sentry.setNext(curNode);

用java实现单链表之链表翻转_第6张图片
    第四步,需要将curNode指针指向prevNode的next节点。

curNode = prevNode.getNext();

用java实现单链表之链表翻转_第7张图片
    如果curNode != null,则while继续执行。
    第二次循环,执行上面四步结果如下:
用java实现单链表之链表翻转_第8张图片

2.3 测试

  • 测试代码
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());

    }

测试结果
  如果将测试结果粘贴到这里的话,博客提示:
用java实现单链表之链表翻转_第9张图片

  • 测试结果如下:
    用java实现单链表之链表翻转_第10张图片
    这时间差距还是差挺大的。

3. 最后

学习没有捷径,后续还会写关于数据结构与算法的文章,欢迎交流。
            用java实现单链表之链表翻转_第11张图片

你可能感兴趣的:(数据结构与算法)