玩转数据结构(06)--链表_2

链表(Linked List)

(在上篇博客的基础上继续)

四、链表的遍历,查询和修改

示例代码:LinkedList.java

public class LinkedList<E> {

    private class Node{
        public E e;
        public Node next;

        public Node(E e, Node next){
            this.e = e;
            this.next = next;
        }

        public Node(E e){
            this(e, null);
        }

        public Node(){
            this(null, null);
        }

        @Override
        public String toString(){
            return e.toString();
        }
    }

    private Node dummyHead;
    private int size;

    public LinkedList(){
        dummyHead = new Node();
        size = 0;
    }

    // 获取链表中的元素个数
    public int getSize(){
        return size;
    }

    // 返回链表是否为空
    public boolean isEmpty(){
        return size == 0;
    }

    // 在链表的index(0-based)位置添加新的元素e
    // 在链表中不是一个常用的操作,练习用:)
    public void add(int index, E e){

        if(index < 0 || index > size)
            throw new IllegalArgumentException("Add failed. Illegal index.");

        Node prev = dummyHead;
        for(int i = 0 ; i < index ; i ++)
            prev = prev.next;

        prev.next = new Node(e, prev.next);
        size ++;
    }

    // 在链表头添加新的元素e
    public void addFirst(E e){
        add(0, e);
    }

    // 在链表末尾添加新的元素e
    public void addLast(E e){
        add(size, e);
    }

    // 获得链表的第index(0-based)个位置的元素
    // 在链表中不是一个常用的操作,练习用:)
    public E get(int index){	//(新增代码)

        if(index < 0 || index >= size)		//判断 Index 合法性
            throw new IllegalArgumentException("Get failed. Illegal index.");

        Node cur = dummyHead.next;		//Node 型 cur[当前] 从 dummyHead 的 next 从索引为0(也就是从第一个元素)开始
        for(int i = 0 ; i < index ; i ++)	//遍历元素
            cur = cur.next;
        return cur.e;		//e 就是第 Index 位置的元素
    }

    // 获得链表的第一个元素
    public E getFirst(){
        return get(0); 
    }

    // 获得链表的最后一个元素
    public E getLast(){
        return get(size - 1);
    }

    // 修改链表的第index(0-based)个位置的元素为e
    // 在链表中不是一个常用的操作,练习用:)
    public void set(int index, E e){	//(修改代码)
        if(index < 0 || index >= size)
            throw new IllegalArgumentException("Set failed. Illegal index.");

        Node cur = dummyHead.next;
        for(int i = 0 ; i < index ; i ++)
            cur = cur.next;
        cur.e = e;
    }

    // 查找链表中是否有元素e
    public boolean contains(E e){	//(修改代码)
        Node cur = dummyHead.next;
        while(cur != null){		//判断当前节点是否为有效节点
            if(cur.e.equals(e))	//判断 e 是否为用户传来的节点e
                return true;
            cur = cur.next;
        }
        return false;
    }

    @Override
    public String toString(){	//(修改代码)
        StringBuilder res = new StringBuilder();

//        Node cur = dummyHead.next;	//遍历(从第一个元素开始)
//        while(cur != null){
//            res.append(cur + "->");	//res 中加入 cur节点+ "->"
//            cur = cur.next;	//查看下一个节点 
//        }
        for(Node cur = dummyHead.next ; cur != null ; cur = cur.next)	//上面三行的优雅写法
            res.append(cur + "->");
        res.append("NULL");	//到达链表结尾

        return res.toString();
    }
}

Main.java

public class Main {

    public static void main(String[] args) {

        LinkedList<Integer> linkedList = new LinkedList<>();	//声明LinkedList,承载整型
        for(int i = 0 ; i < 5 ; i ++){	//为 LinkedList 添加 从0-4 这5个元素
            linkedList.addFirst(i);
            System.out.println(linkedList);
        }

        linkedList.add(2, 666); 	//在索引为2的位置添加 666
        System.out.println(linkedList);
    }
}

