2022/4/12 数据结构和算法 数组与链表(单/双链表)

目录

1.数组(Array)

        1.1 数组的特点

        1.2 数组的优点:

        1.3 数组的缺点:

2.链表(ListNode)

        2.1 链表的特点

        2.2链表的类型

                2.2.1 单向链表

                2.2.2 双向链表

                2.2.3 循环链表

        2.3 链表的优点

        2.4 链表的缺点

3.数组和链表的区别

4.数组和链表的使用场景

5.实现数组的增删改查(ArrayList)

        5.1 自定义数组类

        5.2 数组测试类

        5.3 数组测试类结果

6.实现链表的增删改查(LinkedList)

        6.1 自定义单链表类

        6.2 自定义单链表测试类

        6.3 单链表测试类结果

        6.4 自定义双链表类

        6.5 双链表测试类

        6.6 双链表测试类结果


1.数组(Array)

        1.1 数组的特点

所谓数组,就是相同数据类型的元素按一定顺序排列的集合数组的存储区间是连续的,占用内存比较大,故空间复杂的很大。但数组的二分查找时间复杂度小,都是O(1);数组的特点是:查询简单,增加和删除困难;

  1.  在内存中,数组是一块连续的区域
  2. 数组需要预留空间
    1.   在使用前需要提前申请所占内存的大小,如果提前不知道需要的空间大小时,预先申请就可能会浪费内存空间,即数组的空间利用率较低。注:数组的空间在编译阶段就需要进行确定,所以需要提前给出数组空间的大小(在运行阶段是不允许改变的)
  3. 在数组起始位置处,插入数据和删除数据效率低。
    1. 插入数据时,待插入位置的元素和他后面的所有元素都需要向后搬移
    2. 删除数据时,待删除位置后面的所有元素都需要向前搬移。
  4. 随机访问效率很高,时间复杂度可以达到O(1)
    1.   因为数组的内存是连续的,想要访问那个元素,直接从数组的首地址向后偏移就可以访问到了。
  5. 数组开辟的空间,在不够使用的时候需要进行扩容;扩容的话,就涉及到需要把旧数组中的所有元素向新数组中搬移。
  6.  数组的空间是从栈分配的。(栈:先进后出)

        1.2 数组的优点:

  1. 随机访问性强,查找速度快,时间复杂度是0(1)

        1.3 数组的缺点:

  1. 从头部删除、从头部插入的效率低,时间复杂度是o(n),因为需要相应的向前搬移和向后搬移。
  2. 空间利用率不高
  3. 内存空间要求高,必须要有足够的连续的内存空间。
  4. 数组的空间大小是固定的,不能进行动态扩展。

2.链表(ListNode)

        2.1 链表的特点

所谓链表,链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。链表由一系列结点(链表中每一个元素称为结点)组成,结点可以在运行时动态生成。每个结点包括两个部分:一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域。 相比于线性表顺序结构,操作复杂。由于不必须按顺序存储,链表在插入的时候可以达到O(1)的复杂度,比另一种线性表顺序表快得多,但是查找一个节点或者访问特定编号的节点则需要O(n)的时间,而线性表和顺序表相应的时间复杂度分别是O(logn)和O(1)。

