线性表(1)普通线性表

来源:互联网 发布:c语言代码指令大全 编辑:程序博客网 时间:2024/06/15 02:34

线性表

线性表(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());    }

输出:

3Atrue494

与预期符合。

总结

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

1 0
原创粉丝点击