数据结构算法代码实现——静态单链表(四)

线性表的静态单链表存储结构

//-------线性表的静态单链表存储结构-------
typedef struct{
    ElemType data;
    int cur;
}component,SLinkList[MAXSIZE];

什么是静态链表?

    对于线性链表,也可用一维数组来进行描述。这种描述方法便于在没有指针类型的高级程序设计语言中使用链表结构。
    在如上描述的链表中,数组中结构体一个成员存储数据,另一个成员(游标cur)存放下一个结点的位置。 
    为了和指针型描述的线性表相区别,称这种数组为静态链表。注意:链表的输出不是按数组的位序输出的, 
    而是由数组中的一个指定位置开始根据游标cur输出。

    生成静态链表的方法有两种:一种是在数组中根据需要可以生成多个独立的静态链表,每个链表的头指针在生成的时候在指定。 
    另一种是一个数组只能生成一个静态链表,这种情况可以固定链表的头指针,也就是说在初始化链表的同时就指定了链表的头指针位置 
    (如数组最后一个下标为头指针), 在形参上比上一种方法简单,因为不用传入头指针的位置了, 
    但是缺点是在程序需要使用多个静态链表则要有多个数组,每个数组之间不能相互调剂,空间浪费会较大。

注意:
    在静态链表中,为了辨别数组中那些分量未被使用,解决的方法是:
    将所有空闲结点链接形成一个备用链表,数组下标为 0 的单元为备用链表的头结点(这时,静态链表的头结点就不能再是数组下标为 0 的单元了, 
    需要另外定义)。在静态数组实际有 2 种链表,一个链表上链接的是线性表的结点,另一个链表(备用链表)上链接的是所有没被使用的结点。
当线性表需要新结点时,把备用链表中的首元结点(由L[0].cur 指示)从备用链表中删除,作为新结点,插入线性表。 
当删除线性表中的结点时,被删除的结点插入备用链表中,成为备用链表的首元结点。之所以从备用链表删除结点或向备用链表插入结点都在表头进行,
是因为这样效率最高。 所以操作备用链时需要用户自定义的Malloc和Free函数。

静态链表的基本操作

我们只以第一种方法为例:

  1. 头文件请参考顺序表文章

  2. 自定义的Malloc和Free函数,从备用链申请空间、释放空间备用链回收结点。

//   申请结点和删除结点  都是在备用链的表头后操作
//--------算法2.15  申请结点
int Malloc(SLinkList L){
     int i;
     i =L[0].cur;
     if(i){
        L[0].cur =L[i].cur;//把备用链的第一个结点做申请的结点,在把第二个结点和备用链连接起来。
     }
     return i;
 }

 //-------算法2.15 删除结点(回收到备用备用链中)
 void Free(SLinkList L,int n){
    L[n].cur =L[0].cur; //把备链表的第一个结点位序 接到删除的结点后。 
    L[0].cur =n; //在把删除的结点位序 接到备用链的头结点后
 }

3,基本操作

