数据结构:线性表

线性表的定义

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

数学定义:若将线性表记为( a 1 , a 2 , ⋯ , a i − 1 , a i , a i + 1 , ⋯ , a n a_{1}, a_{2},⋯,a_{i-1},a_{i},a_{i+1},⋯,a_{n} a1,a2,,ai1,ai,ai+1,,an),则表中 a i − 1 a_{i-1} ai1领先于 a i a_{i} ai a i a_{i} ai领先于 a i + 1 a_{i+1} ai+1, 称 a i − 1 a_{i-1} ai1 a i a_{i} ai的直接前驱元素, a i + 1 a_{i+1} ai+1 a i a_{i} ai的直接后继元素。当i=1,2,⋯,n-1时, a i a_{i} ai有且仅有一个直接后继,当i=2,3,⋯,n时, a i a_{i} ai有且仅有一个直接前驱。

案例

A
B
C
D
E

上图为一个线性表,其中长度为5,注意当一个线性表长度为0的时候,成为空表。

在复杂的线性表中,一个数据元素可以由若干个数据项组成。如上图,每种动物都有自己的特点,都有自己的数据项。

线性表的存储结构

顺序存储结构

线性表的顺序存储结构:用一段连续的地址存储单元,依次的把线性表的数据元素存储起来的的结构。
在Java中,ArrayList就是用到了这种结构:
先看声明的变量

	//存储空间的初始分配量
    private static final int DEFAULT_CAPACITY = 10;
    
    //初始化存储空间,默认空表
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
    
    //当前数组,用来存储数据元素。说明ArrayList底层就是一个数组。
    transient Object[] elementData;
    
    //线性表当前长度或者数组当前长度
     private int size;

线性表顺序存储结构的插入

	/**
     * 把element的数据元素插入到index的下标中
     */
	public void add(int index, E element) {
		//如果下标大于最大和长度或者下标小于0抛异常。
        if (index > size || index < 0)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
		//计算是否要扩容
        ensureCapacityInternal(size + 1); 
        //将elementData数组的index的下表开始,把size - index个数据复制到elementData的index+1开始的下标上。
        System.arraycopy(elementData, index, elementData, index + 1,
                         size - index);
         //用数组下标赋值,即插入数组                
        elementData[index] = element;
        size++;//长度加一
    }

线性表顺序存储结构的删除

	/**
     * 删除下标为index的元素,并返回移除的元素
     *
     */
    public E remove(int index) {
    	//如果下标大于长度,抛异常
        if (index >= size)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
		//计数器
        modCount++;
        //获取数组中下标为index的元素
        E oldValue = (E) elementData[index];
		//要copy的数组的长度
        int numMoved = size - index - 1;
        if (numMoved > 0)
        	//将elementData数组的index+1的下表开始,把numMoved个数据复制到elementData的index开始的下标上。
            System.arraycopy(elementData, index+1, elementData, index,numMoved);
        //把最后的下标置为空                 
        elementData[--size] = null; // clear to let GC do its work
		//最后返回移除的数据
        return oldValue;
    }

线性表顺序存储结构的删除

		//根据下标直接返回数组元素
		public E get(int index) {
            if (index < 0 || index >= this.size)
              throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
            if (ArrayList.this.modCount != this.modCount)
                throw new ConcurrentModificationException();
            return (E) ArrayList.this.elementData[offset + index];
        }

线性表顺序存储结构的优缺点:

优点缺点
1、无须为表中的逻辑关系增加额外的存储空间1、插入与删除需要移动大量的元素
2、可以快速的插入与删除2、当线性表长度变化较大的时候,难以确定存储空间的容量
3、造成存储空间的“碎片”

链式存储结构

对数据元素 a i a_{i} ai来说,除 了有本身的数据域外,还需要存储一个直接指向后继的存储位置的指示器。这个指示器叫指针。

包含数据域跟指针的数据元素称为节点。

由n个节点连接成一个链表,称为线性表的链式存储结构,每个节点包含两个指针,一个指向前驱节点,一个指向后继节点的链表称为双向链表。

在Java中LinkList就是用到了这种结构:

节点

 /**
  * LinkedList<E> 中的内部类定义的节点
 **/
private static class Node<E> {
        E item;//数据域
        LinkedList.Node<E> next;//指向上一个节点的指针
        LinkedList.Node<E> prev;//指向下一个节点的指针

        Node(LinkedList.Node<E> var1, E var2, LinkedList.Node<E> var3) {
            this.item = var2;//数据域
            this.next = var3;//指向后继节点的指针
            this.prev = var1;//指向前驱节点的指针
        }
    }

双向链表定义

	transient int size;//两边长度
    transient LinkedList.Node<E> first;//链表的头节点
    transient LinkedList.Node<E> last;//链表的尾节点

