你真的知道链表和数组的区别吗?

来源:Java大联盟

作者:南风

对一名程序猿来讲,使用哪种语言来开发程序不是最重要的,数据结构和算法才是核心,是程序猿的内功,最终决定你的技术上限。如果你想拔高自己的水平,提高核心竞争力,数据结构和算法是必须要学的,今天就带大家一起来学习链表的概念,并用 Java 语言实现一个链表的结构。

什么是链表?

链表是一种最常见的数据结构,其内部数据呈线性排列,属于线性表结构,什么是线性表?表中的数据按顺序依次排列,就像用一条线把数据串联起来一样。

链表就是这种排布方式,特点是添加数据和删除数据速度快,但是查询数据会比较耗时,这是因为链表在内存中的存储结构造成的。

涛声依旧注:更准确地说是链表根据下标找到元素比较耗时。因为链表根据下标定位不到元素,只能从头开始一个一个遍历。

这里我们可以将数组与链表进行对比,数组大家应该都很熟悉,学过 Java 的都会用,但是你真的了解它在内存中的存储结构吗?

数组的特点是查询数据很快,添加数据和删除数据效率低,这一特征与链表恰好相反,数组的缺陷正是链表的优势,数组的优势则是链表的缺陷,所以二者对比着来记,效果会更好。

来说说为什么数组和链表的特点恰好相反,首先来看看二者在内存中的存储结构。

数组和链表都是线性表结构,数组在内存中是一串连续的内存空间,比如定义一个 int 类型数组,int[] array = new int[6],计算机会为 array 分配一块连续的空间,如下图所示。

1000-1003 这段空间用来存储数组中的第一个元素 array[0],1004-1007 的空间用来存储 array[1],以此类推数组中的每个元素都对应一块大小为 4 byte 的空间,这种结构就决定了数组查询数据速度很快,只需要知道首地址(在栈内存中记录的就是数组的首地址,可以直接获取),再结合寻址公式就可以很快找到对应元素的地址,从而取出数据。

数组的寻址公式:i_address = first_address + data_size*i

带入上述案例中,比如要找到数组中第 3 个元素,也就是下标为 2 ,该元素的首地址即 2_address = 1000 + 2*4 = 1008,计算机只需要执行一个简单的数学运算就可以找到元素的首地址,进而取出对应的值,对于计算机来讲,简单数学运算的耗时几乎可以忽略不计,所以数组查询数据速度非常快。

涛声依旧注:更准确地说,数组是根据其下标定位元素很快(可以运用寻址公式),进而找到目标元素,而不是查找某一个具体的值很快,比如说要查找5,那即使是排好序的数组,用二分查找一般也需要log(n)次才能查找到。而链表即使知道下标也定位不到元素。

也正是因为这种结构导致数组添加和删除数据效率很低,因为这两种操作不仅仅是在数组中添加或者移除一个元素那么简单,同时还需要移动其他已存在的元素。

数组中各个元素的内存地址都是连续,不间断的,删除某个元素之后需要保证数组仍然是连续的,所以就需要移动数据,比如要删除 array[2],删除之后需要依次将 array[3]、array[4]、array[5] 向前移动一位,如下图所示。

同理,如果此时将 0 添加到数组中的第 2 位,即 array[1] 的位置,同样需要先将 array[1] 及其之后的各个元素依次向后移动 1 位,给新数据腾出位置才能添加,如下图所示。

因为要移动元素,所以无论是添加数据还是删除数据,效率都不高。

搞清楚数组的存储结构之后,我们再来看看链表的存储结构,在内存中,链表中的数据是分散的,无须存储在一块连续的内存空间中,如下图所示。

链表中存储了 3 个元素分别是 1、2、3,每个元素都有一个指针,指向下一个元素的内存地址,1 的指针就指向 2 的内存地址 1008,2 的指针就指向 3 的内存地址 1020,依次类推。

不同元素之间的物理空间间隔也是不确定的,所以这样的结构就无法通过一个固定的公式来求出某个元素的内存地址,只能从首元素开始依次向后查找,直到找到目标元素。如果目标元素位于链表的最后一位,则需要遍历整个链表才能找到它,效率很低。

同样,正是因为这样的结构,使得链表添加和删除元素效率很高,无须移动其他已存在的元素,只需要修改元素指针即可。比如,删除 2,则只需要将 1 的指针指向 3 即可,如下图所示。

添加元素也是一样,要在 2 和 3 之间添加元素 0 ,只需要随机分配一块空间存储 0,然后将 2 的指针指向 0,0 的指针指向 3 即可,如下图所示。

涛声依旧注:元素2中存储了元素0的地址:2000,也就是2下面的数字。

