这篇文章的主要内容:了解链表的基本结构、链表的基本操作。
一、链表的基本结构
typedef int SLTDataType;
//定义链表数据域数据类型(递归定义中的递定义,因为没有归......)
typedef struct SListNode {
SLTDataType data;//数据域
struct SListNode* next;//指针域
}SLTNode;
//单链表节点数据域存放数据,指针域存放后继结点的指针!!!
//其实也就是一节一节的火车
这里说两点:
1.指针域存放的是后继节点的唯一指针。在后续的操作中,要牢记这一点。(顺序问题,直接修改指针,还是先把该指针存起来)
2.每个节点都是一个指针结构体,而不再是具体的数据类型了(有利于后续代码理解 数据类型)
二、基本操作
1.先看头文件(用来存放一些类似于‘目录’的东西)
#include <stdio.h>
#include <stdlib.h>
#include <assert.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** pphead, SLTDataType x);
//在指定位置之前插入数据
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x);
//在指定位置之后插入数据
void SLTInsertAfter(SLTNode* pos, SLTDataType x);
//删除pos节点
void SLTErase(SLTNode** pphead, SLTNode* pos);
//删除pos之后的节点
void SLTEraseAfter(SLTNode* pos);
//销毁链表
void SListDesTroy(SLTNode** pphead);
这里重点说明一下二级指针的用处
1.首先,我们有一个链表(物理结构),如图:
2.我们用指针变量 plist 指向头节点
通过遍历plist,我们可以找到链表的每一个节点。要修改结构体(节点),我们就用结构体指针。
本质上来说,phead就是个变量,指针变量;头节点是结构体变量。
3.如果我们要通过函数来修改链表,将 plist 的传址给函数。函数调用完之后,plist发生了什么变化?
传值呢?又有什么区别?
代码参考:
#include <stdlib.h>
typedef struct SLNode {
int val;
struct SLnode* next;
}SLNode;
void SLPrint(SLNode* phead) {
SLNode* tail = phead;
while (tail !=NULL) {
printf("%d->", tail->val);
tail = tail->next;
}
printf("NULL");
}
void SLPushFront(SLNode** phead,int x) {
SLNode* newnode = (SLNode*)malloc(sizeof(SLNode));
if (newnode == NULL) {
perror("malloc fail");
return;
}
newnode->val = x;
newnode->next = NULL;
if (*phead == NULL) {
*phead = newnode;
return;
}
newnode->next = *phead;
*phead = newnode;
}
void SLPushFront1(SLNode* phead ) {
phead->val = 100;
}
int main() {
SLNode* plist=NULL;
SLPushFront(&plist, 4);
SLPushFront(&plist, 3);
SLPushFront(&plist, 2);
SLPushFront(&plist, 1);
//SLPushFront1(plist, 100);
SLPrint(plist);
return 0;
}
要修改指针变量plist,我们就用plist的指针(二级指针)。
要修改结构体,我们就用结构体指针(一级指针)。
我们再来一张图片进行加深理解
2.1具体实现对应函数的源文件
#include "SList.h"
void SLTPrint(SLTNode* phead) {
SLTNode* pcur = phead;
while (pcur)//等同于pcur!= NULL
{
printf("%d->", pcur->data);
pcur = pcur->next;
}
printf("NULL\n");
}
SLTNode* SLTBuyNode(SLTDataType x)
{
SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
newnode->data = x;
newnode->next = NULL;
return newnode;
}
//链表的尾插和头插
void SLTPushBack(SLTNode** pphead, SLTDataType x) {
assert(pphead); //pphead指向不能为空
SLTNode* newnode = SLTBuyNode(x);
SLTNode* ptail = *pphead;
//链表可不可以为空?
//如果是空链表,那么尾插的结点即为头结点
if (*pphead == NULL)
{
*pphead = newnode;
return;
}
//如果链表不是空链表,那么找到尾结点,进行尾插
while (ptail->next != NULL)
{
ptail = ptail->next;
}
//循环完之后,ptail此时就是尾结点了。可以进行尾插了。
//newnode->next = NULL;
ptail->next = newnode;
}
void SLTPushFront(SLTNode** pphead, SLTDataType x) {
assert(pphead);
SLTNode* newnode = SLTBuyNode(x);
newnode->next = *pphead;
*pphead = newnode;
}
//链表的尾删、头删
void SLTPopBack(SLTNode** pphead) {
assert(pphead);//头指针地址不能为空
//空指针,也是有地址的,并且地址不为空。
assert(*pphead);//头指针不能为空
//先找到尾结点,在找到尾结点的前驱节点。
SLTNode* ptail = *pphead;//尾结点
SLTNode* prev = NULL;//尾结点的前驱节点
//注:当单链表只有一个节点的时候,是没有前驱节点的,需要特殊处理
//链表只有一个节点时:
if ((*pphead)->next == NULL)
{
free(*pphead);
*pphead = NULL;
return;
}
//链表有多个节点(2个及以上)时
while (ptail->next != NULL) {
prev = ptail;
ptail = ptail->next;
}
prev->next = NULL;
//释放尾结点,并将尾结点的指针域置为空
free(ptail);
ptail= NULL;
}
void SLTPopFront(SLTNode** pphead) {
assert(pphead);
assert(*pphead);
SLTNode* newnode = (*pphead)->next;
free(*pphead);
*pphead = newnode;
}
//查找
SLTNode* SLTFind(SLTNode** pphead, SLTDataType x) {
assert(pphead);
SLTNode* pcur = *pphead;
while (pcur->next!= NULL)
{
if (pcur->data == x)
{
return pcur;
}
pcur = pcur->next;
}
return NULL;
}
//在指定位置之前插入数据
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x) {
assert(pphead);
assert(*pphead);
assert(pos);
//如果pos是头结点,它没有前驱节点,直接头插
if (pos == *pphead)
{
SLTPushFront(pphead,x);
return;
}
//pos不是头节点
SLTNode* prev = *pphead;
while (prev->next != pos) {
prev = prev->next;
}
SLTNode* newnode = SLTBuyNode(x);
newnode->next = prev->next;
prev->next = newnode;
}
//在指定位置之后插入数据
void SLTInsertAfter(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(pos);
assert(pphead);
assert(*pphead);
//如果只有一个头节点,没有前驱节点,直接头删
if (pos == *pphead) {
SLTPopFront(pphead);
return;
}
//有2个及以上的节点,就需要找pos的前驱节点了
SLTNode* prev = *pphead;//pos的前驱节点
while (prev->next != pos) {
prev = prev->next;
}
prev->next = pos->next;
free(pos);
pos = NULL;
}
//删除pos之后的节点
void SLTEraseAfter(SLTNode* pos) {
assert(pos);
assert(pos->next);
//pos pos->next pos->next->next
//pos pos->next->next
SLTNode* del = pos->next;
pos->next = pos->next->next;
//free(pos->next),为什么不行?
free(del);
del = NULL;
}
//销毁链表
void SListDesTroy(SLTNode** pphead) {
assert(pphead);
assert(*pphead);
SLTNode* del = *pphead;
SLTNode* delAfter = NULL;
while (del)
{
delAfter = del->next;
free(del);
del = delAfter;
}
*pphead = NULL;
}
2.2重要点:
要考虑执行当前操作时,单链表是否为空(头插第一个节点时;在指定位置之前插入节点......)也就是说,当我们需要修改节点的前驱节点指针域时,该前驱节点是否存在?如果没有前驱节点,那么问题就比较简单;如果有前驱节点,那么需要注意顺序问题。
什么是顺序问题?就如文章开头所说:每个节点的指针域,存放的都是后继节点的唯一指针,在修改指针域时,是否会使之前的指向丢失?
SLTNode* del = pos->next;
pos->next = pos->next->next;
//free(pos->next),为什么不行?
free(del);
del = NULL;
举个例子,以上代码,为什么需要新节点del?为什么不能直接free?
3.用于测试的源文件
#define _CRT_SECURE_NO_WARNINGS 1
#include "SList.h"
void test0()
{
SLTNode* plist = NULL;
SLTPrint(plist);
//尾插测试
SLTPushBack(&plist, 1);
SLTPrint(plist);
SLTPushBack(&plist, 2);
SLTPrint(plist);
SLTPushBack(&plist, 3);
SLTPrint(plist);
SLTPushBack(&plist, 4);
SLTPrint(plist);
printf("\n");
//头插测试
//SLTPushFront(&plist, 200);
//SLTPrint(plist);
//SLTPushFront(&plist, 0);
//SLTPrint(plist);
//尾删
//SLTPopBack(&plist);
//SLTPrint(plist);
//SLTPopBack(&plist);
//SLTPrint(plist);
//头删
/*SLTPopFront(&plist);
SLTPrint(plist);
SLTPopFront(&plist);
SLTPrint(plist);*/
}
void test1()
{
SLTNode* plist = NULL;
SLTPrint(plist);
//尾插测试
SLTPushBack(&plist, 1);
SLTPrint(plist);
SLTPushBack(&plist, 2);
SLTPrint(plist);
SLTPushBack(&plist, 3);
SLTPrint(plist);
SLTPushBack(&plist, 4);
SLTPrint(plist);
printf("\n");
SLTNode* FindRet = SLTFind(&plist, 2);
if (FindRet) {
printf("找到了\n");
}
else {
printf("未找到\n");
}
SLTInsert(&plist, FindRet, 100);
SLTPrint(plist);
SLTInsertAfter(FindRet, 000);
SLTPrint(plist);
SLTEraseAfter(FindRet);
SLTPrint(plist);
SLTErase(&plist, FindRet);
SLTPrint(plist);
//SListDesTroy(&plist);
//SLTPrint(plist);
}
int main()
{
//test0();
test1();
return 0;
}