代码改变世界

数据结构顺序表

2010-05-21 08:49  ScriptZhang  阅读(5381)  评论(0编辑  收藏  举报

    线性表(List)是由n(n≥0)个相同类型的数据元素构成的有限序列。对于这个定义应该注意两个概念:一是“有限”,指的是线性表中的数据元素的个数是有限的,线性表中的每一个数据元素都有自己的位置(Position)。本书不讨论数据元素个数无限的线性表。二是“相同类型”,指的是线性表中的数据元素都属于同一种类型。虽然有的线性表具有不同类型的数据元素,但本书中所讨论的线性表中的数据元素都属于同一类型。    

   线性表通常记为:L=(a1,a2,…,ai-1,ai, ai+1,…,an),L是英文单词list的第1个字母。L中包含n个数据元素,下标表示数据元素在线性表中的位置。a1是线性表中第一个位置的数据元素,我们称作第一个元素。an是线性表中最后一个位置的数据元素,我们称作最后一个元素。n为线性表的表长,n=0时的线性表被称为空表(Empty List)。
线性表中的数据元素之间存在着前后次序的位置关系,将ai-1称为ai的直接前驱,将ai称为ai+1的直接后继。除a1外,其余元素只有一个直接前驱,因为a1是第一个元素,所以它没有前驱。除an外,其余元素只有一个直接后继,因为an是最后一个元素,所以它没有后继。
线性表的形式化定义为:线性表(List)简记为L,是一个二元组,
L = (D, R)
其中:D是数据元素的有限集合。
R是数据元素之间关系的有限集合。
在实际生活中线性表的例子很多。例如,1到100的偶数就是一个线性表:
(2,4,6,…,100)
表中数据元素的类型是自然数。某个办公室的职员姓名(假设每个职员的姓名都不一样)也可以用一个线性表来表示:
(“zhangsan”,”lisi”,”wangwu”,”zhaoliu”,”sunqi”,”huangba”)
表中数据元素的类型为字符串。
在一个复杂的线性表中,一个数据元素是一个记录,由若干个数据项组成,含有大量记录的线性表又称文件(File)。例如,例子1.1中的学生信息表就是一个线性表,表中的每一行是一个记录。一个记录由学号、姓名、行政班级、性别和出生年月等数据项组成。

线性表的基本操作

在选择线性表的表示法之前,程序设计人员首先应该考虑这种表示法要支持的基本操作。从线性表的定义可知,一个线性表在长度上应该能够增长和缩短,也就是说,线性表最重要的操作应该能够在线性表的任何位置插入和删除元素。除此之外,应该可以有办法获得元素的值、读出这个值或者修改这个值。也应该可以生成和清除(或者重新初始化)线性表。
数据结构的运算是定义在逻辑结构层次上的,而运算的具体实现是建立在物理存储结构层次上的。因此,把线性表的操作作为逻辑结构的一部分,每个操作的具体实现只有在确定了线性表的存储结构之后才能完成。数据结构的基本运算不是它的全部运算,而是一些常用的基本运算,而每一个基本运算在实现时也可以根据不同的存储结构派生出一系列相关的运算来。
由于现在只考虑线性表的基本操作,所以以C#接口的形式表示线性表,接口中的方法成员表示基本操作。并且,为了使线性表对任何数据类型都适用,数据元素的类型使用泛型的类型参数。在实际创建线性表时,元素的实际类型可以用应用程序中任何方便的数据类型来代替,比如用简单的整型或者用户自定义的更复杂的类型来代替。

线性表的接口如下所示。
public interface IListDS<T> {
int GetLength(); //求长度
void Clear(); //清空操作
bool IsEmpty(); //判断线性表是否为空
void Append(T item); //附加操作
void Insert(T item, int i); //插入操作
T Delete(int i); //删除操作
T GetElem(int i); //取表元
int Locate(T value); //按值查找
}

为了和.NET框架中的接口IList相区分,在IList后面加了“DS”,“DS”表示数据结构。下面对线性表的基本操作进行说明。
1、求长度:GetLength()
初始条件:线性表存在;
操作结果:返回线性表中所有数据元素的个数。
2、清空操作:Clear()
初始条件:线性表存在且有数据元素;
操作结果:从线性表中清除所有数据元素,线性表为空。
3、判断线性表是否为空:IsEmpty()
初始条件:线性表存在;
操作结果:如果线性表为空返回true,否则返回false。
4、附加操作:Append(T item)
初始条件:线性表存在且未满;
操作结果:将值为item的新元素添加到表的末尾。
5、插入操作:Insert(T item, int i)
初始条件:线性表存在,插入位置正确()(1≤i≤n+1,n为插入前的表长)。
操作结果:在线性表的第i个位置上插入一个值为item的新元素,这样使得原序号为i,i+1,…,n的数据元素的序号变为i+1,i+2,…,n+1,插入后表长=原表长+1。
6、删除操作:Delete(int i)
初始条件:线性表存在且不为空,删除位置正确(1≤i≤n,n为删除前的表长)。
操作结果:在线性表中删除序号为i的数据元素,返回删除后的数据元素。删除后使原序号为i+1,i+2,…,n的数据元素的序号变为i,i+1,…,n-1,删除后表长=原表长-1。
7、取表元:GetElem(int i)
初始条件:线性表存在,所取数据元素位置正确(1≤i≤n,n为线性表的表长);
操作结果:返回线性表中第i个数据元素。
8、按值查找:Locate(T value)
初始条件:线性表存在。
操作结果:在线性表中查找值为value的数据元素,其结果返回在线性表中首次出现的值为value的数据元素的序号,称为查找成功;否则,在线性表中未找到值为value的数据元素,返回一个特殊值表示查找失败。
实际上,在.NET框架中,许多类的求长度、判断满和判断空等操作都用属性来表示,这里在接口中定义为方法是为了说明数据结构的操作运算。实际上,属性也是方法。在后面章节的数据结构如栈、队列等的处理也是如此,就不一一说明了。

 

在计算机内,保存线性表最简单、最自然的方式,就是把表中的元素一个接一个地放进顺序的存储单元,这就是线性表的顺序存储(Sequence Storage)。线性表的顺序存储是指在内存中用一块地址连续的空间依次存放线性表的数据元素,用这种方式存储的线性表叫顺序表(Sequence List),如图所示。顺序表的特点是表中相邻的数据元素在内存中存储位置也相邻。

顺序表的存储结构示意图
   假设顺序表中的每个数据元素占w个存储单元,设第i个数据元素的存储地址为Loc(ai),则有:
Loc(ai)= Loc(a1)+(i-1)*w 1≤i≤n
式中的Loc(a1)表示第一个数据元素a1的存储地址,也是顺序表的起始存储地址,称为顺序表的基地址(Base Address)。也就是说,只要知道顺序表的基地址和每个数据元素所占的存储单元的个数就可以求出顺序表中任何一个数据元素的存储地址。并且,由于计算顺序表中每个数据元素存储地址的时间相同,所以顺序表具有随机存取的特点。

    C#语言中的数组在内存中占用的存储空间就是一组连续的存储区域,因此,数组具有随机存取的特点。所以,数组天生具有表示顺序表的数据存储区域的特性。

   把顺序表看作是一个泛型类,类名叫SeqList<T>。”Seq”是英文单词”Sequence”的前三个字母。SeqList<T>类实现了接口IListDS<T>。用数组来存储顺序表中的元素,在SeqList<T>类中用字段data来表示。由于经常需要在顺序表中插入或删除数据元素,所以要求顺序表的表长是可变的。因此,数组的容量需要设计得特别大,可以用System.Array的Length属性来表示。但为了说明顺序表的最大长度(顺序表的容量),在SeqList<T>类中用字段maxsize来表示。maxsize的值可以根据实际需要修改,这通过SeqList<T>类中构造器的参数size来实现。顺序表中的元素由data[0]开始依次顺序存放,由于顺序表中的实际元素个数一般达不到maxsize,因此,在SeqList<T>类中需要一个字段last表示顺序表中最后一个数据元素在数组中的位置。如果顺序表中有数据元素时,last的变化范围是0到maxsize-1,如果顺序表为空,last=-1。具体表示见图2.1所示。由于顺序表空间的限制,当往顺序表中插入数据元素需要判断顺序表是否已满,顺序表已满不能插入元素。所以,SeqList<T>类除了要实现接口IListDS<T>中的方法外,还需要实现判断顺序表是否已满等成员方法。
顺序表类SeqList<T>的实现说明如下所示。

代码
public class SeqList<T>:IList<T>
{
private int maxsize;//顺序表的容量
private T[] data; //数组,用于存储数据表中的元素
private int last;//指示顺序表最后一个元素的位置

#region IList<T> 成员

//索引器
public int IndexOf(T item)
{
throw new NotImplementedException();
}

public T this[int index]
{
get { return data[index]; }
set { data[index] = value; }
}
//最后一个数据元素位置属性
public int Last
{
get { return last; }
}
//容量属性
public int Maxsize
{
get { return maxsize; }
set { maxsize = value; }
}
//构造器
public SeqList(int size)
{
data
= new T[size];
maxsize
= size;
last
= -1;
}

//求顺序表的长度
public int GetLength()
{
return last + 1;
}
//清空顺序表
public void Clear()
{
last
= -1;
}
//判断顺序表是否为空
public bool IsEmpty()
{
if (last == -1)
{
return true;
}
else
{
return false;
}
}
//判断顺序表是否为满
public bool IsFull()
{
if (last == maxsize - 1)
{
return true;
}
else
{
return false;
}
}
//在顺序表末尾添加新元素
public void Append(T item)
{
if (IsFull())
{
Console.WriteLine(
"List is full");

}
data[
++last] = item;
}

//在顺序表的第i个数据元素的位置插入一个数据元素
public void Insert(int i,T item)
{
if (IsFull())
{
Console.WriteLine(
"List is full");
return;
}
if(i<1||i>last+2)
{
Console.WriteLine(
"Position is error");
}
if(i==last+2)
{
data[last
+1]=item;
}
else
{
for(int j=last;j>i-1;--j)
{
data[j
+1]=data[j];
}
data[i
-1]=item;

}
++last;
}

//删除顺序表的第i数据元素
public T Detete(int i)
{
T tmp
= default(T);
if (IsEmpty())
{
Console.WriteLine(
"List is empty");
return tmp;
}
if (i < 1 || i > last + 1)
{
Console.WriteLine(
"Position is error");
return tmp;
}
if (i == last + 1)
{
tmp
= data[last--];

}
else
{
tmp
= data[i - 1];
for (int j = i; j <= last; ++j)
{
data[j]
= data[j + 1];
}
}
--last;
return tmp;
}
//获得顺序表的第i数据元素
public T GetElem(int i)
{
if(IsEmpty()||(i<1)||(i>last+1))
{
Console.WriteLine(
"List is empty or Position is error ");
return default(T);
}
return data[i-1];
}
//在顺序表中查找值为value的元素
public int Locate(T value)
{
if (IsEmpty())
{
Console.WriteLine(
"List is empty");
return -1;

}
int i = 0;
for (i = 0; i <= last; ++i)
{
if(value.Equals(data[i]))
{
break;}
}
if (i > last)
{
return -1;
}
return i;
}
//对顺序表进行倒序
public void Reverse()
{
T tmp
= default(T);
int len = GetLength();
for (int i = 0; i < len / 2; ++i)
{
tmp
= data[i];
data[i]
= data[len - i];
data[len
- i] = tmp;
}

}
//合并两个顺序表
public SeqList<int> Merge(SeqList<int> La, SeqList<int> Lb)
{
SeqList
<int> Lc = new SeqList<int>(La.maxsize + Lb.maxsize);
int i = 0;
int j = 0;
//int k = 0;

while ((i <= (La.GetLength() - 1) && (j <= Lb.GetLength() - 1)))
{
if (La[i] < Lb[j])
{
Lc.Append(La[i
++]);
}
else
{
Lc.Append(Lb[j
++]);
}
}
//a表中还有数据元素
while (i <= La.GetLength() - 1)
{
Lc.Append(La[i
++]);

}
//b表中还有数据元素
while (j <= Lb.GetLength() - 1)
{
Lc.Append(Lb[j
++]);
}


return Lc;
}
//从表中删除相同的数据元素,再组成顺序表
public SeqList<int> Purge(SeqList<int> La)
{
SeqList
<int> Lb = new SeqList<int>(La.maxsize);
//将a表中的第一个数据元素赋给b表
Lb.Append(La[0]);
//依次处理a表中的数据元素
for (int i = 1; i < La.GetLength() - 1; ++i)
{
int j = 0;

//查看b表中有无与a表中相同的数据元素
for (j = 0; j < Lb.GetLength() - 1; --j)
{
if (La[i].CompareTo(Lb[j]) == 0)
{
break;
}
}
//没有相同的数据元素,将a表中的数据元素附加到b表末尾
if (j > Lb.GetLength() - 1)
{
Lb.Append(La[i]);
}

}
return Lb;
}
//
public void RemoveAt(int index)
{
throw new NotImplementedException();
}
//寻找最大值
public T GetMax()
{

T tmp
= default(T);
for (int i = 0; i < maxsize; i++)
{



if (data[i].Equals(data[i + 1]))
tmp
= data[maxsize - 1];
}
return tmp;
}
//寻找最小值
public T GetMin()
{

T tmp
= default(T);
tmp
=data[0];


return tmp;
}
//将表分割成两个表,大于0的元素放在lc中,小于0的元素放在lb中
//大于0的部分
public SeqList<int> filte1(SeqList<int> La)
{

SeqList
<int> Lc=new SeqList<int>(La.maxsize);



for (int k = 0; k < La.GetLength() - 1;k++ )
{
if (La[k] > 0)
{
Lc.Append(La.data[k
++]);
}

}
return Lc;

}
//小于0的部分
public SeqList<int> filte2(SeqList<int> la)
{
SeqList
<int> lb=new SeqList<int>(la.maxsize);
//int i = 0;

for (int k = 0; k < la.maxsize - 1;k++)
{
if (la.data[k] < 0)
{
lb.Append(la.data[k
++]);


}

}
return lb;

}
//输出顺序表的全部元素
public void Display()
{
T tmp
= default(T);
//int len = GetLength();
for (int i = 0; i < maxsize-1; i++)
{
tmp
= data[i];

Console.WriteLine(tmp.ToString());
}

}
//复制顺序表的算法
public SeqList<int> LinkClone(SeqList<int> lb)
{


SeqList
<int> la = new SeqList<int>(maxsize);
for (int j = 0; j < maxsize - 1;j++ )
{
la.Append(lb.data[j]);

}
return la;
}

private void Append(object p)
{
throw new NotImplementedException();
}

#endregion
}