线性表 (顺序表 和 单链表)

线性表:(List)
是n个具有 相同特性 的数据元素的 有限序列 。 线性表是一种在实际中广泛使用的数据结构,常见的线性表:顺序表、链表、栈、队列、字符串
线性表在逻辑上线性结构 ,也就说是连续的一条直线。但是在物理结构上并不一定是连续的,线性表在物理上存储时,通常以 数组链式结构 的形式存储
在这里插入图片描述
在这里插入图片描述

1、顺序表:

(1)神马是顺序表:(ArrayList)
是用一段 物理地址连续 的存储单元依次存储数据元素的 线性结构(逻辑上也连续)
一般采用 数组 存储,也就是在数组上增删查改

① 从Java语法的角度:
List 是接口 ; ArrayList 是类,实现了 List
② 从数据结构的角度:
List 表示线性表, ArrayList 表示顺序表
通过 ArrayList 实现 List 。。
表达了顺序表是一种线性表

(2)分类:
静态 顺序表: 使用 定长数组 存储
动态 顺序表: 使用 动态开辟的数组 存储

(3)接口的实现:(下面这个为一个简单的顺序表)

在这里插入图片描述

import java.util.Arrays;
/**
 *  顺序表
 */
class MyArrayList {
    
    
    public int[] elem; // null
    public int usedSize; // 0  表示数组中元素的个数

    public MyArrayList() {
    
    
        this.elem = new int[5];
        this.usedSize = 0;
    }

    // 打印顺序表
    public void display() {
    
    
        for(int i = 0;i < usedSize;i++) {
    
    
            System.out.print(elem[i]+" ");
        }
        System.out.println();
    }

    //在 pos 位置新增元素
    public void add(int pos,int data) {
    
    
        if(pos < 0 || pos > this.usedSize) {
    
    
            System.out.println("该位置不合法!");
        }
        //扩容
        if(this.usedSize == this.elem.length) {
    
    
            this.elem = Arrays.copyOf(this.elem,2*this.elem.length);
        }
        for(int i = this.usedSize;i >= pos;i--) {
    
    
            this.elem[i+1] = this.elem[i];
        }
        this.elem[pos] = data;
        this.usedSize++;

    }

    //判定是否含某个元素
    public boolean contains(int toFind) {
    
    
        for(int i = 0;i < this.usedSize;i++) {
    
    
            if(elem[i] == toFind) {
    
    
                return true;
            }
        }
        return false;
    }

    // 查找某个元素对应的位置
    public int search(int toFind) {
    
    
        for(int i = 0;i < this.usedSize;i++) {
    
    
            if (elem[i] == toFind) {
    
    
                return i+1;
            }
        }
        return -1;
    }

    // 获取 pos 位置的元素
    public int getPos(int pos) {
    
    
        if(pos < 0 || pos >= this.usedSize) {
    
    
            System.out.println("该位置不合法!");
        }
        return elem[pos-1];
    }

    // 给 pos 位置的元素设为 value
    public void setPos(int pos,int value) {
    
    
        if(pos < 0 || pos > this.usedSize) {
    
    
            System.out.println("该位置不合法!");
        }
        this.elem[pos] = value;
    }

    //删除第一次出现的关键字 key
    public void remove(int toRemove) {
    
    
        //找到是否有 toRemove
        int index = search(toRemove);
        if(index == -1) {
    
    
            System.out.println("么找到该元素!");
            return;
        }
        for(int i = index;i < this.usedSize-1;i++) {
    
    
            this.elem[i] = this.elem[i+1];
        }
        this.usedSize--;
    }
 
    //获取顺序表长度
    public int size() {
    
    
        return this.usedSize;
    }

    //清除顺序表
    /**
     * 如果是引用类型: elem[i] = null;
     */
    public void clear() {
    
    
        this.usedSize = 0; // 仅限于简单类型
    }
}
public class table {
    
    
    public static void main(String[] args) {
    
    
        MyArrayList myArrayList = new MyArrayList();
        myArrayList.add(0,1);//往顺序表里添数
        myArrayList.add(1,2);
        myArrayList.add(2,3);
        myArrayList.add(3,4);
        myArrayList.display();//打印数组
        System.out.println(myArrayList.contains(6));//判断数组中是否有 6
        System.out.println("所找的数在第"+myArrayList.search(6)+"个位置");
        System.out.println(myArrayList.getPos(2));//获取 2 位置的元素
        myArrayList.setPos(2,12);//将 2 位置的数值换为 12
        myArrayList.display();//打印数组
        myArrayList.remove(6);//删除 6
        myArrayList.display();
    }
}

  1. 顺序表中间/头部的插入删除,时间复杂度为O(N)
  2. 增容需要申请新空间,拷贝数据,释放旧空间。会有不小的消耗。
  3. 增容一般是呈2倍的增长,势必会有一定的空间浪费。例如当前容量为100,满了以后增容到200,我们再继续插入了5个数据,后面没有数据插入了,那么就浪费了95个数据空间。

为了解决顺序表的弊端,我们引入了单链表:

2、单链表:

(1)神马是单链表 :
是一种 物理存储结构非连续 存储结构,数据元素的 逻辑顺序 是通过链表中的 引用链接次序 实现的 。(逻辑连续)