所以在链表中,无论是添加还是删除元素,都只需要修改相关节点的指针即可,效率很高。

搞清楚链表的结构之后,我们使用 Java 语言来实现一个单链表的结构。

package com.southwind.link;

public class SingleLinkedList {
     public int size;
     public Node head;

     /**
    * 链表头部添加元素
    * @param obj
    * @return
     */
     public Node addHead(Object obj){
         Node newNode = new Node(obj);
         if(size == 0){
             this.head = newNode;
         }else{
             newNode.next = head;
             head = newNode;
         }
         size++;
         return newNode;
     }

     /**
    * 在指定位置插入元素
    * @return
     */
     public Node insert(int index,Object obj){
         if(index >= size){
             return null;
         }
         Node newNode = new Node(obj);
         if(index == 0){
             newNode.next = head;
             head = newNode;
         }else{
             Node target = head;
             Node previous = head;
             int pos = 0;
             while(pos != index){
                 previous = target;
                 target = target.next;
                 pos++;
             }
             previous.next = newNode;
             newNode.next = target;
         }
         size++;
         return newNode;
     }

     /**
    * 删除链表头部元素
    * @return
     */
     public Node removeHead(){
         if(size > 0){
             Node node = head;
             head = head.next;
             size--;
             return node;
         }else{
             return null;
         }
     }

     /**
    * 删除指定位置元素
    * @return
     */
     public Node remove(int index){
         if(index >= size){
             return null;
         }
         Node result = head;
         if(index == 0){
             head = head.next;
         }else{
             Node target = head;
             Node previous = head;
             int pos = 0;
             while(pos != index){
                 previous = target;
                 target = target.next;
                 pos++;
             }
             previous.next = target.next;
             result = target;
         }
         size--;
         return result;
     }

     /**
     * 删除指定元素
     * @return
     */
     public Node removeNode(Object obj){
         Node target = head;
         Node previous = head;
         if(obj.equals(target.data)){
             head = head.next;
             size--;
         }else{
             while(target.next!=null){
                 if(obj.equals(target.next.data)){
                     previous = target;
                     target = target.next;
                     size--;
                     break;
                 }else{
                     target = target.next;
                     previous = previous.next;
                 }
             }
             previous.next = target.next;
         }
         return target;
     }

     /**
     * 返回指定元素
     * @return
     */
     public Node findNode(Object obj){
         Node target = head;
         while(target.next!=null){
             if(obj.equals(target.data)){
                 return target;
             }else{
                 target = target.next;
             }
         }
         return null;
     }

     /**
     * 输出链表元素
     */
     public void show(){
         if(size > 0){
             Node node = head;
             int length = size;
             System.out.print("[");
             while(length > 0){
                 if(length == 1){
                     System.out.print(node.data);
                 }else{
                     System.out.print(node.data+",");
                 }
                 node = node.next;
                 length--;
             }
             System.out.println("]");
         }else{
             System.out.println("[]");
         }
     }
}

对上述代码进行测试。

package com.southwind.link;

public class Test {
     public static void main(String[] args) {
         SingleLinkedList linkedList = new SingleLinkedList();
         linkedList.addHead(6);
         linkedList.addHead(5);
         linkedList.addHead(4);
         linkedList.addHead(3);
         linkedList.addHead(2);
         linkedList.addHead(1);
         linkedList.show();
         System.out.println("*********在index=3处添加元素11*********");
         linkedList.insert(3,11);
         linkedList.show();
         System.out.println("*********在index=7处添加元素33*********");
         linkedList.insert(7,33);
         linkedList.show();
         System.out.println("*********在index=6处添加元素22*********");
         linkedList.insert(6,22);
         linkedList.show();
         System.out.println("*********删除头部元素*********");
         linkedList.removeHead();
         linkedList.show();
         System.out.println("*********删除index=7处元素*********");
         linkedList.remove(7);
         linkedList.show();
         System.out.println("*********删除index=6处元素*********");
         linkedList.remove(6);
         linkedList.show();
         System.out.println("*********删除index=3处元素*********");
         linkedList.remove(3);
         linkedList.show();
         System.out.println("*********删除指定元素3*********");
         linkedList.removeNode(3);
         linkedList.show();
         System.out.println("*********删除指定元素33*********");
         linkedList.removeNode(33);
         linkedList.show();
         System.out.println("*********返回指定元素2*********");
         System.out.println(linkedList.findNode(2));
         System.out.println("*********返回指定元素22*********");
         System.out.println(linkedList.findNode(22));
     }
}

结果如下图所示。

原文发布于微信公众号 - 趣谈编程(qutanbiancheng)

原文发表时间:2019-06-04

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

扫码关注云+社区

领取腾讯云代金券