链表:链表存储区间离散,占用内存比较宽松,故空间复杂度很小,但时间复杂度很大,达O(N)。链表的特点是:查询相对于数组困难,增加和删除容易。

  1. 在内存中,元素的空间可以在任意地方,空间是分散的,不需要连续
  2. 链表中的元素有两个属性,一个是元素的值,另一个是指针,此指针标记了下一个元素的地址。
    1. 每一个数据都会保存下一个数据的内存地址,通过该地址就可以找到下一个数据
  3. 查找数据时间效率低,时间复杂度是o(n)
    1. 因为链表的空间是分散的,所以不具有随机访问性,如果需要访问某个位置的数据,需要从第一个数开始找起,依次往后遍历,知道找到待查询的位置,故可能在查找某个元素时,时间复杂度是o(n)
  4. 空间不需要提前指定大小,是动态申请的,根据需求动态的申请和删除内存空间,扩展方便,故空间的利用率较高
  5. 任意位置插入元素和删除元素时间效率较高,时间复杂度是o(1)
  6. 链表的空间是从堆中分配的。(堆:先进先出,后进后出)

        2.2链表的类型

                2.2.1 单向链表

 单向链表包含两个域,一个是信息域,一个是指针域。也就是单向链表的节点被分成两部分,一部分是保存或显示关于节点的信息,第二部分存储下一个节点的地址,而最后一个节点则指向一个空值。

                2.2.2 双向链表

 从上图可以很清晰的看出,每个节点有2个链接,一个是指向前一个节点(当此链接为第一个链接时,指向的是空值或空列表),另一个则指向后一个节点(当此链接为最后一个链接时,指向的是空值或空列表)。意思就是说双向链表有2个指针,一个是指向前一个节点的指针,另一个则指向后一个节点的指针。

                2.2.3 循环链表

 循环链表就是首节点和末节点被连接在一起。循环链表中第一个节点之前就是最后一个节点,反之亦然。

        2.3 链表的优点

  1. 任意位置插入元素和删除元素的速度快,时间复杂度是o(1)
  2. 内存利用率高,不会浪费内存
  3. 链表的空间大小不固定,可以动态拓展。

        2.4 链表的缺点

  1. 随机访问效率低,时间复杂度是o(1)

3.数组和链表的区别

数组和链表的区别主要表现在以下几个方面

比较项数组链表
逻辑结构

(1)数组在内存中连续; (2)使用数组之前,必须事先固定数组长度,不支持动态改变数组大小;(3) 数组元素增加时,有可能会数组越界;(4) 数组元素减少时,会造成内存浪费;(5)数组增删时需要移动其它元素

(1)链表采用动态内存分配的方式,在内存中不连续 (2)支持动态增加或者删除元素 (3)需要时可以使用malloc或者new来申请内存,不用时使用free或者delete来释放内存
内存结构数组从栈上分配内存,使用方便,但是自由度小链表从堆上分配内存,自由度大,但是要注意内存泄漏
访问效率数组在内存中顺序存储,可通过下标访问,访问效率高链表访问效率低,如果想要访问某个元素,需要从头遍历
越界问题数组的大小是固定的,所以存在访问越界的风险只要可以申请得到链表空间,链表就无越界风险

4.数组和链表的使用场景

数组(顺序表)和链表各有短长,在实际应用中,应该具体问题具体分析,通常有以下方面的考虑

比较项数组使用场景链表使用场景
空间数组的存储空间是栈上分配的,存储密度大,当要求存储的大小变化不大时,且可以事先确定大小,宜采用数组存储数据链表的存储空间是堆上动态申请的,当要求存储的长度变化较大时,且事先无法估量数据规模,宜采用链表存储
时间数组访问效率高。当线性表的操作主要是进行查找,很少插入和删除时,宜采用数组结构链表插入、删除效率高,当线性表要求频繁插入和删除时,宜采用链表结构

5.实现数组的增删改查(ArrayList)

        5.1 自定义数组类

package Test;

public class MyArrayList {
   private Object[] array;


   /*添加元素add方法*/
    public void  add(Object ob){
       if(array==null){
           array=new Object[1];
           array[0]=ob;
       }else{
           Object[] temp=new Object[array.length+1];
           /*循环赋值*/
           for (int i = 0; i < array.length; i++) {
               temp[i]=array[i];
           }
           temp[array.length]=ob;
           array=temp;
       }
   }
   /*修改元素upd方法*/
   public boolean set(int index,Object ob){
       if(index<0||index>array.length){
           return false;
       }
       array[index]=ob;
       return true;
   }


   /*删除元素del方法*/
    public boolean delete(int index){
        Object[] temp=new Object[array.length-1];
        if(index<0||index>temp.length){
            return false;
        }
            /*删除对应下标的元素*/
            /*循环赋值*/
            for (int i = 0; i < temp.length; i++) {
                if(i>=index){
                    temp[i]=array[i+1];
                }else{
                    temp[i]=array[i];
                }
            }
            array=temp;
            return true;
    }

