zoukankan      html  css  js  c++  java
  • 数据结构 -- 链表(LinkedList)

      链表是一种物理存储单元上非连续、非顺序的存储结构数据元素的逻辑顺序是通过链表中的指针链接次序实现的。链表由一系列结点(链表中每一个元素称为结点)组成,结点可以在运行时动态生成。

      每个结点包括两个部分:一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域。

      相比于线性表顺序结构,操作复杂。由于不必须按顺序存储,链表在插入的时候可以达到O(1)的复杂度,比另一种线性表顺序表快得多,但是查找一个节点或者访问特定编号的节点则需要O(n)的时间,而线性表和顺序表相应的时间复杂度分别是O(logn)和O(1)。

      链表这类数据结构,有点像生活中的火车,一节车厢连着下一节车厢,在火车里面,只有到了4号车厢你才能进入5号车厢,一般情况下,不可能直接在3号车厢绕过4号车厢进入5号车厢。不过更准确来说,火车是双向链表,也就是说在4号车厢也可以反向进入3号车厢。

    二、链表种类

        1.1)单向链表:

          element:用来存放元素

          next:用来指向下一个节点元素

          通过每个结点的指针指向下一个结点从而链接起来的结构,最后一个节点的next指向null。

          

        1.2)单向循环链表

          element、next 跟前面一样

          在单向链表的最后一个节点的next会指向头节点,而不是指向null,这样存成一个环

          

        1.3)双向链表

          element:存放元素

          pre:用来指向前一个元素

          next:指向后一个元素

          双向链表是包含两个指针的,pre指向前一个节点,next指向后一个节点,但是第一个节点head的pre指向null,最后一个节点的tail指向null。

          

        1.4)双向循环链表

          element、pre、next 跟前面的一样

          第一个节点的pre指向最后一个节点,最后一个节点的next指向第一个节点,也形成一个“环”。

          

      时间复杂度

      (1)添加操作:O(n)

        ① addLast(e): O(n)  ②addFirst(e): O(1)  ③add(index, e): O(n/2)=O(n)

      (2)删除操作:O(n)

        ① removeLast(e): O(n)  ②removeFirst(e): O(1)  ③remove(index, e): O(n/2)=O(n)

      (3)查找操作:O(n)

        ① get(index): O(n)  ②contains(e): O(n) 

      (4)修改操作:O(n)

        ① set(index,e): O(n)

    三、代码实现

      单链表的实现

    图解如下:

    插入

    删除

     

    代码

    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;
        }
    
        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 addList(E e){
            add(size,e);
        }
    
        //获得链表的第index位置的元素. 在链表中不是一个常用的操作,练习用
        public E get(int index){
            if(index < 0 || index >= size){
                throw  new IllegalArgumentException("index Illegal");
            }
    
            Node cur = dummyHead.next;
            for (int i =0; i<index; i++){
                cur = cur.next;
            }
            return cur.e;
        }
    
        public E getLast(){
            return get(size - 1);
        }
        public E getFirst(){
            return get(0);
        }
    
        public void set(int index,E e){
            if(index < 0 || index >= size){
                throw  new IllegalArgumentException("index Illegal");
            }
    
            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){
                throw  new IllegalArgumentException("Remove failed. Index is illegal.");
            }
            Node prev = dummyHead;
            for (int i = 0; i < index; i++){
                prev = prev.next;
            }
            Node retNode = prev.next;
            prev.next = retNode.next;
            //删除retNode节点,则它的下一个节点指引就不需要了,设置null,让GC干活
            retNode.next = null;
            size --;
            return retNode.e;
        }
    
        public E removeFirst(){
            return remove(0);
        }
    
        public E removeLast(){
            return remove(size - 1);
        }
    
        public void removeElement(E e){
            Node prev = dummyHead;
            while (prev != 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 stringBuilder = new StringBuilder();
            Node cur = dummyHead.next;
            while( cur != null){
                stringBuilder.append(cur + "- >");
                cur = cur.next;
            }
            stringBuilder.append("null");
            return  stringBuilder.toString();
        }
    }

     测试代码

    public class LinkedListTest{
        public static void main(String[] args) {
            LinkedList linkedList = new LinkedList();
            for (int i=0; i< 10; i++){
                linkedList.add(i,i*10);
                System.out.println(linkedList);
            }
            linkedList.remove(1);
            System.out.println(linkedList);
            linkedList.removeElement(50);
            System.out.println(linkedList);
        }
    }
    //测试结果
    0- >null 0- >10- >null 0- >10- >20- >null 0- >10- >20- >30- >null 0- >10- >20- >30- >40- >null 0- >10- >20- >30- >40- >50- >null 0- >10- >20- >30- >40- >50- >60- >null 0- >10- >20- >30- >40- >50- >60- >70- >null 0- >10- >20- >30- >40- >50- >60- >70- >80- >null 0- >10- >20- >30- >40- >50- >60- >70- >80- >90- >null 0- >20- >30- >40- >50- >60- >70- >80- >90- >null //删除第二个位置的10 0- >20- >30- >40- >60- >70- >80- >90- >null //删除指定元素50

      链表实现队列

    import com.wj.queue.Queue; //Queue连接:https://www.cnblogs.com/FondWang/p/11808221.html
    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(){
                this(null,null);
            }
    
            public Node(E e){
                this(e,null);
            }
    
            @Override
            public String toString() {
                return e.toString();
            }
        }
    
        private Node 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(Object o) {
            if (tail == null){
                tail = new Node((E) o);
                head = tail;
            }else {
                tail.next = new Node((E) o);
                tail = tail.next;
            }
            size++;
        }
        //出队
        @Override
        public E dequeue() {
            if (isEmpty()){
                throw new IllegalArgumentException("Cannot dequeue from an empty queue.");
            }
            Node retNode = head;
            head = head.next;
            retNode.next = null;
            if (head == null){
                tail = null;
            }
            size--;
            return retNode.e;
        }
        //返回队首元素
        @Override
        public E getFront() {
            if (isEmpty()){
                throw new IllegalArgumentException("Cannot dequeue from an empty queue.");
            }
            return head.e;
        }
    
        @Override
        public String toString() {
            StringBuilder stringBuilder = new StringBuilder();
            stringBuilder.append("Queue: front ");
            Node cur = head;
            while (cur != null){
                stringBuilder.append(cur + "- >");
                cur = cur.next;
            }
            stringBuilder.append("NULL tail");
            return stringBuilder.toString();
        }
    }

     测试类

    public class LinkedListQueueTest{
         public static void main(String[] args) {
            LinkedListQueue linkedListQueue = new LinkedListQueue();
            for (int i=0; i<10; i++){
                linkedListQueue.enqueue(i);  //入队
                System.out.println(linkedListQueue);
                if (i % 3 ==0){
                    linkedListQueue.dequeue();  //符合条件,出队
                }
            }
        }
    }
    //测试结果
    Queue: front 0- >NULL tail
    Queue: front 1- >NULL tail
    Queue: front 1- >2- >NULL tail
    Queue: front 1- >2- >3- >NULL tail
    Queue: front 2- >3- >4- >NULL tail
    Queue: front 2- >3- >4- >5- >NULL tail
    Queue: front 2- >3- >4- >5- >6- >NULL tail
    Queue: front 3- >4- >5- >6- >7- >NULL tail
    Queue: front 3- >4- >5- >6- >7- >8- >NULL tail
    Queue: front 3- >4- >5- >6- >7- >8- >9- >NULL tail

      链表实现栈 

    import com.wj.stack.Stack; //详情连接:https://www.cnblogs.com/FondWang/p/11809042.html
    public class LinkedListStack<E> implements Stack<E> {
    
        private LinkedList<E> linkedList;
    
        public LinkedListStack(){
            linkedList = new LinkedList<>();
        }
        @Override
        public int getSize() {
            return linkedList.getSize();
        }
    
        @Override
        public boolean isEmpty() {
            return linkedList.isEmpty();
        }
        
        //压入
        public void push(Object o) {
            linkedList.addFirst((E) o);
        }
        //移除
        @Override
        public E pop() {
            return linkedList.removeFirst();
        }
        //返回栈首元素
        @Override
        public E peek() {
            return linkedList.getFirst();
        }
    
        public String toString(){
            StringBuilder stringBuilder = new StringBuilder();
            stringBuilder.append("Stack: top ");
            stringBuilder.append(linkedList);
            return stringBuilder.toString();
        }
    }

    测试类

    public class LinkedListStackTest{
            public static void main(String[] args) {
            LinkedListStack stack = new LinkedListStack();
            for (int i =0; i< 10; i++){
                stack.push(i); //压入
                System.out.println(stack);
            }
    
            System.out.println("=============");
            stack.pop(); //移除
            System.out.println(stack);
        }
    }
    //测试结果
    Stack: top 0- >null
    Stack: top 1- >0- >null
    Stack: top 2- >1- >0- >null
    Stack: top 3- >2- >1- >0- >null
    Stack: top 4- >3- >2- >1- >0- >null
    Stack: top 5- >4- >3- >2- >1- >0- >null
    Stack: top 6- >5- >4- >3- >2- >1- >0- >null
    Stack: top 7- >6- >5- >4- >3- >2- >1- >0- >null
    Stack: top 8- >7- >6- >5- >4- >3- >2- >1- >0- >null
    Stack: top 9- >8- >7- >6- >5- >4- >3- >2- >1- >0- >null //将第一个移除
    =============
    Stack: top 8- >7- >6- >5- >4- >3- >2- >1- >0- >null

    四、效率对比

    栈的对比

    import com.wj.stack.ArrayStack; //详情连接:https://www.cnblogs.com/FondWang/p/11809042.html
    import com.wj.stack.Stack;
    import java.util.Random;
    public class StackEfficiency {
        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 - time1: " + time1);
    
            LinkedListStack<Integer> linkedListStack = new LinkedListStack();
            double time2 = testStack(linkedListStack,opCount);
            System.out.println("LinkedListStack - time2: " + time2);
    
        }
    }
    //测试结果,添加和删除
    ArrayStack - time1: 7.071363002
    LinkedListStack - time2: 0.006395025
    //说明数组增删 效率要低于 链表。
    //对于增删数组的时间复杂度是O(n),而链表是O(1)

    队列的对比

    import com.wj.queue.ArrayQueue;//详情:https://www.cnblogs.com/FondWang/p/11808221.html
    import com.wj.queue.LoopQueue;
    import com.wj.queue.Queue;
    import java.util.Random;
    public class MainQueue {
        private static double testStack(Queue<Integer> queue, int opCount){
            long startTime = System.nanoTime();
            Random random = new Random();
            for (int i=0; i < opCount; i ++){
                queue.enqueue(random.nextInt(Integer.MAX_VALUE));
            }
            for (int i=0; i < opCount; i ++){
                queue.dequeue();
            }
    
            long endTime = System.nanoTime();
            return (endTime - startTime) / 1000000000.0;
        }
        public static void main(String[] args) {
            int opCount = 1000000;
          
            ArrayQueue<Integer> arrayQueue = new ArrayQueue();
            double time1 = testStack(arrayQueue,opCount);
            System.out.println("ArrayStack - time1: " + time1);
    
            LinkedListQueue<Integer> linkedListQueue = new LinkedListQueue();
            double time2 = testStack(linkedListQueue,opCount);
            System.out.println("LinkedListStack - time2: " + time2);
    
            LoopQueue<Integer> loopQueue = new LoopQueue();
            double time3 = testStack(loopQueue,opCount);
            System.out.println("LoopQueue - time3: " + time3);
        }
    }
    //测试结果
    ArrayQueue - time1: 368.014648575
    LinkedListQueue - time2: 0.395344446
    LoopQueue - time3: 0.045756219
    //对于增删操作,数组(O(n))的时间复杂度高于链表队列循环队列,所以数组最慢
    //链表和队列。队列增删只在队首和队尾,时间复杂度低于链表,所以循环队列要高于链表队列。
  • 相关阅读:
    Jenkins安装部署及使用
    Jenkins详细安装与构建部署使用教程
    线程池使用拒绝策略时需要注意的坑
    线程池的4种拒绝策略
    neo4j allshortestpaths查询路径不准确问题
    程序员必备的网站之Tutorialspoint
    All shortest paths between a set of nodes
    Neo4j/Cypher: All paths between two nodes with a relationship property filter
    12款好看的英文字体下载《可以免费用于商业用途》
    国外经典设计:12个漂亮的移动APP网站案例
  • 原文地址:https://www.cnblogs.com/FondWang/p/11809153.html
Copyright © 2011-2022 走看看