データ構造は、アルゴリズムコードを実装 - 線形形態(その2)の順番を表します

直線状の順序を表します

线性表的顺序表示:指的是一组地址连续的存储单元依次存储线性表的数据元素。

そして、メソッド特徴的な配列テーブルを格納

在日常生活中,我们通常更喜欢使用连续的存储空间来存放各种物品。        

好,举个例子:假设宿舍内的床铺用编号1-6,宿舍内成员按年龄分出老大-老六,老大使用编号为1的床铺。依次类推。
这种安排方式可以使我们通过床铺编号快速找到同学。 

顺序表的存储方式也与此类似,下面讨论顺序表的特点: 
    1. 顺序存储结构的逻辑存储和存储结构一致
    2. 访问每个数据元素所花费的时间相同,利用数学公式
    3. 存取元素的方法称为随机存取法,也称为随机存储结构。

代表シーケンシャルストレージ構造

//----线性表的动态分配顺序存储结构----
#define LIST_INIT_SIZE 10 //线性表存储空间的初始分配量
#define LISTADD 5  //线性表存储空间的分配增量
typedef struct {
    ElemType * elem; //存储空间基址(数组的基地址)
    int length;//当前长度
    int listsize;//当前分配的存储总容量(sizeof(ElemType)为单位)
}Sqlist;

参照ヤン魏分は、22ページを教科書。

テーブルの動作の基本シーケンス(C言語)

基本的な操作は、操作が教科書を2.3,2.4,2.5,2.6アルゴリズムをも達成されるオペレーション19の教科書12を含みます。
C言語コード:

1、初期の文書を与えます:

     #include<stdio.h> 
     #include<stdlib.h> 
      #include<malloc.h> 
     // 函数结果状态代码 
     #define TRUE 1
     #define FALSE 0
     #define OK 1
     #define ERROR 0
     #define OVERFLOW -1
     typedef int Status; //Status是函数的类型,其值是函数结果状态代码,如OK等 
     typedef int Boolean; // Boolean是布尔类型

図2に示すように、基本的な動作は、コードに与えられます。

//----线性表的动态分配顺序存储结构----
#define LIST_INIT_SIZE 10 //线性表存储空间的初始分配量
#define LISTADD 5  //线性表存储空间的分配增量
typedef struct {
	ElemType * elem; //存储空间基址(数组的基地址)
	int length;//当前长度
	int listsize;//当前分配的存储总容量(sizeof(ElemType)为单位)
}Sqlist;

//----顺序表示的线性表的基本操作(12个包括了书中有关操作的算法)

// 1,  InitList(&L) 操作结果:构造一个空的顺序表L。书中算法2.3
Status InitList(Sqlist *L){
	(*L).elem=(ElemType *)malloc(LIST_INIT_SIZE*sizeof(ElemType));
	if(!(*L).elem) exit(OVERFLOW);//exit(-1) 存储空间失败
	(*L).length=0;  //初始化空表长度为0
	(*L).listsize=LIST_INIT_SIZE;  //初始化空表的存储总容量  
	
	return OK;  //返回状态码 1
}

// 2,  DestroyList(&L)  初始条件:顺序表L已存在。 操作结果:销毁顺序表L。
Status DestroyList(Sqlist *L){
	free((*L).elem);
	(*L).elem=NULL;  //指向空
	(*L).length=0;
	(*L).listsize=0;  

	return OK;
}

//3,   ClearList(&L)  初始条件:顺序表L已经存在。操作结果:将L重置为空表。
Status ClearList(Sqlist *L){
	(*L).length=0;  //清空当前长度
	return OK;
}

//4,   ListEmpty(L)    初始条件:顺序表L已存在。操作结果:若顺序表L为空返回TRUE,否则返回FLASE。
Status ListEmpty(Sqlist L){
	 if(L.length==0)
		return TRUE;
	 else
		return FALSE;
}

//5,    ListLength(L)    初始条件:顺序表L已存在。操作结果:返回L中数据元素的个数。
int ListLength(Sqlist L){
   return L.length;
 }

