文章目录
单链表
1.单链表的介绍
链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表
中的指针链接次序实现的 。
在上篇博客中,我们可以很清晰的看到顺序表的结构,但是链表不可以,链表的链接就是由指针指引的,
一个数据,他可能间隔着N个内存空间,但是它们却又是实实在在相连的,为了详细说明链表,我准备这么几张图片:
我们看到,单链表就像一个火车,由一个指针来确定他们不同节点的位置,从而串接起来,但是我们要了解一件事,图片中的箭头是虚拟的,是想象的,我们能确定下一个节点位置只有指针;
2.单链表的实现
c
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<malloc.h>
typedef int SeDataType;
//定义结点
typedef struct SeqList
{
SeDataType data;
struct SeqList* next;
}SeNode;
//开辟节点
SeNode* BuySeqListNode(SeNode* phead, SeDataType x)
//销毁
void SeqListDestroy(SeNode** phead);
//打印
void SeqListPrint(SeNode* phead);
//尾插
void SeqListPushBack(SeNode** phead, SeDataType x);
//尾删
void SeqLIstPopBack(SeNode** phead);
//头插
void SeqListPushFront(SeNode** phead, SeDataType x);
//头删
void SeqListPopFront(SeNode** phead);
//pos位置之后插入
void SeqListInsterAfter(SeNode* pos, SeDataType x);
//删除pos位置
void SeqListEraseAfter(SeNode* pos);
为了实现单链表,我们需要实现以上接口
2.1.1单链表结点的创建与销毁
//初始化
SeNode* BuySeqListNode(SeDataType x)
{
SeNode* newnode = (SeNode*)malloc(sizeof(SeNode));//创建结点
if (newnode == NULL)//判断结点是否生成成功
{
perror("BuySeqListNode");
exit(-1);
}
newnode->data = x;//赋值
newnode->next = NULL;
return newnode;//返回结点地址
}
//销毁
void SeqListDestroy(SeNode** phead)
{
assert(phead);//判断是否为空
while (*phead)//循环free空间
{
SeNode* next = (*phead)->next;//记录下一个节点,避免链接丢失
free(*phead);//释放
*phead = next;
}
*phead = NULL;//置空
}
当读完这串代码后,我们会发现这两个函数使用了完全不一样的指针类型.开辟节点使用的是一级指针,销毁结点使用的是二级指针.要注意,这是本篇博客难度最高的知识点,考验各位对指针的理解程度
在调用PushBack函数后,我们成功创建了一个节点node,我们在此时这个结点是孤立的
那么如何让让plist和node链接起来呢.那就是指针.,在此时,如果我们想要用pilst链接node,必须传递plist的地址,只有传递地址才能让plist的指向改变.如果不传递地址,后续操作只是一份临时拷贝
此时,一个纠结点就出现了.我plist本事就是指针,为什么要传递指针的地址呢.
我们来看一张图片
这张图生动的展示了指针本质上的区别.指针也是一种变量,是变量就会有地址.想要改变变量,就必须通过地址改变;在对plist进行修改的时候,虽然plist就是指针,但想要改变他存贮的值,就必须传递本身的地址,这就是为什么传递二级指针的原因.
2.1.2单链表尾插
如何进行尾插操作呢,我们要想明白一件事情,在我们创建的Sqnode类型中, next指向下一个结点.所以,如果我们想要尾插,必须找到next的前一个结点进行链接
void SeqListPushBack(SeNode** phead, SeDataType x)
{
assert(phead);//判断指针是否为空
SeNode* node = BuySeqListNode(x);//开辟节点
if (*phead == NULL)//如果*phead为空,代表链表为空,之间赋值替换即可
{
*phead = node;
}
else
{
SeNode* tail = *phead;
while (tail->next != NULL)//如果不为空,则遍历链表,在最后一个结点进行链接
{
tail = tail->next;
}
tail->next = node;
}
}
此时,我们完成了尾插接口的实现,如何判断程序是否正确呢,我们可以通过调试直观地观察,也可以提前将打印接口实现观察,我们使用实现打印接口.
2.1.3单链表打印
应为我们只是要打印而无需任何改变指针的操作,所以只需要传递一级指针遍历整个数组即可
void SeqListPrint(SeNode* phead)
{
SeNode* cur = phead;//记录位置
while (cur)
{
printf("%d ", cur->data);//打印
cur = cur->next;//赋值遍历
}
}
程序正确,成功运行
2.1.4尾删
尾删的操作与尾插类似,不同的是我们需要遍历到倒数第二个结点,通过倒数第二个节点的next来释放最后一个节点达成尾删操作.因为需要改变内容,所以依旧传递二级指针
void SeqListPopBack(SeNode** phead)
{
assert(phead);//判断指针是否为空
assert(*phead);//判断指针指向的内容是否为空
if ((*phead)->next == NULL)//如果只有一个节点,直接释放
{
free(*phead);
*phead = NULL;
}
else
{
SeNode* tail = *phead;
while (tail->next->next != NULL)//如果多个,遍历到末尾的前一个释放
{
tail = tail->next;
}
free(tail->next);
tail->next = NULL;
}
}
2.1.5头插
头插需要注意的是对于指针的操作,在有多个节点时,需要考虑如何在头插的同时链接原头结点
而在一个节点是直接替换赋值即可.
void SeqListPushFront(SeNode** phead, SeDataType x)
{
assert(phead);//判断指针是否为空
SeNode* newnode = BuySeqListNode(x);//创建新节点
if (*phead == NULL)//当只有一个节点的时候,直接赋值
{
*phead = newnode;
}
else//多个节点是先链接在赋值
{
newnode->next = *phead;
*phead = newnode;
}
}
运行成功
2.1.6头删
头删要注意的与头插类似,直接上图
//头删
void SeqListPopFront(SeNode** phead)
{
assert(phead);//判断指针是否为空
assert(*phead);//判断指针指向的内容是否为空
if ((*phead)->next == NULL)//只有一个节点时,直接释放
{
free(*phead);
*phead = NULL;
}
else//多个节点时,记录新头结点,释放原头结点
{
SeNode* pphead = (*phead)->next;
free(*phead);
*phead = pphead;
}
}
运行成功
2.1.7查找
该接口原理与打印接口相同,同是遍历链表,不同点为该函数会返回一个地址
SeNode* SeqListFind(SeNode* phead,SeDataType x)
{
assert(phead);//判断指针是否为空
SeNode* cur = phead;//赋值遍历
while (cur->next != NULL)
{
if (cur->data == x)//判断相等
{
return cur;//相等返回地址
}
cur = cur->next;
}
return NULL; //不等返回NULL;
}
2.1.8在pos位置之后插入数据
void SeqListInsterAfter(SeNode**phead,SeNode* pos, SeDataType x)
{
assert(pos);//判断指针是否为空
assert(*phead);
if (pos == *phead)//如果相等,就是头插
{
SeqListPushFront(phead,x);
}
else
{
SeNode* node = BuySeqListNode(x);
SeNode* pphead = pos->next;//记录原结点链接的数据
node->next = pphead;//链接
pos->next = node;
}
}
修改成功
2.1.9删除pos位置
void SeqListEraseAfter(SeNode** phead, SeNode* pos)
{
assert(pos);//判断空指针
if (pos==*phead)//如果相等就是头删
{
SeqListPopFront(phead);
}
else
{
SeNode* cur = *phead;
while (cur->next != pos)//记录pos的前一个位置
{
cur = cur->next;
}
cur->next = pos->next;//链接
free(pos);//释放
pos = NULL;
}
}
完整代码
//SeqList.h #define _CRT_SECURE_NO_WARNINGS 1 #pragma once #include<stdio.h> #include<stdlib.h> #include<assert.h> #include<malloc.h> typedef int SeDataType; //定义结点 typedef struct SeqList { SeDataType data; struct SeqList* next; }SeNode; //初始化 void SeqListInit(SeNode** phead); //销毁 void SeqListDestroy(SeNode** phead); //打印 void SeqListPrint(SeNode* phead); //尾插 void SeqListPushBack(SeNode** phead, SeDataType x); //尾删 void SeqListPopBack(SeNode** phead); //头插 void SeqListPushFront(SeNode** phead, SeDataType x); //头删 void SeqListPopFront(SeNode** phead); //pos位置之后插入 void SeqListInsterAfter(SeNode** phead,SeNode* pos, SeDataType x); //删除pos位置 void SeqListEraseAfter(SeNode** phead, SeNode* pos); //SeqList.c #define _CRT_SECURE_NO_WARNINGS 1 #include"SeqList.h" //初始化 SeNode* BuySeqListNode(SeDataType x) { SeNode* newnode = (SeNode*)malloc(sizeof(SeNode));//创建结点 if (newnode == NULL)//判断结点是否生成成功 { perror("BuySeqListNode"); exit(-1); } newnode->data = x;//赋值 newnode->next = NULL; return newnode;//返回结点地址 } //销毁 void SeqListDestroy(SeNode** phead) { assert(phead);//判断是否为空 while (*phead)//循环free空间 { SeNode* next = (*phead)->next;//记录下一个节点,避免链接丢失 free(*phead);//释放 *phead = next; } *phead = NULL;//置空 } //打印 void SeqListPrint(SeNode* phead) { SeNode* cur = phead;//记录位置 while (cur) { printf("%d->", cur->data);//打印 cur = cur->next;//赋值遍历 } printf("NULL\n"); } //尾插 void SeqListPushBack(SeNode** phead, SeDataType x) { assert(phead);//判断指针是否为空 SeNode* node = BuySeqListNode(x);//开辟节点 if (*phead == NULL)//如果*phead为空,代表链表为空,之间赋值替换即可 { *phead = node; } else { SeNode* tail = *phead; while (tail->next != NULL)//如果不为空,则遍历链表,在最后一个结点进行链接 { tail = tail->next; } tail->next = node; } } //尾删 void SeqListPopBack(SeNode** phead) { assert(phead);//判断指针是否为空 assert(*phead);//判断指针指向的内容是否为空 if ((*phead)->next == NULL)//如果只有一个节点,直接释放 { free(*phead); *phead = NULL; } else { SeNode* tail = *phead; while (tail->next->next != NULL)//如果多个,遍历到末尾的前一个释放 { tail = tail->next; } free(tail->next); tail->next = NULL; } } //头插 void SeqListPushFront(SeNode** phead, SeDataType x) { assert(phead);//判断指针是否为空 SeNode* newnode = BuySeqListNode(x);//创建新节点 if (*phead == NULL)//当只有一个节点的时候,直接赋值 { *phead = newnode; } else//多个节点是先链接在赋值 { newnode->next = *phead; *phead = newnode; } } //头删 void SeqListPopFront(SeNode** phead) { assert(phead);//判断指针是否为空 assert(*phead);//判断指针指向的内容是否为空 if ((*phead)->next == NULL)//只有一个节点时,直接释放 { free(*phead); *phead = NULL; } else//多个节点时,记录新头结点,释放原头结点 { SeNode* pphead = (*phead)->next; free(*phead); *phead = pphead; } } SeNode* SeqListFind(SeNode* phead,SeDataType x) { assert(phead);//判断指针是否为空 SeNode* cur = phead;//赋值遍历 while (cur->next != NULL) { if (cur->data == x)//判断相等 { return cur;//相等返回地址 } cur = cur->next; } return NULL; //不等返回NULL; } //pos位置之后插入 void SeqListInsterAfter(SeNode**phead,SeNode* pos, SeDataType x) { assert(pos);//判断指针是否为空 assert(*phead); if (pos == *phead)//如果相等,就是头插 { SeqListPushFront(phead,x); } else { SeNode* node = BuySeqListNode(x); SeNode* pphead = pos->next;//记录原结点链接的数据 node->next = pphead;//链接 pos->next = node; } } //删除pos位置 void SeqListEraseAfter(SeNode** phead, SeNode* pos) { assert(pos);//判断空指针 if (pos==*phead)//如果相等就是头删 { SeqListPopFront(phead); } else { SeNode* cur = *phead; while (cur->next != pos)//记录pos的前一个位置 { cur = cur->next; } cur->next = pos->next;//链接 free(pos);//释放 pos = NULL; } } //SeqListTest.c #define _CRT_SECURE_NO_WARNINGS 1 #include"SeqList.h" void SeqListTest() { SeNode* plist = NULL; SeqListPushFront(&plist, 5); SeqListPushFront(&plist, 4); SeqListPushFront(&plist, 3); SeqListPushFront(&plist, 2); SeqListPushFront(&plist, 1); SeqListPushFront(&plist, 0); SeqListPrint(plist); SeNode* ptr = SeqListFind(plist, 3); if (ptr != NULL) { SeqListEraseAfter(&plist, ptr); } else { printf("数据有误\n"); } SeqListPrint(plist); /*SeNode* ptr = SeqListFind(plist, 3); if (ptr != NULL) { printf("%p %d", ptr,ptr->data); } else { printf("数据有误\n"); }*/ //SeqListPopFront(&plist); //SeqListPopFront(&plist); //SeqListPopFront(&plist); //SeqListPopFront(&plist); //SeqListPopFront(&plist); //SeqListPrint(plist); //SeqListPopFront(&plist); //SeqListPrint(plist); /*SeqListPushBack(&plist, 1); SeqListPushBack(&plist, 2); SeqListPushBack(&plist, 3); SeqListPushBack(&plist, 4); SeqListPushBack(&plist, 5); SeqListPrint(plist); SeqListPopBack(&plist); SeqListPrint(plist); SeqListPopBack(&plist); SeqListPrint(plist); SeqListPopBack(&plist); SeqListPrint(plist); SeqListPopBack(&plist); SeqListPrint(plist); SeqListPopBack(&plist); SeqListPrint(plist); SeqListDestroy(&plist);*/ } int main() { SeqListTest(); return 0; }
ont(&plist);
//SeqListPrint(plist);
/SeqListPushBack(&plist, 1);
SeqListPushBack(&plist, 2);
SeqListPushBack(&plist, 3);
SeqListPushBack(&plist, 4);
SeqListPushBack(&plist, 5);
SeqListPrint(plist);
SeqListPopBack(&plist);
SeqListPrint(plist);
SeqListPopBack(&plist);
SeqListPrint(plist);
SeqListPopBack(&plist);
SeqListPrint(plist);
SeqListPopBack(&plist);
SeqListPrint(plist);
SeqListPopBack(&plist);
SeqListPrint(plist);
SeqListDestroy(&plist);/}
int main()
{
SeqListTest();
return 0;
}