   /*查询所有*/
    public void getall(){
        for (Object o : array) {
            System.out.println(o);
        }
    }
}

        5.2 数组测试类

package Test;
public class test {
    public static void main(String[] args) {
        MyArrayList array=new MyArrayList();
        array.add(1);
        array.add(2);
        array.add(3);
        System.out.println("数组测试:正序输出:");
        array.getall();
        System.out.println("数组测试:新增:");
        array.add(4);
        array.getall();
        System.out.println("数组测试:修改:");
        array.set(2,"我被修改了!");
        array.getall();
        System.out.println("数组测试:删除:");
        array.delete(2);
        array.getall();
    }
}

        5.3 数组测试类结果

数组测试:正序输出:
1
2
3
数组测试:新增:
1
2
3
4
数组测试:修改:
1
2
我被修改了!
4
数组测试:删除:
1
2
4

进程已结束,退出代码为 0

6.实现链表的增删改查(LinkedList)

        6.1 自定义单链表类

package LinkNode;

public class Node {
    public Object data; //节点的内容
    public Node next;   //下一个节点
    public Node(Object data) {
        this.data = data;
    }

}
package LinkNode;

public class MyLinkedList {
    //根节点  找到链表的唯一途径
    private Node root;

    /*
    * 往尾新增节点
    * */
    public void add(Object data){
        /*新增进来的参数打包陈节点*/
        Node node=new Node(data);
        /*判断头节点是否为空如果是则新增头节点*/
        if(root==null){
            root=node;
        }else{
            Node temp=root;     //用临时节点获得节点的头节点
            while (temp.next!=null){    //循环判断下一个节点是否为空
                temp=temp.next;     //下移一个节点,既是赋值也是移动
            }
            temp.next=node;     //给最后一个节点赋值
        }
    }

    /*
    * 往头新增节点
    * */
   public void addFirst(Object data){
       /*新增进来的参数打包陈节点*/
       Node node=new Node(data);
       /*判断头节点是否为空如果是则新增头节点*/
       if(root==null){
           root=node;
       }else{
           node.next=this.root; //元素指向 原来头部的元素
           this.root=node;  //然后再吧要添加的元素做为头部
       }
   }

    /*
    *按照坐标进行插入
    * */
    private Node search(int index){
        int count=0;          //定义临时变量记录节点总长度
        Node cur=this.root;   //获得当前节点的头节点
        for (int i = 0; i <index-1 ; i++) {   //根据下标找到要插入节点的位置
            cur=cur.next;           //下移一个节点
            count++;
        }
        return cur;         //返回对应下标位置的节点
    }
    /*
    * 验证下标是否正确
    * */
    private void checkIndex(int index){
        if (index<0||index>getLength()){
            throw new UnsupportedOperationException("位置不合法");
        }
    }
    /*
    * 按指定位置插入节点
    * */
    public void addIndex(int index, Object data) {
        //如果输入的下标为0则为在头部插入
        if (index==0){
            addFirst(data);
            //如果输入的下标超过节点下标的长度默认添加到最后一个
        }else if(index>getLength()){
            add(data);
            //如果输入的下标不是0也不是最后一个那么就添加到对应的位置
        }else{
            //新增进来的参数打包成节点
            Node node=new Node(data);
            Node cur=search(index); //获得对应下标的节点
            node.next=cur.next;   //填充增加节点后的节点
            cur.next=node;      //插入对应节点
        }

    }
    /*
    * 获得当前节点的总长度
    * */
    public int getLength(){
        int num=0;              //创建临时计数器
        Node temp=root;         //创建临时变量获得节点的头
        while (temp!=null) {     //循环临时节点
            temp=temp.next;     //下移一个节点,既是赋值也是移动
            num++;
        }
        return num;             //返回当前节点总长度
    }

