线性表(1)普通线性表

线性表

线性表(List):零个或多个数据元素的有限序列

首先明确几个概念
1. 线性表是一个序列,元素之间是有顺序的
2. 若元素存在多个,第一个元素无前驱,最后一个元素无后继,其他每个元素有且只有一个前驱和后继
3. 线性表是强调有限的

这里写图片描述
如上图所示一个线性表,将线性表记为(a1,a2,a3…an)则ai+1领先于ai,ai+1成为ai的直接前驱元素,ai-1称为ai的直接后驱元素。

普通线性表

普通线性表内部封装了一个数组对象用来接收线性表的值。整体特性和数组类似,是比较简单且容易理解的一种数据结构。

模拟实现普通线性表

我们来自己模拟实现一个线性表的类,首先要明确该类的属性和方法
分析如下
1. 增删改查必不可少
2. 表的属性,总容量,当前大小,最大容量

1. 搭建框架

先把准备实现的功能大概搭建起来

/**
 * 带有泛型,表示该线性表接受不同类型的对象
 */
public class MyList <T> {

    private static final int MAXLENGTH = 100;//最大列表长度
    private static final int DEFAULT = 10;
    private int size = 0;//当前列表长度初始值
    private Object[] data ;//存储list数据的数组
    private Object[] emptyObj = {};//空表,clear的时候使用

    /**
     * 默认构造函数
     */
    public MyList(){
        initList();
    }
    /**
     * 指定容量的构造函数
     */
    public MyList(int capacity){
        ensure(capacity);
    }




    /**
     * 初始化操作,建立默认大小为10的列表
     */
    private void initList(){
        data = new Object[DEFAULT]; 
    }


    /**
     * 数组扩容
     * @param capacity
     */
    private void ensure(int capacity){

    }

    /**
     * 清空
     */
    void clear(){
        data = emptyObj;//data引用指向空列表
    }

    /**
     * 判断list是否为空,true表示空否则false
     * @return
     */
    boolean isEmpty(){
        return this.size == 0;
    }

    /**
     * 添加新元素到list
     * @param t
     */
    void add(T t){

    }


    /**
     * 返回指定索引位置的元素
     * @param idx
     * @return
     */
    T get(int idx){
       return null;
    }

    /**
     * 判断list中是否包含 t 元素
     * @param t
     * @return
     */
    boolean contain(T t){
        return false;
    }


    /**
     * 向 list 指定 idx 位置插入 t 元素
     * @param indx
     * @param t
     * @return
     */
    boolean insert(int idx,T t){
        return null;
    }

    /**
     * 返回list的长度
     * @return
     */
    int length(){
        return this.size;
    }

    /**
     * A∪B操作,把旧的list没有的lst元素插入到list中,返回组合之后的新list
     * @param lst
     * @return
     */
    MyList<T> union(MyList<T> lst){
        return this;
    }

}

框架的搭建参考了java.util.List接口的一些方法,总之先把需要或者说准备实现的功能先写个框框出来,然后再一个一个的分析并实现。

1. insert():添加新元素

增加新元素这时最基本的功能了,我们先来实现这个方法:
首先的分析一下,我们最终是要向MyArrayList中添加的值是放到该类的private Object[] data数组中去的,所以增加元素的时候就像向数组增加新元素一样了。

/**
     * 添加新元素到list
     * @param t
     */
    void add(T t){
        //判断是否越界
        if(this.size > MAXLENGTH){
            throw new RuntimeException("超过界限");
        }
        //当前size大于data数组长度的时候
        if(this.size > data.length-1){
            ensure(this.size+1);//数组扩容
        }
        data[this.size++]=t;//数组中插入新元素的值
    }

注意该方法里面用到了ensure(int capacity);方法,当数组容量不够的时候,用来动态增加数组的容量,而其他的就向在数组里面插入新数据一样简单明了了。

2. ensure():数组扩容

当当前线性表的容量不够的时候我们就要给内部数组扩容,用来接收更多的值,就向上一个insert方法中用到的情况一样。

首先分析一下:
因为是内部数组扩容,其实原理是简单粗暴的,就是把新创建一个需要的大小的数组容量,然后把旧的数组的值一个一个赋值给新数组就行了,说白了就是两个数组的拷贝,把小容量数组内容拷贝到大容量手中,然后让类内部的数组引用指向新创建的数组,达到扩容的效果。明白了原理实现起来就简单了。
这里写图片描述

   /**
     * 数组扩容
     * @param capacity
     */
    private void ensure(int capacity){
        Object[] oldData = data;//旧的数组
        int oldLen = data.length;//旧数组的长度
        data = new Object[capacity];//创建新的数组
        //遍历旧的数组,赋值给新数组
        for(int i=0;i<oldLen;i++){
            data[i] = oldData[i];
        }
    }
