假期连载之一

关于本连载的说明请查阅http://www.hanyi.name/blog/?p=118

一、常用数据结构分析(概念、ADTADT实现、线性表的顺序存储)

 

一) 数据即是指描述客观事物的数值、字符以及能输入机器且能被处理的各种符号集合。数据元素是指组成数据的基本单位,是数据集合的个体。相应的,数据结构就是性质相同的数据元素的集合,是数据的一个子集。

数据结构的内容大概包括以下三个方面:

1、  逻辑结构(通常)

     集合结构,结构中的数据元素除了同属于一个集合的关系外,无其他关系。

     线性结构,结构中的数据元素之间存在一对一的线性关系。

     数型结构,结构中的数据元素之间存在着一对多的层次关系。

     图状结构或网状结构,结构中的数据元素之间存在着多对多的任意关系。

2、  存储结构

     顺序映像,即顺序存储结构。

     非顺序映像,即非顺序存储结构。

3、  运算集合

数据结构另一个重要内容即是对一类数据的相关运算操作。

 

       二) 抽象数据类型(Abstract Data Type,通常称ADT),即包括一个相应的数学模型以及该模型上应用的各种运算,它集中体现了程序设计过程中的分解、抽象和信息隐藏等原则。

对于如何实现抽象数据类型,一般有三种方法,这也是目前程序设计的三个重要方法。

1、面向过程的程序设计,根据逻辑结构选择适当的存储结构,并按要求设计出相应的子程序或子函数,一般使用C语言、Pascal语言描述。

2、“包”、“模型”的设计方法,是面向模块结构的表示和实现,一般是指DODAda语言、module-2语言和Pascal语言所支持的方法。

3、面向对象程序设计,借助对象描述数据类型,存储结构和操作函数被放在一个整体结构中,例如C++Java等语言提供了此功能的支持。

 

在今后的连载内容中,我们将采用面向过程与面向对象相结合的方法,即给出两种描述,分别使用C语言和C++语言实现,方便大家在各方面的理解。高级算法部分主要以伪码形式给出,适当利用高级程序设计语言加以说明。

 

) 分别用CC++抽象数据类型实现的举例。

       ADT<ADT>{

              数据对象:数据对象的定义。

              结构关系:结构关系的定义。

              基本操作:基本操作的定义。

}ADT<ADT>

1、  C语言实现

C语言中我们通常使用typedef自定义类型和结构体实现数据类型的集合。例如关于一个“学生”的集合:

typedef struct node{

       char Name[20];

       char Sex;

       int Age;

       float Money;

}Employee,*EmpPtr;

       值得说明的是,typedef同时定义了两个新的数据类型名,Employeestruct node结构体类型名,而EmpPtr指结构体指针类型,通常代替struct node *类型名。

2、  C++语言实现

C++由于其特殊的特性对ADT有多种表示方法,通常使用自定义抽象类、自定义模版和void struct *三种方法,其中void struct *由于模版类的使用已经很少再使用,如有需要读者可以参考C语言的方法使用。

     自定义抽象类,与Java不同,C++中并不提供abstract关键字声明抽象类,在确需要使用到抽象类时,须采用例如virtual void draw() = 0;的函数声明方法,称为“纯虚函数”,拥有“纯虚函数”的类自动为抽象类。

     自定义模版template,目的是使抽象出的对象并不依赖于抽象类的数据类型。下面给出一个抽象类的声明和使用方法:

template<class T>

class Point{

private:

T x;

     T y;

public:

     Point(T,T);

     Point(Point p);

     T get_x();

     T get_y();

     virtual void draw() = 0;

}

int main()

{

     Point<int> a;      

     return 0;

}

 

       ) 线性表

       线性表Linear List是指n个类型相同的数据元素的有限序列,对n>0,除第一个元素无直接前驱、最后一个元素无直接后继外,其余每个元素只有一个直接前驱和直接后继,线性表的抽象数据类型定义如下:

ADT LinearList{

数据元素:D={ai| ai D0,i=0,1,2,…,n,n>=0, D0为某一数据对象};

结构关系:R={<ai ,ai+1>| ai , ai+1D0,i=0,1,2,…,n-1};

基本操作:

        InitList(L);             //初始化L为空表

        DestroyList(L);      //L销毁

        ClearList(L);          //L置为空表

        EmptyList(L);        //如果L为空则返回TRUE,否则返回FALSE

        ListLength(L);        //如果L为空返回0,否则返回FALSE

        Locate(L,e);           //如果L中存在元素e,则将当前指针指向元素e的所在位置,并返回TRUE,否则返回FALSE

        GetData(L,i);         //返回L中第i个元素的值

        InsList(L,i,e);         //L中第i个位置之前插入新的数据元素eL的长度加1

        DelList(L,i,e);        //删除L中第i个数据元素,并用e返回其值,L的长度减1

}ADT LinearList

 