输出:先将 0-4 这5个数添加到链表中;再将 666 添加到 索引为2【第三个】的位置。

五、从链表中删除元素

有虚拟头结点的链表如图所示:

想要删除索引为 2 的元素,步骤:

1.先找到所要删除元素的前一个节点,即为图中的 1

2.prev 对应节点的 next 就是所要删除的节点,称为 delNode,

3.将 prev 对应节点的 next 赋值称为要删除的节点 delNode 的 next;即 执行 prev.next = delNode.next,从某种意义上来说,就将索引为 2 的元素从链表中删除了。 

4.让 索引为 2 的元素的 next 与链表整个脱离开来,即让 delNode 的 next 指向 NULL,即执行 delNode.next = null ;通过这个测试,就将索引为 2 的元素真正的从链表中删除。

示例代码:LinkedList.java

public class LinkedList<E> {

    private class Node{
        public E e;
        public Node next;

        public Node(E e, Node next){
            this.e = e;
            this.next = next;
        }

        public Node(E e){
            this(e, null);
        }

        public Node(){
            this(null, null);
        }

        @Override
        public String toString(){
            return e.toString();
        }
    }

    private Node dummyHead;
    private int size;

    public LinkedList(){
        dummyHead = new Node();
        size = 0;
    }

    // 获取链表中的元素个数
    public int getSize(){
        return size;
    }

    // 返回链表是否为空
    public boolean isEmpty(){
        return size == 0;
    }

    // 在链表的index(0-based)位置添加新的元素e
    // 在链表中不是一个常用的操作,练习用:)
    public void add(int index, E e){

        if(index < 0 || index > size)
            throw new IllegalArgumentException("Add failed. Illegal index.");

        Node prev = dummyHead;
        for(int i = 0 ; i < index ; i ++)
            prev = prev.next;

        prev.next = new Node(e, prev.next);
        size ++;
    }

    // 在链表头添加新的元素e
    public void addFirst(E e){
        add(0, e);
    }

    // 在链表末尾添加新的元素e
    public void addLast(E e){
        add(size, e);
    }

    // 获得链表的第index(0-based)个位置的元素
    // 在链表中不是一个常用的操作,练习用:)
    public E get(int index){

        if(index < 0 || index >= size)
            throw new IllegalArgumentException("Get failed. Illegal index.");

        Node cur = dummyHead.next;
        for(int i = 0 ; i < index ; i ++)
            cur = cur.next;
        return cur.e;
    }

    // 获得链表的第一个元素
    public E getFirst(){
        return get(0);
    }

    // 获得链表的最后一个元素
    public E getLast(){
        return get(size - 1);
    }

    // 修改链表的第index(0-based)个位置的元素为e
    // 在链表中不是一个常用的操作,练习用:)
    public void set(int index, E e){
        if(index < 0 || index >= size)
            throw new IllegalArgumentException("Set failed. Illegal index.");

        Node cur = dummyHead.next;
        for(int i = 0 ; i < index ; i ++)
            cur = cur.next;
        cur.e = e;
    }

    // 查找链表中是否有元素e
    public boolean contains(E e){
        Node cur = dummyHead.next;
        while(cur != null){
            if(cur.e.equals(e))
                return true;
            cur = cur.next;
        }
        return false;
    }

    // 从链表中删除index(0-based)位置的元素, 返回删除的元素 
    // 在链表中不是一个常用的操作,练习用:)
    public E remove(int index){			//(添加代码)
        if(index < 0 || index >= size)	//验证 index 合法性
            throw new IllegalArgumentException("Remove failed. Index is illegal.");

        Node prev = dummyHead;	//prev初始是从 dummyHead 开始
        for(int i = 0 ; i < index ; i ++) 
            prev = prev.next;	//prev存储待删除节点的之前的节点

        Node retNode = prev.next;	//prev对应节点的 next 就是所要删除的节点retNode
        prev.next = retNode.next;	//将 prev 对应节点的 next 赋值称为要删除的节点 retNode 的 next
        retNode.next = null;	//让 retNode 的 next 指向 NULL,将其从链表中删除
        size --;

        return retNode.e;
    }