    /*
    * 删除节点
    * */
    public void delete(int index){
        //判断当前链表是否为空
        if (root.next==null){   //空链表
            System.out.println("链表为空,无法删除!");
            return;
        }
        /*删除头节点需要单独判断*/
        if(index==0){
            root=root.next;
        }else{
            int num=0;              //创建临时计数器
            boolean flag=false;     //标志是否找到待删除的节点
            Node temp=root;         //创建临时变量获得节点的头
            while (temp!=null){     //循环临时节点
                if(num==index-1){   //找到删除节点位置的上一个节点
                    temp.next=temp.next.next;   //将当前节点替换为下一个节点
                    flag=true;      //找到了
                    break;
                }
                temp=temp.next;     //下移一个节点,既是赋值也是移动
                num++;              //临时节点++
            }
            if (flag){
            }else{
                System.out.println("没有找到对应的下标!");
            }
        }
    }

    /*
    * 修改节点
    * */
    public void update(int index,Object date){
        int num=0;              //创建临时计数器
        Node temp=root;         //创建临时变量获得节点的头
        while (temp!=null){     //循环临时节点
           if(num==index){      //当下标与输入的下标一致时
               temp.data=date;  //替换当前节点的内容
           }
            temp=temp.next;     //下移一个节点,既是赋值也是移动
            num++;              //临时节点++
        }
    }

    /*
    * 查询指定下标的节点内容
    * */
    public void getByindex(int index){
        int num=0;              //创建临时计数器
        Node temp=root;         //创建临时变量获得节点的头
        while (temp!=null){     //循环临时节点
            if(num==index){      //当下标与输入的下标一致时
                System.out.println(temp.data);      //输出该节点的内容
            }
            temp=temp.next;     //循环赋值and循环节点
            num++;              //临时节点++
        }
        if(index<0){
            System.out.println("节点下标不能小于0!");
        }else if(index>num){
            System.out.println("节点下标不能大于总长度!");
        }
    }

    /*查询全部链表*/
    public void getall(){
        Node temp=root;    //借用头节点用于循环整个链表
        while (temp.next!=null){
            System.out.println(temp.data);
            temp=temp.next;     //下移一个节点,既是赋值也是移动
        }
        /*最后一个节点一定会被遗漏,所以这里手动输出一下*/
        System.out.println(temp.data);
    }
}

        6.2 自定义单链表测试类

package LinkNode;

public class NodeTest {
    public static void main(String[] args) {
        MyLinkedList linkedlist=new MyLinkedList();
        linkedlist.add(11);
        linkedlist.add(22);
        linkedlist.add(33);
        System.out.println("单链表测试:正序输出:");
        linkedlist.getall();
        System.out.println("单链表测试:增加:");
        linkedlist.add(44);
        linkedlist.getall();
        System.out.println("单链表测试:头增:");
        linkedlist.addFirst("现在我是链头了!");
        linkedlist.getall();
        System.out.println("单链表测试:按指定位置新增:");
        linkedlist.addIndex(2,"想去哪就去哪!");
        linkedlist.getall();
        System.out.println("单链表测试:修改:");
        linkedlist.update(2,"2号节点被修改了!");
        linkedlist.getall();
        System.out.println("单链表测试:删除:");
        linkedlist.delete(5);
        linkedlist.getall();

    }
}

        6.3 单链表测试类结果

单链表测试:正序输出:
11
22
33
单链表测试:增加:
11
22
33
44
单链表测试:头增:
现在我是链头了!
11
22
33
44
单链表测试:按指定位置新增:
现在我是链头了!
11
想去哪就去哪!
22
33
44
单链表测试:修改:
现在我是链头了!
11
2号节点被修改了!
22
33
44
单链表测试:删除:
现在我是链头了!
11
2号节点被修改了!
22
33

进程已结束,退出代码为 0

        6.4 自定义双链表类

package DoubleLinkNode;

public class Node {
    public Object data; //节点的内容
    public Node next;   //下一个节点
    public Node prev;   //上一个节点

