单向链表反转

单向链表的反转是一个出现频次非常高的面试题,我曾经就在这个题上吃过亏,但是不要紧,咱研究一下也就出来了。

先说链表的单向特点:

1. 只有从前向后才能找到某一个节点

2. 最后一个节点的下一个节点地址(也就是我们通常所说的next指针)是null

3. 链表有可能是个空链表,即:它的首个节点元素就是null

4. 链表通常都有add、insert、delete、contains等几个方法

而单向链表的反转,又是一个很考验人的问题,我在网上查了很多资料,最简单的是递归反转,但是我个人认为这个方法不好,因为每次递归调用方法或者函数,都要开辟新的函数栈,系统性能开销太大,而且链表如果很长,有可能会导致栈溢出所以应当使用循环的方式,而网上查找到的循环实现方式,都太过复杂,咱们来个简单的:

先看链表的定义(MyLinkedList.java):

import com.google.gson.Gson;

/**
 * 单向链表
 * Created by lidawei on 2017/2/10.
 */
public class MyLinkedList<T> {
    private Node<T> root;
    private int position;
    private int size;

    /**
     * 构造器
     */
    public MyLinkedList() {}

    /**
     * 添加元素
     * @param data 指定节点数据
     */
    public void add(T data) {
        Node<T> node = new Node<>();
        node.data = data;
        if (root == null) {
            root = node;
            size++;
            return;
        }

        Node<T> current = root;
        while (current.next != null) {
            current = current.next;
        }
        current.next = node;
        size++;
    }

    /**
     * 插入元素
     * @param index 插入到指定的位置,此位置的数据会向后移动,索引位置从0开始
     * @param data 指定节点数据
     */
    public void insert(int index, T data) {
        checkRange(index);
        Node<T> previous = root, current = root;
        while (index != position) {
            position++;
            previous = current;
            current = current.next;
        }

        Node<T> obj = new Node<>();
        obj.data = data;

        previous.next = obj;
        obj.next = current;
        size++;
    }

    /**
     * 删除元素
     * @param index 指定索引位置,此位置的数据被删除之后后面的元素会依次向前递补
     */
    public void delete(int index) {
        checkRange(index);
        Node<T> previous = root, current = root;
        int pos = 0;
        while (current.next != null) {
            if (pos == index) {
                break;
            }
            pos++;
            previous = current;
            current = current.next;
        }
        previous.next = current.next;
        size--;
    }

    /**
     * 是否包含指定的数据
     * @param data 节点数据
     * @return 包含时返回true,否则返回false
     */
    public boolean contains(T data) {
        Node<T> current = root;
        while (current != null) {
            if (current.data.equals(data))
                return true;
            else
                current = current.next;
        }
        return false;
    }

    /**
     * 获得链表长度
     * @return 返回一个整数,代表链表节点的数量
     */
    public int size() {
        return size;
    }

    private void checkRange(int index) {
        if (index < 0 ||index >= size) {
            throw new IllegalArgumentException("索引值已经越界!");
        }
    }

    /**
     * 反转链表,此操作是原地反转,也就是会修改现有的链接结构,不会创建新的链表。
     */
    public void reverse() {
        if (root == null || root.next == null)   return;

        Node<T> previous = root;
        Node<T> current = previous.next;
        Node<T> temp;
        while (current != null) {
            temp = current.next;
            current.next = previous;
            previous = current;
            current = temp;
        }
        root.next = null;
        root = previous;
    }

    @Override
    public String toString() {
        StringBuffer sb = new StringBuffer(size * 64);
        sb.append("[");
        Node<T> current = root;
        Gson gson = new Gson();
        while (current != null) {
            sb.append(gson.toJson(current.data));
            sb.append(",");
            current = current.next;
        }
        int pos = sb.lastIndexOf(",");
        if (pos != -1) {
            sb.deleteCharAt(pos);
        }
        sb.append("]");

        return sb.toString();
    }

    private class Node<T> {
        Node next;
        T data;

        @Override
        public String toString() {
            Gson gson = new Gson();
            return gson.toJson(this);
        }
    }
}
定义中我已经写好了reverse方法,这个方法的实现很简单,看懂下面这张图,就全明白了:


注意:

图中的保存后续节点、更新指向、移动操作指针都是在循环中进行的,循环的条件当然是current != null。


请大家对照图和代码过一遍,马上就能明白了,好了,下面是我的测试用例(已经覆盖了所有方法):

import org.junit.Assert;
import org.junit.Test;

public class MyLinkedListTest {
    @Test
    public void givenCharactorWhenInsertThenIncreaseOne() {
        MyLinkedList<Character> list = new MyLinkedList<>();
        list.add('a');
        list.add('b');
        list.add('c');
        System.out.println(list);

        list.insert(2, 'e');

        System.out.println(list);
    }

    @Test
    public void givenIntegerWhenFindThenRight() {
        MyLinkedList<Integer> list = new MyLinkedList<>();
        list.add(11);
        list.add(22);
        Assert.assertTrue(list.contains(11));
        Assert.assertTrue(list.contains(22));
    }

    @Test
    public void givenIntegerWhenDeleteThenShrinkOne() {
        MyLinkedList<Integer> list = new MyLinkedList<>();
        list.add(1);
        list.add(2);
        list.add(3);
        System.out.println(list);

        list.delete(2);

        System.out.println(list);
    }

    @Test
    public void givenDoubleWhenDeleteThenCheckSize() {
        MyLinkedList<Double> list = new MyLinkedList<>();
        list.add(1.0);
        list.add(2.1);
        list.add(3.2);
        System.out.println(list);

        Assert.assertTrue(list.size() == 3);
    }

    @Test
    public void givenIntegerWhenReverseThenRight() {
        MyLinkedList<Integer> list = new MyLinkedList<>();
        list.add(1);
        list.add(2);
        list.add(3);
        list.add(4);
        list.add(5);
        System.out.println(list);
        System.out.println("Reverse...");

        list.reverse();

        System.out.println(list);
    }

    @Test
    public void givenStringWhenReverseThenRight() {
        MyLinkedList<String> list = new MyLinkedList<>();
        list.add("abc");
        list.add("def");
        list.add("hij");
        list.add("klmn");
        list.add("zzz");
        System.out.println(list);
        System.out.println("Reverse...");

        list.reverse();

        System.out.println(list);
    }
}

运行结果:



哈哈。。。是不是很简单?!!!

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

互联网速递520

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值