(2)表现形式:(一共是 8 种)
聪明的你尝试自己组合一下。。。
单向 、 双向
带头 、 不带头
循环 、 非循环

下来我们简单介绍两种形式的:(面试问的最多!)
链表 由一个一个的 节点组成
你问我节点是干嘛的?
节点 当然是用来 存储数据 的了。。。

data :数据,不一定是整型的
next :下一个节点的引用(地址)

单向 不带头 非循环链表

(头节点可能会随时改变)
在这里插入图片描述
写一个简单的 单向 不带头 非循环链表

/**
 * 单链表
 */
class Node {
    
    
    public int data;
    public Node next; // 表示下个节点的引用
    public Node(int data) {
    
    
        this.data = data;
    }
}
public class SingleLinkedList {
    
    
    public static void main(String[] args) {
    
    
        SingleLinkedList singleLinkedList = new SingleLinkedList();
        singleLinkedList.addFirst(6);
        singleLinkedList.addLast(1);
        singleLinkedList.addFirst(4);
        singleLinkedList.addLast(8);
        singleLinkedList.addLast(1);
        singleLinkedList.addLast(6);
        singleLinkedList.display();
        //singleLinkedList.remove(1);
        //singleLinkedList.display();
        System.out.println(singleLinkedList.contains(6));
        singleLinkedList.removeAllKey(6);
        singleLinkedList.display();
    }

    public Node head;// 标识头节点
    //头插法
    public void addFirst(int data) {
    
    
        Node node = new Node(data);
        node.next = this.head;
        this.head = node;
    }

    // 打印
    public void display() {
    
    
        Node cur = this.head;
        while(cur != null) {
    
    
            System.out.print(cur.data+" ");
            cur = cur.next;
        }
        System.out.println();
    }

    // 尾插法
    public void addLast(int data) {
    
    
        Node node = new Node(data);
        if(this.head == null) {
    
    
            this.head = node;
        } else {
    
    
            Node cur = this.head;
            while(cur.next != null) {
    
    
                cur = cur.next;
            }
            cur.next = node;
        }
    }

    // 检查下标是否合法
    public boolean checkIndex(int index) {
    
    
        if(index == 0 || index > this.getLength()) {
    
    
            System.out.println("下标不合法!");
            return false;
        }
        return true;
    }

    // 任意位置插入,第一个数据节点为0号下标
    public void addIndex(int index,int data) {
    
    
        if(!checkIndex(index)) {
    
    
            return;
        }
        if(index == 0) {
    
    
            addFirst(data);
            return;
        }
        if(index == this.getLength()) {
    
    
            addLast(data);
            return;
        }
        Node cur = searchPrev(index);//cur 此时保存的就是 index-1 位置节点的引用
        Node node = new Node(data);
        node.next = cur.next;
        cur.next = node;


    }
    public int getLength() {
    
    
        int count = 0;
        Node cur = this.head;
        while(cur != null) {
    
    
            count++;
            cur = cur.next;
        }
        return count;
    }
    //查找index的位置,找到并返回引用
    public Node searchPrev(int index) {
    
    
        Node cur = this.head;
        int count = 0;
        while(count < index-1) {
    
    
            cur = cur.next;
            count++;
        }
        return cur;

    }

    // 找前驱
    public Node searchPrevNode(int key) {
    
    
        Node cul = this.head;
        while (cul.next != null) {
    
    
            if(cul.next.data == key) {
    
    
                return cul;
            }
            cul = cul.next;
        }
        return null;
    }
    //删除第一次出现关键字 key 的节点
    public void remove(int key) {
    
    
        // 头节点是要删除的节点
        if(this.head == null) return;
        if(this.head.data == key) {
    
    
            this.head = this.head.next;
            return;
        }
        Node cul = searchPrevNode(key);
        if(cul == null) {
    
    
            System.out.println("没有你要删除的数字!");
            return;
        }
        Node del = cul.next; // 所要删除的节点
        cul.next = del.next;
    }
    
    // 判断是否包含某个元素
    public boolean contains(int toFind) {
    
    
        Node cul = this.head;
        while (cul != null) {
    
    
            if(cul.data == toFind) {
    
    
                return true;
            }
            cul = cul.next;
        }
        return false;
    }

    // 删除所有值为 key 的节点
    public void removeAllKey(int key) {
    
    
        if(this.head == null) return;
        Node prev = this.head;
        Node cul = this.head.next;
        while(cul != null) {
    
    
            if (cul.data == key) {
    
    
                prev.next = cul.next;
                cul = cul.next;
            } else {
    
    
                prev = cul;
                cul = cul.next;
            }
        }
        if(this.head.data == key) {
    
    
            this.head = this.head.next;
        }
    }
}

3、顺序表和链表的区别:(面试)

区别:

顺序表:(物理上和逻辑上都是连续的)
(1)空间连续、支持随机访问(底层是数组)
(2)中间或前面部分的插入删除时间复杂度是O(N)
(3)增容代价大
链表: (逻辑上连续,物理上不一定连续)
(1)以结点为单位存储, 不支持随机访问
(2)任意位置插入删除时间复杂度为 O(1)
(3)没有增容问题,插入一个开辟一个空间

猜你喜欢

转载自blog.csdn.net/qq_45658339/article/details/108920853
今日推荐