复习笔记---数据结构(2)---线性表---顺序存储及单向链表

逻辑结构中的线性结构,有多种实现,比如线性表、队列、栈,这篇文章主要讲的是线性表,包括如何用java代码实现简易的线性表。

何为线性表?
     通俗地理解,就是将多个数据元素一个一个串起来,拼成一个线性表,用java来表示,可以用方法来对这个线性表进行各种操作。

java代码实现
先定义一个接口,包括相关的操作线性表的方法

/*
通过接口定义一组线性表中的操作
 */
public interface MyList {
    int getSize(); //返回线性表中元素的个数
    Object get(int i); //获取索引值为i的元素
    boolean isEmpty(); //判断线性表是否为空
    void insert(int i,Object e); //在线性表的i索引值添加元素e
    boolean insertBefore(); //在线性表元素p前面插入元素e
    boolean insertAfter(); //在线性表元素p后面插入元素e
    boolean contains(Object e); //判断线性表中是否包含元素e
    int indexOf(Object e); //返回线性表中元素e的索引值,有则返回,无则返回-1
    Object remove(Object e); //删除线性表中第一个与e相同的元素,并返回该元素
    Object remove(int i); //删除线性表中索引值为i的元素,并返回该元素
    Object replace(int i,Object e); //使用元素e替换线性表中i位置的元素,并返回旧的元素
}

对于线性表,可以有两种存储方式,顺序存储和链式存储。



先讲顺序存储,顺序存储长这个样子
在这里插入图片描述
其实就是java中的数组,不过涉及到两个操作,一个是数据元素的插入,一个是数据元素的删除

数据的插入是这么一回事
在这里插入图片描述
数据的删除是这么一回事
在这里插入图片描述
具体的代码如下:
  如何掌握呢?以下是我自己的方法
1)实现MyList接口,有IDE可直接生成方法重写
2)先定义数组和容量,定义size来保存元素个数(插入一个数据元素时就size++)
3)定义构造方法,一个是默认初始化容量的数组,一个是自行定义容量的数组
4)当方法参数中需要传入下标时,一般需要先进行越界判断;当插入数据时,先判断是否已满;当删除数据时,先判断数组是否为空。第四点比较重要,后面其他的数据结构代码中,同样适用
5)方法的注释都写的很清楚了,代码也都不难,很容易理解

public class MyArrayList implements MyList {
    private Object[] elements; //定义数组保存数据元素
    private static final int DEFAULT_CAPACITY = 16; //数组的默认初始化容量
    private int size; //保存数据元素的个数

    //构造方法
    public MyArrayList(){
        elements = new Object[DEFAULT_CAPACITY];
    }

    //可指定容量的构造方法
    public MyArrayList(int initialCapacity){
        elements = new Object[initialCapacity];
    }

    //返回元素的个数
    @Override
    public int getSize() {
        return size;
    }

    @Override
    public Object get(int i) {
        //判断i是否越界
        if( i < 0 || i >= size ){
            throw new IndexOutOfBoundsException(i+"越界");
        }
        return elements[i];
    }

    @Override
    public boolean isEmpty() {
        return size == 0;
    }

    @Override
    public void insert(int i, Object e) {
        //先判断索引值i是否越界
        if( i < 0 || i > size ){
            throw new IndexOutOfBoundsException(i+"越界");
        }
        //如果数组已满,需要对数组扩容
        if( size >= elements.length ){
            expandSpace(); //数组扩容
        }
        //从i开始,把元素依次后移
        for( int j = size ; j > i ; j-- ){
            elements[j] = elements[j-1];
        }
        // 把元素e存储到i位置
        elements[i] = e;
        //元素的个数+1
        size++;
    }

    //数组扩容方法
    public void expandSpace(){
        //定义一个更大的数组,m默认是扩容两倍
        Object[] newElements = new Object[elements.length*2];
        // 把原来的数组内容复制到新的数组,
        for( int i = 0 ; i < elements.length ; i++){
            newElements[i] = elements[i];
        }
        // 让原来的数组名指向新的数组
        elements = newElements;
    }

