当前位置: 首页 > news >正文

数据结构: 线性表(无哨兵位单链表实现)

文章目录

  • 1. 线性表的链式表示: 链表
    • 1.1 顺序表的优缺点
    • 1.2 链表的概念
    • 1.3 链表的优缺点
    • 1.4 链表的结构
  • 2. 单链表的定义
    • 2.1 单链表的结构体
    • 2.2 接口函数
  • 3. 接口函数的实现
    • 3.1 动态申请一个结点 (BuySListNode)
    • 3.2 单链表打印 (SListPrint)
    • 3.3 单链表尾插 (SListPushBack)
    • 3.4 单链表头插 (SListPushFront)
    • 3.5 单链表尾删 (SListPopBack)
    • 3.6 单链表头删 (SListPopFront)
    • 3.7 单链表查找 (SListFind)
    • 3.8 单链表在 pos 位置之前插入 (SListInsert)
    • 3.9 单链表在 pos 位置上删除 (SListErase)
    • 3.10 单链表在 pos 位置之后插入 (SListInsertAfter)
    • 3.11 单链表在 pos 位置之后删除 (SListEraseAfter)
    • 3.12 单链表销毁 (SListDestroy)
  • 4. 完整代码

1. 线性表的链式表示: 链表

1.1 顺序表的优缺点

在介绍链表之前, 先回顾一下顺序表的优缺点
在这里插入图片描述

  • 顺序表的优点:

存储密度高: 无需为表示表中元素之间的逻辑关系而增加额外的存储空间.
随机访问: 通过首地址和元素序号可以在时间 O ( 1 ) O(1) O(1) 内找到指定的元素.

  • 顺序表的缺点:

顺序表逻辑上相邻的元素物理上也相邻, 插入和删除操作需要移动大量元素,时间复杂度为 O ( N ) O(N) O(N).
当线性表长度变化较大时, 难以确定存储空间的容量
造成存储空间的"碎片"

1.2 链表的概念

为了避免顺序表插入和删除的线性开销, 我们允许表可以不连续存储, 否则表的部分或全部需要整体移动.
在这里插入图片描述

链表由一系列不必在内存中相连的结构组成.

每个结构均含有表元素和指向包含该元素后继元的结构的指针.我们称为 next 指针.最后一个单元的 next 指针指向 NULL ;该值由C定义并且不能与其他指针混淆.ANSI C 规定 NULL 为 0

如果 P 被声明为一个指向一个结构的指针, 那么存储在 P中的值就被解释为主存中的一个位置, 在该位置能够找到一个结构.

该结构的一个域可以通过 P->FieldName 访问, 其中 FieldName 是我们想要考察的域的名字.


以下是表的具体表示, 即物理存储结构. 这个表含有 5 个结构, 内存分给它们的位置分别是 1000, 800, 712, 992 和 692.

第一个结构的指针含有值 800, 它提供了第二个结构所在的位置. 其余每个结构也都有一个指针用于类似的目的. 通过指针值, 可以访问到下一结构体的位置. 为了访问该表, 需要知道该表的第一个单元.

在这里插入图片描述

1.3 链表的优缺点

由此可以分析出单链表的优缺点:

  • 链表的优点

更加合理使用空间: 按需申请空间, 不用则释放空间
元素的插入和删除效率更高, 只需要 O ( 1 ) O(1) O(1) 的时间
不存在空间浪费

  • 链表的缺点

访问速度慢: 查找某一元素需要从头开始依次查找, 消耗 O ( N ) O(N) O(N) 的时间
存储密度低: 每个元素还需要额外空间存放指针空间, 用于将表中数据链接起来

1.4 链表的结构

typedef int SLTDateType;typedef struct SListNode
{SLTDateType data;struct SListNode* next;
}SListNode;
  • 逻辑结构: 便于理解想象出来的, 实际不存在, 一般用箭头表示, 实际箭头不存在
    在这里插入图片描述

  • 物理结构: 主存中实际的存储方式, 不一定是处在连续存储的空间
    在这里插入图片描述

2. 单链表的定义

2.1 单链表的结构体

在这里插入图片描述

typedef int SLTDateType;typedef struct SListNode
{SLTDateType data;struct SListNode* next;
}SListNode;
  1. 为了后续代码便于复用, 使用 typedef 将数据类型和结构体类型重命名
  1. 每一个单链表结点包含两个域: 数据域data 和指针域next
    struct SListNode* next实现结构体指向结构体

2.2 接口函数

// 动态申请一个结点
SListNode* BuySListNode(SLTDataType x);
// 单链表打印
void SListPrint(SListNode* pHead);
// 单链表尾插
void SListPushBack(SListNode** ppHead, SLTDataType x);
// 单链表头插
void SListPushFront(SListNode** ppHead, SLTDataType x);
// 单链表尾删
void SListPopBack(SListNode** pHead);
// 单链表头删
void SListPopFront(SListNode** pHead);
// 单链表查找
SListNode* SListFind(SListNode* pHead, SLTDataType x);
// 单链表在 pos 位置之前插入
void SListInsert(SListNode** ppHead, SListNode* pos, SLTDataType x);
// 单链表在 pos 位置上删除
void SListErase(SListNode** ppHead, SListNode* pos);
// 单链表在 pos 位置之后插入
void SListInsertAfter(SListNode* pos, SLTDataType x);
// 单链表在 pos 位置之后删除
void SListEraseAfter(SListNode* pos);
// 单链表的销毁
void SListDestroy(SListNode** ppHead);

3. 接口函数的实现

3.1 动态申请一个结点 (BuySListNode)

单链表插入元素必然要新向内存动态申请一个结点的空间, 这个操作可以直接封装成一个函数, 便于后续创建结点使用.

  • SList.h