//-------算法2.14     一个数组可产生多个静态链表
 void InitSpace(SLinkList space) 
 { /* 将一维数组space中各分量链成一个备用链表,space[0].cur为头指针。“0”表示空指针 
		此时不需要设置静态链表的头指针,在初始化的时候在产生,这样也就可以产生多个静态链表了。
	 */
   int i;
   for(i=0;i
   
   
    
    next;
	 }
	 //5
	 L[w].cur =b; //备用链表的第一个结点接到链表的尾部
	return OK;
	 

/*
   int j,k,i;
   i=L[n].cur; // 此链表第一个结点的位置 
   L[n].cur=0; // 链表空 
   k=L[0].cur;  备用链表第一个结点的位置 
   L[0].cur=i;  把链表的结点连到备用链表的表头 
   while(i)  没到链表尾 
   {
     j=i;
     i=L[i].cur;  指向下一个元素 
   
   L[j].cur=k; 备用链表的第一个结点接到链表的尾部 
   return OK;
	   */
 }

 //3,判断链表是否为空
 /* 判断数组L中表头位序为n的链表是否空,若是空表返回TRUE;否则返回FALSE */
 Status ListEmpty(SLinkList L,int n) { 
   if(L[n].cur==0) 
     return TRUE;
   else
     return FALSE;
 }

 //4,链表的长度
 /* 返回L中表头位序为n的链表的数据元素个数 */
 int ListLength(SLinkList L,int n){ 
   int j=0,i;
   i=L[n].cur; // i指向第一个结点
   while(i) //没到静态链表尾 
   {
     i=L[i].cur; // 指向下一个元素
     j++;
   }
   return j;
 }

 //5,获取指定链表的第i个位置数据元素值
 /* 用e返回数组L中表头位序为n的链表的第i个元素的值 */
 Status GetElem(SLinkList L,int n, int i,ElemType *e){ 
   int j,k=n; // k指向表头结点位序
   if(i<1||i>ListLength(L,n)) // i值不合理
     return ERROR;
   for(j=1;j<=i;j++) //查找第i个结点的位序 
     k=L[k].cur; 
   *e =L[k].data;
   return OK;
 }

 //6,查找结点在数组L中的位序
 /* 在L中表头位序为n的静态单链表中查找 值为e的元素。
    若找到,则返回它在数组L中的位序,否则返回0 */
 int LocateElem(SLinkList L,int n,ElemType e){ 
   int i =L[n].cur; // i指向表中第一个结点
   while(i && L[i].data!=e) // 在表中顺依次查找(注意:e不能是字符串)
     i =L[i].cur; //移动指针位序
   return i;
 }

 //7,查找前驱
 /* 初始条件:在L中表头位序为n的静态单链表已存在 
    操作结果:若cur_e是此单链表的数据元素,且不是第一个, 
             则用pre_e返回它的前驱,否则操作失败,pre_e无定义 */
 Status PriorElem(SLinkList L,int n,ElemType cur_e,ElemType *pre_e){ 
   int j,i=L[n].cur; // i指向链表第一个结点,j记住前驱指针位序
   do// 向后移动结点 
   { 
     j =i;  //记住前驱
     i =L[i].cur;
   }while(i && L[i].data!=cur_e);
   if(i) // 找到该元素  
   {
     *pre_e =L[j].data; //返回前驱数据
     return OK;
   }
   return ERROR;
 }

 //8,查找后继
 /* 初始条件:在L中表头位序为n的静态单链表已存在 
    操作结果:若cur_e是此单链表的数据元素,且不是最后一个, 
    则用next_e返回它的后继,否则操作失败,next_e无定义 */
 Status NextElem(SLinkList L,int n,ElemType cur_e,ElemType *next_e){ 
   int i;  //使用静态链表的特点
   i =LocateElem(L,n,cur_e); // 在链表中查找值为cur_e的元素在数组L中的位序
   if(i) // 在静态单链表中存在元素cur_e 
   {
     i =L[i].cur; // cur_e的后继的位序
     if(i) // cur_e有后继 
     {
       *next_e=L[i].data;
       return OK; // cur_e元素有后继 
     }
   }
   return ERROR; // L不存在cur_e元素,cur_e元素无后继 
 }

 //9,插入操作
 /* 在数组L中表头位序为n的链表的第i个元素之前插入新的数据元素e */
 Status ListInsert(SLinkList L,int n,int i,ElemType e){
   int l,j,k =n; // k指向链表头 
   if(i<1||i>ListLength(L,n)+1) //与单链表不同的是,需要要先判断i的合法性。毕竟还是数组,要是先申请了空间
					//i值不合法则浪费了空间,顺序表的插入操作也是先判断i值哦。
     return ERROR;
   j =Malloc(L); // 申请新结点
   //需要关心一下是否还有空间,
   if(j) // 申请成功
   {
     L[j].data=e; // 赋值给新结点
     for(l=1;l
    
    
     
     ListLength(L,n))
     return ERROR;
   for(j=1;j
     
     
    
    
   
   

4,测试上述操作代码

#include"ch2.h"
#define MAXSIZE 100
typedef int ElemType;
#include"Static_linked.c"

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

 void visit(ElemType c) /* 与顺序表不同 */
 {
   printf("%d ",c);
 }

 void main()
 {
	 SLinkList L;
	 Status i;
	 ElemType e,e1;
	 int j,j1,La,Lb;
	 
	 InitSpace(L); // 建立备用链表 ,书中算法2.14
		//利用数组L 生成两个静态链表La和Lb

	 //1, 测试第一个基本操作(构造单链表L)
	 printf("1,测试第一个基本操作(构造静态链表)\n");
	 La=InitList(L); // 初始化链表La ,La是头指针位序
     	Lb=InitList(L); // 初始化链表Lb 
	 printf("  成功构造两个静态链表La和Lb\n");
	 printf("\n");

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

	 for(j=1;j<=5;j++){ //在La和Lb表尾后插入数据元素
		ListInsert(L,La,j,j); //与顺序表和单链表参数都不同
		ListInsert(L,Lb,j,j*2);
	 }
	 printf("  在La的表尾依次插入1~5后:La=");
	 ListTraverse(L,La,visit);
	 printf("  在Lb的表尾依次插入1~10偶数后:Lb=");
	 ListTraverse(L,Lb,visit);
	 printf("\n");

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

	 i=ListEmpty(L,La);
	 printf("  La是否空:i=%d(1:是 0:否)\n",i);
	 i=ClearList(L,La);
  	 printf("  清空La后:La="); ListTraverse(L,La,visit);
	 i=ListEmpty(L,La);
	 printf("  La是否空:i=%d(1:是 0:否)\n",i);
	 printf("\n");


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

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

	 //5, 测试第五个操作
	 printf("5, 测试第五个操作(取得第La表中第3个位置的数据元素)\n");
	
	 i=GetElem(L,Lb,3,&e);
     if(i)
       printf("Lb表的第%d个元素的值为:%d\n",3,e);
     else
       printf("Lb表不存在第%d个元素!\n",3,e);
	 printf("\n");

	 //6,测试第六个操作
	 printf("6,测试第六个操作(LocateElem)\n");
	 
	 j=LocateElem(L,Lb,2);
	 if(j>0)
		 printf("  在Lb表中存在2相同的元素,在数组L的位序是:%d【备用链表头0、La表头1、Lb表头2、La表头的第一个结点3(现在被回收到了备用链表了)】\n",j);
	 else
		 printf("  在L表中不存在与2相同的数据元素\n");
	 printf("\n");

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

	 for(j=1;j<=2;j++){ // 测试头两个数据
		 GetElem(L,Lb,j,&e1); // 把Lb表第j个数据赋给e1
		 i=PriorElem(L,Lb,e1,&e); // 求e1数据元素的前驱
		 if(i==ERROR)
		   printf("  元素%d无前驱\n",e1);
		 else
		   printf("  元素< %d的前驱为:%d >\n",e1,e);
	   }
	 for(j=ListLength(L,Lb)-1;j<=ListLength(L,Lb);j++){ // 最后两个数据
		 GetElem(L,Lb,j,&e1); // 把第j个数据赋给e1
		 i=NextElem(L,Lb,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,Lb);  //注意要把表L的长度先保存到一个变量中,因为在删除时,表的长度会自减一的。
	 for(j=j1+1;j>=j1;j--){
		 i=ListDelete(L,Lb,j,&e); // 删除第j个数据
		 if(i==ERROR)
		   printf("  删除第%d个数据元素失败\n",j);
		 else
		   printf("  删除第%d个数据元素值为:%d\n",j,e);
	 }
	 printf("\n");

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

	 ListTraverse(L,Lb,visit);

 }

5,测试结果图:

这里写图片描述

较复杂的操作算法实现

教材算法2.17求集合运算(A-B)∪(B-A)。

假设由终端输入集合元素,先建立表示集合A的静态链表S,而后在输入集合B的元素的同时查找S表,若存在和B相同的元素,则从S表中删除之, 
否则将此元素插入S表。

代码实现:

#include"ch2.h"
#define MAXSIZE 20
typedef char ElemType;
#include"Static_linked.c"
 void visit(ElemType elem)
 {
   printf("%c ",elem);
 }
/*
	算法自然语言描述:
	1,初始化数组space,并把所有的空闲结点生成一个备用链(数组位序0为备用链表的头指针)。
	2,申请结点空间,S指向此结点,并作为一个静态链表A的头指针,移动指针r也指向S
	3,输入A表的长度m,for循环m次创建表A的数据:先申请结点空间,在输入数据元素,在把此结点接到表A尾,移动r指向表尾。
	4,把表A尾的cur设置为0
	5,输入B表的长度n,for循环n次输入表B的数据b:
			 首先要遍历表A查找是否有该数据元素,并用p指向上一位置结点,k指向待删除结点。
			 如果不存在(k==表尾.cur):则申请结点插入表尾,注意r的位置不变。
			 如果存在:则修改指针域,删除k指向的结点,注意要是删除的是表尾即r,则要移动r的位置。
*/
void difference(SLinkList space,int *S){
	/* 依次输入集合A和B的元素,在一维数组space中建立表示集合(A-B)∪(B-A) */
   /* 的静态链表,S为其头指针。假设备用空间足够大,space[0].cur为备用空间的头指针 */
	int r,k,p;
	int m,n,i,j;
	ElemType b;

	//产生备用链表
	InitSpace(space); //初始化备用空间

	*S =Malloc(space); //生成S的头结点
	r =*S; //指向S头指针

	printf("请输入集合A元素个数m:");
    scanf("%d%*c",&m); // %*c吃掉回车符 
    printf("请输入集合A的元素(共%d个):",m);
	for(j=0;j
    
    

测试结果图:
这里写图片描述

注意:用scanf输入字符时要注意不要使用空格,还需要用%*c来吃掉输入完成时的回车键(百度学习)。

发布了17 篇原创文章 · 获赞 21 · 访问量 4万+

猜你喜欢

转载自blog.csdn.net/hou1620089770/article/details/46376095