数据结构之线性表(JAVA版)

本文介绍了线性表的两种存储结构:顺序存储结构和链式存储结构,并详细阐述了它们的特点、优缺点及实现方式。包括顺序表的数组实现和链表的不同类型如单链表、循环链表和双向链表。
应用程序后在那个的数据大致有四种基本的逻辑结构:
  • 集合:数据元素之间只有"同属于一个集合"的关系
  • 线性结构:数据元素之间存在一个对一个的关系
  • 树形结构:数据元素之间存在一个对多个关系
  • 图形结构或网状结构:数据元素之间存在多个对多个的关系


对于数据不同的逻辑结构,计算机在物理磁盘上通常有两种屋里存储结构

  • 顺序存储结构
  • 链式存储结构
本篇博文主要讲的是线性结构,而线性结构主要是线性表,非线性结构主要是树和图。
线性表的基本特征:
  • 总存在唯一的第一个数据元素
  • 总存在唯一的最后一个数据元素
  • 除第一个数据元素外,集合中的每一个数据元素都只有一个前驱的数据元素
  • 除最后一个数据元素外,集合中的每一个数据元素都只有一个后继的数据元素


1.线性表的顺序存储结构:是指用一组地址连续的存储单元一次存放线性表的元素。为了使用顺序结构实现线性表,程序通常会采用数组来保存线性中的元素,是一种随机存储的数据结构,适合随机访问。java中ArrayList类是线性表的数组实现。

package com.ruicai.dsx;

import java.util.Arrays;

/**
 * 线性表的顺序存储结构
 * @author 邓绍祥
 * @param <T>泛型,
 */
public class SequenceList<T> {
	private int DEFAULT_SIZE=16;//默认大小
	private int capacity;		//保存数组的长度
	private Object[] elementData;//对象数组,用来保存顺序表的元素
	private int size=0;//顺序表中的当前的元素个数。
	
	
	/*
	 * 无参数构造方法,给数组的长度赋上默认大小的值
	 * 给存放元素的对象数组开辟capacity大小的空间
	 * 只要一实例化此对象,就实例化了一个能装16个元素的表。此时是空表
	 */
	public SequenceList(){
		capacity=DEFAULT_SIZE;
		elementData=new Object[capacity];
	}
	
	/*
	 * 有参构造方法,先调用无参构造出一个能装16个元素的顺序线性表。
	 * 然后以一个初始化元素来创建顺序线性表
	 */
	public SequenceList(T element){
		this();
		elementData[0]=element;
		size++;
	}
	
	/**
	 * 以指定长度的数组来创建顺序线性表
	 * @param element 指定线性表中的第一个元素
	 * @param initSize 指定顺序线性表底层的数组的长度。
	 */
	public SequenceList(T element,int initSize){
		capacity=1;	//容量设置为1
		
		while(capacity<initSize){
			//<<= :先左移,再将左移后的结果赋值。将capacity的值左移一位,结果是是二进制的10,转为十进制的结果为2
			//如此capacity刚好大于initSize的最小的2的n次方
			capacity<<=1;		
		}
		elementData=new Object[capacity];//根据capacity的大小开辟相应的空间
		elementData[0]=element;	
		size++;
	}
	
	/**
	 * 获取线性表的长度
	 * @return
	 */
	public int getLength(){
		return size;
	}
	
	public T getElement(int i){
		
		if(i<0||i>size-1){
			throw new IndexOutOfBoundsException("数组越界");
		}
		return (T)elementData[i];
	}
	
	/**
	 * 查询顺序线性表中指定元素的索引
	 * @param element 指定的元素
	 * @return 返回索引
	 */
	public int getIndex(T element){
		for(int i=0;i<size;i++){
			if(elementData[i].equals(element)){
				return i;
			}
		}
		return -1;	//返回-1则代表顺序线性表中无该元素
	}
	
	/**
	 * 给顺序线性表扩容,判断如果给定的参数大于当前顺序表的容量,则扩容,
	 * 否则什么也不做。
	 * @param minCapacity
	 */
	private void ensureCapacity(int minCapacity){
		if(minCapacity>capacity){
			while(capacity<minCapacity){
				capacity<<=1;//左移一位,扩容
			}
			//Array.copyOf(arg1,arg2)是数组的复制操作,第一参数是要复制的数组对象,第二参数是要复制的长度,如果长度超多原数组的长度
			//则保留数组默认值
			elementData=Arrays.copyOf(elementData, capacity);//如此变实现了扩容
		}
	}
	