SListNode* BuySListNode(SLTDataType x);
  • SList.c
// 动态申请一个结点
SListNode* BuySListNode(SLTDataType x)
{SListNode* newNode = (SListNode*)malloc(sizeof(SListNode));if (newNode == NULL){perror("BuySListNode:");exit(-1);}newNode->data = x;newNode->next = NULL;return newNode;
}
  • test.c
void SListTest1()
{SListNode* newNode = BuySListNode(3);free(newNode);
}int main(void)
{SListTest1();return 0;
}

通过调试,创建结点成功:
在这里插入图片描述

3.2 单链表打印 (SListPrint)

在这里插入图片描述

通过plist的地址访问到第一个结点, 打印该结点的数据值, 同时访问下一结点直至该结点是空指针.

在访问链表的时候, 最好使用一个临时指针来访问, 避免后续增删元素的时候对链表首地址进行修改

  • SList.h
void SListPrint(SListNode* pHead);
  • SList.c
// 单链表打印
void SListPrint(SListNode* pHead)
{SListNode* cur = pHead;  //定义一个局部变量指针用来访问链表//从头依次访问链表, cur不为空进入循环while(cur != NULL){printf("%d -> ", cur->data);    //打印结构的数据域cur = cur->next;    //使cur指向下一结点}printf("NULL\n");
}
  • test.c
    后续其他功能测试都会用到, 这里就先不测试了

3.3 单链表尾插 (SListPushBack)

在这里插入图片描述

创建一个新结点, 让原最后一个结点指向新结点, 同时新结点指向空指针.

  • SList.h
void SListPushBack(SListNode** ppHead, SLTDateType x);

涉及对结构体指针的修改, 需要用到二级指针.虽然是尾插,但如果该链表没有一个元素, 就需要将新结点的地址赋值给实参pList;想要函数修改指针的值,就需要形参是二级指针,也就是函数需要传址调用.

在这里插入图片描述

  • SList.c
// 单链表尾插
void SListPushBack(SListNode** ppHead, SLTDateType x);
{SListNode* newNode = BuySListNode(x);   //创建一个新结点if(*ppHead == NULL)    //没有结点, 直接将新结点的地址赋值{*ppHead = newNode;  //修改结构体指针, 需要用到二级指针}else    //有结点, 依次访问直到最后一个元素{SListNode* tail = *ppHead;  //tail 用于访问链表while (tail->next != NULL){tail = tail->next;  }tail->next = newNode;   //修改结构体, 只要用到一级指针}
}
  1. 首先创建一个新结点newNode
  1. 接着判断链表是否为空,若为空,则直接使用二级指针将新结点的地址赋值给实参pList
  1. 若链表不为空,则只需要修改结构体的内容,用到一级指针即可.当访问到该结点的next为空时,进行尾插操作.
  • test.c
void SListTest1()
{SListNode* pList = NULL;SListPushBack(&pList, 1);SListPushBack(&pList, 2);SListPushBack(&pList, 3);SListPushBack(&pList, 4);SListPushBack(&pList, 5);SListPushBack(&pList, 6);SListPrint(pList);SListDestroy(&pList);
}int main(void)
{SListTest1();return 0;
}

测试运行结果如下:
在这里插入图片描述

3.4 单链表头插 (SListPushFront)

在这里插入图片描述

修改pList,让pList指向新结点,同时新结点的next指向原链表第一个结点

  • SList.h
void SListPushFront(SListNode** ppHead, SLTDateType x);

同样这里需要修改实参pList的值,函数的形参需要传入二级指针.

  • SList.c
// 单链表的头插
void SListPushFront(SListNode** ppHead, SLTDateType x)
{SListNode* newNode = BuySListNode(x);   //创建新结点newNode->next = *ppHead;    //新结点指向原链表第一个结点*ppHead = newNode;          //更新头结点
}
  1. 首先创建新结点newNode
  1. 随后先将新结点指向原头结点, 再更头结点的值.
    注意这两步不可颠倒,否则头结点的地址就丢失了
  • test.c
void SListTest1()
{SListNode* pList = NULL;SListPushBack(&pList, 1);SListPushBack(&pList, 2);SListPushBack(&pList, 3);SListPushBack(&pList, 4);SListPushBack(&pList, 5);SListPushBack(&pList, 6);SListPrint(pList);SListPushFront(&pList, -1);SListPushFront(&pList, -2);SListPushFront(&pList, -3);SListPushFront(&pList, -4);SListPushFront(&pList, -5);SListPushFront(&pList, -6);SListPrint(pList);SListDestroy(&pList);
}int main(void)
{SListTest1();return 0;
}

测试结果如下:
在这里插入图片描述

3.5 单链表尾删 (SListPopBack)

在这里插入图片描述

注意分三种情况

  • SList.h
void SListPopBack(SListNode** ppHead);

同样有修改结构体指针的情况,需要用到二级指针

  • SList.c
// 单链表的尾删
void SListPopBack(SListNode** ppHead)
{assert(*ppHead);    //没有结点的情况if ((*ppHead)->next == NULL)  //只有一个结点的情况{free(*ppHead);*ppHead = NULL; //修改实参的值需要用到二级指针}else    //有两个结点以上的情况{SListNode* tail = *ppHead;   //tail用来访问结构体成员while (tail->next->next != NULL) //需要修改倒数第二个结点的值, 则访问到倒数第二个结点即停止{tail = tail->next;}free(tail->next);  //先释放最后一个结点的空间tail->next = NULL;  //倒数第二个结点指向NULL}
}
  1. 若链表为空,没有结点,程序直接结束
  1. 若只有一个结点,则需要用到二级指针来修改实参pList的值为NULL,同时释放头结点空间
  1. 若有两个及以上结点,只用修改结构体.若要修改倒数第二个结点的值,则只要访问到倒数第二个结点就可以了.这是单向链表,不可返回访问
  • test.c
