从头开始嵌入式编程学习-数据结构-线性表-链式线性表

学习流程主要包括分为11个部分

  1. 绪论
  2. 线性表
  3. 栈和队列
  4. 递归和分治思想
  5. 串(KMP算法)
  6. 数组和广义表
  7. 树和二叉树
  8. 动态存储管理
  9. 查找
  10. 排序

目录

二 线性表 

2.3 线性表的链式表示

2.3.1 线性表的单链表存储结构(C 语言中用结构指针表示):

2.3.2 单链表的初始化

2.3.3 单链表-获取单链表的第i个元素的函数实现

2.3.4  单链表结点的插入​编辑

2.3.5  单链表结点的删除

2.3.6  malloc()和free()函数

2.3.7  头插法建立单链表

2.3.8  尾插法建立单链表

2.3.9  归并两个(有序)单链表

2.3.10  输出整个单链表

2.3.10  计算单链表的长度

2.3.11  修改单链表结点的值 

2.3.12  完整代码 + 测试结果


二 线性表 

        上节中,线性表的顺序存储结构特点是:逻辑关系上相邻的两个元素在物理位置上也相邻。它的弱点很明显,在进行插入和删除操作时,需要移动大量的元素。本节讨论链式存储结构,它不要求逻辑相邻的元素物理上也相邻,所以它没有顺序结构的弱点,但也失去了顺序表可以随机存储(在给定元素位置的情况下,顺序表可以在常数时间内(O(1)时间复杂度))的优点。

2.3 线性表的链式表示

        特点:用一组任意的存储单元存储线性表的数据元素(连续与否都可)。

        所以为了表示a_{i}和直接后继a_{i+1}之间的逻辑关系,除了存储a_{i}的信息,还需要存储直接后继a_{i+1}的信息(存储位置)。两部分组成a_{i}的存储映像,称为结点

        它包括两个域,数据域指针域(其中存储的信息成为指针或者)。n个结点(a_{i}(1\leq i\leq n))的存储印象,链结成一个链表,即为线性链表单链表

(a_{1},a_{2},...,a_{n})

        线性链表的存储必须从头指针(指示链表中第一个结点的存储位置)开始进行。由于最后一个元素没有直接后继,则线性链表最后一个节点的指针为NULL。

2.3.1 线性表的单链表存储结构(C 语言中用结构指针表示):

#include <stdio.h>
#include <stdlib.h>

//- - - - - 线性表的单链表存储结构 - - - - -

// 线性表的单链表存储结构体
typedef struct ListNode{
	int data;				// 数据域,存储节点的数据元素
	struct ListNode *next;	// 指针域,指向下一个节点的指针 next:结构体指针变量,指向结构体的地址
} ListNode, *LinkList;		// ListNode用于表示struct ListNode结构体,简化了结构体类型的引用
							// LinkList这个别名表示指向struct ListNode的结构体类型的指针,它
							// 使得可以直接使用LinkList来声明指向链表节点的指针变量

        假设L是单链表的头指针,它指向表中的第一个结点。若L为空(L=NULL),则表示线性表为空。单链表通常以一个特殊的结点作为头结点,头结点的数据与可以不存储任何信息,也可以存储线性表长度等附加信息,头结点的指针域指向第一个结点的指针。

        虽然单链表中的两个元素之间没有固定联系,但是每个元素的存储位置包含在前驱结点的信息中。所以我们有:假设p是指向线性表中第i个元素(结点a_{i})的指针,则p->next是指向i+1元素(结点a_{i+1})的指针。则有p->data = a_{i}p->next->data = a_{i+1}。所以单链表中,取得第i个元素,必须从头指针开始寻找,所以单链表是非随机存储的存储结构。下面是单链表的初始化获取单链表的第i个元素的函数实现。

2.3.2 单链表的初始化

// 初始化单链表(初始化为空链表)
void InitList(LinkList* L) {	// 链表接受一个指向指针LinkList的指针LinkList* L 这样可以在函数内部修改指针的值
	*L = (LinkList)malloc(sizeof(ListNode)); // *L是L指针变量的解引用,表示获取指针变量LinkList的值,指针变量
											 // LinkList存储的是struct ListNode的首地址
	// malloc函数,动态分配内存,接受一个参数,返回指向分配内存起始地址的指针。
	// (LinkList)类型甄嬛。它将malloc函数返回的通用指针 void *类型转化为LinkList类型(struct ListNode)的指针
	if (!(*L)) {
		printf("内存分配失败!\n");
		exit(-1);
	}
	(*L)->next = NULL;
}