    //在指定的元素前插入一个元素
    @Override
    public boolean insertBefore(Object p,Object e) {
        //确定元素p在线性表中的位置
        int index = indexOf(p);
        if( index < 0 ){
            return false; //p元素不存在,插入不成功
        }
        //插入元素
        insert(index,e);
        return true;
    }

    //在指定的元素后插入一个元素
    @Override
    public boolean insertAfter(Object p,Object e) {
        //确定元素p在线性表中的位置
        int index = indexOf(p);
        if( index < 0 ){
            return false; //p元素不存在,插入不成功
        }
        //插入元素
        insert(index+1,e);
        return true;
    }

    @Override
    public boolean contains(Object e) {
        return indexOf(e) >= 0;
    }

    @Override
    public int indexOf(Object e) {
        //如果添加的为null,则判断数组中是否有元素为null,有则返回
        if(e == null){
            for (int i = 0; i < size; i++) {
                if (elements[i] == null){
                    return i;
                }
            }
        }else { //不为空才能进行equals
            //遍历数组
            for (int i = 0; i < size; i++) {
                if(e.equals(elements[i])){
                    return i;
                }
            }
        }
        return -1;
    }

    @Override
    public Object remove(Object e) {
        //获得e在线性表中的索引值
        int index = indexOf(e);
        if( index < 0 ){
            return null; //线性表中不存在元素e
        }
        return remove(index);
    }

    @Override
    public Object remove(int i) {
        //判断i是否越界
        if( i < 0 || i >= size ){
            throw new IndexOutOfBoundsException(i+"越界");
        }
        //把要删除的元素保存起来
        Object old = elements[i];
        //把i+1开始的元素依次前移
        for( int j = i; j < size - 1; j++ ){
            elements[j] = elements[j+1];
        }
        //把最后的元素置为null
        elements[size-1] = null;
        //修改元素的个数
        size--;
        return old;
    }

    @Override
    public Object replace(int i, Object e) {
        //判断索引值是否越界
        if( i < 0 || i >= size ){
            throw new IndexOutOfBoundsException(i+"越界");
        }
        //保存原来的值
        Object old = elements[i];
        //替换
        elements[i] = e;
        //把原来的元素值返回
        return old;
    }

    @Override
    public String toString() {
        //遍历数组中的每个元素
        //把线性表中每个元素连接起来
        StringBuilder sb = new StringBuilder();
        sb.append("[");
        for (int i = 0; i < size; i++) {
            sb.append(elements[i]);
            if( i < size-1 ){
                sb.append(",");
            }
        }
        sb.append("]");
        return sb.toString();
    }
}

可以定义一个测试类,来测试我们所写代码

package 线性表;

public class MyArrayListTest {
    public static void main(String[] args) {
        MyArrayList list1 = new MyArrayList();
        //1)判断为空
        System.out.println(list1.isEmpty()); //true
        System.out.println(list1.getSize()); //0
        //2)添加元素
        list1.insert(0,"aa");
        list1.insert(1,"bb");
        list1.insert(0,"cc");
        System.out.println(list1.isEmpty()); //false
        System.out.println(list1.getSize()); //3
        //3)打印输出
        System.out.println(list1);
        //4)判断是否存在
        System.out.println(list1.indexOf("cc")); //0
        System.out.println(list1.indexOf("bb")); //2
        System.out.println(list1.indexOf("dd")); //-1
        System.out.println(list1.contains("aa")); //true
        System.out.println(list1.contains("xx")); //false
        //5)删除
        list1.remove("dd");
        System.out.println(list1); //[cc,aa,bb]
        list1.remove("bb");
        System.out.println(list1); //[cc,aa]
        list1.remove(0);
        System.out.println(list1); //[aa]
        //6)替换
        list1.insert(0,"xx");
        list1.insert(0,"oo");
        list1.insert(0,"yy");
        System.out.println(list1); //[yy,oo,xx,aa]
        list1.replace(0,"YY");
        System.out.println(list1); //[YY,oo,xx,aa]
        //7)返回指定索引的元素
        System.out.println(list1.get(0)); //YY
        System.out.println(list1.get(1)); //oo
//        System.out.println(list1.get(33)); //java.lang.IndexOutOfBoundsException
        //8)在指定元素的前面/后面插入另外的元素
        list1.insertBefore("oo","JJ"); //[YY,JJ,oo,xx,aa]
        System.out.println(list1);
        list1.insertAfter("oo","jj"); //[YY,JJ,oo,jj,xx,aa]
        System.out.println(list1);
        list1.insertAfter("TT","BB"); //[YY,JJ,oo,jj,xx,aa]
        System.out.println(list1);
    }
}

顺序存储的特点:
1)查询易。从代码可看出,return elements[i] 可看出,不需要遍历数组,根据下标就可以直接返回,得到要查询的元素。
2)插入、删除难。从代码可看出,在索引 i 插入时,需要将 i 后面的所有元素都循环赋值实现后移;删除索引 i 的元素,需要将 i 后面的所有元素都循环赋值实现前移;当元素个数较多的时候,执行效率很低。





再讲链式存储,链式存储长这样子
在这里插入图片描述
这个叫做单向链表,双向链表后面讲。可以看到,计算机给我们的数据元素分配的空间是不连续的。该数据元素在链表中称为结点(Node,在java中用一个类来表示),data和next都是这个结点的数据项,很容易理解的,看看这段java代码你就懂了。

private class Node{
	Object data; //这个是存储的数据
	Node next; //这个引用用来指向下一个结点,这样才能将所有的结点连在一起,形成链表
    public Node(Object data, Node next) {
       this.data = data;
       this.next = next;
    }
}

你可能会好奇,为什么这个类使用private,因为这是作为一个内部类,放在链表这个类里的,看看下面的代码你就懂了

public class MySingleLink implements MyList {
    private Node head;
    private int size;
    @Override
    public int getSize() {
        return size;
    }

    @Override
    public Object get(int i) {
        checkIndex(i);
        Node pNode = getNode(i);
        return pNode.data;
    }

    @Override
    public boolean isEmpty() {
        return size == 0;
    }

    //添加结点分多种情况
    //一、头结点为null的时候,链表不存在,刚刚添加的结点是头结点
    //二、insert(0,xxx);
    //    生成一个新结点newNode,newNode.next=head(此时head为null),head=newNode
    //三、insert(2,ooo);
    //    在2这个位置插入一个元素,需要将新节点的地址给1的next;需要将原结点2的地址给新结点的next
    @Override
    public void insert(int i, Object e) {
        //判断索引值是否越界
        if( i < 0 || i > size ){
            throw new IndexOutOfBoundsException(i+"越界");
        }
        //创建结点
        Node newNode = new Node(e,null);
        //头结点为null的情况
        if( head == null ){
            head = newNode;
        }else {
            //在0位置插入结点
            if( i == 0){
                newNode.next = head; //修改新结点的next域指向原来的头结点
                head = newNode; //刚插入的结点就是新的头结点
            }else {
                //插入结点。先找到i-1这个结点
                Node pNode = head;
                for(int x = 1; x < i; x++){
                    pNode = pNode.next; //不断将next赋值给自身
                }
                //注意,先修改新结点的next指针域,再修改i-1结点的指针域
                newNode.next = pNode.next;
                pNode.next = newNode;
            }
        }
        //元素个数+1
        size++;
    }

    @Override
    public boolean insertBefore(Object p, Object e) {
        int index = indexOf(p);
        if ( index < 0 ){
            return false; //元素p不存在
        }
        insert(index,e);
        return true;
    }

    @Override
    public boolean insertAfter(Object p, Object e) {
        int index = indexOf(p);
        if ( index < 0 ){
            return false; //元素p不存在
        }
        insert(index+1,e);
        return true;
    }

    @Override
    public boolean contains(Object e) {
        return indexOf(e) >= 0;
    }