双向链表插入

/**
 * 把数据var2 插入到 链表的var1位置上
**/
public void add(int var1, E var2) {
		//检查插入的位置是否合适>0,并且<size
        this.checkPositionIndex(var1);
        if (var1 == this.size) {
        	//插入末尾
            this.linkLast(var2);
        } else {
        	//插入中间
            this.linkBefore(var2, this.node(var1));
        }

    }
    //要把var1的节点插入到var2后
    void linkBefore(E var1, LinkedList.Node<E> var2) {
    	//获取var2的前一节点并赋值var3 
        LinkedList.Node var3 = var2.prev;
        //创建数据域为var1的节点var4 ,并把指针var3作为前驱节点,var2作为后继节点
        LinkedList.Node var4 = new LinkedList.Node(var3, var1, var2);
        //var2的前驱节点为var4
        var2.prev = var4;
        //var3 为空,表示var2为头结点,则新节点var4就作为头结点
        if (var3 == null) {
            this.first = var4;
        } else {
        	//var3的后继节点为var4
            var3.next = var4;
        }
		//链表长度加1
        ++this.size;
        //计数器加1
        ++this.modCount;
    }

如下图
在这里插入图片描述
双向链表删除

//移除链表中为var1的元素,返回是否移除成功标识
public boolean remove(Object var1) {
        LinkedList.Node var2;
        //如果移除的元素为空
        if (var1 == null) {
        	//从头结点开始遍历
            for(var2 = this.first; var2 != null; var2 = var2.next) {
            	//数据域为空移除
                if (var2.item == null) {
                    this.unlink(var2);
                    return true;
                }
            }
        } else {
        	//从头结点开始遍历
            for(var2 = this.first; var2 != null; var2 = var2.next) {
            	//数据域为空移除
                if (var1.equals(var2.item)) {
                	//移除
                    this.unlink(var2);
                    return true;
                }
            }
        }
        return false;
    }
    //移除var1节点
    E unlink(LinkedList.Node<E> var1) {
        Object var2 = var1.item;//获取数据域
        LinkedList.Node var3 = var1.next;//获取节点的后继节点
        LinkedList.Node var4 = var1.prev;//获取前驱节点
        //如果前驱节点为空,这表示var1就是头结点,这时要把var1移除的话,就把var1的后继节点var3 当做头结点。
        if (var4 == null) {
            this.first = var3;
        } else {
            var4.next = var3;//把原节点var1的前驱元素var4的后继节点不再指向var1而是指向var1的后继节点var3。
            var1.prev = null;//把原节点var1的前驱节点置为空。
        }
//如果后继节点为空,这表示var1就是尾结点,这时要把var1移除的话,就把var1的后继节点var4 当做尾结点。
        if (var3 == null) {
            this.last = var4;
        } else {
            var3.prev = var4;//把原节点var1的后继元素var3的前驱节点不再指向var1而是指向var1的前驱节点var4。
            var1.next = null;//把原节点var1的后继节点置为空。
        }
		//把原节点var1的数据域置为空
        var1.item = null;
        --this.size;//长度减一
        ++this.modCount;//计数器加一
        return var2;//返回原节点var1的数据域
    }

如下图所示
在这里插入图片描述
线性表链式存储结构查找

	//获取下表为var1的节点
	public E get(int var1) {
		//检查下标异常情况
        this.checkElementIndex(var1);
        //返回节点的数据域
        return this.node(var1).item;
	}
	//根据下标查找节点
	LinkedList.Node<E> node(int var1) {
        LinkedList.Node var2;
        int var3;
        //二分法思想,下标在0到size/2之间则从链表头开始遍历
        if (var1 < this.size >> 1) {
            var2 = this.first;

            for(var3 = 0; var3 < var1; ++var3) {
                var2 = var2.next;
            }

            return var2;
        } else {
            var2 = this.last;
			//二分法思想,下标在size/2到size之间则从链表尾开始遍历
            for(var3 = this.size - 1; var3 > var1; --var3) {
                var2 = var2.prev;
            }

            return var2;
        }
    }

线性表链式存储结构的优缺点:

优点缺点
在插入和删除的操作时,只需要修改指针,不需要移动其他元素,效率高不需要提前分配空间,但查找元素需要遍历整个链表,效率低

总结

若线性表需要频繁查找,很少插入与删除的操作时,应该使用顺序存储结构(ArrayList)。若需要频繁插入或者删除时,应该考虑链式存储结构(LinkList)。
当线性表中的元素个数变化较大或者根本不知道多大时,最好使用链式存储结构,这样就不需要考虑存储空间的大小问题。

本文主要参考《大话数据结构》

下一节 数据结构:栈

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值