【数据结构】顺序表

  •  List接口是由多个类实现的:1、ArrayList类 2、LinkedList类 3、Vector类(通过Stack类实现)
  • 学习List接口:1、学习ArrayList类 -> 2、学习顺序表 -> 3、学习线性表

1、线性表

  • 定义:相同特性数据元素的有限序列,线性表就是一种数据结构;
  • 常见的线性表:顺序表、链表、栈、队列… 
  • 线性表在逻辑上是线性结构,也就是说在逻辑上是连续存储的;但是在物理结构上就不一定连续存储;

2、顺序表

  • 顺序表就是在物理结构上连续存储的一种线性结构;
  • 顺序表是采用数组进行存储数据的,所以是连续存储;
  • 顺序表中主要实现的功能就是对数据进行增删改查,也就是对数组进行增删改查;
  • -> 为什么不直接使用数组进行存储呢?

    1. 数组并不能实现一些特有功能
    2. 顺序表就是在数组的基础上增加一些特有的功能和方法
    3. 将带有特有功能的数组,组织成一种新的数据类型,这个新数据类型就是顺序表
  • 顺序表是一种新的数据类型,这种新的类型是用来操作数组的;

顺序表的实现(MyArrayList)

  • 我们在写这个顺序表时,首先要明白顺序表是什么;
  • 顺序表中主要实现的功能就是对数据进行增删改查,也就是对数组进行增删改查;
  • 我们需要通过三方面进行编写代码:1、MyArrayList类(也就是顺序表类)  2、ArrayListInterface接口(也就是功能接口) 3、异常类,所以代码大的分为了这三部分;

 

1、MyArrayList类

重点、难点:新增方法

重点、难点:删除方法

MyArrayList代码:
package ArrayList;

import java.util.Arrays;

/**
 *  顺序表就是使用数组实现的,只是有一些特有的功能
 *  1、先要有一个数组
 *  2、获取到顺序表的长度->这个长度就是数组使用长度
 *  3、打印顺序表->遍历数组
 *  4、新增元素->注意:新增完元素后一定要将useSize++
 *  5、判断当前位置是否合法->不合法应该抛出异常
 *  6、查找元素、获取元素下标、获取元素
 *  7、删除元素->1、判断顺序表是否为空;2、查找元素下标;3、从前向后依次覆盖
 *  8、清空顺序表
 */
public class MyArrayList implements ArrayListInterface{
    public int[] array; // 定义的数组
    public int useSize; // 数组使用的大小
    public static final int DEFAULT_CAPACITY = 10; // 数组默认大小10

    // 初始化
    public MyArrayList() {
        // 将数组初始化
        this.array = new int[DEFAULT_CAPACITY]; // 初始化数组
    }


    @Override
    public int size() {
        // 因为是数组使用长度,并且使用长度是公开的,所以直接返回
        return useSize;
    }

    @Override
    public void display() {
        // 打印顺序表
        // 也就是遍历顺序
        for (int i = 0; i < size(); i++) {
            // 将数组中的所有元素打印出来
            System.out.print(array[i] + " ");
        }
        System.out.println();
    }

    @Override
    public void add(int data) {
        // 增加元素,默认增加到数组的最后位置
        // 1、数组是否满 -> 所以先写isFull
        if(isFull()) {
            // 满了进行扩容
            array = Arrays.copyOf(array,array.length);
        }
        // 满了之后会进行新增元素,没满也会进行新增元素,所以新增元素写在外面
        // 将useSize位置放上这个元素
        array[useSize] = data;
        // 新增一个元素之后需要将useSize++
        useSize++;
    }

    @Override
    public boolean isFull() {
        // 判断一下,数组是否满
        // 满了并不代表数组越界,所以这里的判断应该是==
        // 我最开始写的是>=
        if(useSize == array.length) {
            return true; // 如果满了,返回true
        }
        return false; // 没满返回false
    }

    @Override
    public boolean isEmpty() {
        // 判断是否为空
        // 顺序表中没有任何元素时则为空
        if(useSize == 0) {
            return true;
        }
        return false;
    }

    @Override
    public void checkPosInAdd(int pos) {
        // 判断当前位置是都合法
        // 当前的下标小于0,大于数组长度 -> 这样考虑是有缺陷的,如果当前位置前面是null,那么就不能新增元素的;
        // 所以当前位置大于的不是数组长度,而是大于useSize+1处就不能新增元素了
        if(pos < 0 || pos > size()) {
            // 其实不合法应该抛出异常的
            // System.out.println(pos + "当前位置不合法");
            throw new PosException(pos + "当前位置不合法");
        }
        System.out.println(pos + "当前位置合法");
    }

    @Override
    public void add(int pos, int data) {
        // 在特定位置新增元素
        // 当前位置是否合法?
        // 如果数组是满的呢?

        checkPosInAdd(pos);
        if(isFull()) {
            array = Arrays.copyOf(array,array.length);
        }

        // 位置合法情况下
        // 在此位置处,需要先将元素向后移动
        // 这里有一个细节useSize-1,下标比长度少1
        for (int i = useSize-1; i >= pos; i--) {
            // 从后向前移动元素
            // 将前一个元素放到后一个元素的位置
            array[i+1] = array[i];
        }
        // 新增元素
        // 最后useSize++
        array[pos] = data;
        useSize++;
    }