	/**
	 * 向线性表指定位置插入一个元素
	 * @param element
	 * @param index
	 */
	public void insert(T element,int index){
		if(index<0||index>size){
			throw new IndexOutOfBoundsException("线性表索引越界");
		}
		ensureCapacity(size+1);//将数组的容量加一
		//将index后的元素往后移动一个
		System.arraycopy(elementData, index, elementData, index+1, size-index);
		elementData[index]=element;
		size++;
	}
	
	/**
	 * 在线性表的尾处添加一个元素
	 * @param element
	 */
	public void add(T element){
		insert(element, size);
	}
	
	/**
	 * 删除顺序表中指定的索引处的元素,并返回该值
	 * @param index
	 * @return 将该值返回
	 */
	public T delete(int index){
		if(index<0||index>size-1){
			throw new IndexOutOfBoundsException("数组越界");
		}
		T odlValue=(T)elementData[index];
		int numMoved=size-index-1;//需要移动的次数,
		if(numMoved>0){
			System.arraycopy(elementData, index+1, elementData, index, numMoved);
		}
		//清空最后一个元素,因为最后一个元素还存在
		elementData[--size]=null;
		return odlValue;
	}
	
	/**
	 * 删除最后一个元素
	 * @return
	 */
	public T remove(){
		return delete(size-1);
	}
	
	/**
	 * 判断线性表是否为空
	 * @return
	 */
	public boolean isEmpty(){
		return size==0;
	}
	
	/**
	 * 清空线性表
	 */
	public void clear(){
		Arrays.fill(elementData,null);
		size=0;
	}
	
	/**
	 * 自己的toString方法
	 */
	public String toString(){
		if(size==0){
			return "[]";
		}else {
			StringBuilder sb=new StringBuilder("[");
			for(int i=0;i<size;i++){
				sb.append(elementData[i].toString()+",");
			}
			int len=sb.length();
			return sb.delete(len-2, len).append("]").toString();
		}
		
	}
	
}

2.线性表链式存储结构:将采用一组地址的任意的存储单元存放线性表中的数据元素。
链表又可分为:
  • 单链表:每个节点只保留一个引用,该引用指向当前节点的下一个节点,没有引用指向头结点,尾节点的next引用为null。
  • 循环链表:一种首尾相连的链表。
  • 双向链表:每个节点有两个引用,一个指向当前节点的上一个节点,另外一个指向当前节点的下一个节点。

下面给出线性表双向链表的实现:java中LinkedList是线性表的链式实现,是一个双向链表。
package com.ruicai.test;

/**
 * description:双向链表
 * @author:dsx_zz@qq.com
 * @date:2018/2/8 11:03
 */
public class LinkedList<T> {

    //定义一个内部类Node,Node实例代表链表的节点
    private  class  Node{
        //保存节点的数据,定义为泛型
        private T data;
        //指向上个节点的引用,上个节点也是Node,
        private Node prev;
        //指向下个节点的引用,下个节点也是Node
        private Node next;
        //无参构造
        private Node(){

        }

        //初始化全部属性的构造器
        public Node(T data,Node prev,Node next){
            this.data=data;
            this.prev=prev;
            this.next=next;
        }
    }

    //保存该链表的头节点
    private Node header;
    //保存该链表的尾节点
    private Node tail;
    //保存该链表中已包含的节点数
    private int size;
    //创建空链表
    public LinkedList(){

        //空链表,header和tail都是null
        header=null;
        tail=null;
    }

    //以指定数据元素来创建链表,该链表只有一个元素
    public LinkedList(T element){
        header=new Node(element,null,null);
        //只有一个节点,header,tail都指向该节点
        tail=header;
        size++;
    }

    //包含全属性的构造函数
    public LinkedList(Node header,Node tail,int size){
        this.header=header;
        this.tail=tail;
        this.size=size;
    }
    //返回链表的长度,节点个数
    public int length(){
        return size;
    }

    //获取链式线性表中索引为index处的元素
    public T get(int index){

        return getNodeByIndex(index).data;
    }