//6,    GetElem(L,i,&e)  初始条件:顺序表L已存在(1<=i<=ListLength(L))。操作结果:用e返回L中i个数据元素的值。
Status  GetElem(Sqlist L,int i,ElemType *e){
	if(i<1||i>L.length)
		exit(ERROR);
	*e=L.elem[i-1];//*e=*(L.elem+i-1)
	return OK;
}

//7,    LocateElem(L,e,compare())  初始条件:顺序表L已存在,compare()的数据元素判定函数。
//				操作结果:返回L中第1个与e的满足关系compare()的数据元素的位序。若这样的数据元素不存在,则返回0.
//       书中算法2.6(进行两个元素之间的比较) 时间复杂度为O(L.length)
int  LocateElem(Sqlist L,ElemType e,Status (*compare)(ElemType,ElemType)){//compare()形参是函数指针
/*
	
	*/
	int i =1;            //i的初值为第一个元素的位序
	ElemType *p=L.elem;  //p的初值为第一个元素的存储位置

	while(i<=L.length && !(*compare)((*p++),e)){
		++i;
	}
	if(i<=L.length) return i;
	else
	return 0;  
}

//8,    PriorElem(L,cur_e,&pre_e)  初始条件:顺序表L已存在。
//操作结果:若cur_e是L的数据元素,且不是第一个,则用pre_e返回它的前驱,否则操作失败,pre_e无定义。
Status PriorElem(Sqlist L,ElemType cur_e,ElemType *pre_e){
	ElemType *p =L.elem+1;  //指针变量p 指向第二个数据元素
	int i =2;

	while(i<=L.length && (*p)!=cur_e){  //注意不能写成,(*p++)!=cur_e,虽然它先判断*p,在+1,
	                                    //但是在条件不成立时会多后移一个位置
		p++;
		++i;
	}
	if(i>L.length){
		return ERROR;
	}else{
		*pre_e=(*--p);  //p先自减,pre_e就指向该数据元素的前驱结点
		return OK;
	}
}

//9,     NextElem(L,cur_e,&next_e)  初始条件:顺序表L已存在。
//操作结果:若cur_e是L的数据元素,且不是第一个,则用next_e返回它的前驱,否则操作失败,next_e无定义。
Status   NextElem(Sqlist L,ElemType cur_e,ElemType *next_e){
	ElemType *p =L.elem;
	int i=1;

	while(i
   
   
    
    =L.length){
		return ERROR;
	}else{
		*next_e =(*++p);  //next_e指向该数据元素的后继结点
		return OK;
	}
}

//10,   ListInsert(Sqlist *L,i,e)  初始条件:顺序表L已存在,1<=i<=ListLength(L)+1。
                                //操作结果:在L中第i个位置之前插入新的数据元素e,L的长度加1
/*
	书中算法2.4
	算法语言描述:
		  1,检查插入位置i的有效性,插入位置i的有效范围是1<=i<=ListLength(L)+1(注意:顺序表尾也能执行插入操作)。
		  2,在插入之前先检查顺序表是否已满(判断顺序表的length是否等于listsize),在顺序表满的时候不能进行插入运算,需要申请内存空间。
		  3,后移i位置以后的数据元素,为新数据元素让去第i个位置。
		  4,将新元素e插入第i个位置。
		  5,将当前的顺序表length加1.
*/
Status ListInsert(Sqlist *L,int i,ElemType e){
	ElemType *q,*p,*newbase=NULL;
	//第一步
	if(i<1 || i>(*L).length+1){  //i的值不合法
		return ERROR;
	}
	//第二步
	if( (*L).length >=(*L).listsize){  /* 当前存储空间已满,增加分配 */
		newbase =(ElemType *)realloc((*L).elem , ((*L).listsize+LISTADD)*sizeof(ElemType));
		if(!newbase) exit(OVERFLOW);
		(*L).elem =newbase;  //新基址
		(*L).listsize +=LISTADD;  //增加存储容量
	}
	//第三步
	q =&((*L).elem[i-1]);  //q为插入位置(或q =(*L).elem+i-1。)
	for(p =&((*L).elem[(*L).length-1]);p >=q;--p){ //或p =((*L).elem+(*L).length-1)
		*(p+1) =*p;
	}
		/*或
		int j;
		for(j =(*L).length;j >=i;--j){
			(*L).elem[j] =(*L).elem[j-1];  //或 *((*L).elem+j) =((*L).elem+j-1)
		}
		(*L).elem[j-1]=e;*/
	//第四步
	*q =e; //插入数据元素
	//第五步
	++(*L).length;
	return OK;
}