    public Node(Object data) {
        this.data = data;
    }

}
package DoubleLinkNode;

/**
 * 单向链表与双向链表的区别
 * 1.单向链表,查找的方向只能是一个方向,而双向链表可以向前或者向后查找
 * 2.单向链表不能自我删除,需要靠辅助节点,而双向链表,则可以自我删除。
 */
public class MyLinkedList {
    //根节点  找到链表的唯一途径
    private Node root;
    //未节点
    private Node last;
    /*
    * 添加一个节点到双向列表的最后
    * */
    public void add(Object data){
        /*新增进来的参数打包陈节点*/
        Node node=new Node(data);
        /*判断头节点是否为空如果是则新增头节点*/
        if(root==null){
            root=node;
        }else{
            Node temp=root;     //用临时节点获得节点的头节点
            while (temp.next!=null){    //循环判断下一个节点是否为空
                temp=temp.next;     //下移一个节点,既是赋值也是移动
            }
            temp.next=node;     //给最后一个节点赋值
            node.prev=temp;     //尾节点指向前一个节点,形成一个双向链表
        }
    }

    /*
    * 往头新增节点
    * */
   public void addFirst(Object data){
       /*新增进来的参数打包陈节点*/
       Node node=new Node(data);
       /*判断头节点是否为空如果是则新增头节点*/
       if(root==null){
           root=node;
       }else{
           node.next=this.root; //元素指向 原来头部的元素
           this.root=node;  //然后再吧要添加的元素做为头部
       }
   }

    /*
    *按照坐标进行插入
    * */
    private Node search(int index){
        int count=0;          //定义临时变量记录节点总长度
        Node cur=this.root;   //获得当前节点的头节点
        for (int i = 0; i <index-1 ; i++) {   //根据下标找到要插入节点的位置
            cur=cur.next;           //下移一个节点
            count++;
        }
        return cur;         //返回对应下标位置的节点
    }
    /*
    * 验证下标是否正确
    * */
    private void checkIndex(int index){
        if (index<0||index>getLength()){
            throw new UnsupportedOperationException("位置不合法");
        }
    }
    /*
    * 按指定位置插入节点
    * */
    public void addIndex(int index, Object data) {
        //如果输入的下标为0则为在头部插入
        if (index==0){
            addFirst(data);
            //如果输入的下标超过节点下标的长度默认添加到最后一个
        }else if(index>getLength()){
            add(data);
            //如果输入的下标不是0也不是最后一个那么就添加到对应的位置
        }else{
            //新增进来的参数打包成节点
            Node node=new Node(data);
            Node cur=search(index); //获得对应下标的节点
            node.next=cur.next;   //填充增加节点后的节点
            cur.next=node;      //插入对应节点
        }

    }
    /*
    * 获得当前节点的总长度
    * */
    public int getLength(){
        int num=0;              //创建临时计数器
        Node temp=root;         //创建临时变量获得节点的头
        while (temp!=null) {     //循环临时节点
            temp=temp.next;     //下移一个节点,既是赋值也是移动
            num++;
        }
        return num;             //返回当前节点总长度
    }

    /*
    * 删除节点
    * 说明
    * 1 对于双向链表,我们可以直接找到要删除的这个节点
    * 2 找到后,自我删除即可
    * */
    public void delete(int index){
        //判断当前链表是否为空
        if (root.next==null){   //空链表
            System.out.println("链表为空,无法删除!");
            return;
        }
        /*删除头节点需要单独判断*/
        if(index==0){
            root=root.next;
        }else{
            boolean flag=false;     //标志是否找到待删除的节点
            int num=0;              //创建临时计数器
            Node temp=root;         //创建临时变量获得节点的头
            while (temp!=null){     //循环临时节点
                if(num==index){   //找到删除节点的位置
                   // temp.next=temp.next.next;   //将当前节点替换为下一个节点  单链表
                    temp.prev.next=temp.next;
                    //如果是最后一个节点,就不需要执行下面这句话,否则出现空指针异常
                    if(temp.next!=null){
                        temp.next.prev=temp.prev;
                    }
                    flag=true;      //找到了
                    break;
                }
                temp=temp.next;     //下移一个节点,既是赋值也是移动
                num++;              //临时节点++
            }
            if (flag){
            }else{
                System.out.println("没有找到对应的下标!");
            }
        }
    }

