(四) 数据结构之链表

链表

链表是一种真正意义上的动态线性结构, 因为他的底层是动态的. 而动态数组底层是静态ede, 依靠resize解决固定容量问题

为什么链表很重要

  1. 最简单的动态数据结构
  2. 更深入的理解引用
  3. 更深入的理解递归
  4. 辅助组成其他数据结构

特性

对于链表来说, 数据存储在一个节点中(Node), 需要通过一个指针的引用next寻找下一个节点, 所以丧失了随机访问数据的能力, 必须遍历寻找

java代码是这样的

class Node{
    E e;
    Node next;
}

数组和链表的对比

数组

  1. 数组最好用于索引有语义的情况. 比如scorse[2]
  2. 最大的优点, 支持快速查询

链表

  1. 链表不适合用于索引有语义的情况
  2. 最大的优点: 动态

链表的实现

代码实现

package pers.jssd.linkedlist;

public class LinkedList<E> {
	/**
	 * 链表中存储数据的内部类
	 */
	private class Node {
		/**
		 * 存储数据
		 */
		E e;
		/**
		 * 指向下一个节点
		 */
		Node next;

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

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

	/**
	 * 虚拟头节点
	 */
	private Node dummyHead;

	/**
	 * 链表的大小, 存储的元素个数
	 */
	private int size;

	/**
	 * 新建一个链表, 默认带有头节点
	 */
	public LinkedList() {
		dummyHead = new Node(null, null);
		size = 0;
	}

	/**
	 * 在索引为index(0-bases)的位置添加元素e
	 *
	 * @param index 要添加的元素位置
	 * @param e     添加的元素
	 */
	public void add(int index, E e) {
		if (index < 0 || index > size) {
			throw new IllegalArgumentException("index out of bounds");
		}

		Node previous = dummyHead;
		for (int i = 0; i < index; i++) {
			previous = previous.next;
		}
		previous.next = new Node(e, previous.next);
		size ++;
	}

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

	/**
	 * 在链表尾部添加元素
	 *
	 * @param e 要添加的元素
	 */
	public void addLast(E e) {
		add(size, e);
	}

	/**
	 * 取得链表中元素的个数
	 *
	 * @return int 返回链表中元素的个数
	 */
	public int getSize() {
		return size;
	}

	/**
	 * 查看链表是否为空
	 *
	 * @return true链表为空, false则链表不为空
	 */
	public boolean isEmpty() {
		return size == 0;
	}

	/**
	 * 查看链表中是否存在某个元素
	 *
	 * @param e 查看链表中是否存在的元素
	 * @return true则链表中存在此元素, false则链表中不存在此元素
	 */
	public boolean contain(E e) {
		Node cur = dummyHead.next;
		while (cur != null) {
			if (cur.e.equals(e)) {
				return true;
			}
			cur = cur.next;
		}
		return false;
	}

	/**
	 * 取得链表中下标为index(0-bases)的元素
	 *
	 * @param index 取得元素的下标 (0-base)
	 * @return 返回取得的元素
	 */
	public E get(int index) {
		if (index < 0 || index >= size) {
			throw new IllegalArgumentException("get error, index out of bound");
		}
		Node cur = dummyHead.next;
		for (int i = 0; i < index; i++) {
			cur = cur.next;
		}
		return cur.e;
	}

	/**
	 * 取得链表头部元素
	 * @return 返回取得的元素
	 */
	public E getFirst() {
		return get(0);
	}

	/**
	 * 返回链表尾部元素
	 * @return 返回取得的元素
	 */
	public E getLast() {
		return get(size - 1);
	}

	/**
	 * 在索引为index的位置, 更改元素为e
	 * @param index 更改元素的位置
	 * @param e 应改为的目标元素
	 */
	public void set(int index, E e) {
		Node cur = dummyHead.next;
		for (int i = 0; i < index; i++) {
			cur = cur.next;
		}
		cur.e = e;
	}

	/**
	 * 删除索引在index(0-base)位置的元素, 并返回
	 * @param index 要删除的元素位置(0-base)
	 * @return 返回删除的元素
	 */
	public E remove(int index) {
		if (index < 0 || index >= size) {
			throw new IllegalArgumentException("remove error, index is out of bound");
		}

		Node previous = dummyHead;
		for (int i = 0; i < index; i++) {
			previous = previous.next;
		}
		Node res = previous.next;
		previous.next = res.next;
		res.next = null;
		size --;
		return  res.e;
	}

	/**
	 * 删除第一个元素
	 * @return 返回删除的元素
	 */
	public E removeFirst() {
		return remove(0);
	}

	/**
	 * 删除最后一个元素
	 * @return 返回删除的元素
	 */
	public E removeLast() {
		return remove(size - 1);
	}

	/**
	 * 删除指定元素
	 * @param e 要删除的指定元素
	 */
	public void removeElement(E e) {
		Node pre = dummyHead;
		while (pre.next != null) {
			if (e.equals(pre.next.e)) {
				Node delNode = pre.next;
				pre = pre.next;
				delNode.next = null;
				size --;
				return;
			}
			pre = pre.next;
		}
	}

	@Override
	public String toString() {
		StringBuilder sb = new StringBuilder();
		sb.append("size = ").append(size).append("  ");
		Node cur = dummyHead.next;
		sb.append("[");
		while (cur.next != null) {
			sb.append(cur.e).append(", ");
			cur = cur.next;
		}
		sb.append(cur.e).append("]");

		return sb.toString();
	}
}

复杂度分析

  1. 添加元素

    在头部添加元素的时候, 直接添加就可以所以是O(1)

    在尾部添加元素的时候, 需要遍历到链表的尾部, 所以复杂度是O(n)