    // 从链表中删除第一个元素, 返回删除的元素
    public E removeFirst(){	//(添加代码)
        return remove(0);
    }

    // 从链表中删除最后一个元素, 返回删除的元素
    public E removeLast(){	//(添加代码)
        return remove(size - 1);
    }

    // 从链表中删除元素e
    public void removeElement(E e){	//(添加代码)

        Node prev = dummyHead;
        while(prev.next != null){
            if(prev.next.e.equals(e))
                break;
            prev = prev.next;
        }

        if(prev.next != null){
            Node delNode = prev.next;
            prev.next = delNode.next;
            delNode.next = null;
            size --;
        }
    }

    @Override
    public String toString(){
        StringBuilder res = new StringBuilder();

        Node cur = dummyHead.next;
        while(cur != null){
            res.append(cur + "->");
            cur = cur.next;
        }
        res.append("NULL");

        return res.toString();
    }
}

Main.java

public class Main {

    public static void main(String[] args) {

        LinkedList<Integer> linkedList = new LinkedList<>();
        for(int i = 0 ; i < 5 ; i ++){
            linkedList.addFirst(i);
            System.out.println(linkedList);
        }

        linkedList.add(2, 666);
        System.out.println(linkedList);

        linkedList.remove(2);
        System.out.println(linkedList);

        linkedList.removeFirst();
        System.out.println(linkedList);

        linkedList.removeLast();
        System.out.println(linkedList);
    }
}

输出:先将 0-4 这5个数添加到链表中;再将 666 添加到 索引为2【第三个】的位置;再将索引为 2 位置的666删除,再删除链表首元素,再删除链表末元素,得到最终结果。  

六、链表时间复杂度分析

1.添加操作:O(n)

addLast(e)     ---O(n) [需要从链表头开始比遍历到链表尾]

addFirst(e)     ---O(1)[直接添加即可]

addIndex(e)   ---O(n/2) = O(n)【均摊】

2.删除操作

removeLast(e)    ---O(n)[需要从头找到最后一个元素的前一个位置的节点]

removeFirst(e)   ---O(1)[虚拟头节点就是第一个元素的前一个节点]

removeIndex(e)   ---O(n/2) = O(n)

3.修改操作(链表不支持随机访问)

set(index e)    ---O(n)[修改元素必须从头遍历找到要修改元素的位置]

4.查找操作

get(index)   ---O(n)[需要从头遍历整个链表]

contains(e)    ---O(n)[需要从头遍历整个链表]

总结:

七、 使用链表实现栈

将链表头当做栈顶,用链表作为栈的底层实现来完成栈的结构

示例代码:

Stack.java

public interface Stack<E> {

    int getSize();
    boolean isEmpty();
    void push(E e);
    E pop();
    E peek();
}

LinkedListStack.java

public class LinkedListStack<E> implements Stack<E> {	//实现 Stack 接口

    private LinkedList<E> list;		//私有链表对象list

    public LinkedListStack(){
        list = new LinkedList<>();	//链表初始化
    }

    @Override
    public int getSize(){
        return list.getSize();
    }

    @Override
    public boolean isEmpty(){
        return list.isEmpty();
    }

    @Override
    public void push(E e){	//向栈中添加元素e
        list.addFirst(e);	//链表头是栈顶
    }

    @Override 
    public E pop(){		//从栈中取出元素
        return list.removeFirst();
    }

    @Override
    public E peek(){
        return list.getFirst();	//查看栈顶元素
    }

    @Override
    public String toString(){
        StringBuilder res = new StringBuilder();
        res.append("Stack: top ");
        res.append(list);
        return res.toString();
    }

