1. 顺序表的存储结构
顺序表的存储结构可以借助于高级程序设计语言中的数组来表示,一维数组的下标与元素在线性表中的序号相对应。线性表的顺序存储结构可用C语言中动态分配的一维数组定义如下:
/*线性表的动态分配顺序存储结构(用一维数组)*/
#define INIT_SIZE 100 //线性表存储空间的初始分配量
#define INCREMENT 10 //线性表存储空间的分配增量
typedef struct
{
ElemType *elem; //存储空间基地址
int length; //当前长度
int listsize; //当前分配的存储容量
}SqList;
在上述定义中,ElemType为顺序表中数据元素的类型,SqList是顺序表类型。
为了令算法具有通用性,使其尽可能地适用于各种可能的场合,将要处理的数据类型加以抽象,使其适用于不同类型的数据,是提高代码通用性的重要手段。
ElemType类型根据实际问题需要灵活定义:
/* 定义ElemType为int类型 */
typedef int ElemType;
或者,有学生数据类型定义如下:
typedef struct date
{ int year;
int month;
int day;
}DATE;
typedef struct student
{
int num;
char name[20];
char sex;
DATE birthday;
float score;
}STUDENT;
/* 定义ElemType为STUDENT类型 */
typedef STUDENT ElemType;
顺序表中数据类型ElemType可以多种多样,但是在编程实现算法时针对不同数据类型,每类数据元素的输入输出是有区别的,顺序表的基本操作算法要在计算机上执行,须针对ElemType类型数据编写输入、输出、比较等函数:
void input(ElemType &s);
void output(ElemType s);
int equals(ElemType a,ElemType b);
C语言函数基本的参数传递机制是值传递,被调函数对形式参数的任何操作都是作为局部变量进行,不会影响主调函数的实参变量的值。如果需要将函数中变化的形式参数的值反映在实际参数中,在C语言的实现中,就需要通过指针变量作形式参数,接收变量的地址,达到修改实参变量值的目的。
C++语言中用引用作函数的形参,被调函数对形参做的任何操作都影响了主调函数中的实参变量值,而操作一个变量比操作一个指针要简单的多,为了便于算法描述,本书函数参数传递机制采用有两种方式:值传递和引用传递。如果不想修改实参的值,函数参数传递按值传递;如果需要修改实参的值,则使用引用传递。
举例说明,在定义输入函数void input(ElemType &s);时,在函数体内需要输入主调函数中的实参变量的值,也就是说要改变主调函数中的实参变量的值,因此函数形参定义为引用;在定义输出函数void output(ElemType s);时,在函数体内不需要改变主调函数中的实参变量的值,只需读取主调函数中的实参变量的值,因此函数形参定义为变量,采用值传递。
顺序表的初始化操作、销毁操作、插入操作和删除操作,在函数体内改变了顺序表L的数据成员的值,因此函数形参为引用。顺序表的查找操作、遍历操作,在函数体内不改变顺序表L的数据成员的值,函数形参为变量,采用值传递。
下面举例说明如何在线性表的顺序存储结构上实现线性表的基本操作。
2. 顺序表的初始化操作
void InitList(SqList&L)
{ // 操作结果:构造一个空的顺序线性表L
L.elem=(ElemType*)malloc( INIT_SIZE*sizeof(ElemType));
if(!L.elem)
exit(-1); // 存储分配失败
L.length=0; // 空表长度为0
L.listsize=INIT_SIZE; // 初始存储容量
}
3. 顺序表的遍历操作
void ListTraverse(SqList L,void(*vi)(ElemType ))
{ // 初始条件:顺序线性表L已存在
// 操作结果:依次对L的每个数据元素调用函数vi()
ElemType *p;
int i;
p=L.elem;
for(i=1; i<=L.length; i++)
vi(*p++);
printf("\n");
}
在执行ListTraverse()函数输出顺序表的所有数据元素时,用函数指针vi来实现对output()函数的调用。
在执行遍历函数输出顺序表的所有数据元素时,用函数指针vi来实现对output()函数的调用。
4. 顺序表的插入运算
线性表的插入运算是指在表的第i (1≤i≤n+1)个位置,插入一个新元素x,使长度为n的线性表 (a1,…,ai−1,ai,…,an) 变成长度为n+1的线性表( a1,…,ai−1,x,ai+1,…,an) 。
算法思想:用顺序表作为线性表的存储结构时,由于结点的物理顺序必须和结点的逻辑顺序保持一致,因此我们必须将原表中位置n-1,n-2,…,i-1上的结点,依次后移到位置n,n-1,…,i上,空出第i-1个位置,然后在该位置上插入新结点x。当i=n+1时,是指在线性表的末尾插入结点,所以无需移动结点,直接将x插入表的末尾即可。
算法分析:
- 最好的情况:当i=n+1时(插入尾元素),移动次数为0;
- 最坏的情况:当i=1时(插入第一个元素),移动次数为n;
- 平均情况:在位置i插入新元素x,需要将ai~an的元素均后移一次,移动次数为n-i+1。假设在等概率下pi(pi=1/(n+1)),移动元素的平均次数为:
插入算法的主要时间花费在元素移动上,所以算法平均时间复杂度为O(n)。
5. 代码实现
完成顺序表的初始化操作,遍历操作及插入操作三个子函数的定义,具体要求如下:
- void InitList(SqList &L); //构造一个空的顺序表L
- void ListTraverse(SqList L,void(*vi)(ElemType ));// 依次调用函数vi()输出顺序表L的每个数据元素
- int ListInsert(SqList &L,int i,ElemType e);//在顺序表L中第i个位置之前插入新的数据元素
#include <stdlib.h>
#include <stdio.h>
#include <iostream>
using namespace std;
#define INIT_SIZE 5
#define INCREMENT 10
# define OK 1
# define ERROR 0
/* 定义ElemType为int类型 */
typedef int ElemType;
void input(ElemType &s);
void output(ElemType s);
int equals(ElemType a,ElemType b);
/* 顺序表类型定义 */
typedef struct
{
ElemType *elem; //存储空间基地址
int length; //当前长度
int listsize; //当前分配的存储容量
}SqList;
void InitList(SqList&L);
int ListInsert(SqList &L,int i,ElemType e);
void ListTraverse(SqList L,void(*vi)(ElemType ) );
int main() //main() function
{
SqList A;
ElemType e;
InitList(A);
int n,i;
// cout<<"Please input the list number ";
cin>>n;
for(i=1;i<=n;i++)
{
cin>>e;
ListInsert(A, i, e);
}
//cout<<"请输入插入的位置:"<<endl;
cin>>i;
//cout<<"请输入插入的值:"<<endl;
input(e);
if( ListInsert(A,i,e) )
{
cout<<"插入成功,插入后顺序表如下:"<<endl;
ListTraverse(A,output) ;
}
else
cout<<"插入位置不合法,插入失败!"<<endl;
return 0;
}
/*****ElemType类型元素的基本操作*****/
void input(ElemType &s)
{
cin>>s;
}
void output(ElemType s)
{
cout<<s<<" ";
}
int equals(ElemType a,ElemType b)
{
if(a==b)
return 1;
else
return 0;
}
/*****顺序表的基本操作*****/
void InitList(SqList&L)
{ // 操作结果:构造一个空的顺序线性表L
L.elem = (ElemType*)malloc(INIT_SIZE*sizeof(ElemType));
if(!L.elem) exit(-1);
L.length = 0;
L.listsize = INIT_SIZE;
}
int ListInsert(SqList &L,int i,ElemType e)
{ // 初始条件:顺序线性表L已存在,1≤i≤ListLength(L)+1
// 操作结果:在L中第i个位置之前插入新的数据元素e,L的长度加1
ElemType *newbase, *q, *p;
if (i<1||i>L.length+1)
return 0;
if(L.length>=L.listsize){
if(!(newbase=(ElemType*)realloc(L.elem,(L.listsize+INCREMENT)*sizeof(ElemType))))
return 0;
L.elem = newbase;
L.listsize += INCREMENT;
}
q = L.elem+i-1;
for(p=L.elem+L.length-1; p>=q; --p){
*(p+1) = *p;
}
*q=e;
++L.length;
return 1;
}
void ListTraverse(SqList L,void(*vi)(ElemType ) )
{ // 初始条件:顺序线性表L已存在
// 操作结果:依次对L的每个数据元素调用函数vi()输出
ElemType *p;
int i;
p = L.elem;
for (i=1; i<=L.length; i++){
vi(*p++);
}
printf("\n");
}