JAVA数据结构--------线性表

1、线性表定义:
java

线性表是由n(n>=0)个类型相同的数据元素组成的有限序列,第一个元素无前驱元素,最后一个无后继元素,其余元素有且仅有一个前驱和一个后继。数组

线性表接口LList的定义:app

package com.list;

public interface LList<T> {
	
	boolean isEmpty();  //判断线性表是否为空
	int length();       //返回线性表的长度
	T get(int i);       //返回第i个元素
	void set(int i,T x); //设置第i个元素为x
	void inset(int i,T x);//插入x做为第i个元素
	void append(T x);    //在线性表最后插入x元素
	T remove(int i);     //删除第i个元素并返回被删除的对象
	void removeAll();    //删除线性表的全部元素
	T search(T key);     //查找,返回首次出现的关键字为key元素

}

2、线性表的顺序表示和实现函数

一、线性表的顺序存储结构:用一组连续的内存单元依次存放线性表的数据元素,元素内存的物理存储次序与他们在线性表中的逻辑次序相同。顺序存储的线性表也称为顺序表。this

    顺序表是一种随机存取结构。存取任何一个元素的时间复杂度是O(1)。顺序表一般采用数组存储数据。指针

二、顺序表实现类及其操做code

    分析:顺序表采用数组存储数据元素,因此要有一个成员变量elements存放线性表的一维数组。同时还要有成员变量len表示顺序表的长度,且len<=elements.length。对象

package com.list.impl;

import com.list.LList;

public class SeqList<T> implements LList<T> {
	/****************************初始化*************************************************/

	private Object[] elements;  //顺序表采用数据存放数据元素
	private int len;            //顺序表的长度,记录实际元素个数
	//无参的构造函数
	public SeqList(){
		this(64);        //建立默认容量的数组
	}
	//有参构造函数
	public SeqList(int size){
		this.elements=new Object[size];  //初始化存储数组
		this.len=0;                     //设顺序表的初始长度为0
	}
	
	/***************************操做***********************************************/
	
	/**
	 * 判断顺序表是否为空
	 */
	public boolean isEmpty() {
		//根据线性表的实际长度来判断
		return this.len==0;
	}
	
	public int length() {
		return this.len;
	}
	
	/**
	 * 取第i个元素在数组中是[i-1]
	 */
	public T get(int i) {
		if(i>=1 && i<=this.len){
			return (T) elements[i-1];
		}
		return null;
	}
	
	/**
	 * 设置第i个元素的值为x
	 */
	public void set(int i, T x) {
		if(x==null){
			return;
		}
		if(i>=1 && i<=this.len){
			elements[i-1]=x;
		}
		else{
			throw new IndexOutOfBoundsException(i+"");
		}
	}
	/**
	 * 返回顺序表全部元素的描述字符串,形式为(,)覆盖Object中的toString()方法
	 */
	public String toString(){
		String str="(";
		if(this.len>0){
			str+=this.elements[0].toString();
		}
		for(int i=1;i<this.len;i++){
			str+=","+elements[i].toString();
		}
		return str+")";
	}
	/**
	 * 插入x做为第i个元素
	 */
	public void insert(int i, T x) {
		if(x==null){
			return;
		}
		//若是数组已满,则扩充顺序表容量
		if(this.len==elements.length){
			Object[] temp=this.elements;     //建立临时temp变量引用elements数组
			this.elements=new Object[temp.length*2];  //从新申请一个容量更大的数组
			//复制数组元素
			for(int j=1;j<=temp.length;j++){
				this.elements[j-1]=temp[j-1];
			}
		}
		//若是i小于1,则设i为1
		if(i<1){
			i=1;
		}
		//若是插入位置大于线性表的长度,至关于在最后插入数据,则就设i为len
		if(i>this.len){
			i=this.len+1;
		}
		//元素后移,len+1
	/*	for(int j=i;j<=this.len-1;j++){
			elements[j]=elements[j-1];
		}*/
		for(int j=this.len-1;j>=i;j--){
			this.elements[j+1]=this.elements[j];
		}
		this.elements[i-1]=x;
		this.len++;
		
	}
	//在顺序表的最后插入元素x
	public void append(T x) {
		this.insert(this.len, x);
	}
	//删除顺序表的第i个元素,并返回此元素
	public T remove(int i) {
		// TODO Auto-generated method stub
		return null;
	}
	//删除线性表中的全部元素
	public void removeAll() {
		this.len=0;
	}