    public static void main(String[] args) {

        LinkedListStack<Integer> stack = new LinkedListStack<>();	//声明LinkedListStack的对象stack

        for(int i = 0 ; i < 5 ; i ++){	//向栈中压入5个元素
            stack.push(i);
            System.out.println(stack);
        }

        stack.pop();	//从栈中取出元素
        System.out.println(stack);
    }
}

输出:

比较栈与链表的性能差异:

ArrayStack.java

public class ArrayStack<E> implements Stack<E> {

    private Array<E> array;

    public ArrayStack(int capacity){
        array = new Array<>(capacity);
    }

    public ArrayStack(){
        array = new Array<>();
    }

    @Override
    public int getSize(){
        return array.getSize();
    }

    @Override
    public boolean isEmpty(){
        return array.isEmpty();
    }

    public int getCapacity(){
        return array.getCapacity();
    }

    @Override
    public void push(E e){
        array.addLast(e);
    }

    @Override
    public E pop(){
        return array.removeLast();
    }

    @Override
    public E peek(){
        return array.getLast();
    }

    @Override
    public String toString(){
        StringBuilder res = new StringBuilder();
        res.append("Stack: ");
        res.append('[');
        for(int i = 0 ; i < array.getSize() ; i ++){
            res.append(array.get(i));
            if(i != array.getSize() - 1)
                res.append(", ");
        }
        res.append("] top");
        return res.toString();
    }

    public static void main(String[] args) {

        ArrayStack<Integer> stack = new ArrayStack<>();

        for(int i = 0 ; i < 5 ; i ++){
            stack.push(i);
            System.out.println(stack);
        }

        stack.pop();
        System.out.println(stack);
    }
}

Main.java

import java.util.Random;

public class Main {

    // 测试使用stack运行opCount个push和pop操作所需要的时间,单位:秒
    private static double testStack(Stack<Integer> stack, int opCount){

        long startTime = System.nanoTime();			//计时开始

        Random random = new Random();	//生成随机数
        for(int i = 0 ; i < opCount ; i ++)
            stack.push(random.nextInt(Integer.MAX_VALUE)); //压入栈中
        for(int i = 0 ; i < opCount ; i ++)
            stack.pop();					//从栈中拿出

        long endTime = System.nanoTime();			//计时结束

        return (endTime - startTime) / 1000000000.0;		//纳秒转换为秒
    }

    public static void main(String[] args) {

        int opCount = 100000;

        ArrayStack<Integer> arrayStack = new ArrayStack<>();
        double time1 = testStack(arrayStack, opCount);
        System.out.println("ArrayStack, time: " + time1 + " s");

        LinkedListStack<Integer> linkedListStack = new LinkedListStack<>();
        double time2 = testStack(linkedListStack, opCount);
        System.out.println("LinkedListStack, time: " + time2 + " s");

        // 其实这个时间比较很复杂,因为LinkedListStack中包含更多的new操作
    }
}

输出:

链表栈要比数组栈要快一些,数组栈中,经常需要重新分配整个静态数组,将原来的静态元素分配到新的数组中,比较耗时。

但这个结果不一定对,因为这二者的时间复杂度都是同一级别的,不存在过多的差距。如果输入量增到1000000时,可能数组栈更快了,链表每次new一个空间需要耗时。

八、 使用链表实现队列

在链表的头进行增加或删除操作比较容易因为有 head 帮助我们标记头部,想要在尾部也进行该操作,加入 tail 帮助我们标记尾部即可使增加操作更加容易,但删除操作需要找到 tail 的前一项,也只能通过遍历的方式来找到。故从 head 端删除元素,从tail端插入元素;如果链表为空时,由于没有 dummyHead,要注意链表为空的情况。

示例代码:

Queue.java

public interface Queue<E> {

    int getSize();
    boolean isEmpty();
    void enqueue(E e);
    E dequeue();
    E getFront();
}