void SListTest2()
{SListNode* pList = NULL;SListPushBack(&pList, 1);SListPushBack(&pList, 2);SListPushBack(&pList, 3);SListPushBack(&pList, 4);SListPushBack(&pList, 5);SListPushBack(&pList, 6);SListPrint(pList);SListPopBack(&pList);SListPopBack(&pList);SListPrint(pList);SListPopBack(&pList);SListPopBack(&pList);SListPopBack(&pList);SListPrint(pList);  SListPopBack(&pList);       //一个结点SListPrint(pList);SListPopBack(&pList);       //没有结点SListPrint(pList);SListDestroy(&pList);
}int main(void)
{SListTest2();return 0;
}

测试运行结果如下:
在这里插入图片描述

3.6 单链表头删 (SListPopFront)

在这里插入图片描述

单链表头删则只有两种情况,若链表为空直接终止程序.链表不为空,使pList指向头结点指向的空间.

  • SList.h
void SListPopFront(SListNode** ppHead);

同样有修改结构体指针的情况,需要用到二级指针

  • SList.c
// 单链表头删
void SListPopFront(SListNode** ppHead)
{assert(*ppHead);    //链表为空//链表非空SListNode* newNode = (*ppHead)->next;   //记录新的头结点free(*ppHead);      //释放原头结点空间*ppHead = newNode;  //更新头结点
}
  1. 首先判断链表是否为空,若链表为空程序直接结束
  1. 接着先记录新的头结点,释放原头结点空间后,更新头结点.
    同样两个操作不可颠倒,否则原头结点失去位置,造成内存泄漏
  • test.c
void SListTest3()
{SListNode* pList = NULL;SListPushBack(&pList, 1);SListPushBack(&pList, 2);SListPushBack(&pList, 3);SListPushBack(&pList, 4);SListPushBack(&pList, 5);SListPushBack(&pList, 6);SListPrint(pList);SListPopFront(&pList);SListPopFront(&pList);SListPrint(pList);SListPopFront(&pList);SListPopFront(&pList);SListPopFront(&pList);SListPrint(pList);  SListPopFront(&pList);      SListPrint(pList);SListPopFront(&pList);       //没有结点SListPrint(pList);SListDestroy(&pList);
}int main(void)
{SListTest3();return 0;
}

测试运行结果如下:
在这里插入图片描述

3.7 单链表查找 (SListFind)

在这里插入图片描述

从头依次访问每一个结点,并与要查找的值进行比较,若找到则直接返回该结点的地址,若找不到则返回空指针.

  • SList.h
SListNode* SListFind(SListNode* pHead, SLTDateType x);
  • SList.c
// 单链表查找
SListNode* SListFind(SListNode* pHead, SLTDateType x)
{SListNode* cur = pHead; //cur访问每个结点while (cur != NULL){if (cur->data == x){return cur;}cur = cur->next;}return NULL;
}
  • test.c
void SListTest4()
{SListNode* pList = NULL;SListPushBack(&pList, 1);SListPushBack(&pList, 2);SListPushBack(&pList, 3);SListPushBack(&pList, 4);SListPushBack(&pList, 5);SListPushBack(&pList, 6);SListPrint(pList);SListNode* pos;pos = SListFind(pList, 3);SListPrint(pos);pos = SListFind(pList, 6);SListPrint(pos);pos = SListFind(pList, 8);SListPrint(pos);SListDestroy(&pList);
}int main(void)
{SListTest4();return 0;
}

测试运行结果如下:
在这里插入图片描述

3.8 单链表在 pos 位置之前插入 (SListInsert)

pos位置的之前插入分为两种情况: pos 指向头结点 和 其他情况
在这里插入图片描述

  • SList.h
void SListInsert(SListNode** ppHead, SListNode* pos, SLTDataType x);

涉及对实参 pList 的修改需要用到二级指针

  • SList.c
// 单链表在 pos 位置之前插入
void SListInsert(SListNode** ppHead, SListNode* pos, SLTDataType x)
{assert(ppHead);assert(pos);SListNode* newNode = BuySListNode(x);	//创建新结点if (*ppHead == pos)	//如果pos指向第一个结点{//头插newNode->next = *ppHead;*ppHead = newNode;}else{SListNode* prev = *ppHead;	//prev用于访问到pos前一个结点的位置while (prev->next != pos){prev = prev->next;}//插入newNode->next = pos;prev->next = newNode;}
}
  1. 首先确保 ppHeadpos 值合法
  1. 创建新结点后, 判断 pos 是否指向头结点, 若指向头结点, 直接头插操作就可以了, 注意要使用二级指针以修改实参 pList 的值
  1. pos 不指向头结点, 因为要涉及对 pos 指向结点的前一个结点进行修改, 所以定义 prev 顺序访问单链表直至访问到 pos 指向结点的前一个结点. 这时进行插入操作, 直接修改 newNodeprevnext 即可
  • test.c
void SListTest5()
{SListNode* pList = NULL;SListPushBack(&pList, 1);SListPushBack(&pList, 2);SListPushBack(&pList, 3);SListPushBack(&pList, 4);SListPushBack(&pList, 5);SListPushBack(&pList, 6);SListPrint(pList);SListNode* pos;pos = SListFind(pList, 1);if (pos != NULL){SListInsert(&pList, pos, -1);}SListPrint(pList);pos = SListFind(pList, 5);if (pos != NULL){SListInsert(&pList, pos, -5);}SListPrint(pList);pos = SListFind(pList, 10);if (pos != NULL){SListInsert(&pList, pos, -10);}SListPrint(pList);SListDestroy(&pList);}int main(void)
{SListTest5();return 0;
}

测试运行结果如下:
在这里插入图片描述

3.9 单链表在 pos 位置上删除 (SListErase)

单链表在 pos 位置上删除, 也是有两种情况: 删除的是头结点 和 删除的不是头结点

在这里插入图片描述

  • SList.h
void SListErase(SListNode** ppHead, SListNode* pos);

涉及对实参 pList 的修改需要用到二级指针

  • SList.c
// 单链表在 pos 位置上删除
void SListErase(SListNode** ppHead, SListNode* pos)
{	assert(ppHead);assert(pos);if (*ppHead == pos)	//如果 pos 指向第一个结点{				*ppHead = pos->next;free(pos);		//释放空间}else	//有两个及以上结点{SListNode* prev = *ppHead;	//prev 访问到pos之前的一个结点while (prev->next != pos){	prev = prev->next;}//删除prev->next = pos->next;free(pos);}
}
  1. 首先确保 ppHeadpos 的值合法
  1. 接着判断 pos 是否指向头结点, 如果指向头结点, 则按照头删的方式进行删除结点
  1. 如果 pos 不指向头结点, 因为需要修改 pos 前面的结点的数据, 所以定义了变量 prev, 顺序访问单链表直至访问到 pos 位置结点的前一个结点, 直接 prev->next = pos->next
  • test.c
void SListTest6()
{SListNode* pList = NULL;SListPushBack(&pList, 1);SListPushBack(&pList, 2);SListPushBack(&pList, 3);SListPushBack(&pList, 4);SListPushBack(&pList, 5);SListPushBack(&pList, 6);SListPrint(pList);SListNode* pos;pos = SListFind(pList, 1);if (pos != NULL){SListErase(&pList, pos);}SListPrint(pList);pos = SListFind(pList, 5);if (pos != NULL){SListErase(&pList, pos);;}SListPrint(pList);pos = SListFind(pList, 10);if (pos != NULL){SListErase(&pList, pos);}SListPrint(pList);SListDestroy(&pList);
}int main(void)
{SListTest6();return 0;
}

测试运行结果如下:
在这里插入图片描述

3.10 单链表在 pos 位置之后插入 (SListInsertAfter)

直接插入即可, 时间复杂度相比 SListInsert 要少很多
在这里插入图片描述

  • SList.h
void SListInsertAfter(SListNode* pos, SLTDataType x);
  • SList.c
// 单链表在 pos 位置后插入
void SListInsertAfter(SListNode* pos, SLTDataType x)
{assert(pos);	//确保插入位置合法SListNode* newNode = BuySListNode(x);newNode->next = pos->next;pos->next = newNode;
}		 
  1. 确保 pos 的值合法
  1. 注意 newNode->next = pos->next;pos->next = newNode;的顺序不可以颠倒, 否则会出现 newNode 指向自己的情况
  • test.c
void SListTest7()
{SListNode* pList = NULL;SListPushBack(&pList, 1);	SListPushBack(&pList, 2);	SListPushBack(&pList, 3);	SListPushBack(&pList, 4);	SListPushBack(&pList, 5);	SListPushBack(&pList, 6);	SListPrint(pList);SListNode* pos;pos = SListFind(pList, 1);if (pos != NULL){SListInsertAfter(pos, -1);}SListPrint(pList);	pos = SListFind(pList, 6);if (pos != NULL){SListInsertAfter(pos, -6);}SListPrint(pList);pos = SListFind(pList, 10);if (pos != NULL){SListInsertAfter(pos, -10);}SListPrint(pList);SListDestroy(&pList);
}int main(void)
{SListTest7();return 0;
}

测试运行结果如下:
在这里插入图片描述

3.11 单链表在 pos 位置之后删除 (SListEraseAfter)

直接改变 pos 位置结点的指向即可, pos 不可指向链表结尾

在这里插入图片描述

  • SList.h
void SListEraseAfter(SListNode* pos);
  • SList.c
// 单链表在 pos 位置之后删除
void SListEraseAfter(SListNode* pos)
{assert(pos);	//确保删除位置合法assert(pos->next);SListNode* deleteNode = pos->next;pos->next = deleteNode->next;free(deleteNode);
}
  1. 确保 pospos->next 合法
  1. 将要删除的结点命名为 deleteNode, 随后直接修改 pos 位置的 next, 并释放空间即可
  • test.c
void SListTest7()
{SListNode* pList = NULL;SListPushBack(&pList, 1);	SListPushBack(&pList, 2);	SListPushBack(&pList, 3);	SListPushBack(&pList, 4);	SListPushBack(&pList, 5);	SListPushBack(&pList, 6);	SListPrint(pList);SListNode* pos;pos = SListFind(pList, 1);if (pos != NULL){SListEraseAfter(pos);}SListPrint(pList);pos = SListFind(pList, 4);if (pos != NULL){SListEraseAfter(pos);}SListPrint(pList);pos = SListFind(pList, 6);if (pos != NULL){SListEraseAfter(pos);}SListPrint(pList); SListDestroy(&pList);
}int main(void)
{SListTest7();return 0;
}

测试运行结果如下:
在这里插入图片描述

3.12 单链表销毁 (SListDestroy)

  • SList.h
void SListDestroy(SListNode** ppHead);

涉及对实参 pList 的修改需要用到二级指针

  • SList.c
// 单链表销毁
void SListDestroy(SListNode** ppHead)
{assert(ppHead);SListNode* cur = *ppHead;while (cur != NULL){SListNode* next = cur->next;free(cur);cur = next;}*ppHead = NULL;
}

顺序访问链表并依次释放结点空间


由此可以看出: 单链表更多的还是头插和头删是最便利的, 在后面的复杂数据结构中会用到单链表, 算法相关笔试题也是单链表居多(单链表的坑比较多!)

4. 完整代码

  • SList.h
#pragma once#include <stdio.h>
#include <stdlib.h>
#include <assert.h>typedef int SLTDataType;
//单链表结点结构体
typedef struct SListNode
{SLTDataType data;struct SListNode* next;
}SListNode;//动态申请一个结点
SListNode* BuySListNode(SLTDataType x);
//单链表打印
void SListPrint(SListNode* pHead);
//单链表尾插
void SListPushBack(SListNode** ppHead, SLTDataType x);
//单链表头插
void SListPushFront(SListNode** ppHead, SLTDataType x);
//单链表尾删
void SListPopBack(SListNode** pHead);
//单链表头删
void SListPopFront(SListNode** pHead);
//单链表查找
SListNode* SListFind(SListNode* pHead, SLTDataType x);
//单链表在 pos 位置之前插入
void SListInsert(SListNode** ppHead, SListNode* pos, SLTDataType x);
//单链表在 pos 位置上删除
void SListErase(SListNode** ppHead, SListNode* pos);
//单链表在 pos 位置之后插入
void SListInsertAfter(SListNode* pos, SLTDataType x);
//单链表在 pos 位置之后删除
void SListEraseAfter(SListNode* pos);
//单链表的销毁
void SListDestroy(SListNode** ppHead);
  • SList.c
#include "SList.h"// 动态申请一个结点
SListNode* BuySListNode(SLTDataType x)
{SListNode* newNode = (SListNode*)malloc(sizeof(SListNode));if (newNode == NULL){perror("BuySListNode:");exit(-1);}newNode->data = x;newNode->next = NULL;return newNode;
}// 单链表打印
void SListPrint(SListNode* pHead)
{SListNode* cur = pHead;  //定义一个局部变量指针用来访问链表//从头依次访问链表, cur不为空进入循环while(cur != NULL){printf("%d -> ", cur->data);    //打印结构的数据域cur = cur->next;    //使cur指向下一结点}printf("NULL\n");
}// 单链表尾插
void SListPushBack(SListNode** ppHead, SLTDataType x)
{assert(ppHead);SListNode* newNode = BuySListNode(x);   //创建一个新结点if(*ppHead == NULL)    //没有结点, 直接将新结点的地址赋值{*ppHead = newNode;  //修改结构体指针, 需要用到二级指针}else    //有结点, 依次访问直到最后一个元素{SListNode* tail = *ppHead;  //tail 用于访问链表while (tail->next != NULL){tail = tail->next;  }tail->next = newNode;   //修改结构体, 只要用到一级指针}
}// 单链表的头插
void SListPushFront(SListNode** ppHead, SLTDataType x)
{SListNode* newNode = BuySListNode(x);   //创建新结点newNode->next = *ppHead;    //新结点指向原链表第一个结点*ppHead = newNode;          //更新头结点
}// 单链表的尾删
void SListPopBack(SListNode** ppHead)
{assert(ppHead);assert(*ppHead);    //没有结点的情况if ((*ppHead)->next == NULL)  //只有一个结点的情况{free(*ppHead);*ppHead = NULL; //修改实参的值需要用到二级指针}else    //有两个结点以上的情况{SListNode* tail = *ppHead;   //tail用来访问结构体成员while (tail->next->next != NULL) //需要修改倒数第二个结点的值, 则访问到倒数第二个结点即停止{tail = tail->next;}free(tail->next);  //先释放最后一个结点的空间tail->next = NULL;  //倒数第二个结点指向NULL}
}// 单链表头删
void SListPopFront(SListNode** ppHead)
{	assert(ppHead);assert(*ppHead);    //链表为空//链表非空SListNode* newNode = (*ppHead)->next;   //记录新的头结点free(*ppHead);      //释放原头结点空间*ppHead = newNode;  //更新头结点
}// 单链表查找
SListNode* SListFind(SListNode* pHead, SLTDataType x) 
{SListNode* cur = pHead; //cur访问每个结点while (cur != NULL){if (cur->data == x){return cur;}cur = cur->next;}return NULL;
}// 指定位置前插
void SListInsert(SListNode** ppHead, SListNode* pos, SLTDataType x)
{assert(ppHead);assert(pos);SListNode* newNode = BuySListNode(x);	//创建新结点if (*ppHead == pos)	//如果pos指向第一个结点{//头插newNode->next = *ppHead;*ppHead = newNode;}else{SListNode* prev = *ppHead;	//prev用于访问到pos前一个结点的位置while (prev->next != pos){prev = prev->next;}//插入newNode->next = pos;prev->next = newNode;}
}// 单链表在 pos 位置上删除
void SListErase(SListNode** ppHead, SListNode* pos)
{	assert(ppHead);assert(pos);if (*ppHead == pos)	//如果 pos 指向第一个结点{				*ppHead = pos->next;free(pos);		//释放空间}else	//有两个及以上结点{SListNode* prev = *ppHead;	//prev 访问到pos之前的一个结点while (prev->next != pos){	prev = prev->next;}//删除prev->next = pos->next;free(pos);}
}// 单链表销毁
void SListDestroy(SListNode** ppHead)
{assert(ppHead);SListNode* cur = *ppHead;while (cur != NULL){SListNode* next = cur->next;free(cur);cur = next;}*ppHead = NULL;
}// 单链表在 pos 位置后插入
void SListInsertAfter(SListNode* pos, SLTDataType x)
{assert(pos);	//确保插入位置合法SListNode* newNode = BuySListNode(x);newNode->next = pos->next;pos->next = newNode;
}		 // 单链表在 pos 位置之后删除
void SListEraseAfter(SListNode* pos)
{assert(pos);	//确保删除位置合法assert(pos->next);SListNode* deleteNode = pos->next;pos->next = deleteNode->next;free(deleteNode);
}	
  • test.c