//11, ListDelete(Sqlist &L,int i,ElemType &e) 初始条件:顺序线性表L已存在,1<=i<=ListLength(L) 
//												操作结果:删除L的第i个数据元素,并用e返回其值,L的长度减1 
/*
	书中算法2.5
	算法语言描述:
		  1,检查插入位置i的有效性,删除位置i的有效范围是1<=i<=ListLength(L)。
		  2,在插入之前先检查顺序表是否为空(length!=0)。
		  3,前移i+1位置以后的数据元素(注意:从i+1的位置开始,要覆盖第i个位置)。
		  4,将当前的顺序表length减1.
*/
Status ListDelete(Sqlist *L,int i,ElemType *e){
	ElemType *q,*p=NULL;
	//第一步,第二步
	if(i <1 || i>(*L).length) return ERROR; //注意此处包含了第二步的检查
	//第三步
	p =&((*L).elem[i-1]); //p为被删除元素的位置
	*e =*p;
	q =(*L).elem+(*L).length-1;
	for(++p;p <=q;++p){  //
		*(p-1) =(*p);
	}
		/*或
		p =&((*L).elem[i-1]); //p为被删除元素的位置
		*e =*p;
		for(q =&((*L).elem[(*L).length-1]);p 
    
    
   
   

3,然后给出测试上述操作的C代码:
测试:

#include"ch2.h"
typedef int ElemType;
#include"sequence_list.c"

 Status equal(ElemType c1,ElemType c2) 
 {
   if(c1==c2)
     return TRUE;
   else
     return FALSE;
 }

 void visit(ElemType *c)
 {
   printf("%d ",*c);
 }

 void main()
 {
	 Sqlist L;
	 Status i;
	 int j,j1;
	 ElemType e,e1;
	 

	 //1, 测试第一个基本操作(构造顺序表L)
	 printf("1,测试第一个基本操作(构造顺序表L)\n");

	 i=InitList(&L);
	 printf("  初始化顺序表L后:L.elem=%d L.length=%d L.listsize=%d\n",L.elem,L.length,L.listsize);
	 printf("\n");

	 //2, 测试第十个基本操作(插入数据元素)
	 printf("2,测试第十个基本操作(插入数据元素)\n");

	 for(j=1;j<=5;j++){ 
		ListInsert(&L,1,j);
	 }
	 printf("  在L的表头插入1-5数据后:L.elem=%d(没有改变) L.length=%d L.listsize=%d\n",L.elem,L.length,L.listsize);
	 printf("  在L的表头依次插入1~5后:*L.elem=");
	 for(j=1;j<=5;j++){
		printf("%d ",L.elem[j-1]);  //或 *(L.elem+j-1)
	 }
	 printf("\n");
		//测试存储空间已满,增加分配
	     for(j=6;j<=11;j++){ 
			ListInsert(&L,j,j);
		 }
		 printf("  在L表头插入1-5数据后,然后在 L表尾 追加6-11:L.elem=%d(有可能不改变) L.length=%d(改变) L.listsize=%d(改变)\n",L.elem,L.length,L.listsize);
		 printf("  此时L表中数据元素:*L.elem=");
		 for(j=1;j<=11;j++){
			printf("%d ",L.elem[j-1]);  //或 *(L.elem+j-1)
		 }
		 printf("\n");
	 printf("\n");

	 //3, 测试第三、四个操作
	 printf("3, 测试第三、四个操作\n");

	 i=ListEmpty(L);
	 printf("  L是否空:i=%d(1:是 0:否)\n",i);
	 i=ClearList(&L);
  	 printf("  清空L后:L.elem=%u L.length=%d L.listsize=%d\n",L.elem,L.length,L.listsize);
	 i=ListEmpty(L);
	 printf("  L是否空:i=%d(1:是 0:否)\n",i);
	 
		//再次插入数据1-11
		for(j=1;j<=11;j++){//注意如果j=0时,将会在插入操作中触发第一步i值不合法。
				ListInsert(&L,j,j);
		}
	 printf("  有插入了11条数据\n");
	 ListTraverse(L,visit);
	 printf("\n");


	 //4,测试第五个操作
	 printf("4,测试第五个操作(L的数据元素个数)\n");

	 printf("  L表的长度=%d\n",L.length);
	 printf("\n");

	 //5, 测试第六个操作
	 printf("5, 测试第六个操作(取得第5个位置的数据元素)\n");
	
	 i=GetElem(L,5,&e);
	 printf("  第5个位置的数据元素:%d\n",e);
	 printf("\n");

	 //6,测试第七个操作
	 printf("6,测试第七个操作(LocateElem)\n");
	 
	 j=LocateElem(L,11,equal);
	 if(i>0)
		 printf("  在L表中存在于11相同的元素,位序是:%d\n",j);
	 else
		 printf("  在L表中不存在与11相同的数据元素\n");
	 printf("\n");

	 //7,测试第八、九个操作
	 printf("7,测试第八、九个操作\n");

	 for(j=1;j<=2;j++){ // 测试头两个数据
		 GetElem(L,j,&e1); // 把第j个数据赋给e1
		 i=PriorElem(L,e1,&e); // 求e1数据元素的前驱
		 if(i==ERROR)
		   printf("  元素%d无前驱\n",e1);
		 else
		   printf("  元素< %d的前驱为:%d >\n",e1,e);
	   }
	 for(j=ListLength(L)-1;j<=ListLength(L);j++){ // 最后两个数据
		 GetElem(L,j,&e1); // 把第j个数据赋给e1
		 i=NextElem(L,e1,&e); // 求e1的后继
		 if(i==ERROR)
		   printf("  元素%d无后继\n",e1);
		 else
		   printf("  元素< %d的后继为:%d >\n",e1,e);
	 }
	 printf("\n");

	 //8,测试第十个操作(删除操作)
	 printf("8,测试第十一个操作(删除操作)\n");

	 j1=ListLength(L);  //注意要把表L的长度先保存到一个变量中,因为在删除时,表的长度会自减一的。
	 for(j=j1+1;j>=j1;j--){
		 i=ListDelete(&L,j,&e); // 删除第j个数据
		 if(i==ERROR)
		   printf("  删除第%d个数据失败\n",j);
		 else
		   printf("  删除的元素值为:%d\n",e);
	 }
	 printf("\n");

	 //9,测试第十二个操作
	 printf("9,测试第十二个操作\n");

	 ListTraverse(L,visit);
	 printf("\n");

	 //10,测试第二个操作
	 printf("10,测试第二个操作\n");

	 DestroyList(&L);
     printf("  销毁L后:L.elem=%u L.length=%d L.listsize=%d\n",L.elem,L.length,L.listsize);
 }

4,最后在给出测试结果图:

这里写图片描述

5,总结

上述操作中需要重点掌握的算法是,初始化、插入、删除等等。比较重点的基本操作在代码中都有自然语言算法描述和注释。
如需详细了解,请见教材。
下面,我们简单了解一下比较重要操作的问题:
1,书中算法2.4是插入操作:
    假定插入线性表中任一元素的概率相同(都为1/n+1),则插入一个元素平均需要移动元素的个数是n/2。
个数取决于插入的位置,假定在顺序表任何插入位置的概率是相等的,则平均情况下,需要移动一半的数据元素。

2,书中算法2.5是删除操作:
    假定删除线性表中任一元素的概率相同(都为1/n),则删除一个元素平均需要移动元素的个数是(n-1)/2。
    删除的时间复杂度也是O(n)。

3,书中算法2.6是根据满足关系执行操作
    基本操作是“进行两个元素之间的比较”,需要遍历表中的数据元素与e比较。可见时间复杂度与表的长度有关,
    所以时间复杂度为O(L.length)。

较复杂的操作算法实现

上述的算法是一些基本的操作,教材中的2.1,2.2,2.7算法是较复杂的算法(多种基本操作的组合使用)。
合并后的表仍然是非递减有序排列。

1. 算法2.1

C语言算法实现:

#include"ch2.h"
typedef int ElemType;
#include"sequence_list.c"

Status equal(ElemType a,ElemType b){
	if(a == b){
		return TRUE;
	}else{
		return FALSE;
	}
}
/*
	自然语言描述算法:
		1,求的顺序表的长度
		2,依次访问Lb中的数据元素
			若取得数据元素与La中的不相同,则插入之。
*/
void Union(Sqlist *La,Sqlist Lb){
	ElemType e;
	int la_len,lb_len;
	int i;

	//第一步
	la_len =ListLength(*La);
	lb_len =ListLength(Lb);
	//第二步
	for(i=1;i<=lb_len;++i){
		GetElem(Lb,i,&e);//依次取得Lb的数据元素
		if(!LocateElem( *La,e,equal)){//不相同则插入之
			ListInsert( La,++la_len,e);
		}
	}
}
void print(ElemType *c)
 {
   printf("%d ",*c);
 }
void main(){
	Sqlist La , Lb;
	Status i;
	int j;
	//初始化顺序表La和Lb
	printf("1,初始化顺序表La和Lb\n");
	i =InitList(&La); //构造空的顺序表
	if(i==1){ //成功
		for(j=1;j <=3;j++){ //注意如果j=0时,将会在插入操作中触发第一步i值不合法。
			ListInsert(&La,j,j); //插入操作
		}
	}
	printf("初始化成功表La的数据元素:");
	ListTraverse(La,print);

	InitList(&Lb);
	for(j=1;j<=11;j++){
		ListInsert(&Lb,j,j);
	}
	printf("初始化成功表La的数据元素:");
	ListTraverse(Lb,print);
	printf("\n");

	//执行Union方法合并La和Lb表,并用La存储合并后数据元素
	printf("2,执行Union方法合并La和Lb表,并用La存储合并后数据元素\n");
	Union(&La,Lb);
	printf("合并后La的数据:");
	ListTraverse(La,print);
	printf("\n");

}

结果图:
这里写图片描述

2. 算法2.2和算法2.7 和 书中26页对2.7的改进算法 都在下述代码中

需要一个计算时间的文件,用于测试不同算法的时间:

```
#include <windows.h>

void kaishi(DWORD *start){
    *start = GetTickCount();

}
void end(DWORD *stop){
    *stop = GetTickCount();
}
void time_printf(DWORD start,DWORD stop){
    printf("time: %lld ms\n", stop - start);
}
```

C语言算法实现:

#include"ch2.h"
typedef int ElemType;
#include"sequence_list.c"
#include"time_.c"
/*
	算法分析:
	1,初始化表Lc,并定义3个标志变量i,j,k
	2,利用while循环,取得表La和Lb相同位序的数据元素,比较之在插入表Lc中,使之产生前后位序。
	3,判断表La和Lb中是否还有剩余的数据元素,在插入表Lc的尾部。 
*/

/*
	算法2.2
*/
void MergeList(Sqlist La,Sqlist Lb,Sqlist *Lc){
	int i=1,j=1,k =0;//记忆 表La、Lb、Lc的当前长度
	int la_len,lb_len;
	ElemType ai,bj;
	//第一步
	InitList(Lc);
	//第二步
	la_len =ListLength(La); 
	lb_len =ListLength(Lb);
	while((i<=la_len) && (j<=lb_len)){
		//取得数据元素
		GetElem(La,i,&ai);
		GetElem(Lb,j,&bj);
		//比较
		if(ai<=bj){
			ListInsert(Lc,++k,ai); //插入之
			++i;
		}else{
			ListInsert(Lc,++k,bj);
			++j;
		}
	}
	//第三步
	while(i<=la_len){
		GetElem(La,i,&ai);
		ListInsert(Lc,++k,ai);
		i++;
	}
	while(j<=lb_len){
		GetElem(Lb,j,&bj);
		ListInsert(Lc,++k,bj);
		j++;
	}
}

/*
	算法2.7利用指针操作数据元素赋值
*/
void MergeList_2(Sqlist La,Sqlist Lb,Sqlist *Lc){
	ElemType *a,*b,  *a_last,*b_last,  *c=NULL;
	//初始化表Lc
	InitList(Lc);

	//利用指针指向La和Lb中elem数据元素的头和尾
	a =La.elem;
	b =Lb.elem;
	a_last =La.elem+ListLength(La)-1;
	b_last =Lb.elem+ListLength(Lb)-1;

	//重新定义初始化表Lc
	(*Lc).length=(*Lc).listsize=ListLength(La)+ListLength(Lb); 
	(*Lc).elem=(ElemType *)malloc((*Lc).listsize*sizeof(ElemType));
	if(!(*Lc).elem) exit(OVERFLOW);
	c =(*Lc).elem;

	//第二步
	while(a<=a_last && b
    
    
     
     b)
		i=-1;
	return i;
}
Status equal(ElemType a,ElemType b){
	if(a == b){
		return TRUE;
	}else{
		return FALSE;
	}
}

void print(ElemType *c)
 {
   printf("%d ",*c);
 } 
/*
	对算法2.7的改进
*/
void MergeList_3(Sqlist La,Sqlist Lb,Sqlist *Lc){
	ElemType *a,*b,  *a_last,*b_last,  *c=NULL;
	//初始化表Lc
	InitList(Lc);
	
	//利用指针指向La和Lb中elem数据元素的头和尾
	a =La.elem;
	b =Lb.elem;
	a_last =La.elem+ListLength(La)-1;
	b_last =Lb.elem+ListLength(Lb)-1;

	//重新定义初始化表Lc
	(*Lc).listsize=ListLength(La)+ListLength(Lb); //注意:不用设置length,假设有重复,则在调用ListTraverse会输出野指针
	(*Lc).elem=(ElemType *)malloc((*Lc).listsize*sizeof(ElemType));
	if(!(*Lc).elem) exit(OVERFLOW);
	c =(*Lc).elem;

	//第二步
	while(a<=a_last && b
     
     
    
    

首先看算法结果测试图:
这里写图片描述

然后看不同算法的时间比较图(含2.1算法union())

   对于时间的比较 在最后被注释的代码,可以自行修改代码进行不同算法的时间比较 。下面我们进行两次比较:算法2.1 
、算法2.2的比较 和算法2.2与2.7的比较。

(1)算法2.1、算法2.2的比较结果图:
这里写图片描述

(2)算法2.2 、算法2.7的比较结果图
这里写图片描述

3. 总结

对于算法的评论,可以依据时间复杂度,当然这不是评价的唯一标准。

    1,首先先看算法2.1:
    在此算法中的一些基本操作如GetElem、ListInsert(在表尾插入)的执行时间复杂度和表长无关。LocateElem
的执行时间和表长成正比,所以算法2.1的时间复杂度为O(ListLength(LA) * ListLength(LB))。
    2,在看算法2.2:
    在此算法的基本操作和算法2.1相同,但是算法时间复杂度却不同,算法2.2的时间复杂度为O(ListLength(LA) + ListLength(LB))。
    从上面的时间比较图(1),就可以看出在有100000条数据时,算法2.2相对算法2.1节省了1800倍。
    3,最后看算法2.7:
    在此算法中的基本操作是“元素赋值”,没有上述的一些基本操作。它的时间复杂度也是O(ListLength(LA) + ListLength(LB))。
    但是从上面的时间比较图(2),可以看出算法2.7相对算法2.2节省了230倍的时间。所以当两个算法的时间复杂度相同,但是由于不同的基本操作,
可能会有很多的解决方案,我们要选择最优的解决方案,否则在对于大数据的问题将很难解决。
    4,算法2.7的改进是为了达到2.1的效果,具体的分析可见教材。注意的是,算法2.1只是链接两个表并去掉重复元素,想将Lb中元素插入La,
算法2.7的改进需要先对表进行数据元素的排序。由此可见,若以线性表表示集合并进行集合的各种运算,应先对表中元素进行排序。

最后讨论一下顺序存储的缺点

人无完人,金无赤足。在数据结构中也是如此,顺序表既然有特点、有优点,顺序表的缺点:
  1. 对顺序表做插入、删除时,需要大量的移动数据元素。
  2. 线性表需要预先分配空间,必须按最大空间分配,存储空间得不到充分利用,造成内存浪费。
发布了17 篇原创文章 · 获赞 21 · 访问量 4万+

おすすめ

転載: blog.csdn.net/hou1620089770/article/details/46128409