    /*
    * 双链表修改节点与单链表一致
    * */
    public void update(int index,Object date){
        int num=0;              //创建临时计数器
        Node temp=root;         //创建临时变量获得节点的头
        while (temp!=null){     //循环临时节点
           if(num==index){      //当下标与输入的下标一致时
               temp.data=date;  //替换当前节点的内容
           }
            temp=temp.next;     //下移一个节点,既是赋值也是移动
            num++;              //临时节点++
        }
    }

    /*
    * 查询指定下标的节点内容
    * */
    public void getByindex(int index){
        int num=0;              //创建临时计数器
        Node temp=root;         //创建临时变量获得节点的头
        while (temp!=null){     //循环临时节点
            if(num==index){      //当下标与输入的下标一致时
                System.out.println(temp.data);      //输出该节点的内容
            }
            temp=temp.next;     //循环赋值and循环节点
            num++;              //临时节点++
        }
        if(index<0){
            System.out.println("节点下标不能小于0!");
        }else if(index>num){
            System.out.println("节点下标不能大于总长度!");
        }
    }

    /*查询链表所有节点*/
    public void AscList(){
        Node temp=root;    //借用头节点用于循环整个链表
        while (temp.next!=null){
            System.out.println(temp.data);
            temp=temp.next;     //下移一个节点,既是赋值也是移动
        }
        /*最后一个节点一定会被遗漏,所以这里手动输出一下*/
        System.out.println(temp.data);
    }

    /*倒序输出*/
    public void DescList(){
        Node temp=root;    //借用头节点用于循环整个链表
        while (temp.next!=null){
            temp=temp.next;     //下移一个节点,既是赋值也是移动
        }
        while (temp.prev!=null){
            System.out.println(temp.data);
            temp=temp.prev;     //上移一个节点,既是赋值也是移动
        }
        /*最后一个节点一定会被遗漏,所以这里手动输出一下*/
        System.out.println(temp.data);
    }
}

        6.5 双链表测试类

package DoubleLinkNode;

public class NodeTest {
    public static void main(String[] args) {
        MyLinkedList linkedlist=new MyLinkedList();
        linkedlist.add(11);
        linkedlist.add(22);
        linkedlist.add(33);
        System.out.println("双向链表测试:正序输出:");
        linkedlist.AscList();

        System.out.println("双向链表测试:倒序输出:");
        linkedlist.DescList();

        System.out.println("双向链表测试:新增:");
        linkedlist.add("44");
        linkedlist.AscList();

        System.out.println("双向链表测试:按指定位置新增:");
        linkedlist.addIndex(2,"我是2号节点!");
        linkedlist.AscList();

        System.out.println("双向链表测试:头增:");
        linkedlist.addFirst("现在我是链头了哦!");
        linkedlist.AscList();

        System.out.println("双向链表测试:修改:");
        linkedlist.update(2,"2号节点被修改了!");
        linkedlist.AscList();

        System.out.println("双向链表测试:删除:");
        linkedlist.delete(2);
        linkedlist.AscList();
    }
}

        6.6 双链表测试类结果

双向链表测试:正序输出:
11
22
33
双向链表测试:倒序输出:
33
22
11
双向链表测试:新增:
11
22
33
44
双向链表测试:按指定位置新增:
11
22
我是2号节点!
33
44
双向链表测试:头增:
现在我是链头了哦!
11
22
我是2号节点!
33
44
双向链表测试:修改:
现在我是链头了哦!
11
2号节点被修改了!
我是2号节点!
33
44
双向链表测试:删除:
现在我是链头了哦!
11
我是2号节点!
33
44

进程已结束,退出代码为 0

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Abcdzzr

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

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

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

打赏作者

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

抵扣说明:

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

余额充值