	public T search(T key) {
		// TODO Auto-generated method stub
		return null;
	}
	

}

   三、顺序表操做的效率分析接口

      ①、存取任何一个元素的时间复杂度为O(1)。
内存

      ②、插入删除效率很低,在等几率状况下时间复杂度为O(n)。

    四、顺序表的深拷贝和浅拷贝

        拷贝构造方法:一个类的构造方法,若是其参数是该类对象,则称为拷贝构造方法。器主要做用是复制对象。

      (1)、顺序表的浅拷贝

              将当前对象的各成员变量赋值为实际参数对应各成员变量值,称为浅拷贝。

              当成员变量的数据类型为基本数据类型时,浅拷贝可以实现对象复制功能。当成员变量的数据类型时引用类型时,浅拷贝只复制了对象引用,并无真正实现对象复制功能。

    public SeqList(SeqList<T> list){
        
        this.elements=list.elements;
        this.len=list.len;
    }

        两个对象拥有同一个数组,形成修改、删除、插入等操做结果相互影响。

      (2)、顺序表的深拷贝

              当一个类包含引用类型的成员变量时,该类声明的拷贝构造函数,不只要复制对象的全部基本类型成员变量值,还要从新申请引用类型变量占用的动态存储空间,并复制其中全部对象,这种复制方式成为深拷贝。

public SeqList(SqeList<T> list){
    
    this.len=list.len;
    this.elements=new Object[list.elements.length];
    for(int i=0;i<list.elements.length;i++){
        this.elements[i]=list.elements[i];
    }
}

    五、顺序表比较相等

        比较两个对象顺序表是否相等,是指他们的长度相同而且各对应元素相等。覆盖Object的equals()方法:

public boolean equals(Object obj){

    if(this==obj){
      return true;
    }
    if(obj instanceof SeqList){
        SeqList<T> list=(SeqList)obj;
        if(this.length==list.length){
            for(int i=0;i<this.length;i++)
                if(!(this.get(i)).equals(list.get(i)))
                return false;
          return true;
        }
    }
    return false;
}

3、线性表的链式表示和实现

一、线性表的链式存储:用若干地址分散的存储单元存储数据元素,逻辑上相邻的数据元素在物理位置上不必定相邻,必须采用附加信息表示数据元素之间的顺序关系。所以,存储一个数据元素的存储单元至少包含两部分:数据域和地址域。

二、单链表类实现及其操做:

分析:由于线性表的链式存储结构,逻辑上相邻的物理位置上不必定相邻,因此存储单元至少要包含数据域和地址域。

结点Node实现:

package com.list.impl;

public class Node<T> {
	
	private T data; //数据域,保存数据元素
	private Node<T> next;//地址与,引用后继结点
	public Node(){
		this(null,null);
	}
	public Node(T data,Node<T> next){
		this.data=data;
		this.next=next;
	}

}

Node类时“自引用类”,指一个类声明包含一个引用当前类的对象的成员变量。Node类的一个对象表示单链表中的一个结点,经过next链,将两个结点连接到一块儿。

创建连接:

Node<String> p=new Node<String>("A",null);
Node<String> q=new Node<String>("B",null);
p.next=q;

或者

Node<String> q=new Node<String>("B",null);
Node<String> p=new Node<String>("A",q);

head存储线性链表第一结点的地址,称为头指针。head==null时,表示空链表。

单链表的遍历:遍历单链表是指从第一个结点开始,沿着结点的next链,依次访问单链表中的每一个结点,而且每一个结点只访问一次。遍历单链表操做不能改变头指针head,所以须要声明一个变量p指向当前访问结点。p从head指向的结点开始访问,再沿着next链到达后继结点,逐个访问。

Node<T> p=head;
while(p!=null){
    System.out.println(p.data.toString());
    p=p.next;
}

三、带头结点的单链表

带头结点的单链表是指,在单链表的第一个结点以前增长一个特殊的结点,称为头结点。头结点的做用是使全部链表(包括空表)的头指针非空,并使单链表的插入、删除操做不须要区分是否为空表或是否在第一个位置进行,从而与其余位置的插入、删除操做一致。

package com.list.impl;

import com.list.LList;


/**
 * 构造带头结点的单链表
 * @author wangning
 *
 * @param <T>
 */
public class LinkList<T> implements LList<T>{
	/****************构造链表**************************/
	