2.3.3 单链表-获取单链表的第i个元素的函数实现

// 获取单链表的第i个元素
int GetElem(LinkList* L, int i) { // 链表接受一个指向指针LinkList的指针LinkList* L
	int j = 1; //计数器
	LinkList p = (*L)->next; // 初始化p指向第一个结点 其中L->next表示下一个结点的地址
	while (p && j < i) { //指针向后查找,直到p指向第i个元素,或p不存在
		p = p->next;
		j++;
	}
	if (!p || j > i) { //当p不存在时,j>i 有两种可能链表中元素的数量小于i或者i<=0
		printf("第%d个元素不存在!\n", i);
		exit(-1);
	}
	return p->data; // 返回结点的值
}

        在获取单链表的第i个元素的函数中,其基本操作为while循环中的循环条件p指针的存在比较i,j的大小,还有后移指针p。while循环中语句的频度与被查元素在表中的位置有关,若1\leq i\leq n,则频度为i-1(因为要编译到第i个元素的前一个结点),否则频度为n(如果i超出了有效范围(小于1或者大于n),那么就需要遍历整个链表直到最后一个节点,循环的频度就是n)。因此该算法的时间复杂度为O(n)

2.3.4  单链表结点的插入

        如上图所示想要把c结点插入到a结点和b结点的中间。我们需要修改结点a的指针域,令其指向结点c,而结点c的指针域,需要指向结点b。从而实现了abc,三个结点之间逻辑关系的变化。

        同样的,我们要在a,b结点之间插入一个新的结点x。首先得生成一个结点x,然后再将其插入到单链表中。我们需要修改结点a的指针域,令其指向结点x,而结点x的指针域应指向结点b。即

x->next = a->next;a->next =x

         函数实现为:

// 在带头结点的单链线性表L中第i个位置前插入元素
void InsertList(LinkList* L, int i, int data) {
	int j = 0;
	LinkList p = *L;
	// 找到第i-1个结点,待插入位置的前一个结点
	while (p && j < i - 1) { //j从0开始
		p = p->next;
		j++;
	}
	// 插入位置无效的情况,i<1或者大于链表长度+1
	if (!p || j > i - 1) {
		printf("插入位置无效!\n");
		exit(-1);
	}
	// 创建新的结点
	ListNode* newNode = (LinkList*)malloc(sizeof(ListNode));
	if (!newNode) {
		printf("内存分配失败!\n");
		exit(-1);
	}
	newNode->data = data;
	newNode->next = p->next; //p为上一个结点
	p->next = newNode;
}

2.3.5  单链表结点的删除

        与插入类似想要删除abc结点中的结点b时,只需要修改a指针的指针域即可。

a->next = a->next->next

        函数实现为:

// 在带头结点的单链线性表L中删除第i个位置的结点,并返回其值
int DeleteList(LinkList* L, int i) {
	int j = 0;
	int elem;
	LinkList p = *L;
	// 找到第i-1个结点,待删除位置的前一个结点
	while (p->next && j < i - 1) {
		p = p->next;
		j++;
	}
	// 考虑到删除位置无效的情况,i<1或者大于链表长度+1
	if (!(p->next) || j > i - 1) {
		printf("删除位置不合理!\n");
		exit(-1);
	}
	LinkList q = p->next;
	p->next = q->next;	// 等价于p->next = p->next->next
	elem = q->data;	// 获取释放结点的值
	free(q);	// 释放结点
	q = NULL;
	return elem;
}

        可见,在已知链表中元素插入或删除的确切位置的情况下,在单链表中插入或删除一个结点时,仅需要改变指针而不需要移动元素。在函数实现中容易看出,我们的单链表的插入和删除的时间复杂度为O(n)。因为在第i个结点之前插入或删除第i个结点,都必须找到第i-1个结点(需要修改指针的结点)。

2.3.6  malloc()和free()函数

        在上面的新增和删除结点的代码实现中,我们使用了malloc和free函数。下面来详细了解一下这两个函数。 

malloc()函数:

作用:      用于在堆内存中动态分配一块指定大小的内存空间。

用法:      void* malloc(size_t size); 类型为void* (指向未知类型的指针),size参数表示要分配的内存块的字节数。返回值是一个指向分配内存块起始地址的指针。如果分配失败,返回NULL。

特点:     1.内存分配:用于在堆内存中动态分配一块指定大小的内存空间。

                2.动态分配:返回的指针类型是void*,可以动态根据需要分配所需内存大小。

                3.未初始化:分配的内存块内容是初始化的,可能包含随机值,需要手动初始化。

                4.生命周期:分配的内存在程序的整个生命周期内有效,直到使用free() 函数释放。

注意事项:

                1.错误检查:使用malloc分配内存,需要检查返回的指针是否为NULL,以确保分配成功

                2.内存泄漏:使用动态分配的内存后,务必记得使用free()释放内存,避免内存泄漏

                3.指针类型:返回的指针类型为void*,需要根据需要进行类型转换,如int*,char*

                4.越界访问:分配的内存块一旦超出作用范围就无法访问,可能导致悬空指针的问题

free()函数:

作用:      用于释放之前分配的内存块,将其返回给系统,以便系统可以重新分配给其他程序使用

用法:      void free(void* ptr); ptr参数是之前通过malloc(),calloc()(分配内存时侯会清零),realoc()                     (新分配已经存在的内存空间的大小),函数分配的内存块的指针。

特点:     1.内存释放:函数用于释放动态分配的内存块,将其返回给系统,使该内存块可以重新                    分配给其他程序使用。

                当调用free()函数释放内存时,实际上是将内存块标记为可重用,一遍后续的内存分配操作可以再次使用这块内存。操作系统负责管理系统的内存资源,而编译器通过系统调用向操作系统请求内存分配。因此,free()函数告诉操作系统归还了一块内存,并使得该内存可以被重新分配。虽然再释放内存后,指针仍然存在,但是方位已经释放的内存块将导致未定义的行为。因此,为了避免悬空指针 (指针指向的内存空间已被释放/不再有效)的问题,通常再释放内存后,将指针设置为NULL。

                2.指针失效:释放的只恨不在生效,访问已释放的内存块是未定义的行为,可能导致程                    序崩溃,或者异常。

注意事项:

                1.指针有效性:传入的指针必须是之前通过动态分配函数获得的指针。

                2.无故释放:释放只能一次,释放两次或两次以上会出现错误(释放空指针例外,释放空                  指针等于什么也没做)

                3.悬空指针:释放内存后,建议将指针设置为NULL,以避免悬挂指针的问题。

2.3.7  头插法建立单链表

        由上面的知识,我们可以知道,建立线性表的链式存储结构的过程是一个动态生成链表的过程。即从"空表"的初始状态起,依次建立各个元素结点,并逐个插入链表,下面是一个从表尾到表头逆向建立单链表的算法(头插法),其时间复杂度为O(n)(建立整个单链表的时间复杂度)。

// 头插法 建立单链表
void HeadCreateList(LinkList* L) {
	int n;
	printf("头插法,输入插入元素的个数:");
	scanf("%d", &n);
	for (int i = 0; i < n; i++) {
		ListNode* newNode = (ListNode*)malloc(sizeof(ListNode));	// 生成新结点
		if (!newNode) {
			printf("内存分配失败!\n");
			exit(-1);
		}
		printf("输入插入的第%d个结点数据域的值为:",i+1);
		scanf("%d", & newNode->data);	// 将读取的数据放入结点的数据域中
		newNode->next = (*L)->next;		// 将新节点插入到头结点之后
		(*L)->next = newNode;
	}
}

头插法:

        1.介绍:插入新结点时,将新结点插入到链表的头部

        2.步骤:创建一个新结点,并将新结点的next指针指向原链表的头节点,再将链表的头指针指向新结点。

        3.特点:链表的顺序与插入顺序相反

头插法注意事项(防断链):

        1.创建新结点时候保存原链表的头指针,这样可以确保在插入新结点之后,仍然能够访问原链表的所有节点。

        2.更新原链表的头指针,将新结点插入到链表的头部后,更新链表的头指针,使其插入新的结点。

        3.使用临时变量保存原链表的头指针,更新链表的头指针之前可以使用临时变量保存原链表的头指针,以便后续使用(这样可以确保,再插入新结点之后仍然可以访问原链表的头部结点以及后续结点)。

        4.检查新结点的next指针是否正确设置,确保新结点next指针正确的指向原链表的头结点,防止链表撕裂。

2.3.8  尾插法建立单链表

尾插法:

        1.介绍:插入新结点时候,将新结点插入到链表的尾部。

        2.步骤:创建一个新结点,如果链表为空,则将新结点作为链表的头结点,否则,遍历链表找到最后一个结点,将最后一个结点的next指针指向新结点。

        3.特点:1.尾插法链表顺序与插入顺序一致。2.尾插法的时间复杂度取决于链表的长度,如果链表的长度为n,则尾插法的时间复杂度为O(n)因为需要遍历到最后一个结点。

// 尾插法 建立单链表
void TailCreateList(LinkList* L) {
	int n;
	printf("尾插法,输入插入元素的个数:");
	scanf("%d", &n);
	ListNode* tail = *L;	// 创建一个指向链表尾部的指针始终指向头结点
	while (tail->next != NULL) {
		tail = tail->next;	//遍历链表直到找到末尾结点
	}
	for (int i = 0; i < n; i++) {
		ListNode* newNode = (ListNode*)malloc(sizeof(ListNode));	// 生成新结点
		if (!newNode) {
			printf("内存分配失败!\n");
			exit(-1);
		}
		printf("输入插入的第%d个结点数据域的值为:", i + 1);
		scanf("%d", &newNode->data);	// 将读取的数据放入结点的数据域中
		newNode->next = NULL;		// 将新节点指针域设置为NULL,因此它为最后一个结点
		tail->next = newNode;	// 将新节点接到链表的尾部
		tail = newNode;		//更新链表尾部的指针,指向新插入的结点
	}
}

2.3.9  归并两个(有序)单链表

        按照在2.2顺序线性表中归并两个(有序)顺序表的思想,我们需要设立三个指针变量pa,pb,pc,其中pa,pb分别指向La,Lb表中待比较插入的结点(显然,指针的初始状态为:当La,Lb非空时,pa,pb分别指向La,Lb表中的第一个结点),而pc则指向Lc表中最后一个结点,,当pa->data <= pb->data,则将pa所指向的结点连接到pc所指向的结点之后,否则将pb所指向的结点连接到pc所指向的结点之后。直到pa,pb指向表的末尾NULL(即有一个表中的元素已经归并完),则只需要将另外一个表的剩余段连接在pc所指向的结点之后即可。

// 归并 两个有序(递增)链表
void MergeList(LinkList* La, LinkList* Lb, LinkList* Lc) {
	// 归并La,Lb得到的新的线性表Lc的元素也是按照递增排列的
	LinkList pa = (*La)->next, pb = (*Lb)->next, pc = *Lc;
	// 此处赋值pc = *Lc,是因为Lc是一个只含有头结点的空链表,(*Lb)->next=NULL
	// 所以用 *Lc 来表示访问链表的头指针的地址
	while (pa && pb) {
		if (pa->data <= pb->data) {	// 若当前的pa结点小于pb结点值
			pc->next = pa;	// 将pc所指向的结点的next指针指向pa
			pc = pa;		// 将pc移动到新加入的结点pa所在的位置
			pa = pa->next;	// 将pa指向下一个结点
		}
		else {
			pc->next = pb;
			pc = pb;
			pb = pb->next;
		}
	}
	pc->next = pa ? pa : pb; //判断pa是否为NULL,pa为NULL则pc->next = pb
	free(*La);	// 释放指针La所指向的链表的内存空间
	*La = NULL;	// 将指针La指向的链表的首地址置为空,防止出现指针悬挂
	free(*Lb);
	*Lb = NULL;
}

        容易看出相比顺序线性表中归并,链表的归并时间复杂度相同,空间复杂度不同,在归并为一个链表时候,不需要另建新链表的结点空间,而是将原来两个链表中结点之间的关系接触,重新按照元素非递减的关系将所有节点链接成一个链表。

2.3.10  输出整个单链表

// 输出整个单链表
void printList(LinkList* L) {
	// 如果链表为空或者只有头结点,则输出提示并退出程序
	if (*L == NULL || (*L)->next == NULL) {
		printf("List is empty!\n");
		exit(-1);
	}
	// 指向第一个实际结点的指针
	LinkList head = (*L)->next;
	printf("List:");
	// 遍历表的每一个节点,输出结点的数据值
	while (head)
	{
		printf("%d ->", head->data);
		head = head->next;
	}
	printf("NULL\n");
}

2.3.10  计算单链表的长度

// 计算链表长度的函数
int LengthList(LinkList* L) {
	// 如果链表为空,输入提示信息并返回长度为0
	if (*L == NULL) {
		printf("List is empty!\n");
		return 0;
	}
	int length = 0;	// 初始化长度
	LinkList tail = (*L)->next;	// 从链表的第一个结点开始遍历
	while (tail) {
		length++;
		tail = tail->next;
	}
	return length;
}

2.3.11  修改单链表结点的值 

// 修改单链表结点的值
void updataNodeList(LinkList* L, int position, int element) {
	// 如果链表为空或者只有头结点,则输出提示并退出程序
	if (*L == NULL || (*L)->next == NULL) {
		printf("List is empty!\n");
		exit(-1);
	}
	// 计算链表的长度 
	int length = LengthList(L); // 在这里L是一个指向链表头指针的指针LinkList* L
								// 因为LengthList(LinkList* L)函数需要传入LinkList* L类型的参数
								// 所以直接用L就可以了
	// 检查输入位置是否有效
	if (position<1 || position>length) {
		printf("Invalid position!\n");
		exit(-1);
	}
	int i = 1;
	LinkList tail = (*L)->next;
	// 找到指定位置的结点
	while (tail && i < position) {
		tail = tail->next;
		i++;
	}
	// 如果找到了指定位置的结点则更新结点的值
	if (tail && i == position) {
		tail->data = element;
		printf("Node at position &d updated successfully is %d\n", position, element);
	}
}

2.3.12  完整代码 + 测试结果

#include <stdio.h>
#include <stdlib.h>

//- - - - - 线性表的单链表存储结构 - - - - -

// 线性表的单链表存储结构体
typedef struct ListNode{
	int data;				// 数据域,存储节点的数据元素
	struct ListNode *next;	// 指针域,指向下一个节点的指针 next:结构体指针变量,指向结构体的地址
} ListNode, *LinkList;		// ListNode用于表示struct ListNode结构体,简化了结构体类型的引用
							// LinkList这个别名表示指向struct ListNode的结构体类型的指针,它
							// 使得可以直接使用LinkList来声明指向链表节点的指针变量

// 初始化单链表(初始化为空链表)
void InitList(LinkList* L) {	// 链表接受一个指向指针LinkList的指针LinkList* L 这样可以在函数内部修改指针的值
	*L = (LinkList)malloc(sizeof(ListNode)); // *L是L指针变量的解引用,表示获取指针变量LinkList的值,指针变量
											 // LinkList存储的是struct ListNode的首地址
	// malloc函数,动态分配内存,接受一个参数,返回指向分配内存起始地址的指针。
	// (LinkList)类型甄嬛。它将malloc函数返回的通用指针 void *类型转化为LinkList类型(struct ListNode)的指针
	if (!(*L)) {
		printf("内存分配失败!\n");
		exit(-1);
	}
	(*L)->next = NULL;
}

// 获取单链表的第i个元素
int GetElem(LinkList* L, int i) { // 链表接受一个指向指针LinkList的指针LinkList* L
	int j = 1; //计数器
	LinkList p = (*L)->next; // 初始化p指向第一个结点 其中L->next表示下一个结点的地址
	while (p && j < i) { //指针向后查找,直到p指向第i个元素,或p不存在
		p = p->next;
		j++;
	}
	if (!p || j > i) { //当p不存在时,j>i 有两种可能链表中元素的数量小于i或者i<=0
		printf("第%d个元素不存在!\n", i);
		exit(-1);
	}
	return p->data; // 返回结点的值
}

// 在带头结点的单链线性表L中第i个位置前插入元素
void InsertList(LinkList* L, int i, int data) {
	int j = 0;
	LinkList p = *L;
	// 找到第i-1个结点,待插入位置的前一个结点
	while (p && j < i - 1) { //j从0开始
		p = p->next;
		j++;
	}
	// 插入位置无效的情况,i<1或者大于链表长度+1
	if (!p || j > i - 1) {
		printf("插入位置无效!\n");
		exit(-1);
	}
	// 创建新的结点
	ListNode* newNode = (LinkList*)malloc(sizeof(ListNode));
	if (!newNode) {
		printf("内存分配失败!\n");
		exit(-1);
	}
	newNode->data = data;
	newNode->next = p->next; //p为上一个结点
	p->next = newNode;
}