    在任意位置添加元素的时候, 平均复杂度是O(n/2), 所以是O(n)

    综合上述, 添加元素的复杂度为O(n)

  2. 删除元素

    删除元素与添加元素基本差不多, 所以复杂度为O(n)

  3. 获取元素

    使用链表获取一个元素, 需要遍历查找, 所以复杂度为O(n)

  4. 是否包含一个元素 contain

    使用链表查看是否包含一个元素, 需要遍历, 所以复杂度为O(n)

使用链表实现栈

代码实现

package pers.jssd.stack;

public class LinkStack<E> implements Stack<E> {
	private LinkedList<E> list;

	public LinkStack() {
		list = new LinkedList<>();
	}

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

	@Override
	public void push(E e) {
		list.addFirst(e);
	}

	@Override
	public E pop() {
		return list.removeFirst();
	}

	@Override
	public E peek() {
		return list.getFirst();
	}

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

	@Override
	public String toString() {
		return "LinkStack{" +
				"list= --> TOP" + list +
				'}';
	}
}

与数组栈进行比较

package pers.jssd.test;

import pers.jssd.stack.ArrayStack;
import pers.jssd.stack.LinkStack;
import pers.jssd.stack.Stack;

public class CompareStack {
    public static void main(String[] args) {
        ArrayStack<Integer> arrayStack = new ArrayStack<>();
        LinkStack<Integer> linkStack = new LinkStack<>();
        double time1 = test(arrayStack, 100000);
        double time2 = test(linkStack, 100000);

        System.out.println("time1 = " + time1);
        System.out.println("time2 = " + time2);
    }

    /**
     * 测试队列执行一定次数使用的时间
     *
     * @param stack   执行的队列
     * @param opCount 执行的次数
     * @return 返回执行的时间
     */
    @SuppressWarnings("unchecked")
    private static double test(Stack stack, int opCount) {
        long start = System.nanoTime();
        for (int i = 0; i < opCount; i++) {
            stack.push(i);
            if (i % 3 == 2) {
                stack.pop();
            }
        }
        long end = System.nanoTime();
        return (end - start) / 1000000.0;
    }
}

结果为

time1 = 8.4901
time2 = 7.7555

使用链表实现队列

因为队列要先进后出, 所以, 我们对链表添加一个指向尾部的指针, 便于操作, 同时提高性能

代码实现

package pers.jssd.queue;

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

    private class Node {
        /**
         * 存储数据
         */
        E e;
        /**
         * 指向下一个节点
         */
        Node next;

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

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

    private Node head, tail;
    private int size;

    public LinkedQueue() {
        head = tail = null;
        size = 0;
    }

    @Override
    public void enqueue(E e) {
        if (tail == null) {
            tail = new Node(e, null);
            head = tail;
        } else {
            Node newNode = new Node(e, null);
            tail.next = newNode;
            tail = newNode;
        }
        size ++;
    }

    @Override
    public E dequeue() {
        if (isEmpty()) {
            throw new IllegalArgumentException("dequeue error");
        }
        Node res = head;
        head = head.next;
        if (head == null) {
            tail = null;
        }
        size --;
        return res.e;
    }

    @Override
    public E getFront() {
        return head.e;
    }

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

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

    @Override
    public String toString() {
       StringBuilder sb = new StringBuilder();
        sb.append("size = ").append(size);
        sb.append(" tail-->[");
        Node cur = head;
        while (cur.next != null) {
            sb.append(cur.e).append(", ");
            cur = cur.next;
        }
        sb.append(cur.e).append("]<--head");
        return sb.toString();
    }
}

测试代码

package pers.jssd.test;

import pers.jssd.queue.ArrayQueue;
import pers.jssd.queue.LinkedQueue;
import pers.jssd.queue.Queue;

public class TestLinkedQueue {
    public static void main(String[] args) {
        Queue<Integer> queue = new LinkedQueue<>();
        for (int i = 0; i < 10; i++) {
            queue.enqueue(i);
            System.out.println("queue = " + queue);

            if (i % 3 == 2) {
                queue.dequeue();
                System.out.println("queue = " + queue);
            }
        }
    }
}

与循环队列, 数组队列进行比较

package pers.jssd.test;

import pers.jssd.queue.ArrayQueue;
import pers.jssd.queue.LinkedQueue;
import pers.jssd.queue.LoopQueue;
import pers.jssd.queue.Queue;

/**
 * @author jssd
 * Create 2019-07-24 9:49
 */
public class CompareQueue {
	public static void main(String[] args) {
		int opCount = 1000000;
		ArrayQueue<Integer> arrayQueue = new ArrayQueue<>();
		LoopQueue<Integer> loopQueue = new LoopQueue<>();
		LinkedQueue<Integer> linkedQueue = new LinkedQueue<>();
		System.out.println("arrayQueue use time = " + test(arrayQueue, opCount));
		System.out.println("loopQueue use time = " + test(loopQueue, opCount));
		System.out.println("linkedQueue use time = " + test(linkedQueue, opCount));
	}

	/**
	 * 测试队列执行一定次数使用的时间
	 * @param queue 执行的队列
	 * @param opCount 执行的次数
	 * @return 返回执行的时间
	 */
	@SuppressWarnings("unchecked")
	private static double test(Queue queue, int opCount) {
		long start = System.nanoTime();
		for (int i = 0; i < opCount; i++) {
			queue.enqueue(i);
			if (i % 3 == 2) {
				queue.dequeue();
			}
		}
		long end = System.nanoTime();
		return (end - start) / 1000000000.0;
	}
}

结果如下

arrayQueue use time = 29.0617751
loopQueue use time = 0.0409683
linkedQueue use time = 0.0774326
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值