您的位置:首页 > 理论基础 > 数据结构算法

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

2015-08-02 00:00 316 查看
摘要: java数据结构复习------线性表

一、线性表定义:

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

线性表接口LList的定义:

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元素

}

二、线性表的顺序表示和实现

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

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

2、顺序表实现类及其操作

分析:顺序表采用数组存储数据元素,所以要有一个成员变量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;
}

}

3、顺序表操作的效率分析

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

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

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

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

(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];
}
}

5、顺序表比较相等

比较两个对象顺序表是否相等,是指他们的长度相同并且各对应元素相等。覆盖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;
}

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

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

2、单链表类实现及其操作:

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

结点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;
}

3、带头结点的单链表

带头结点的单链表是指,在单链表的第一个结点之前增加一个特殊的结点,称为头结点。头结点的作用是使所有链表(包括空表)的头指针非空,并使单链表的插入、删除操作不需要区分是否为空表或是否在第一个位置进行,从而与其他位置的插入、删除操作一致。

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;
}

}

4、 单链表操作的效率分析

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作为单链表的尾指针。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: