数据结构(C语言版)-第2章-线性表(2)

来源书籍:
数据结构(C语言版)(第2版·微课版) 秦锋 汤亚玲主编 清华大学出版社 2021年12月
书籍链接:
(http://www.tup.tsinghua.edu.cn/booksCenter/book_09444301.html#)

1.线性表

1.1 单链表基本运算的实现

1.1.1 创建空单链表

#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>
#include<string.h>
#define MAXSIZE 100 

这段代码包含了标准输入输出库的头文件 stdio.h,标准库的头文件 stdlib.h,内存分配相关库的头文件 malloc.h,以及字符串处理库的头文件 string.h。最后一行是一个宏定义,将MAXSIZE定义为100。在后续程序中可以使用MAXSIZE代替100。这样做可以方便程序的修改和维护,提高代码的可读性和可维护性。

typedef int DataType;
typedef  struct  node{
    
      
	DataType data;  /*每个元素数据信息*/
    struct node *next;  /*存放后继元素的地址*/
} LNode,*LinkList;

这段代码定义了一个单链表结构,其中:
DataType被定义为int类型,用于存储链表中每个元素的数据信息。
struct node表示链表的节点,包括一个DataType类型的data成员,用于存储节点的数据信息,以及一个struct node类型的next指针,用于存储下一个节点的地址。
LNode是一个struct node类型的别名,方便后续代码的书写。
LinkList是一个struct node类型的指针别名,用于存储链表的头指针,方便对链表的操作。

这段代码定义了一个链表结构体,其中包含两个成员变量:DataType data 和 struct node *next。DataType 是一个类型定义,表示元素的数据类型,可以根据需要进行修改。LNode 表示链表节点,其中 data 成员变量存储节点数据next 成员变量存储指向下一个节点的指针。LinkList 则是一个指向 LNode 的指针类型,用于表示链表的头节点。

//创建空单链表
LinkList  Creat_LinkList( )
{
    
      /*创建空单链表,入口参数:无;返回值:单链表的头指针,0代表创建失败,非0表成功*/
	LinkList  H;
	H=(LinkList )malloc(sizeof(LNode));
	if (H)   /*确认创建头结点创建是否成功,若成功,修改单链表头结点的指针域为0表空表*/
		H->next=NULL;
    return H;
} 

在这里插入图片描述

这段代码实现了创建一个空单链表的函数。函数名为Creat_LinkList,返回值为单链表的头指针LinkList,如果创建失败则返回0。
函数中首先定义了一个LinkList类型头指针H,然后使用malloc函数为头结点动态分配内存空间,并判断分配是否成功。如果分配成功,则将头结点的指针域next设为NULL,表示该单链表为空表。
最后返回头指针H。

H=(LinkList )malloc(sizeof(LNode));

这行代码是在动态内存中分配了一个LNode结构体的空间,并将其地址赋给指针变量H。它的作用是创建链表的头节点。通过调用malloc函数,分配了内存空间,这个内存空间的大小LNode结构体的大小,由sizeof(LNode)计算得出。然后将分配的空间的地址强制转换为LinkList类型,以便将其赋给指针变量H,成为链表的头指针。

1.1.2 销毁单链表

//销毁单链表
void  Destroy_LinkList(LinkList *H)
{
    
      /*销毁单链表,入口参数:单链表头指针的地址*/
	LinkList p,q;
	p= *H;
	while ( p)   /*释放单链表的所有结点*/
	{
    
       
		q=p;
		p=p->next;
		free(q); 
	}  /*while */
	*H=NULL; 
}

这段代码是单链表的销毁操作,用于释放单链表中所有结点的内存空间。
其中,入口参数是单链表头指针的地址(即指向头结点指针的指针),使用指针的指针是为了修改头指针的值,将其置为NULL。
在函数中,定义两个指针p和q,p指向当前需要被释放的结点,q指向p的前一个结点。循环遍历整个单链表,依次释放每个结点的内存空间,直到p指向空指针。循环结束后,将头指针的值置为NULL,表示单链表已被销毁。
需要注意的是,在释放结点的内存空间之前,需要将p指向下一个结点,避免释放后无法访问下一个结点的情况发生。

问:为何p指向当前需要被释放的结点,q指向p的前一个结点?
在销毁单链表的过程中,需要依次释放链表中的每一个结点,而且在释放当前结点时,需要保留指向下一个结点的指针,因为在释放当前结点之后,下一个结点将变成当前结点。所以在循环中,使用p指针指向当前需要被释放的结点,使用q指向p的前一个结点,以便在释放p之后,修改q的指针域,将其指向下一个结点,同时保留链表的完整性。

1.1.3 求表长

//求表长 
int  Length_LinkList (LinkList H)
{
    
     /*求单链表表长,入口参数:单链表头指针,出口参数:表长,-1表示单链表不存在。*/
	LinkList  p=H;   /* p指向头结点*/
	int  count= -1;  /*H带头结点所以从-1开始*/
	while ( p)  /* p所指的是第 count + 1 个结点*/
	{
    
       
		p=p->next; 
		count++; 
	}  /*while */
	return (count);
} 

这段代码是求单链表的长度,函数名为Length_LinkList。传入参数为单链表的头指针H,返回单链表的长度。函数中首先定义了一个指针变量p指向头结点,然后定义一个计数器count,初始值为-1,因为单链表头结点不计入表长,然后使用循环遍历单链表,每遍历一个结点,计数器count自增1,直到遍历结束。最后返回计数器count的值即为单链表的长度。
需要注意的是,当单链表为空时,count的初始值为-1,此时返回的长度值为-1,表示单链表不存在。

1.1.4 查找操作

按序号查找

 查找第i个数据元素 
LinkList  Locate_LinkList_Pos( LinkList  H, int  i)
{
    
    /*i不正确或者链表不存在返回NULL,i==0返回头指针,否则返回第 i 结点的指针*/
	LinkList   p;
	int j;
	p=H;  j=0;
	while (p && j<i )     /*查找第i个结点*/     
	{
    
         
		p=p->next; 
		j++;
	} /*while*/
	if(j==i) 
	{
    
    
		return (p);
	}
	else{
    
    
		printf("参数i错或单链表不存在\n");
		return (NULL);
	} 
}

这段代码实现了在单链表中查找第i个数据元素,其中i表示查找的位置,H是单链表的头指针。若i小于等于0或者单链表不存在,则返回NULL。如果i大于0,从头结点开始遍历单链表,查找第i个结点,并返回该结点的指针。如果i大于单链表的长度,则返回NULL。

while (p && j<i )     /*查找第i个结点*/     
	{
    
         
		p=p->next; 
		j++;
	}

这段代码是用来查找单链表中第i个数据元素所在结点的指针,其中i为输入的参数。p指针最开始指向单链表的头结点,j为0,然后不断地往下遍历链表,每遍历一个结点j自增1,直到j等于i或者遍历到链表末尾(p为NULL)为止。当j等于i时,p指向的就是第i个数据元素所在结点的指针,返回该指针;否则,返回NULL。

p && j<i 

p && j<i 是 while 循环的条件之一。这个条件表示在链表不为空且还没有到达第 i 个节点时,继续遍历链表。当链表为空或已经到达第 i 个节点时,循环终止。

问:p为头结点是空吗?
p是头结点,不是数据结点,因此不存储数据,但是它的 next 指针可能指向单链表中的第一个数据结点,因此在遍历单链表时需要跳过头结点,从头结点的 next 开始遍历。在这个循环中,p 的初始值为头结点,如果 p 不为 NULL(即单链表非空)并且当前遍历到的结点位置小于要查找的位置 i,则继续向后遍历,直到找到第 i 个结点或者遍历到单链表末尾。

按值查找

//按值查找
LinkList  Locate_LinkList_Value( LinkList  H, DataType  x)
{
    
     /*在单链表中查找值为x的结点,入口参数:单链表指针,检索元素*/
/*出口参数:找到后返回其指针,否则返回NULL*/
    LinkList p=H->next;
    while ( p && p->data != x)
          p=p->next;  
    return (p);
}

这个函数实现的是在单链表中按值查找某个元素的结点,并返回该结点的指针。其中,入口参数包括单链表的头指针H和要查找的元素x,返回值为找到元素的结点指针,如果没找到则返回NULL
函数的具体实现是,从单链表的第一个元素开始,依次向后遍历每个元素,如果该元素不等于要查找的元素x,则继续向后遍历,直到遍历到单链表的最后一个元素或找到了要查找的元素x。如果找到了要查找的元素,则返回该元素所在结点的指针,否则返回NULL。

1.1.5 插入

//插入 
int  Insert_LinkList( LinkList H, int i, DataType x)
{
    
      /*在单链表H的第i个位置前插入值为x的结点,入口参数:单链表,插入位置,插入元素*/
/*返回参数:成功标志,0不成功,1成功*/
    LinkList   p, q;
	p= Locate_LinkList_Pos ( H, i-1);  /*找第i-1个结点地址,见算法2.11 */
	if (!p)
	{
    
      
		printf("i有误\n");
		return (0);
	}
	q=(LinkList) malloc(sizeof(LNode)); 
	if (!q) 
	{
    
       
		printf("申请空间失败\n");
		return (0); 
	}     /*申请空间失败,不能插入*/
	q->data=x; 
	q->next=p->next;       /*新结点插入在第i-1个结点的后面*/
	p->next=q;
	return 1;     /*插入成功,则返回*/
 }

这段代码实现了在单链表中的第i个位置前插入值为x的结点,具体实现如下:
调用Locate_LinkList_Pos函数找到第i-1个结点的地址p,如果i有误,则返回0表示不成功。
分配一个新的结点q,并将x存入q的data域中。
将q插入到第i-1个结点的后面,即q的next指向p的next,p的next指向q
返回1表示成功插入。
这里的关键是调用了Locate_LinkList_Pos函数找到第i-1个结点的地址,这个函数的实现在前面的代码里已经给出过了。

	q->data=x; 
	q->next=p->next;       /*新结点插入在第i-1个结点的后面*/
	p->next=q;

在这段代码中,首先创建一个新的结点q并将x赋值给它的data域。然后,将q的next指针指向p的下一个结点,即第i个结点,这样就把第i-1个结点和第i个结点分别连接到了新结点q的前后。最后,将p的next指针指向q,这样就完成了在第i个位置前插入新结点q的操作。

1.1.6 删除

 //删除
int  Del_LinkList(LinkList  H,int i)
{
    
     /*删除单链表H上的第i个结点,入口参数:单链表,删除元素序号,返回参数:成功标志,
0不成功,1成功*/
   	LinkList   p, q;
	if (H==NULL||H->next==NULL)
	{
    
       printf("链表不存在或者空表不能删除\n");
		return (0);   
	}
	p= Locate_LinkList_Pos( H, i-1);  /*找第i-1个结点地址,见算法2.11*/
	if (p==NULL||p->next==NULL) 
	{
    
       printf("参数 i 错\n");
		return (0);    /*第i个结点不存在*/
	} 
	q=p->next;        /*q指向第i个结点*/
	p->next=q->next;   /*从链表中删除*/
	free(q);            /*释放第i个结点空间 */
	return (1);
}

这是一个单链表的删除操作函数,其功能是删除链表中的第i个节点,输入参数为单链表的头结点指针H和欲删除结点的序号i,返回值为1表示删除成功,为0则表示删除失败。在函数中,首先判断单链表是否为空表,如果是则返回0表示删除失败。然后在单链表中查找第i-1个节点,如果找到,则将第i-1个节点的next指针指向第i+1个节点,即跳过第i个节点,将其从链表中删除。最后释放第i个节点所占用的空间,返回1表示删除成功。

	if (H==NULL||H->next==NULL)
	{
    
       printf("链表不存在或者空表不能删除\n");
		return (0);   
	}

这段代码是判断链表是否存在或为空表,若是,则输出提示信息"链表不存在或者空表不能删除"并返回0,表示删除不成功。因为链表不存在或为空表时,根本就没有结点可供删除。

H==NULL||H->next==NULL

这段代码是用来判断单链表是否为空,如果是空表,则不能进行删除操作。判断的方法是通过判断单链表头节点的指针 H其 next 指针是否为 NULL 来进行的。
具体来说,如果 H 指针为 NULL,说明单链表本身就不存在;如果 H->next 指针为 NULL,说明单链表只有一个头节点,没有其他节点,即单链表为空表。如果单链表为空表,则不能进行删除操作,直接返回 0,表示删除不成功。

p==NULL||p->next==NULL

这行代码是用于检查指定位置 i 是否存在,如果指定位置 i 不存在,那么删除操作就不会执行,该行代码会返回删除失败的标志,即返回 0。
具体来说,该行代码有两个判断条件:
p == NULL:如果找到第 i - 1 个节点,但是该节点的指针为 NULL,则说明链表长度不足 i - 1,即指定的位置 i 不存在。
p->next == NULL:如果找到第 i - 1 个节点,但是该节点的下一个节点指针为 NULL,则说明指定位置 i 上的节点不存在,即指定的位置 i 不存在。
如果上述两个条件都不满足,那么就说明指定位置 i 存在,可以进行删除操作。

	q=p->next;        /*q指向第i个结点*/
	p->next=q->next;   /*从链表中删除*/
	free(q);            /*释放第i个结点空间 */

这段代码实现了在单链表H上删除第i个元素的操作。首先,用指针p找到第i-1个结点的地址,然后用指针q指向第i个结点将第i-1个结点的next指向第i+1个结点,从而删除了第i个结点。最后,释放第i个结点的内存空间。

1.1.7 单链表的遍历

//单链表的遍历         
void DispList(LinkList   L)  /*输出线性表*/
{
    
    
    LinkList p;
    int i=1;
    p=L->next;
    printf("  NO           DATA   \n");
    
    while(p)
    {
    
    
        printf("  %d           %d",i,p->data);
        printf("\n");
        i++;
        p=p->next;
     }
       printf("\n");  
    printf("目前表中元素总数:%d\n",i-1);
    printf("\n"); 
}

这是一个输出单链表元素的函数,函数名为DispList,接受一个单链表L作为参数,没有返回值。
函数内部首先定义一个指针p指向L的第一个节点,然后使用while循环遍历单链表L,每遍历一个节点,就输出节点的序号和数据值,同时将计数器i加1,最后输出单链表中元素总数。输出的格式为两列,第一列是节点的序号,第二列是节点的数据值。
输出完单链表的所有元素后,函数会输出单链表中元素总数。

p=L->next;

在这段代码中,L 是一个指向头结点的指针,头结点中不存储数据,只是一个辅助结点。因此,我们需要将 p 指向头结点的下一个结点,也就是第一个实际存储数据的结点,以便遍历整个单链表。所以, p=L->next; 将指针 p 指向单链表的第一个结点

printf("  %d           %d",i,p->data);

这行代码的作用是输出当前节点的编号和对应的数据值。其中,i 是一个计数器,表示当前节点的编号,p->data 表示当前节点存储的数据值。


p=p->next;

这行代码将当前结点指针p指向它的下一个结点,用于遍历单链表。

1.1.8 单链表的逆置

//单链表的逆置 
void  Reverse_LinkList (LinkList H)
{
    
        
	LinkList  p,q;
	p=H->next;  /*p指向第一个数据结点*/
	H->next=NULL;  /*将原链表置为空表H*/  
	while (p)  
	{
    
       q=p; 
		p=p->next;
		q->next=H->next;   /*将当前结点插到头结点的后面*/
		H->next=q;
	} /*while*/
}

这段代码实现了单链表的逆置功能。首先,将指针p指向单链表的第一个数据结点,然后将头结点的next域置为NULL,相当于将原链表置为空表。接着进入while循环,每次将p指向的结点摘下来,保存在q中,然后将p指向下一个结点。接着将q插入到头结点的后面,实现了单链表的逆置。循环执行直到p为空,即原链表中的所有结点都已经被逆置。

这段代码是实现单链表的逆置。其基本思想是,从原链表头结点开始,依次取出每个结点,插入到新链表的头部。
具体实现方法是:定义两个指针p和qp指向原链表的第一个数据结点q指向p的后继结点。然后,将p从原链表中断开,将其插入到新链表的头部。接着,将q指向p的后继结点,重复以上操作,直至原链表遍历完毕。最后,将原链表的头结点指针置为NULL,新链表的头结点指向原链表的尾结点。
这样就完成了单链表的逆置操作。

猜你喜欢

转载自blog.csdn.net/aaaccc444/article/details/130497025