3. T get(int idx):返回指定索引位置的值

返回指定索引位置的数值,这个是比较简单而且容易理解的,本质就是返回内部数组的指定索引出的数值,相当于arr[idx],
需要注意的是当idx索引位置不合法的情况要主动抛出异常。

/**
     * 返回指定索引位置的元素
     * @param idx
     * @return
     */
    T get(int idx){
        //先判断
        if(idx > data.length || idx < 0){
            throw new RuntimeException("idx越界或者小于零!");
        }
        Object obj = data[idx];

        return (T)obj;
    }
4. boolean contain(T t):检测是否包含指定对象

本质就是,遍历整个数组,一个一个比较看里面是否有对应的t,如果有就返回true否则返回false。

     /**
         * 判断list中是否包含 t 元素
         * @param t
         * @return
         */
        boolean contain(T t){
            for(int i=0;i<this.size;i++){
                if(data[i] == t){
                    return true;
                }
            }
            return false;
        }
5. boolean insert(int idx,T t):指定位置出插入数值

这里要注意一个问题:
1. 插入元素,数组的长度肯定会比之前的长度增加一,首先要扩容。
2. 当idx在最后一个索引位置(数组的末尾)的时候直接扩容,插入即可。
3. 否则就要把插入位置之后的所有元素向后移动一位

/**
     * 向 list 指定 idx 位置插入 t 元素
     * @param indx
     * @param t
     * @return
     */
    boolean insert(int idx,T t){
        //判断是否合法
        if( idx < 0 || idx > data.length || idx == this.MAXLENGTH ){
            throw new RuntimeException("idx越界或者小于零!");
        }

        ensure(this.size+1);//数组扩大容量

        //如果在末尾
        if(idx == this.size){
            data[idx] = t;
            this.size++;
            return true;
        }

        //把要插入元素之后的所有元素向后移动一位
        for(int i=this.size-1;i >= idx;i--){
            data[i+1] = data[i];//每一个元素向后移动一位
        }

        this.size++;
        data[idx] = t;
        return true;
    }
6.T remove(int idx):删除指定idx位置并返回该位置的元素

首先分析:
1. 如果idx在数组末尾那么直接删除返回就可以了
2. 否则要把删除的值先赋给中间变量,然后idx之后的元素向前移动一位

    /**
     * 删除指定idx位置并返回该位置的元素
     * @param indx
     * @return
     */
    T remove(int idx){
        T t = null;

        //判断是否合法
        if( idx < 0 || idx > data.length || idx == this.MAXLENGTH ){
            throw new RuntimeException("idx越界或者小于零!");
        }

        //返回指定索引位置的值
        t = (T) data[idx];

        //如果在末尾
        if(idx == this.size){
            this.size--;
        }

        //不在末尾,把idx元素向前移动
        for(int i=idx;i<this.size;i++){
            data[i] = data[i+1];
        }

        return t;

    }
7. MyList union(MyList lst):对两个表U操作,返回一个新的表

分析:
也就是创建一个新的数组,长度为两个表的当前容量,然后把表1和表2的值一个一个赋值给新表,最后返回新表

/**
     * A∪B操作,把旧的list没有的lst元素插入到list中,返回组合之后的新list
     * @param lst
     * @return
     */
    MyList<T> union(MyList<T> lst){

        int len_o = this.length();//旧list的长度
        int len_n = lst.length();//新的list长度

        T t;//接收旧的list中没有的元素

        for(int i=0;i<len_n;i++){
            t = lst.get(i);//得到新lst中的元素
            if( !this.contain(t) ){//如果旧的list中没有,就在list尾部添加
                this.insert(len_o++,t);
            }
        }

        return this;
    }

测试

我们把一个list线性表的基本操作方法完成,下面进行测试,看是否正确?

public static void main(String[] args) {
        MyList<String>list = new MyList<String>();
        list.add("A");
        list.add("B");
        list.add("C");
        System.out.println(list.length());//3
        System.out.println(list.get(0));//A
        System.out.println(list.insert(0, "AA"));//true
        System.out.println(list.length());//4
        for(int i=0;i<90;i++){
            list.add("new"+i);
        }
        System.out.println(list.length());
    }

输出:

3
A
true
4
94

与预期符合。

总结

完成了类的创建,运行一切良好,但是我们发现这个程序是有缺陷的,在数组扩容unsure的之后,每次都要一次一次的移动元素,该方法的时间复杂度为
O(n),在进行insert操作的时候不仅要扩容还要再次遍历数组把数组的值向后移动一位,时间复杂度是O(n),两个O(n)在小数据量的处理的时候察觉不到
,但是当数据量比较大的时候就会花费很长时间了。链表轻松了解决该问题。下一篇博客详细叙述。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值