来源书籍:
数据结构(C语言版)(第2版·微课版) 秦锋 汤亚玲主编 清华大学出版社 2021年12月
书籍链接:http://www.tup.tsinghua.edu.cn/booksCenter/book_09444301.html#
1.线性表
1.1 顺序表上基本运算的实现
1.1.1 顺序表的初始化
#include <stdio.h>
#include <malloc.h>
#define MAXSIZE 100
这段代码定义了预处理指令#include和宏定义:
stdio.h是C语言标准库头文件,提供了输入和输出函数的定义,如printf和scanf等。
malloc.h是动态内存分配函数的头文件,提供了malloc和free等函数的定义。
MAXSIZE是定义的宏,表示顺序表的最大长度为100。
typedef int DataType;
typedef struct
{
DataType data[MAXSIZE];
int length;
} SeqList,*PSeqList; /*顺序表类型定义*/
在这段代码中,typedef 关键字用于创建两个新类型:
SeqList:它是一个结构体类型,包含了一个 DataType 类型的数组 data 和一个整数类型的 length,用于表示顺序表中元素的个数。
PSeqList:它是一个指针类型,指向 SeqList 结构体类型,它可以被用于声明指向顺序表的指针变量。
SeqList 是一个结构体类型名,而 *PSeqList 是一个指向 SeqList 结构体的指针类型名。因此,当我们声明一个 SeqList 类型的变量时,我们可以写成 SeqList mylist;,而当我们声明一个指向 SeqList 类型的指针变量时,我们可以写成 PSeqList plist;。在这个例子中,SeqList 和 *PSeqList 实际上是等价的,因为它们都是用于表示顺序表的数据类型。
//顺序表的初始化
PSeqList Init_SeqList()
{
/*创建一顺序表,入口参数无,返回一个指向顺序表的指针,指针值为零表示分配空间失败*/
PSeqList PL;
PL =( PSeqList )malloc(sizeof(SeqList));
if (PL) /*若PL=0表示分配失败*/
PL -> length =0;
return (PL);
}
这是一个C语言函数Init_SeqList(),用于创建一个新的顺序表(也称为数组或列表),并返回一个指向它的指针。
第一行声明了一个名为Init_SeqList()的函数,它返回一个指向SeqList结构的指针。
第三行使用malloc()函数分配了一个新的SeqList结构的内存,并返回一个指向分配内存块的指针。sizeof(SeqList)参数指定了要分配的内存块的大小,即SeqList结构的大小。(PSeqList)强制类型转换用于将malloc()的返回值转换为PL指针的类型,即指向SeqList结构的指针类型。如果malloc()函数无法分配内存块,则返回一个NULL指针。
第四行检查指针PL是否不等于NULL,这意味着malloc()成功为SeqList结构分配了内存。如果PL等于NULL,则表示内存分配失败。
第五行将SeqList结构中的length字段设置为0,表示新创建的顺序表中没有任何元素。
1.1.2 求顺序表的长度
//求顺序表的长度
int Length_SeqList (PSeqList PL)
{
/*求顺序表的长度,入口参数:为顺序表,返回表长*/
return (PL->length);
}
这段代码是一个 C 语言函数 Length_SeqList(),它用于计算顺序表的长度并返回该长度。函数的参数是一个指向顺序表的指针 PL,函数返回一个整数值表示该顺序表的长度。
函数返回顺序表的长度,该长度存储在 SeqList 结构体中的 length 成员变量中。**PL->length 表示指针 PL 所指向的 SeqList 结构体中的 length 成员变量的值。**函数返回该值作为函数的返回值。
1.1.3 顺序表的检索操作
//顺序表的检索操作
int Location_SeqList (PSeqList PL, DataType x)
{
/*顺序表检索,入口参数:为顺序表,检索元素,返回元素位置,0示查找失败*/
int i=0;
while (i< PL->length && PL->data[i]!= x)
i++;
if (i>=PL->length) return 0;
else return (i+1);
}
这是一个 C 语言函数 Location_SeqList(),用于在给定的顺序表中查找指定的元素 x,并返回该元素在顺序表中的位置。
第一行声明了一个名为 Location_SeqList() 的函数,它有两个参数:一个指向 SeqList 结构的指针 PL,表示待查找的顺序表;一个数据类型为 DataType 的参数 x,表示待查找的元素。该函数返回一个整数值,表示指定元素在顺序表中的位置,如果未找到则返回0。
第二行定义了一个整数变量 i,并初始化为0,它将被用于迭代查找顺序表中的元素。
第三行使用一个 while 循环来迭代查找顺序表中的元素,直到找到指定元素 x 或遍历完整个顺序表。该循环有两个条件:顺序表中仍然有元素需要查找(即 i < PL->length),并且当前元素不等于指定元素 x(即 PL->data[i] != x)。如果当前元素等于指定元素,循环将终止。
第五行检查循环是否已经查找完整个顺序表。如果是,它返回0,表示未找到指定元素。否则,它返回当前位置 i+1,表示指定元素在顺序表中的位置。这里将位置加1的原因是,数组下标从0开始计数,但是通常人们更习惯从1开始计数。
问:平均比较次数为?
在顺序表中查找指定元素的平均比较次数可以用如下公式计算:
平均比较次数 = (1 + 2 + … + n) / n = (n+1) / 2
其中,n 表示顺序表中元素的个数。
因为在最坏情况下,需要比较整个顺序表中的元素才能确定指定元素是否存在,所以在平均情况下,顺序表的查找效率是线性的,时间复杂度为 O(n)。但是,在实际情况下,顺序表中的元素通常是无序的,这种情况下可以考虑使用更高效的数据结构,例如二叉搜索树或哈希表等。
1.1.4 顺序表的插入运算
//顺序表的插入运算
int Insert_SeqList(PSeqList PL,int i,DataType x)
{
/*在顺序表的第i个元素之前插入x,入口参数:顺序表指针,插入位置,插入元素,
返回标志,1表示成功,0表示插入位置不合法,-1表示溢出,-2表示表不存在*/
int j;
if (!PL)
{
printf("表不存在\n");
return(-2); /*表不存在,不能插入*/
}
if (PL-> length >= MAXSIZE)
{
printf("表溢出\n");
return(-1); /*表空间已满,不能插入*/
}
if (i<1||i>PL->length+1)/*检查插入位置的合法性*/
{
printf("插入位置不合法\n");
return(0);
}
for(j= PL -> length -1; j>=i-1; j--)
PL ->data[j+1]= PL ->data[j]; /* 移动元素 */
PL->data[i-1]=x; /*新元素插入*/
PL-> length++; /*表长加 1*/
return (1); /*插入成功,返回*/
}
需要注意的是,这段代码并没有考虑到插入位置为表尾的情况,因为在这种情况下可以直接将新元素添加到表尾,不需要移动元素。此外,在移动元素时,应该从表尾开始向插入位置移动,因为从表头开始移动元素会覆盖掉一些原本存在的元素。
问:!PL ?
!PL是一个逻辑非运算符,表示对PL进行逻辑非运算,即如果PL为0(空指针),则返回真,否则返回假。在该函数中,如果顺序表PL不存在,即PL指向空,!PL的结果为真,会执行if语句块中的代码,输出提示信息"表不存在",并返回-2表示插入失败。如果顺序表PL存在,即PL指向一个非空的顺序表结构体,!PL的结果为假,不会执行if语句块中的代码,程序继续往下执行。
1.1.5 顺序表的删除运算
//顺序表的删除运算
int Delete_SeqList(PSeqList PL,int i)
{
/*删除顺序表第i个元素,入口参数:顺序表指针,删除元素位置,
返回标志1表示成功,0表示删除位置不合法,-1表示表不存在*/
int j;
if (!PL)
{
printf("表不存在\n");
return(-1); /*表不存在,不能删除元素*/
}
if(i<1 || i> PL -> length) /*检查删除位置的合法性*/
{
printf ("删除位置不合法\n");
return(0);
}
for(j=i;j< PL -> length;j++)
PL ->data[j-1]= PL ->data[j]; /*向上移动*/
PL -> length --;
return (1); /*删除成功*/
}
该函数的作用是删除顺序表中第i个元素,其入口参数为顺序表指针PL和删除元素位置i。如果PL为0,即指向空的指针,则说明表不存在,返回-1表示删除失败;如果i的值小于1或大于顺序表的长度,即删除位置不合法,返回0表示删除失败。如果以上检查都通过,就需要将第i个元素删除并向上移动其他元素,最后将表长减1,表示删除成功,返回1。
问:上述删除算法中被删除的数据元素没有保存下来,如果要保存下来,该如何做?
如果要保存被删除的数据元素,可以在函数定义中增加一个参数,作为保存被删除元素的变量指针。
例如:
int Delete_SeqList(PSeqList PL, int i, DataType* deletedItem)
函数执行后,被删除的元素将会保存在 deletedItem 所指向的内存中。在函数中,需要修改删除元素的逻辑,将被删除的元素保存在 deletedItem 中。
具体实现可参考以下代码:
int Delete_SeqList(PSeqList PL, int i, DataType* deletedItem)
{
/* 删除顺序表第i个元素,入口参数:顺序表指针,删除元素位置,
返回标志1表示成功,0表示删除位置不合法,-1表示表不存在 */
if (!PL) {
printf("表不存在\n");
return(-1); /*表不存在,不能删除元素*/
}
if (i < 1 || i > PL->length) {
/*检查删除位置的合法性*/
printf("删除位置不合法\n");
return(0);
}
*deletedItem = PL->data[i-1]; /* 保存被删除元素 */
for (int j = i; j < PL->length; j++) {
PL->data[j-1] = PL->data[j]; /*向上移动*/
}
PL->length--;
return (1); /*删除成功*/
}
调用该函数时,需要提供一个指向保存被删除元素的变量的指针。例如:
DataType deletedItem;
int result = Delete_SeqList(seqList, 2, &deletedItem);
if (result == 1) {
printf("删除成功,被删除的元素是:%d\n", deletedItem);
}
这行代码调用了 Delete_SeqList 函数,将 seqList 中的第 2 个元素删除,并将被删除的元素保存在 deletedItem 中。函数返回的结果为整数类型,1 表示删除成功,0 表示删除位置不合法,-1 表示表不存在。
如果 result 的值为 1,则说明删除成功,可以通过 printf 函数输出被删除的元素值。如果 result 的值为 0 或 -1,则说明删除失败,此时不应该输出被删除的元素值。
1.1.6 顺序表的销毁运算
//销毁顺序表
int Destroy_SeqList (PSeqList *PL)
{
/*顺序表检索,入口参数:为顺序表,检索元素,返回元素位置,0示查找失败*/
if(*PL)
free(*PL);
*PL=NULL;
}
这个函数的功能是销毁一个顺序表,参数是一个指向顺序表指针的指针。顺序表销毁后,顺序表指针需要被置为 NULL。
具体实现是:如果顺序表指针不为 NULL,就调用 free() 函数释放其指向的内存空间,然后将顺序表指针置为 NULL。
函数的返回值类型应该是 void,因为销毁顺序表后没有任何返回值。
问:free(PL)?
这行代码是用来释放顺序表占用的内存空间,从而达到销毁顺序表的目的。在使用malloc等函数动态分配内存时,一定要记得在使用完毕后调用free函数释放所占用的内存空间,否则会造成内存泄漏。此处的PL表示指向顺序表结构体的指针,free(*PL)即为释放该指针指向的内存空间。
问:*PL=NULL?
*PL=NULL; 是将传入的指向顺序表的指针设置为 NULL,这是为了避免野指针的出现,以确保程序的安全性。在程序中,已经释放了指向顺序表的内存空间,如果不将指针设置为 NULL,那么该指针仍指向一个已释放的内存空间,这时如果再去使用这个指针,就会出现未定义的行为,甚至会导致程序崩溃。因此,将指针设置为 NULL 是一种良好的编程习惯。
1.1.7 顺序表的遍历
//顺序表的遍历
void DispList(PSeqList L) /*输出线性表*/
{
int i;
printf(" NO DATA \n");
for (i=0;i<L->length;i++)
{
printf(" %d %d",i+1,L->data[i]);
printf("\n");
}
printf("目前表中元素总数:%d\n",L->length);
printf("\n");
}
这段代码实现了顺序表的遍历操作,即按照从前往后的顺序依次输出表中的元素以及对应的下标。
在函数中,首先使用一个for循环遍历表中的每个元素,循环变量i从0开始,每次循环自增1,直到循环结束,也就是遍历完整个表。循环体内,通过printf函数将当前元素的下标i+1以及对应的数据值**L->data[i]**输出到控制台上,中间用制表符"\t"分隔。另外,为了方便查看,输出结果中还加入了表头信息和表长信息。
最终,该函数将整个表遍历一遍,输出了表中所有元素以及表长信息。