// 在带头结点的单链线性表L中删除第i个位置的结点,并返回其值
int DeleteList(LinkList* L, int i) {
	int j = 0;
	int elem;
	LinkList p = *L;
	// 找到第i-1个结点,待删除位置的前一个结点
	while (p->next && j < i - 1) {
		p = p->next;
		j++;
	}
	// 考虑到删除位置无效的情况,i<1或者大于链表长度+1
	if (!(p->next) || j > i - 1) {
		printf("删除位置不合理!\n");
		exit(-1);
	}
	LinkList q = p->next;
	p->next = q->next;	// 等价于p->next = p->next->next
	elem = q->data;	// 获取释放结点的值
	free(q);	// 释放结点
	q = NULL;
	return elem;
}

// 头插法 建立单链表
void HeadCreateList(LinkList* L) {
	int n;
	printf("头插法,输入插入元素的个数:");
	scanf("%d", &n);
	for (int i = 0; i < n; i++) {
		ListNode* newNode = (ListNode*)malloc(sizeof(ListNode));	// 生成新结点
		if (!newNode) {
			printf("内存分配失败!\n");
			exit(-1);
		}
		printf("输入插入的第%d个结点数据域的值为:",i+1);
		scanf("%d", & newNode->data);	// 将读取的数据放入结点的数据域中
		newNode->next = (*L)->next;		// 将新节点插入到头结点之后
		(*L)->next = newNode;
	}
}

// 尾插法 建立单链表
void TailCreateList(LinkList* L) {
	int n;
	printf("尾插法,输入插入元素的个数:");
	scanf("%d", &n);
	ListNode* tail = *L;	// 创建一个tail指针 始终指向头结点
	while (tail->next != NULL) {
		tail = tail->next;	//遍历链表直到找到末尾结点
	}
	for (int i = 0; i < n; i++) {
		ListNode* newNode = (ListNode*)malloc(sizeof(ListNode));	// 生成新结点
		if (!newNode) {
			printf("内存分配失败!\n");
			exit(-1);
		}
		printf("输入插入的第%d个结点数据域的值为:", i + 1);
		scanf("%d", &newNode->data);	// 将读取的数据放入结点的数据域中
		newNode->next = NULL;		// 将新节点指针域设置为NULL,因此它为最后一个结点
		tail->next = newNode;	// 将新节点接到链表的尾部
		tail = newNode;		// 更新tail指针,指向新插入的结点(始终保持它在尾部)
	}
}

// 归并 两个有序(递增)链表
void MergeList(LinkList* La, LinkList* Lb, LinkList* Lc) {
	// 归并La,Lb得到的新的线性表Lc的元素也是按照递增排列的
	LinkList pa = (*La)->next, pb = (*Lb)->next, pc = *Lc;
	// 此处赋值pc = *Lc,是因为Lc是一个只含有头结点的空链表,(*Lb)->next=NULL
	// 所以用 *Lc 来表示访问链表的头指针的地址
	while (pa && pb) {
		if (pa->data <= pb->data) {	// 若当前的pa结点小于pb结点值
			pc->next = pa;	// 将pc所指向的结点的next指针指向pa
			pc = pa;		// 将pc移动到新加入的结点pa所在的位置
			pa = pa->next;	// 将pa指向下一个结点
		}
		else {
			pc->next = pb;
			pc = pb;
			pb = pb->next;
		}
	}
	pc->next = pa ? pa : pb; //判断pa是否为NULL,pa为NULL则pc->next = pb
	free(*La);	// 释放指针La所指向的链表的内存空间
	*La = NULL;	// 将指针La指向的链表的首地址置为空,防止出现指针悬挂
	free(*Lb);
	*Lb = NULL;
}

// 输出整个单链表
void printList(LinkList* L) {
	// 如果链表为空或者只有头结点,则输出提示并退出程序
	if (*L == NULL || (*L)->next == NULL) {
		printf("List is empty!\n");
		exit(-1);
	}
	// 指向第一个实际结点的指针
	LinkList head = (*L)->next;
	printf("List:");
	// 遍历表的每一个节点,输出结点的数据值
	while (head)
	{
		printf("%d ->", head->data);
		head = head->next;
	}
	printf("NULL\n");
}