LinkedListQueue.java

public class LinkedListQueue<E> implements Queue<E> {

    private class Node{
        public E e;
        public Node next;

        public Node(E e, Node next){
            this.e = e;
            this.next = next;
        }

        public Node(E e){
            this(e, null);
        }

        public Node(){
            this(null, null);
        }

        @Override
        public String toString(){
            return e.toString();
        }
    }

    private Node head, tail;	//定义 head 头结点与 tail 尾节点
    private int size;

    public LinkedListQueue(){		//参数为空的情形
        head = null;
        tail = null;
        size = 0;
    }

    @Override
    public int getSize(){
        return size;
    }

    @Override
    public boolean isEmpty(){
        return size == 0;
    }

    @Override
    public void enqueue(E e){	//入队操作
        if(tail == null){	//判断尾是否为空 
            tail = new Node(e);	//出入元素e
            head = tail; 
        }
        else{
            tail.next = new Node(e);	//new 的节点应该在 tail 的 next 的位置
            tail = tail.next;
        }
        size ++;
    }

    @Override
    public E dequeue(){	//出队操作
        if(isEmpty())	//队列为空则抛出异常
            throw new IllegalArgumentException("Cannot dequeue from an empty queue.");

        Node retNode = head;	//出队元素所在的节点应该是 Head 这个位置
        head = head.next;		//新的 head 将会跳过 Node ,直接指向 head.next
        retNode.next = null;	//将 retNode 从链表中断开
        if(head == null)	//链表只要一个元素时
            tail = null;
        size --;
        return retNode.e;
    }

    @Override
    public E getFront(){	//查看队首元素
        if(isEmpty())
            throw new IllegalArgumentException("Queue is empty.");
        return head.e;
    }

    @Override
    public String toString(){
        StringBuilder res = new StringBuilder();
        res.append("Queue: front ");	//链表头的位置是队首,负责出队

        Node cur = head;
        while(cur != null) {
            res.append(cur + "->");
            cur = cur.next;
        }
        res.append("NULL tail");	//链表尾的位置是队尾,负责入队
        return res.toString();
    }

    public static void main(String[] args){

        LinkedListQueue<Integer> queue = new LinkedListQueue<>();
        for(int i = 0 ; i < 10 ; i ++){		//入队10个元素
            queue.enqueue(i);
            System.out.println(queue);

            if(i % 3 == 2){			//每隔3个元素出队1个元素
                queue.dequeue();
                System.out.println(queue);
            }
        }
    }
}

输出:

比较数组、队列、与链表的性能差异:

示例代码:

Main.java

import java.util.Random;

public class Main {

    // 测试使用q运行opCount个enqueueu和dequeue操作所需要的时间,单位:秒
    private static double testQueue(Queue<Integer> q, int opCount){

        long startTime = System.nanoTime();

        Random random = new Random();
        for(int i = 0 ; i < opCount ; i ++)
            q.enqueue(random.nextInt(Integer.MAX_VALUE));
        for(int i = 0 ; i < opCount ; i ++)
            q.dequeue();

        long endTime = System.nanoTime();

        return (endTime - startTime) / 1000000000.0;
    }

    public static void main(String[] args) {

        int opCount = 100000;

        ArrayQueue<Integer> arrayQueue = new ArrayQueue<>();
        double time1 = testQueue(arrayQueue, opCount);
        System.out.println("ArrayQueue, time: " + time1 + " s");

        LoopQueue<Integer> loopQueue = new LoopQueue<>();
        double time2 = testQueue(loopQueue, opCount);
        System.out.println("LoopQueue, time: " + time2 + " s");

        LinkedListQueue<Integer> linkedListQueue = new LinkedListQueue<>();
        double time3 = testQueue(linkedListQueue, opCount);
        System.out.println("LinkedListQueue, time: " + time3 + " s");
    }
}

输出:数组队列--O(n);循环队列--O(1);链表实现队列--O(1)

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值