学习流程主要包括分为11个部分
- 绪论
- 线性表
- 栈和队列
- 递归和分治思想
- 串(KMP算法)
- 数组和广义表
- 树和二叉树
- 图
- 动态存储管理
- 查找
- 排序
目录
2.3.1 线性表的单链表存储结构(C 语言中用结构指针表示):
二 线性表
上节中,线性表的顺序存储结构的特点是:逻辑关系上相邻的两个元素在物理位置上也相邻。它的弱点很明显,在进行插入和删除操作时,需要移动大量的元素。本节讨论链式存储结构,它不要求逻辑相邻的元素物理上也相邻,所以它没有顺序结构的弱点,但也失去了顺序表可以随机存储(在给定元素位置的情况下,顺序表可以在常数时间内(时间复杂度))的优点。
2.3 线性表的链式表示
特点:用一组任意的存储单元存储线性表的数据元素(连续与否都可)。
所以为了表示和直接后继之间的逻辑关系,除了存储的信息,还需要存储直接后继的信息(存储位置)。两部分组成的存储映像,称为结点。
它包括两个域,数据域和指针域(其中存储的信息成为指针或者链)。个结点的存储印象,链结成一个链表,即为线性链表或单链表。
线性链表的存储必须从头指针(指示链表中第一个结点的存储位置)开始进行。由于最后一个元素没有直接后继,则线性链表最后一个节点的指针为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),则表示线性表为空。单链表通常以一个特殊的结点作为头结点,头结点的数据与可以不存储任何信息,也可以存储线性表长度等附加信息,头结点的指针域指向第一个结点的指针。
虽然单链表中的两个元素之间没有固定联系,但是每个元素的存储位置包含在前驱结点的信息中。所以我们有:假设是指向线性表中第个元素(结点)的指针,则是指向元素(结点)的指针。则有;。所以单链表中,取得第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或者大于),那么就需要遍历整个链表直到最后一个节点,循环的频度就是)。因此该算法的时间复杂度为。
2.3.4 单链表结点的插入
如上图所示想要把c结点插入到a结点和b结点的中间。我们需要修改结点a的指针域,令其指向结点c,而结点c的指针域,需要指向结点b。从而实现了abc,三个结点之间逻辑关系的变化。
同样的,我们要在a,b结点之间插入一个新的结点x。首先得生成一个结点x,然后再将其插入到单链表中。我们需要修改结点a的指针域,令其指向结点x,而结点x的指针域应指向结点b。即
函数实现为:
// 在带头结点的单链线性表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指针的指针域即可。
函数实现为:
// 在带头结点的单链线性表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;
}
可见,在已知链表中元素插入或删除的确切位置的情况下,在单链表中插入或删除一个结点时,仅需要改变指针而不需要移动元素。在函数实现中容易看出,我们的单链表的插入和删除的时间复杂度为。因为在第个结点之前插入或删除第i个结点,都必须找到第个结点(需要修改指针的结点)。
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 头插法建立单链表
由上面的知识,我们可以知道,建立线性表的链式存储结构的过程是一个动态生成链表的过程。即从"空表"的初始状态起,依次建立各个元素结点,并逐个插入链表,下面是一个从表尾到表头逆向建立单链表的算法(头插法),其时间复杂度为(建立整个单链表的时间复杂度)。
// 头插法 建立单链表
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,则尾插法的时间复杂度为因为需要遍历到最后一个结点。
// 尾插法 建立单链表
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