    //链表中没有索引值
    @Override
    public int indexOf(Object e) { //输入值,返回结点的索引值
        int i = 0; //保存元素e的索引值
        Node pNode = head;
        while(pNode!=null){
            if( e == null && pNode.data == null ){
                return i;
            }else if( e != null && e.equals(pNode.data) ){
                return i;
            }
            i++;
            pNode = pNode.next;
        }
        return -1;
    }

    @Override
    public Object remove(Object e) {
        int index = indexOf(e);
        if( index < 0 ){
            return null;
        }
        return remove(index);
    }

    //一、删除头结点
    //    head = head.next
    //二、删除的不是头结点
    //    i-1的结点.next = i结点.next
    @Override
    public Object remove(int i) {
        if( i < 0 || i >= size ){
            throw new IndexOutOfBoundsException(i+"越界");
        }
        Node pNode = head;
        //删除头结点
        if( i == 0 ){
            head = head.next;
            size--;
            return pNode.data; //返回删除头结点的数据
        }
        //删除的不是头结点
        //找到i-1这个结点
        for( int x = 1 ; x < i ; x++ ){
            pNode = pNode.next;
        }
        Object old = pNode.next.data; //保存结点数据
        pNode.next = pNode.next.next; //修改i-1结点的next,让其指向i+1结点
        size--;
        return old;
    }

    @Override
    public Object replace(int i, Object e) {
        checkIndex(i);
        Node pNode = getNode(i);
        Object old = pNode.data;
        pNode.data = e;
        return old;
    }

    private void checkIndex(int i){
        if( i < 0 || i >= size ){
            throw new IndexOutOfBoundsException(i+"越界");
        }
    }

    private Node getNode(int i){
        if( i < 0 || i >= size ){
            return null;
        }
        if( i == 0 ){
            return head;
        }
        Node pNode = head;
        for (int x = 1; x <= i; x++) {
            pNode = pNode.next;
        }
        return pNode;
    }

    public String toString(){
        //把链表中各个数据域中的数据连接起来
        StringBuilder sb = new StringBuilder();
        sb.append("[");
        Node pNode = head;
        while( pNode != null ){
            sb.append(pNode.data);
            if( pNode.next != null ){
                sb.append(",");
            }
            pNode = pNode.next;
        }
        sb.append("]");
        return sb.toString();
    }

    //定义一个内部类表示单向链表中的结点
    private class Node{
        Object data; //保存数据
        Node next; //下个结点的引用

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

继续定义一个测试类,来测试我们写的代码

package 线性表;

public class MySingleLinkTest {
    public static void main(String[] args) {
        MySingleLink link = new MySingleLink();
        //1)判断
        System.out.println(link.isEmpty());
        System.out.println(link.getSize());
        //2)插入元素
        link.insert(0,"aa");
        link.insert(0,"bb");
        link.insert(0,"cc");
        link.insert(0,"dd");
        //3)直接打印输出
        System.out.println(link); //[dd,cc,bb,aa]
        //4)判断元素是否存在
        System.out.println(link.indexOf("dd")); //0
        System.out.println(link.indexOf("aa")); //3
        System.out.println(link.indexOf("xx")); //-1
        System.out.println(link.contains("cc")); //true
        //5)删除结点
        System.out.println(link.remove("xx")); //null
        System.out.println(link.remove("bb")); //bb
        System.out.println(link); //[dd,cc,aa]
        System.out.println(link.remove(0));
        System.out.println(link); //[cc,aa]
        //6)返回元素,元素替换
        System.out.println(link.get(0)); //cc
        System.out.println(link.replace(0,"CC")); //cc
        System.out.println(link); //[CC,aa]
        //7)在指定元素的前面/后面插入元素
        link.insertBefore("aa","bb");
        link.insertAfter("bb","BB");
        System.out.println(link); //[CC,bb,BB,aa]

    }
}

猜你喜欢

转载自blog.csdn.net/weixin_43724203/article/details/108429832