#include "SList.h"void SListTest1()
{SListNode* pList = NULL;SListPushBack(&pList, 1);SListPushBack(&pList, 2);SListPushBack(&pList, 3);SListPushBack(&pList, 4);SListPushBack(&pList, 5);SListPushBack(&pList, 6);SListPrint(pList);SListPushFront(&pList, -1);SListPushFront(&pList, -2);SListPushFront(&pList, -3);SListPushFront(&pList, -4);SListPushFront(&pList, -5);SListPushFront(&pList, -6);SListPrint(pList);SListDestroy(&pList);
}void SListTest2()
{SListNode* pList = NULL;SListPushBack(&pList, 1);SListPushBack(&pList, 2);SListPushBack(&pList, 3);SListPushBack(&pList, 4);SListPushBack(&pList, 5);SListPushBack(&pList, 6);SListPrint(pList);SListPopBack(&pList);SListPopBack(&pList);SListPrint(pList);SListPopBack(&pList);SListPopBack(&pList);SListPopBack(&pList);SListPrint(pList);SListPopBack(&pList);SListPrint(pList);SListPopBack(&pList);SListPrint(pList);SListDestroy(&pList);
}void SListTest3()
{SListNode* pList = NULL;SListPushBack(&pList, 1);SListPushBack(&pList, 2);SListPushBack(&pList, 3);SListPushBack(&pList, 4);SListPushBack(&pList, 5);SListPushBack(&pList, 6);SListPrint(pList);SListPopFront(&pList);SListPopFront(&pList);SListPrint(pList);SListPopFront(&pList);SListPopFront(&pList);SListPopFront(&pList);SListPrint(pList);  SListPopFront(&pList);      SListPrint(pList);SListPopFront(&pList);       //没有结点SListPrint(pList);SListDestroy(&pList);
}void SListTest4()
{SListNode* pList = NULL;SListPushBack(&pList, 1);SListPushBack(&pList, 2);SListPushBack(&pList, 3);SListPushBack(&pList, 4);SListPushBack(&pList, 5);SListPushBack(&pList, 6);SListPrint(pList);SListNode* pos;pos = SListFind(pList, 3);SListPrint(pos);pos = SListFind(pList, 6);SListPrint(pos);pos = SListFind(pList, 8);SListPrint(pos);SListDestroy(&pList);
}void SListTest5()
{SListNode* pList = NULL;SListPushBack(&pList, 1);SListPushBack(&pList, 2);SListPushBack(&pList, 3);SListPushBack(&pList, 4);SListPushBack(&pList, 5);SListPushBack(&pList, 6);SListPrint(pList);SListNode* pos;pos = SListFind(pList, 1);if (pos != NULL){SListInsert(&pList, pos, -1);}SListPrint(pList);pos = SListFind(pList, 5);if (pos != NULL){SListInsert(&pList, pos, -5);}SListPrint(pList);pos = SListFind(pList, 10);if (pos != NULL){SListInsert(&pList, pos, -10);}SListPrint(pList);SListDestroy(&pList);}void SListTest6()
{SListNode* pList = NULL;SListPushBack(&pList, 1);SListPushBack(&pList, 2);SListPushBack(&pList, 3);SListPushBack(&pList, 4);SListPushBack(&pList, 5);SListPushBack(&pList, 6);SListPrint(pList);SListNode* pos;pos = SListFind(pList, 1);if (pos != NULL){SListErase(&pList, pos);}SListPrint(pList);pos = SListFind(pList, 5);if (pos != NULL){SListErase(&pList, pos);;}SListPrint(pList);pos = SListFind(pList, 10);if (pos != NULL){SListErase(&pList, pos);}SListPrint(pList);SListDestroy(&pList);
}void SListTest7()
{SListNode* pList = NULL;SListPushBack(&pList, 1);	SListPushBack(&pList, 2);	SListPushBack(&pList, 3);	SListPushBack(&pList, 4);	SListPushBack(&pList, 5);	SListPushBack(&pList, 6);	SListPrint(pList);SListNode* pos;pos = SListFind(pList, 1);if (pos != NULL){SListInsertAfter(pos, -1);}SListPrint(pList);	pos = SListFind(pList, 6);if (pos != NULL){SListInsertAfter(pos, -6);}SListPrint(pList);pos = SListFind(pList, 10);if (pos != NULL){SListInsertAfter(pos, -10);}SListPrint(pList);pos = SListFind(pList, -1);if (pos != NULL){SListEraseAfter(pos);}SListPrint(pList);pos = SListFind(pList, 5);if (pos != NULL){SListEraseAfter(pos);}SListPrint(pList);pos = SListFind(pList, -6);if (pos != NULL){SListEraseAfter(pos);}SListPrint(pList); SListDestroy(&pList);
}int main(void)
{//SListTest1();//SListTest2();//SListTest3();//SListTest4();//SListTest5();//SListTest6();SListTest7();return 0;
}

本章完.

相关文章:

数据结构: 线性表(无哨兵位单链表实现)

文章目录 1. 线性表的链式表示: 链表1.1 顺序表的优缺点1.2 链表的概念1.3 链表的优缺点1.4 链表的结构 2. 单链表的定义2.1 单链表的结构体2.2 接口函数 3. 接口函数的实现3.1 动态申请一个结点 (BuySListNode)3.2 单链表打印 (SListPrint)3.3 单链表尾插 (SListPushBack)3.4 …...