    //根据索引index获取指定位置的节点
    public Node getNodeByIndex(int index){
        if(index<0||index>size-1){
            throw new IndexOutOfBoundsException("线性表越界异常");
        }
        if (index<=size/2){ //index小于链表元素个数的一半
            //从header节点开始,这样效率高些
            Node current=header; //从头开始数嘛

            //此循环条件相当于给链式线性表赋索引,
            for (int i=0;i<=size/2&¤t!=null;i++,current=current.next){
                if (i==index){
                    return current;
                }
            }
        }else {
            Node current=tail;//从尾节点开始搜索,for循环的条件倒着来
            for (int i=size-1;i>size/2&¤t!=null;i++,current=current.prev){
                if (i==index){
                    return current;
                }
            }
        }
        return null;
    }

    /**
     * 获取链式线性表指定元素的索引
     * @param element:指定的元素
     * @return  返回索引
     */
    public int getElementIndex(T element){
        Node current=header;
        for (int i=0;i<size&¤t!=null;i++,current=current.next){
            if (current.data.equals(element)){
                return i;
            }
        }
        return -1;
    }

    /**
     * 在指定的索引处插入新元素
     * @param element
     * @param index
     */
    public void insert(T element,int index){
        if(index<0||index>size){
            throw new IndexOutOfBoundsException("线性表索引越界");
        }
        if(header==null){
            addAtTail(element);
        }
    }

    /**
     * 采用尾插法,插入新的节点
     * @param element
     */
    public void addAtTail(T element){
        //如果该链表是空的,则直接创建一个
        if(header==null){
            header=new Node(element,null,null);
        }else{
            /* 创建一个新节点,新节点的prev指向原来的tail节点 */
            Node newNode=new Node(element,tail,null);
            tail.next=newNode;//让原来的尾节点的next指向新节点
            //新节点变为尾节点
            tail=newNode;
        }
        size++;
    }

    /**
     * 采用头插法添加一个新的元素
     * @param element
     */
    public void addAtHeader(T element){
        if(header==null){
            header=new Node(element,null,null);
        }else {
            /*创建一个新节点,新节点的next指向原来链表的header节点*/
            Node newNode=new Node(element,null,header);
            /*原来链表的头节点的prev指向新节点*/
            header.prev=newNode;
            /*newNode变为新的头节点*/
            header=newNode;
        }
        size++;
    }

    /**
     * 删除线性表指定索引处的元素
     * @param index
     * @return 返回被删除的节点
     */
    public T delete(int index){
        if(index<0||index>size-1){
            throw new IndexOutOfBoundsException("链表索引越界");
        }
        Node del=null;
        if(index==0){
            //索引为0,即删除的是头节点
            del=header;
            header=header.next;//将原来头结点的next变为新的头结点
            header.prev=null;//注意顺序,先赋值在删除
        }else {
            Node prev=getNodeByIndex(index-1);//获取要删除节点的前一个
            del=prev.next;//要删除的节点
            /*要删除节点的上一个节点的next指向要删除节点的next*/
            prev.next=del.next;
            if(del.next!=null){
                del.next.prev=prev;
            }
            del.prev=null;
            del.next=null;
        }
        size--;
        return del.data;//
    }

    //删除链式线性表的最后一个元素
    public T remove(){
        return delete(size-1);
    }

    //判断线性表是否为空链表
    public boolean isEmpty(){
        return size==0;
    }

    //清空线性表
    public void clear(){
        header=null;
        tail=null;
        size=0;
    }

    //覆写toString方法
    public String toString(){
        if (isEmpty()){
            return "[]";
        }else {
            StringBuilder sb = new StringBuilder("[");
            for (Node current = header ; current != null
                    ; current = current.next )
            {
                sb.append(current.data.toString() + ", ");
            }
            int len = sb.length();
            return sb.delete(len - 1 , len).append("]").toString();
        }
    }


}

线性表的两种实现比较
  • 空间性能:顺序表:顺序表的存储空间是静态分布的,需要一个长度固定的数组,因此总有部分数组元素被浪费。
    链表:链表的存储空间是动态分布的,因此不会空间浪费。但是由于链表需要而外的空间来为每个节点保存指针,因此要牺牲一部分空间。
  • 时间性能:顺序表:顺序表中元素的逻辑顺序与物理存储顺序是保持一致的,而且支持随机存取。因此顺序表在查找、读取时性能很好。
    链表:链表采用链式结构来保存表内元素,因此在插入、删除元素时性能要好

原博地址:http://zengzhaoshuai.iteye.com/blog/1171547

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值