    @Override
    public boolean contains(int data) {
        // 查找当前元素是否存在
        for (int i = 0; i < size(); i++) {
            if(array[i] == data) {
                return true;
            }
        }
        return false;
    }

    @Override
    public int indexOf(int data) {
        // 返回的是当前值的下标
        for (int i = 0; i < size(); i++) {
            if(array[i] == data) {
                return i; // 存在返回下标
            }
        }
        return -1; // 不存在返回-1
        // 数组中没有-1这个下标
    }

    @Override
    public int get(int pos) {
        // 获取特定位置的元素
        // 判断位置是否合法
        checkPosInAdd(pos);
        // 最开始我写的时候,少了一个判断条件
        // 如果这个顺序表为空,就没有获取到任何元素
        if(isEmpty()) {
            // 如果顺序表为空,抛出异常
            throw new EmptyException("顺序表为空");
        }

        // 合法情况,直接返回该位置元素
        return array[pos];
    }

    @Override
    public void set(int pos, int value) {
        // 给特定位置更新元素值
        // 判断位置是否合法
        checkPosInAdd(pos);

        // 这是给已经有值的位置更新值
        // 如果顺序表为空就没法更新
        if(isEmpty()) {
            // 如果顺序表为空,抛出异常
            throw new EmptyException("顺序表为空");
        }
        // 合法情况,直接返回该位置元素
        array[pos] = value;
    }

    @Override
    public void remove(int key) {
        // 1、顺序表是否为空
        // 2、查找要删除数的下标
        // 3、从前向后一次覆盖
        if(isEmpty()) {
            throw new EmptyException("顺序表为空");
        }
        int pos = indexOf(key); // 查找key元素的下标
        // 找到key元素下标后,进行删除元素
        // 删除元素:当前下标,从前向后依次覆盖
        for (int i = pos; i < size()-1; i++) {
            // 后一个元素覆盖前一个元素
            array[i] = array[i+1];
        }
        // 删除之后useSize--
        useSize--;
    }

    @Override
    public void clear() {
        // 清空顺序表,防止内存泄漏
        // 将每一个对象都置为null

        // 因为这里的对象全都是int类型
        // 所以只需要将useSize置为0
        useSize = 0;
    }
}

2、 ArrayListInterface接口

size() -> 获取顺序表的长度,这个长度是顺序表的使用长度

display() -> 打印顺序表

add() -> 新增元素,默认新增到顺序表的最后

isFull() -> 判断当前顺序表是否满

isEmpty() -> 判断当前顺序表是否为空

checkPosInAdd() -> 判断当前位置是否合法

contains() -> 查看顺序表中是否包含某个元素

indexOf() -> 根据元素返回下标

get() -> 根据位置返回元素

set() -> 更新某个下标的值

remove() -> 删除第一次出现的某个元素

clear() -> 清空顺序表

 ArrayListInterface代码:
package ArrayList;

/**
 *  顺序表中的功能
 *  这些接口中的功能都需要类去实现
 */

public interface ArrayListInterface {
    // 获取顺序表的长度->这个长度是使用长度
    int size();

    // 打印顺序表
    // 根据useSize判断
    void display();

    // 新增元素
    // 默认新增到数组的最后
    void add(int data);

    // 判断当前顺序表是否满
    boolean isFull();

    // 判断当前顺序小是否为空
    boolean isEmpty();

    // 判断当前位置是否合法
    void checkPosInAdd(int pos);

    // 在特定位置新增元素
    // 实现了方法的重载
    void add(int pos, int data);

    // 查找是否包含某个元素
    boolean contains(int data);

    // 查找某个元素对应位置
    int indexOf(int data);

    // 获取特定位置的元素
    int get(int pos);

    // 给特定位置元素更新新值
    void set(int pos, int value);

    // 删除第一次出现的某个值
    void remove(int key);

    // 清空顺序表
    void clear();
}

3、异常类 

1、PosException代码:
package ArrayList;

public class PosException extends RuntimeException{
    // PosException是一个运行时异常

    // 写一个构造方法(有参数、无参数)
    public PosException(){

    }
    public PosException(String message){
        super(message); // 调用父类
    }
}
2、EmptyException代码:
package ArrayList;

public class EmptyException extends RuntimeException{
    public EmptyException(){

    }
    public EmptyException(String message){
        super(message);
    }
}

认识顺序表 (ArrayList)

ArrayList的构造方法