	private Node<T> head; //头指针,指向单链表的头结点
	//默认构造方法,构造空链表,建立头结点,data和next均为null
	public LinkList(){
		this.head=new Node<T>();
	}
	//由指定的数组中的多个对象构造单链表,采用尾插法
	public LinkList(T[] elements){
		this();//建立空链表
		Node<T> rear=this.head;    //rear指向单链表的最后一个结点
		for(int i=0;i<elements.length;i++){
			rear.next=new Node<T>(elements[i],null);  //尾插入,建立结点链入rear结点以后
			rear=rear.next;                //rear指向新的链尾结点
		}
	}
	/*
	 * 头查法构造链表
	public LinkList(T[] elements){
		this();
		for(int i=0;i<elements.length;i++){
			Node<T> p=new Node<T>(elements[i],null);//新增结点
			p.next=head.next;           //带头结点的写法
			head.next=p;               //带头结点的写法
		}
	}
	*/
	
	/**
	 * 判断单链表是否为空
	 */
	public boolean isEmpty() {
		return this.head.next==null;
	}
	/**
	 * 返回单链表的长度
	 */
	public int length() {
		int i=0;
		Node<T> p=this.head.next;
		while(p!=null){
			i++;
			p=p.next;
		}
		return i;
	}
	public String toString(){
		String str="(";
		Node<T> p=this.head.next;
		while(p!=null){
			str+=p.data.toString();
			if(p.next!=null){
				str+=",";
			}
			p=p.next;
		}
		return str+")";
	}
	/**
	 * 返回第i个元素
	 */
	public T get(int i) {
		if(i>=0){
			Node<T> p=this.head.next;  //第一个结点
			for(int j=0;p!=null && j<i; j++){
				p=p.next;
			}
			if(p!=null){
				return p.data;
			}
		}
		return null;
	}
	//设置第i个元素值为x
	public void set(int i, T x) {
			if(x==null){
				return;
			}
			if(i>=0){
				Node<T> p=this.head.next; //第一个结点
				for(int j=0;p!=null && j<i;j++){
					p=p.next;
				}
				if(p!=null){
					p.data=x;
				}
			}
			else
				throw new IndexOutOfBoundsException();
	}
	//将x对象插入在序号为i结点前
	public void insert(int i, T x) {
		if(x==null){
			return;
		}
		Node<T> p=this.head;  //投加点
		for(int j=0;p!=null && j<i;j++){
			p=p.next;          //i结点的前驱
		}
		p.next=new Node<T>(x,p.next);
	}

	public void append(T x) {
		this.insert(Integer.MAX_VALUE, x);
		
	}

	public T remove(int i) {
		if(i>=0){
			Node<T> p=this.head;   //头结点
			for(int j=0;p!=null && j<i; j++){
				p=p.next;             //定位到待删除结点的前去结点
			}
			if(p.next!=null){
				T d=p.next.data;   
				p.next=p.next.next;
				return d;
			}
		}
		return null;
	}

	public void removeAll() {
		this.head.next=null;
	}

	public T search(T key) {
		// TODO Auto-generated method stub
		return null;
	}

}

四、    单链表操做的效率分析

         isEmpty()方法的时间复杂度是O(1);length()方法要遍历单链表,时间复杂度为O(n)。

        单链表是一种顺序存取结构,不是随机存取结构。insert(p,x)。方法在单链表指定p结点以后插入一个结点,时间复杂度为O(1)。insert(i,x)方法插入x做为第i个结点,时间复杂度为O(n),其花费时间视插入位置而定,若在单链表最后插入,则时间复杂度为O(n)。

         对单链表进行插入和删除操做只要改变少许结点的链,不须要移动数据元素。单链表中的结点在删除和插入过程当中,是动态释放和申请的,不须要预先给单链表分配存储空间,从而避免了顺序表因存储空间不足扩充空间和复制元素的过程,提供了运行效率和存储空间利用率。

       提升单链表操做效率的措施:

       因为单链表长度的length()方法须要遍历整个单链表,因此在某些时候须要使用长度的状况下,经历避免两次遍历单链表。

public boolean append(T x){
return insert(this.length(),x);
}

   insert将遍历单链表2次,改成rentrun insert(Integer.MAX_VALUE,x);则只需遍历一次就将x结点插入在单链表以后。若是在单例表中添加某些私有成员变量,则可提升某些操做的效率,例如添加len变量表示单链表长度,添加rear做为单链表的尾指针。

相关文章
相关标签/搜索