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做为单链表的尾指针。