Exploring the Underlying Architecture of CSS3

引言 在现代的网页设计中&#xff0c;CSS&#xff08;层叠样式表&#xff09;起着至关重要的作用。CSS3作为最新的CSS标准&#xff0c;引入了许多令人兴奋的功能和特性。但是&#xff0c;要真正理解CSS3的底层架构实现原理&#xff0c;对于前端开发者来说&#xff0c;是非常重…...

方差分析||判断数据是否符合正态分布

方差分析练习题 练习学习笔记&#xff1a; &#xff08;1&#xff09; 标准差和标准偏差、均方差是一个东西。标准误差和标准误是一个东西。这两个东西有区别。 &#xff08;2&#xff09;单因素方差分析&#xff08;MATLAB求解&#xff09; &#xff08;3&#xff09;使用an…...

java linq多字段排序时间比较

public static void main(String[] args) {//100万条数据List<CrmInvestSaleUserCount> waitAssignUserList new ArrayList<>();for (int i 0; i < 1000000; i) {waitAssignUserList.add(new CrmInvestSaleUserCount().setSales_username("test" i…...

【c++】rand()随机函数的应用(二)——舒尔特方格数字的生成

目录 一、舒尔特方格简介 二、如何生成舒尔特方格 &#xff08;一&#xff09;线性同余法 1、利用线性同余法生成随机数序列的规律 (1) 当a和c选取合适的数时&#xff0c;可以生成周期为m的随机数序列 (2) 种子seed取值也是有周期的 2、利用线性同余法生成5阶舒尔特方格…...

“深入剖析JVM内部机制:探索Java虚拟机的运行原理“

标题&#xff1a;深入剖析JVM内部机制&#xff1a;探索Java虚拟机的运行原理 摘要&#xff1a;本文将深入探讨Java虚拟机&#xff08;JVM&#xff09;的内部机制&#xff0c;包括类加载、内存管理、垃圾回收、即时编译等关键概念和原理&#xff0c;帮助开发者更好地理解JVM的运…...

pandas 新增数据列的几种方式

准备数据 将下面的数据存到csv中 ymd,bWendu,yWendu,tianqi,fengxiang,fengli,aqi,aqiInfo,aqiLevel 2018-01-01,3℃,-6℃,晴~多云,东北风,1-2级,59,良,2 2018-01-02,2℃,-5℃,阴~多云,东北风,1-2级,49,优,1 2018-01-03,2℃,-5℃,多云,北风,1-2级,28,优,1 2018-01-04,0℃,-8℃…...

linux_驱动_iic总线获取si7006温湿度

应用层si7006.c #include<stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #include <stdlib.h> #include <string.h> #include <sys/ioctl.h> #include <arpa/inet.h>…...

虚拟机网络图标不见了

有3台虚拟机之前正常运行的&#xff0c;有一天打开虚拟机发现2台虚拟机的网络连接图标不见了&#xff0c;也ping不通另外两台。 解决&#xff1a;在终端执行以下命令&#xff0c;即可ping通 [roothadoop103 ~]# sudo nmcli network off [roothadoop103 ~]# sudo nmcli network…...

CTF:信息泄露.(CTFHub靶场环境)

CTF&#xff1a;信息泄露.&#xff08;CTFHub靶场环境&#xff09; “ 信息泄露 ” 是指网站无意间向用户泄露敏感信息&#xff0c;泄露了有关于其他用户的数据&#xff0c;例如&#xff1a;另一个用户名的财务信息&#xff0c;敏感的商业 或 商业数据 &#xff0c;还有一些有…...

Redis学习总结

Redis学习总结 文章目录 Redis学习总结Radis基本介绍docker的安装基本数据结构通用命令字符型key的层次结构Hash类型Listset sortedset集合redis的java客户端jedis的使用jedis连接池的配置 SpringDataRedis自定义redistemplate的序列化与反序列化方式stringtemplate的使用 redi…...

云原生全栈体系(二)

Kubernetes实战入门 第一章 Kubernetes基础概念 一、是什么 我们急需一个大规模容器编排系统kubernetes具有以下特性&#xff1a; 服务发现和负载均衡 Kubernetes 可以使用 DNS 名称或自己的 IP 地址公开容器&#xff0c;如果进入容器的流量很大&#xff0c;Kubernetes 可以负…...

C++设计模式之建造者设计模式

C建造者设计模式 什么是建造者设计模式 建造者设计模式是一种创建型设计模式&#xff0c;它是一种将复杂对象的分解为多个独立部分的模式&#xff0c;以便于构建对象的过程可以被抽象出来并独立变化。 该模式有什么优缺点 优点 灵活性&#xff1a;建造者设计模式允许对象的…...

HDFS Erasure coding-纠删码介绍和原理

HDFS Erasure coding-纠删码介绍和原理 三副本策略弊端Erasure Coding&#xff08;EC&#xff09;简介Reed- Solomon&#xff08;RS&#xff09;码 EC架构 三副本策略弊端 为了提供容错能力&#xff0c;hdfs回根据replication factor&#xff08;复制因子&#xff09;在不同的…...

STM32 DHT11

DHT11 DHT11数字温湿度传感器是一款含有已校准数字信号输出的温湿度复合传感器。 使用单总线通信 该传感器包括一个电容式感湿元件和一个NTC测温元件&#xff0c;并于一个高性能8位单片机相连&#xff08;模数转换&#xff09;。 DHT11引脚说明 开漏模式下没有输出高电平的能…...

词法分析器

词法分析器 在早期编译1.0时代&#xff0c;我们的目标是完成程序语言到机器语言的翻译&#xff0c;所以重点在编译器前端&#xff0c;于是我们花费大量时间研究词法分析、语法分析、语义分析等内容。如今的本科编译原理课程&#xff0c;基本上也就到这一层面吧。 在编译2.0时…...

【Spring】Spring之启动过程源码解析

概述 我们说的Spring启动&#xff0c;就是构造ApplicationContext对象以及调用refresh()方法的过程。 Spring启动过程主要做了这么几件事情&#xff1a; 构造一个BeanFactory对象解析配置类&#xff0c;得到BeanDefinition&#xff0c;并注册到BeanFactory中 解析ComponentS…...

状态模式(State)

状态模式是一种行为设计模式&#xff0c;允许一个对象在其内部状态改变时改变它的行为&#xff0c;使其看起来修改了自身所属的类。其别名为状态对象(Objects for States)。 State is a behavior design pattern that allows an object to change its behavior when its inter…...

【uniapp】样式合集

1、修改uni-data-checkbox多选框的样式为单选框的样式 我原先是用的单选&#xff0c;但是单选并不支持选中后&#xff0c;再次点击取消选中&#xff1b;所以我改成了多选&#xff0c;然后改变多选样式&#xff0c;让他看起来像单选 在所在使用的页面上修改样式即可 <uni-d…...

【Spring框架】SpringBoot统一功能处理

目录 用户登录权限校验用户登录拦截器排除所有静态资源练习&#xff1a;登录拦截器拦截器实现原理 统一异常处理统一数据返回格式为什么需要统⼀数据返回格式&#xff1f;统⼀数据返回格式的实现 用户登录权限校验 用户登录拦截器 1.自定义拦截器 package com.example.demo.…...

汽车生产虚拟实训中的技能提升与生产优化​

在制造业蓬勃发展的大背景下&#xff0c;虚拟教学实训宛如一颗璀璨的新星&#xff0c;正发挥着不可或缺且日益凸显的关键作用&#xff0c;源源不断地为企业的稳健前行与创新发展注入磅礴强大的动力。就以汽车制造企业这一极具代表性的行业主体为例&#xff0c;汽车生产线上各类…...

基于Uniapp开发HarmonyOS 5.0旅游应用技术实践

一、技术选型背景 1.跨平台优势 Uniapp采用Vue.js框架&#xff0c;支持"一次开发&#xff0c;多端部署"&#xff0c;可同步生成HarmonyOS、iOS、Android等多平台应用。 2.鸿蒙特性融合 HarmonyOS 5.0的分布式能力与原子化服务&#xff0c;为旅游应用带来&#xf…...

如何在看板中有效管理突发紧急任务

在看板中有效管理突发紧急任务需要&#xff1a;设立专门的紧急任务通道、重新调整任务优先级、保持适度的WIP&#xff08;Work-in-Progress&#xff09;弹性、优化任务处理流程、提高团队应对突发情况的敏捷性。其中&#xff0c;设立专门的紧急任务通道尤为重要&#xff0c;这能…...

第一篇:Agent2Agent (A2A) 协议——协作式人工智能的黎明

AI 领域的快速发展正在催生一个新时代&#xff0c;智能代理&#xff08;agents&#xff09;不再是孤立的个体&#xff0c;而是能够像一个数字团队一样协作。然而&#xff0c;当前 AI 生态系统的碎片化阻碍了这一愿景的实现&#xff0c;导致了“AI 巴别塔问题”——不同代理之间…...

相机从app启动流程

一、流程框架图 二、具体流程分析 1、得到cameralist和对应的静态信息 目录如下: 重点代码分析: 启动相机前,先要通过getCameraIdList获取camera的个数以及id,然后可以通过getCameraCharacteristics获取对应id camera的capabilities(静态信息)进行一些openCamera前的…...

Spring数据访问模块设计

前面我们已经完成了IoC和web模块的设计&#xff0c;聪明的码友立马就知道了&#xff0c;该到数据访问模块了&#xff0c;要不就这俩玩个6啊&#xff0c;查库势在必行&#xff0c;至此&#xff0c;它来了。 一、核心设计理念 1、痛点在哪 应用离不开数据&#xff08;数据库、No…...

浪潮交换机配置track检测实现高速公路收费网络主备切换NQA

浪潮交换机track配置 项目背景高速网络拓扑网络情况分析通信线路收费网络路由 收费汇聚交换机相应配置收费汇聚track配置 项目背景 在实施省内一条高速公路时遇到的需求&#xff0c;本次涉及的主要是收费汇聚交换机的配置&#xff0c;浪潮网络设备在高速项目很少&#xff0c;通…...

代码随想录刷题day30

1、零钱兑换II 给你一个整数数组 coins 表示不同面额的硬币&#xff0c;另给一个整数 amount 表示总金额。 请你计算并返回可以凑成总金额的硬币组合数。如果任何硬币组合都无法凑出总金额&#xff0c;返回 0 。 假设每一种面额的硬币有无限个。 题目数据保证结果符合 32 位带…...

Selenium常用函数介绍

目录 一&#xff0c;元素定位 1.1 cssSeector 1.2 xpath 二&#xff0c;操作测试对象 三&#xff0c;窗口 3.1 案例 3.2 窗口切换 3.3 窗口大小 3.4 屏幕截图 3.5 关闭窗口 四&#xff0c;弹窗 五&#xff0c;等待 六&#xff0c;导航 七&#xff0c;文件上传 …...

Windows安装Miniconda

一、下载 https://www.anaconda.com/download/success 二、安装 三、配置镜像源 Anaconda/Miniconda pip 配置清华镜像源_anaconda配置清华源-CSDN博客 四、常用操作命令 Anaconda/Miniconda 基本操作命令_miniconda创建环境命令-CSDN博客...