1、  线性表的顺序存储(Sequential List),其实就是指线性表基于一维数组的存储表示,数组占用了内存中一段连续空间,其元素的地址映像计算公式为:

Loc(ai)= Loc(a1)+(i-1)*k

       注:上式表示在一个具有n个元素的线性表中,每个元素占k个单元,第一个元素的地址为Loc(a1),则可通过上述公式计算第i个元素的地址Loc(ai)

 

C语言定义线性表的顺序存储结构的静态存储表示如下:

#define MAXSIZE 100

typedef struct

{

       ElemType elem[MAXSIZE];

       int last;

}SeqList;

相应地,其动态存储表示如下:

typedef struct

{

       ElemType *elem;

       int last,maxSize;                   //利用maxSize可方便控制数组空间的大小

}SeqList;

 

同时,我们也给出C++定义的线性表的顺序存储结构的动态存储模版类表示如下:

#include <iostream.h>

#include <stdlib.h>

#include “LinearList.h”

const int defaultSize = 100;          //这里的const指静态常量,在C++定义中,const成员函数不能调用非const成员函数

template <class T>

class SeqList:public LinearList<T>

{

protected:

       T *data;

       int maxSize;

       int last;

       void resize(int newSize)        //改变data数组空间的大小

public:

       ……                                          //方法函数

}

……

       值得注意的是,在顺序表动态存储表示中,由于数组本身不可能被动态改变其大小,因而实际上是通过创建新数组并加以复制的方法实现的,复制的方法涉及到了C++中数组指针的内容,其和指针数组有明显区别,指针数组的定义方式int *p[10]代表p数组中每一个元素都是一个指针,即*p[0]*p[1]…..数组指针一般定义为int (*p)[10],这里仅表示一个指向数组的指针,它们访问内部元素的形式为而这里小结如下:

       提前说明,数组指针与指针数组在C语言和C++之间也存在一些不同,但由于该名词是由C++中率先提出的,因此一般会按照C++标准来定义这些概念,并解释相关问题。例如在指针数组中,一般读取其中元素的方法是:

       int *p=new int[maxSize];

       cout<<*(p+n)<<endl;                  //输出第n+1个元素的值

而在数组指针中,我们一般用以下方式读取数组元素:

       int (*q)[maxSize]={1,2,3,…,maxSize};

       cout<<*(*q+n)<<endl;                 //同上

 

下面给出C++线性表的顺序存储结构的动态存储模版类中重置大小函数reSize()的方法定义:

template <class T>

void SeqList<T>::resize(int newSize)

{

       if(newSize <= 0)

       {     cerr << ”无效的数组大小” <<endl;

       return;

}

       if(newSize != maxSize)

       {

              T* newarray = new T[newSize];

              if(newarray == NULL)

              {

                     cerr << “存储分配错误!” <<endl;

                     exit(1);

}

int n = last+1;

T* srcptr = data;

T* destptr = newarray;

while(n–)

       *destptr++ = *srcptr++;

delete []data;

data = newarray;

maxSize = newSize;

}

}

       在顺序存储结构中,由于动态存储和静态存储的区别,其内部定义的基本运算也不尽相同,我们将给出部分运算的伪码表示:

      

Locate(L,e)                          //线性表的查找

       i=0;

       while i<=L.last && L.elem[i]!=e

              do i++;

       if i<=L.last

           then return i+1;

       else

              then return -1;

 

InsList(L,i,e)                        //线性表的插入

       if i<1 || i>L.last+2

              Then error “插入位置不合法

       if L.last >= maxSize-1

              Then error “线性表已满!”

       for k=L-last k>= i-1 k—              //从线性表尾部开始移动所有L.last+2-i个元素后继一位

              L.elem[k+1]=L.elem[k];

              L.elem[i-1]=e;

              L.last++;

       return OK

 

DelList(L,i,e)                       //线性表的删除

       if i<1 || i>L.last+1;

              Then error “删除位置不合法

       *e=L.elem[i-1];

       for k=i i<=L.last k++

              L.elem[k-1] = L.elem[k];

       L.last–;

       return OK;

 

Merge(LA,LB,LC)                //线性表的合并,按非递减有序序列合并

       while i<=LA.last && j<=LB.last

              do if LA.elem[i] <=LB.elem[j]

              then LC.elem[k]=LA.elem[i];

                     i++;k++;

              else then LC.elem[k] = LB.elem[j];

              j++;k++;

       while i<LA.last

              do LC.elem[k]=LA.elem[i];

              i++;k++;

       while j<= LB.last

              do LC.elem[k] = LB.elem[j];

              j++;k++;

       LC.last=LA.last+1+LB.last+1;

 

(未完待续)