// 在学习一个类时,首先学习这个类的构造方法
        ArrayList<Integer> arrayList = new ArrayList<>(); // 实例化不带参数的构造方法
        System.out.println("实例化不带参数的构造方法");
        arrayList.add(1);
        arrayList.add(2);
        arrayList.add(3);
        System.out.println("--------------------------------------------------------------------------");

        ArrayList<Integer> arrayList1 = new ArrayList<>(10); // 这是实例化带参数的构造方法
        System.out.println("实例化带参数的构造方法,为内存分配空间");
        arrayList.add(10);
        arrayList.add(20);
        arrayList.add(30);
        System.out.println("--------------------------------------------------------------------------");

        ArrayList<Integer> arrayList2 = new ArrayList<>(arrayList);
        System.out.println("这是带参数的构造方法");
        System.out.println("可以这样实例化的原因是:");
        System.out.println("1、后面括号中arrayList这个类是Collection的子类;");
        System.out.println("2、arrayList这个类的类型是Integer;");
        System.out.println("3、E是Integer类型,arrayList这个类的类型是E这个类或这个是E的子类都可以");
        System.out.println("--------------------------------------------------------------------------");

ArrayList的输出

// 顺序表的输出
        System.out.println("顺序表没有具体的方法实现顺序表的输出,可以通过以下方法进行输出:");
        // sout
        System.out.println("sout输出顺序表");
        System.out.println(arrayList2);
        System.out.println("--------------------------------------------------------------------------");

        // fori
        System.out.println("fori输出顺序表");
        for (int i = 0; i < arrayList2.size(); i++) {
            // 这样输出是错的,这是顺序表,不是数组;顺序表是使用数组实现的
            // System.out.println(arrayList2[i]);
            System.out.print(arrayList2.get(i) + " ");
        }
        System.out.println();
        System.out.println("--------------------------------------------------------------------------");

        // foreach
        System.out.println("foreach输出顺序表");
        for (Integer x:arrayList2) {
            System.out.print(x + " ");
        }
        System.out.println();
        System.out.println("--------------------------------------------------------------------------");

        // 迭代器输出顺序表
        System.out.println("迭代器输出顺序表");
        System.out.println("因为:顺序表是Iterable这个接口的子类,所以可以使用迭代器进行输出");
        System.out.println("iterator()这个方法的返回值是Iterator<E>");
        Iterator<Integer> y = arrayList2.iterator();
        while(y.hasNext()) {
            // 判断y的下一个位置是否有值
            System.out.print(y.next() + " "); // 输出y的下一个值
        }
        System.out.println();
        System.out.println("listIterator()这个方法的返回值是ListIterator<E>");
        ListIterator<Integer> z = arrayList2.listIterator();
        while(z.hasNext()) {
            // 判断z的下一个位置是否有值
            System.out.print(z.next() + " "); // 输出z的下一个值
        }
        System.out.println();
        System.out.println("--------------------------------------------------------------------------");

 ArrayList的方法(subList)

// ArrayList中的subList方法
        System.out.println("subList:左闭右开的方法,实现的是顺序表的截取");
        System.out.println("但是subList这个方法并没有开辟新的空间,而是指向所截取顺序表的空间");
        System.out.println("subList()这个方法的返回值是List<E>, 所以需要使用List<E>接收");
        System.out.println();
        System.out.print("这是从arrayList2这个顺序表截取的值,输出list:");
        List<Integer> list = arrayList2.subList(0,2);
        System.out.println(list);
        System.out.print("修改截取后的值,将下标为0处修改为111,输出修改后的list:");
        list.set(0,111);
        System.out.println(list);
        System.out.print("输出顺序表arrayList2:");
        System.out.println(arrayList2);
        System.out.println("说明:subList这个方法并没有开辟新的空间,而是指向所截取顺序表的空间");
        System.out.println("--------------------------------------------------------------------------");

3、链表

  • 链表就是在逻辑上连续,但是物理结构上不一定连续的一种线性结构;
  • 链表是通过每个结点组成,每个结点都有地址,说明结点是一个对象;
  • 链表元素的逻辑顺序是通过链表中的引用链接实现的;
  • -> 链表的分类

    1. 单向带头非循环
    2. 单向不带头非循环
    3. 单向带头循环
    4. 单向不带头循环
    5. 双向带头非循环
    6. 双向不带头非循环
    7. 双向带头循环
    8. 双向不带头循环
  • 链表也是一种数据结构,数据结构的最终目的是为了存储数据;

链表的实现(MyLinkedList)

单向不带头非循环链表

 1、MyLinkedList类
2、LinkedListInterface接口
addFirst -> 头插法
addLast -> 尾插法
addIndex -> 在指定位置插入元素
contains -> 指定元素是否存在于链表中
remove -> 删除指定元素
removeAllKey -> 删除所有指定元素
size-> 求链表的结点数
clear-> 清空链表
display-> 打印链表

 LinkedListInterface代码:

package linkedlist;

public interface LinkedListInterface {

    // 头插法
    void addFirst(int data);

    // 尾插法
    void addLast(int data);

    // 在指定位置插入元素
    void addIndex(int index, int data);

    // 指定元素是否存在于链表中
    boolean contains(int data);

    // 删除指定元素
    void remove(int key);

    // 删除所有指定元素
    void removeAllKey(int key);

    // 求链表长度
    int size();

    // 清空链表
    void clear();

    // 打印链表
    void display();
}

 

文章出处登录后可见!

已经登录?立即刷新

共计人评分,平均

到目前为止还没有投票!成为第一位评论此文章。

(0)
青葱年少的头像青葱年少普通用户
上一篇 2023年12月20日
下一篇 2023年12月20日

相关推荐