// 计算链表长度的函数
int LengthList(LinkList* L) {
	// 如果链表为空,输入提示信息并返回长度为0
	if (*L == NULL) {
		printf("List is empty!\n");
		return 0;
	}
	int length = 0;	// 初始化长度
	LinkList tail = (*L)->next;	// 从链表的第一个结点开始遍历
	while (tail) {
		length++;
		tail = tail->next;
	}
	return length;
}

// 修改单链表结点的值
void updataNodeList(LinkList* L, int position, int element) {
	// 如果链表为空或者只有头结点,则输出提示并退出程序
	if (*L == NULL || (*L)->next == NULL) {
		printf("List is empty!\n");
		exit(-1);
	}
	// 计算链表的长度 
	int length = LengthList(L); // 在这里L是一个指向链表头指针的指针LinkList* L
								// 因为LengthList(LinkList* L)函数需要传入LinkList* L类型的参数
								// 所以直接用L就可以了
	// 检查输入位置是否有效
	if (position<1 || position>length) {
		printf("Invalid position!\n");
		exit(-1);
	}
	int i = 1;
	LinkList tail = (*L)->next;
	// 找到指定位置的结点
	while (tail && i < position) {
		tail = tail->next;
		i++;
	}
	// 如果找到了指定位置的结点则更新结点的值
	if (tail && i == position) {
		tail->data = element;
		printf("Node at position %d updated successfully is %d\n", position, element);
	}
}

int main() {
	// 构建单链表
	LinkList La, Lb, Lc;
	// 初始化单链表
           
	InitList(&La);      
	InitList(&Lb);     
	InitList(&Lc);     
	
	// 头插法建立单链表La
	printf("头插法建立单链表La:\n");
	HeadCreateList(&La);
	// 尾插法建立单链表Lb
	printf("尾插法建立单链表Lb:\n");
	TailCreateList(&Lb);

	// 输出整个单链表
	printf("输出整个单链表La:\n");
	printList(&La);
	printf("输出整个单链表Lb:\n");
	printList(&Lb);

	// 合并两个有序单链表La,Lb到Lc
	printf("合并两个有序单链表La,Lb到Lc:\n");
	MergeList(&La, &Lb, &Lc);
	printf("输出整个单链表Lc:\n");
	printList(&Lc);	

	// 计算链表长度
	printf("链表长度为:%d\n", LengthList(&Lc));

	// 插入一个结点
	printf("Lc链表插入一个结点(1,100):\n");
	InsertList(&Lc, 1, 100);
	printf("输出整个单链表Lc:\n");
	printList(&Lc);
	// 修改一个结点的值
	printf("Lc链表修改一个结点的值(1,50):\n");
	updataNodeList(&Lc, 1, 50);
	printf("输出整个单链表Lc:\n");
	printList(&Lc);
	// 删除一个结点
	printf("删除第1个元素的值为:%d\n", DeleteList(&Lc, 1));
	printf("输出整个单链表Lc:\n");
	printList(&Lc);

	return 0;
}

测试结果:

头插法建立单链表La:
头插法,输入插入元素的个数:2
输入插入的第1个结点数据域的值为:10
输入插入的第2个结点数据域的值为:20
尾插法建立单链表Lb:
尾插法,输入插入元素的个数:2
输入插入的第1个结点数据域的值为:30
输入插入的第2个结点数据域的值为:40
输出整个单链表La:
List:20 ->10 ->NULL
输出整个单链表Lb:
List:30 ->40 ->NULL
合并两个有序单链表La,Lb到Lc:
输出整个单链表Lc:
List:20 ->10 ->30 ->40 ->NULL
链表长度为:4
Lc链表插入一个结点(1,100):
输出整个单链表Lc:
List:100 ->20 ->10 ->30 ->40 ->NULL
Lc链表修改一个结点的值(1,50):
Node at position 1 updated successfully is 50
输出整个单链表Lc:
List:50 ->20 ->10 ->30 ->40 ->NULL
删除第1个元素的值为:50
输出整个单链表Lc:
List:20 ->10 ->30 ->40 ->NULL

  • 28
    点赞
  • 47
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值