一、单链表的概念及性质
1、链表的概念
- 链表是一种物理存储结构上非连续、非顺序的储存结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。
我们看上图,一个链表就很像一节节车厢一样,和顺序表不同的是,链表里的每节“车厢”都是独立申请下来的空间,我们称这一节节车厢为“结点”。我们看上图会发现结点的组成主要有两个部分:当前结点要保存的数据和保存下一个结点的地址(指针变量)。图中指针变量plist保存的是第一个结点的地址,所以我们称plist此时指向第一个结点。如果我们想让plist指向第二个又或者其他结点,只需要修改plist保存的地址内容即可。链表中每个结点都是独立申请的,我们需要通过指针变量来保存下一个结点的位置才能从当前结点找到下一个结点。
2、性质
- 链式结构在逻辑上是连续的,在物理结构上不一定连续。
- 结点一般是从堆上申请的。
- 从堆上申请来的空间,是按照一定策略分配出来的,每次申请空间可能连续也可能不连续。
二、实现单项链表
1、准备工作
- 创建三个文件(如下图所示)
- 头文件SList.h:用来声明函数,定义链表
2.源文件SList.c:用来实现函数
3.测试文件test.c:测试工作(写完一个接口函数测试一下,避免后续的麻烦)
2、实现链表的基本接口
(1)创建一个链表
- 在头文件中创建链表结点结构,和顺序表不用的是顺序表是创建一个数组arr和有效数据和容量大小,而链表这是储存一个数据和指向下一个数据的地址。
typedef int SLTDataType;typedef struct SListNode
{SLTDataType data;struct SListNode* next;
}SLTNode;
(2)链表的打印
//打印
void SLTPrint(SLTNode *phead)
{//重新创建一个pcur指针是为了避免指针指向的改变//导致无法重新找到首节点SLTNode* pcur = phead;//链表遍历和数组有很大区别while (pcur != NULL){printf("%d -> ", pcur->data);//打印完后要指向下一个地址pcur = pcur->next;}printf("NULL\n");
}
这里有一个点需要注意:pcur = pcur->next这里是将pcur的下一个值赋值给pcur这样就能打印出链表的下一个结点了。
(3)尾插
- 在尾插之前还要说一个点,就是要创建结点的代码如下所示
创建一个结点 - 顺序表是扩容,这里是创建结点
//创建一个节点
SLTNode* SLTBuyNode(SLTDataType x)
{//给节点开辟一个空间SLTNode* node = (SLTNode*)malloc(sizeof(SLTNode));if (node == NULL){perror("malloc fail!");exit(1);}//如果不是NULL,就把x放在新的节点上node->data = x;node->next = NULL;return node;
}
这下面是尾插的代码:
//尾插
void SLTPushBack(SLTNode **pphead, SLTDataType x)
{SLTNode* newnode = SLTBuyNode(x);//判断是不是空链表if (*pphead == NULL){*pphead = newnode;}else{//如果链表非空,找尾节点SLTNode* ptail = *pphead;while (ptail->next != NULL) //可以写成ptail->next{//遍历找到尾节点ptail = ptail->next;}//找到后跳出循环,将新创建的节点赋值过去ptail->next = newnode;}
}
(4)头插
//头插
void SLTPushFront(SLTNode** pphead, SLTDataType x)
{assert(pphead);SLTNode* newnode = SLTBuyNode(x);newnode->next = *pphead;*pphead = newnode;
}
头插就相对简单,直接创建新的结点然后插入第一个结点,再把创建的结点作为头节点
(5)尾删
//尾删
void SLTPopBack(SLTNode** pphead)
{//分两种情况,只有一个节点的时候if ((*pphead)->next == NULL){free(*pphead);*pphead = NULL;}else {//第一个是传参的时候不能为空//第二个人是链表不能为空assert(pphead && *pphead);//创建两个指针遍历SLTNode* ptail = *pphead;SLTNode* prec = NULL;while (ptail->next){prec = ptail;ptail = ptail->next;}prec->next = NULL;free(ptail);ptail = NULL;}
}
(6)头删
//头删
void SLTPopFront(SLTNode** pphead)
{assert(*pphead && *pphead);SLTNode* next = (*pphead)->next;free(*pphead);*pphead = next;
}
这里注意释放掉删除的结点,记得将下一个结点置为头节点
(7)查找
//查找
SLTNode* SLTFind(SLTNode* phead, SLTDataType x)
{SLTNode* pcur = phead;while (pcur){if (pcur->data == x){return pcur;}pcur = pcur->next;}//没找到return NULL;
}
循环遍历链表,找到就返回,找不到就返回NULL
(8) 在指定之前位置插入数据
//在指定位置之前插入数据
void SLInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{assert(pphead && pos);if (pos == *pphead){//头插SLTPushFront(pphead, x);}else {//创建一个节点SLTNode* newnode = SLTBuyNode(x);SLTNode* prev = *pphead;while (prev->next != pos){//next不是pos的时候继续往下遍历prev = prev->next;}//prev->next是pos的时候newnode->next = pos;prev->next = newnode;}
}
(9) 在指定位置之后插入数据
//在指定位置之后插入数据
void SLInsertAfter(SLTNode* pos, SLTDataType x)
{assert(pos);SLTNode* newnode = SLTBuyNode(x);newnode->next = pos->next;pos->next = newnode;
}
(10)删除pos结点
//删除pos结点
void SLTErase(SLTNode** pphead, SLTNode* pos)
{assert(pphead && pos);if (pos == *pphead){//要删除的节点就是头节点时SLTPopFront(pphead);}else {SLTNode* prev = *pphead;while (prev->next != pos){prev = prev->next;}prev->next = pos->next;free(pos);pos = NULL;}
}
(11)删除pos之后的结点
//删除pos之后的结点
void SLTEraseAfter(SLTNode* pos)
{assert(pos && pos->next);SLTNode* del = pos->next;pos->next = del->next;free(del);del = NULL;
}
(12)链表的销毁
- 有借有还,再借不难
//销毁链表
void SListDestroy(SLTNode** pphead)
{assert(pphead);SLTNode* pcur = *pphead;while (pcur){SLTNode* next = pcur->next;free(pcur);pcur = next;}*pphead = NULL;
}
三、链表的代码总览
1、SList.h
#pragma once
#include <stdio.h>
#include <assert.h>
#include <stdlib.h>typedef int SLTDataType;typedef struct SListNode
{SLTDataType data;struct SListNode* next;
}SLTNode;//打印
void SLTPrint(SLTNode *phead);
//尾插
void SLTPushBack(SLTNode** pphead, SLTDataType x);
//头插
void SLTPushFront(SLTNode** pphead, SLTDataType x);
//尾删
void SLTPopBack(SLTNode** pphead);
//头删
void SLTPopFront(SLTNode** pphead);
//查找
SLTNode* SLTFind(SLTNode* phead,SLTDataType x);
//在指定位置之前插入数据
void SLInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x);
//在指定位置之后插入数据
void SLInsertAfter(SLTNode* pos, SLTDataType x);
//删除pos结点
void SLTErase(SLTNode** pphead, SLTNode* pos);
//删除pos之后的结点
void SLTEraseAfter(SLTNode* pos);
//销毁链表
void SListDestroy(SLTNode** pphead);
2、SList.c
#define _CRT_SECURE_NO_WARNINGS 1
#include "SList.h"//创建一个节点
SLTNode* SLTBuyNode(SLTDataType x)
{//给节点开辟一个空间SLTNode* node = (SLTNode*)malloc(sizeof(SLTNode));if (node == NULL){perror("malloc fail!");exit(1);}//如果不是NULL,就把x放在新的节点上node->data = x;node->next = NULL;return node;
}
//打印
void SLTPrint(SLTNode *phead)
{//重新创建一个pcur指针是为了避免指针指向的改变//导致无法重新找到首节点SLTNode* pcur = phead;//链表遍历和数组有很大区别while (pcur != NULL){printf("%d -> ", pcur->data);//打印完后要指向下一个地址pcur = pcur->next;}printf("NULL\n");
}
//尾插
void SLTPushBack(SLTNode **pphead, SLTDataType x)
{SLTNode* newnode = SLTBuyNode(x);//判断是不是空链表if (*pphead == NULL){*pphead = newnode;}else{//如果链表非空,找尾节点SLTNode* ptail = *pphead;while (ptail->next != NULL) //可以写成ptail->next{//遍历找到尾节点ptail = ptail->next;}//找到后跳出循环,将新创建的节点赋值过去ptail->next = newnode;}
}
//头插
void SLTPushFront(SLTNode** pphead, SLTDataType x)
{assert(pphead);SLTNode* newnode = SLTBuyNode(x);newnode->next = *pphead;*pphead = newnode;
}
//尾删
void SLTPopBack(SLTNode** pphead)
{//分两种情况,只有一个节点的时候if ((*pphead)->next == NULL){free(*pphead);*pphead = NULL;}else {//第一个是传参的时候不能为空//第二个人是链表不能为空assert(pphead && *pphead);//创建两个指针遍历SLTNode* ptail = *pphead;SLTNode* prec = NULL;while (ptail->next){prec = ptail;ptail = ptail->next;}prec->next = NULL;free(ptail);ptail = NULL;}
}
//头删
void SLTPopFront(SLTNode** pphead)
{assert(*pphead && *pphead);SLTNode* next = (*pphead)->next;free(*pphead);*pphead = next;
}
//查找
SLTNode* SLTFind(SLTNode* phead, SLTDataType x)
{SLTNode* pcur = phead;while (pcur){if (pcur->data == x){return pcur;}pcur = pcur->next;}//没找到return NULL;
}
//在指定位置之前插入数据
void SLInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{assert(pphead && pos);if (pos == *pphead){//头插SLTPushFront(pphead, x);}else {//创建一个节点SLTNode* newnode = SLTBuyNode(x);SLTNode* prev = *pphead;while (prev->next != pos){//next不是pos的时候继续往下遍历prev = prev->next;}//prev->next是pos的时候newnode->next = pos;prev->next = newnode;}
}
//在指定位置之后插入数据
void SLInsertAfter(SLTNode* pos, SLTDataType x)
{assert(pos);SLTNode* newnode = SLTBuyNode(x);newnode->next = pos->next;pos->next = newnode;
}
//删除pos结点
void SLTErase(SLTNode** pphead, SLTNode* pos)
{assert(pphead && pos);if (pos == *pphead){//要删除的节点就是头节点时SLTPopFront(pphead);}else {SLTNode* prev = *pphead;while (prev->next != pos){prev = prev->next;}prev->next = pos->next;free(pos);pos = NULL;}
}
//删除pos之后的结点
void SLTEraseAfter(SLTNode* pos)
{assert(pos && pos->next);SLTNode* del = pos->next;pos->next = del->next;free(del);del = NULL;
}
//销毁链表
void SListDestroy(SLTNode** pphead)
{assert(pphead);SLTNode* pcur = *pphead;while (pcur){SLTNode* next = pcur->next;free(pcur);pcur = next;}*pphead = NULL;
}
3、test.c
#define _CRT_SECURE_NO_WARNINGS 1
#include "SList.h"void test1()
{//手动构建一个链表//创建空间SLTNode* node1 = (SLTNode*)malloc(sizeof(SLTNode));SLTNode* node2 = (SLTNode*)malloc(sizeof(SLTNode));SLTNode* node3 = (SLTNode*)malloc(sizeof(SLTNode));SLTNode* node4 = (SLTNode*)malloc(sizeof(SLTNode));//初始化node1->data = 1;node2->data = 2;node3->data = 3;node4->data = 4;//连接节点node1->next = node2;node2->next = node3;node3->next = node4;node4->next = NULL;//打印SLTNode* plist = node1;SLTPrint(plist);
}
//尾插测试
void test2()
{SLTNode* plist = NULL;//这里是取指针的地址,所以要二级指针来接收SLTPushBack(&plist, 1);SLTPushBack(&plist, 2);SLTPushBack(&plist, 3);SLTPushBack(&plist, 4);SLTPushBack(&plist, 5);//打印SLTPrint(plist);
}
//尾删测试
void test3()
{//先插入数据SLTNode* plist = NULL;SLTPushBack(&plist, 1);SLTPushBack(&plist, 2);SLTPushBack(&plist, 3);SLTPushBack(&plist, 4);SLTPushBack(&plist, 5);//打印SLTPrint(&plist);//尾删SLTPopBack(&plist);//打印SLTPrint(plist);
}
//头插测试
void test4()
{SLTNode* plist = NULL;SLTPushFront(&plist, 1);SLTPushFront(&plist, 2);SLTPushFront(&plist, 3);SLTPushFront(&plist, 4);SLTPushFront(&plist, 5);//打印SLTPrint(plist);
}
//头删测试
void test5()
{SLTNode* plist = NULL;SLTPushFront(&plist, 1);SLTPushFront(&plist, 2);SLTPushFront(&plist, 3);SLTPushFront(&plist, 4);SLTPushFront(&plist, 5);//打印SLTPrint(plist);//头删SLTPopFront(&plist);//打印SLTPrint(plist);
}//查找测试
int test6()
{SLTNode* plist = NULL;SLTPushFront(&plist, 1);SLTPushFront(&plist, 2);SLTPushFront(&plist, 3);SLTPushFront(&plist, 4);SLTPushFront(&plist, 5);//查找SLTNode* ret = SLTFind(plist, 6);if (ret == NULL){printf("未找到!");}else {printf("找到了!");}
}//在指定位置之前插入数据测试
void test7()
{SLTNode* plist = NULL;SLTPushFront(&plist, 1);SLTPushFront(&plist, 2);SLTPushFront(&plist, 3);SLTPushFront(&plist, 4);SLTPushFront(&plist, 5);//查找SLTNode* find = SLTFind(plist, 5);SLInsert(&plist, find, 99);//打印SLTPrint(plist);
}
//在指定位置之后插入数据测试
void test8()
{SLTNode* plist = NULL;SLTPushFront(&plist, 1);SLTPushFront(&plist, 2);SLTPushFront(&plist, 3);SLTPushFront(&plist, 4);SLTPushFront(&plist, 5);//查找SLTNode* find = SLTFind(plist, 1);SLInsertAfter(find, 88);//打印SLTPrint(plist);
}
//删除pos结点测试
void test9()
{SLTNode* plist = NULL;SLTPushFront(&plist, 1);SLTPushFront(&plist, 2);SLTPushFront(&plist, 3);SLTPushFront(&plist, 4);SLTPushFront(&plist, 5);//查找SLTNode* find = SLTFind(plist, 5);SLTErase(&plist, find);//打印SLTPrint(plist);
}
//删除pos之后的结点
void test10()
{SLTNode* plist = NULL;SLTPushFront(&plist, 1);SLTPushFront(&plist, 2);SLTPushFront(&plist, 3);SLTPushFront(&plist, 4);//查找SLTNode* find = SLTFind(plist, 1);SLTEraseAfter(find);//打印SLTPrint(plist);
}
//销毁链表测试
void test11()
{SLTNode* plist = NULL;SLTPushFront(&plist, 1);SLTPushFront(&plist, 2);SLTPushFront(&plist, 3);SLTPushFront(&plist, 4);//打印SLTPrint(plist);//销毁SListDestroy(&plist);//打印SLTPrint(plist);
}int main()
{//test1();//test2();//test3();//test4();//test5();//test6();//test7();//test8();//test9();//test10();test11();return 0;
}