作者:一个喜欢猫咪的的程序员
专栏:《数据结构》
喜欢的话:世间因为少年的挺身而出,而更加瑰丽。 ——《人民日报》
![]()
目录
单链表是一种链式存取的 数据结构 ,用一组地址任意的 存储单元 存放线性表中的数据元素。 链表中的数据是以结点来表示的,每个结点的构成:元素 ( 数据元素 的映象) + 指针 (指示后继元素 存储 位置),元素就是存储数据的存储单元,指针就是连接每个结点的 地址 数据。 链表中的数据是以结点来表示的,每个结点的构成:元素 ( 数据元素 的映象) + 指针 (指示后继元素存储位置),元素就是存储数据的存储单元,指针就是连接每个结点的地址数据。 以“结点的序列”表示线性表称作 线性链表 (单链表),单链表是链式存取的结构。 链接方式存储的线性表简称为链表(Linked List)。 ② 链表中结点的逻辑次序和物理次序不一定相同。
将一个结构体包含两个部分,一个是数据,一个是与本身结构体同类型的结构体指针
- typedef int SLDataType;
- ct SListNode {//单链表
- SLDataType data;//数据
- struct SListNode* next;//指向下一个与本结构体相同的结构体变量的指针
- }SLTNode;
这样就可以在结构体指针找到下一个结构体的位置,第一个指针找到了第二个结构体,第二个结构体指针找到第三个结构体,以此类推....这样就形成了一个链表。

但计算机实际上并没有指向的箭头啊,我们可以通过指针指向下一个结构体的地址来实现!

我们是动态开辟malloc结构体,malloc函数可能会异地扩、本地扩或者NULL,因此我们需要判断malloc返回值是否为空!以及我们需要把地址传回去,防止异地扩后,地址不同!
对于malloc函数不熟悉的同学可以看看我的另外一篇博客:对动态内存开辟有详细解释http://t.csdn.cn/IkDCF
http://t.csdn.cn/IkDCF
言归正传。我们来看一下这个函数如何实现:
- SLTNode* BuySLTNode(SLDataType x)
- {//创建结构体
- SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
- if (newnode == NULL)
- {
- perror("malloc fail");
- exit(-1);
- }
- newnode->data = x;
- newnode->next = NULL;
- return newnode;
- }
我们这种情况是通过写4个语句来将这4个结构体穿起来,那如果我们要n个结构体形成链表呢?
我们结合一个图,来想想怎么把图中代码的能力实现,并实现n个呢?
n个结构体相连,所以肯定是for循环,利用BuyTNode创建动态结构体,我们再将下一个结构体的地址传给结构体的next指针以此类推.....
- SLTNode* CreateSList(int n)
- {//将n个结构体形成链表
- SLTNode* phead = NULL;
- SLTNode* plist = NULL;
- for (int i = 0; i < n; i++)
- {
- SLTNode* newnode = BuySLTNode(i);
- if (plist == NULL)
- {
- phead = plist = newnode;
- }
- else
- {
- plist->next = newnode;
- plist = newnode;
- }
- }
- return phead;
- }
- v
因为使用BuySLTNode创建结构体时,next都为NULL。
plist.next=newnode来形成链表,我们这边有一个矛盾,当第一个结构体时,并没有第二个来赋值,我们可以这样:plist=newnode,这样的时候当newnode为第二个结构体的地址的时候就不为NULL,就会将第二个结构体赋值给第一个结构体的next指针将之前的覆盖。
我们利用一个while循环遍历一遍打印就好。
- void* PrintSList(SLTNode* phead)
- {//打印链表
- SLTNode* cur = phead;
- while (cur!= NULL)
- //如果这里是cur->next != NULL的话,会少一个值,因为cur->next==NULL时,cur->data有值的,它的下一个结构体才没有值
- {
- printf("[%d|%p]->", cur->data,cur->next);
- cur = cur->next;//这里如果想要直接cur->next=...的话有点困难
- }
- printf("NULL");
- }
如果这里是cur->next != NULL的话,会少一个值,因为cur->next==NULL时,cur->data有值的,它的下一个结构体才没有值
我们尾插要先找尾部,即NULL的地方。然后将newnode插到null后面就好了。
我们来看下面这个尾插是否有错?
可能很多人觉得这样没有问题,但实际上是错的!!!
- tail为NULL和tail->next为NULL对于循环条件来说是不一样的。
- tail为NULL代表没有这个结构体,就下图4个结构体为例:当tail指向第4个结构体时,循环不会结束,tail下一个会指向NULL。当循环停止的时候,tail已经指向了NULL,而第四个结构体的next还是指向空,即使我们插进去一个结构体,也会在第四个结构体的时候next到NULL,相当于没有插入!
- tail->next为NULL代表没有下一个结构体了,这样tail->next=newnode就会将之前的尾部结构体和新加入的结构体连接起来形成链表!
当循环条件为tail.next!=NULL时:
- void SLTPushBack(SLTNode* phead, SLDataType x)
- {//尾插
- //assert(phead);//这里不需要断言,空链表也可以尾插
- SLTNode* newnode = BuySLTNode(x);
- SLTNode* tail = phead;
- while (tail->next)
- {
- tail = tail->next;
- }
- tail->next = newnode;
- }
这个是我在plist为NULL时运行结果的截图:
这里错误是由指针传参导致的!
你一定很疑惑把我明明把指针传过去,传地址不是可以改变原来的值吗?没有错啊.
我们先来复习一下:
- 你的老师一定跟你说函数在传值调用时,形参不可以改变实参
!
- 传址调用才可以改变实参!
这是没有错的,但是它是相对而言的。
- 指针本质是一个地址,我们所说的指针的值是一个地址,而不是这个地址指向的值。
- NULL为空指针,NULL=0x0000000000(这是一个地址)。
我们要改变NULL这个指针的时候,注意:我们这里说要改变这个指针是指我们要改变这个地址(0x00000000),而不是这个地址的值。
那我们这里的 SLTNode* phead是不是相当于上图int a。
因此我们要改变NULL这个指针,我们需要传SLTNode**pphead进去,那我们*pphead就可以改变NULL的值了!
-
- void SLTPushBack(SLTNode** pphead, SLDataType x)
- {//尾插
- //assert(phead);//这里不需要断言,空链表也可以尾插
- SLTNode* newnode = BuySLTNode(x);
- if (*pphead == NULL)
- {
- *pphead = newnode;
- }
- else {
- SLTNode* tail = *pphead;
- while (tail->next)
- {
- tail = tail->next;
- }
- tail->next = newnode;
- }
- }
找到尾部,把最后一个结构体删掉,再把前一个结构体的next置为NULL
- void SLTPopBack(SLTNode** pphead)
- {//尾删
- assert(*pphead);
- if ((*pphead)->next == NULL)
- {
- free(*pphead);
- *pphead = NULL;
- }
- else
- {
- SLTNode* tail = *pphead;
- while (tail->next->next)
- {
- tail = tail->next;
- }
- free(tail->next);
- tail->next = NULL;
- }
- }
- void SLTPushFront(SLTNode** pphead, SLDataType x)
- {//头插
- SLTNode* newnode = BuySLTNode(x);
- newnode->next = *pphead;
- *pphead = newnode;
- }
- void SLTPopFront(SLTNode** pphead)
- {//头删
- assert(*pphead);
- SLTNode* next = (*pphead)->next;
- free(*pphead);
- *pphead = next;
- }
- SLTNode* SLTFide(SLTNode* phead, SLDataType x)
- {//查找
- SLTNode* cur = phead;
- while (cur)
- {
- if (cur->data == x)
- {
- return cur;
- }
- cur = cur->next;
- }
- return NULL;
- }
假设pos在d3的位置:

- void SLTInsertAfter(SLTNode* pos, SLDataType x)
- {//在pos位置之后插入
- assert(pos);
- SLTNode* newnode = BuySLTNode(x);
- newnode->next = pos->next;
- pos->next = newnode;
- }
为什么同样是插入数据,头插尾插要传二级指针,而这个函数要传一级指针呢?
在pos位置插入的话,首先这个pos得不为NULL。
而NULL空链表时,pos的值就为NULL,因此这里不需要改变指针的值,所以一级指针就够了!

分为两种情况:
- pos为链表第一个,那就相当于头插;
- 除去第一个以外的位置:
- void SLTInsertFront(SLTNode** pphead, SLTNode* pos, SLDataType x)
- {//在pos之前插入
- assert(pos);
- SLTNode* newnode = BuySLTNode(x);
- if(*pphead == pos)
- {
- SLTPushFront(pphead, x);
- }
- else
- {
- SLTNode* prev = *pphead;
- while (prev->next != pos)
- {
- prev = prev->next;
- }
- prev->next = newnode;
- newnode->next = pos;
- }
- }
找pos位置的下一个位置将它赋给一个指针nextNode,将pos的next连接到nextNode的下一个结构体的位置,这样就跳过这个指针了,还不要忘了释放这个被跳过的空间!!!
- void SLTEraseAfter(SLTNode* pos)
- {//删除pos之后的位置
- assert(pos);
- if (pos->next == NULL)
- {
- return;
- }
- else
- {
- SLTNode* nextNode = pos->next;
- pos->next = nextNode->next;
- free(nextNode);
- }
- }
- void SLTErase(SLTNode** pphead,SLTNode* pos)
- {//删除pos位置
- assert(pos);
- if (pos = *pphead)
- {
- SLTEraseAfter(pos);
- }
- else
- {
- SLTNode* prev = *pphead;
- while (prev->next != pos)
- {
- prev = prev->next;
- }
- prev->next = pos->next;
- free(pos);
- }
- }
链表并不是连续的,只是一个又一个结构体跳过结构体指针链接起来,一次我们要一个一个释放!
- void SLTDestory(SLTNode** pphead)
- {
- SLTNode* cur = *pphead;
- while (cur)
- {
- SLTNode* next = cur->next;
- free(cur);
- cur = next;
- }
- *pphead = NULL;
- }
- #pragma once
- #include<stdio.h>
- #include<stdlib.h>
- #include<assert.h>
- typedef int SLDataType;
- //typedef struct SListNode {//单链表
- // SLDataType data;//数据
- // struct SListNode* next;//指向下一个与本结构体相同的结构体变量的指针
- //
- // //可以这么写吗?
- // //SLTNode* next;//答案是不可以的,SLTNode要在下一行之后才可以使用
- //}SLTNode;
- typedef struct SListNode {//单链表
- SLDataType data;//数据
- struct SListNode* next;//指向下一个与本结构体相同的结构体变量的指针
- }SLTNode;
-
- SLTNode* BuySLTNode(SLDataType x);//创建结构体
- SLTNode* CreateSList(int n);//将n个结构体形成链表
- void PrintSList(SLTNode* phead);//打印链表
-
- void SLTPushBack(SLTNode** pphead, SLDataType x);//尾插
- void SLTPopBack(SLTNode** pphead);//尾删
- void SLTPushFront(SLTNode** pphead, SLDataType x);//头插
- void SLTPopFront(SLTNode** pphead);//头删
- SLTNode* SLTFide(SLTNode* phead, SLDataType x);//查找
- void SLTInsertAfter(SLTNode* pos, SLDataType x);//在pos位置之后插入
- void SLTInsertFront(SLTNode** pphead,SLTNode* pos,SLDataType x);//在pos位置之前插入
- void SLTEraseAfter(SLTNode* pos);//在删除pos之后的位置
- void SLTErase(SLTNode**pphead,SLTNode* pos);//删除pos位置
- void SLTDestory(SLTNode** pphead);
- #include"Slist.h"
- SLTNode* BuySLTNode(SLDataType x)
- {//创建结构体
- SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
- if (newnode == NULL)
- {
- perror("malloc fail");
- exit(-1);
- }
- newnode->data = x;
- newnode->next = NULL;
- return newnode;
- }
- SLTNode* CreateSList(int n)
- {//将n个结构体形成链表
- SLTNode* phead = NULL;
- SLTNode* plist = NULL;
- for (int i = 0; i < n; i++)
- {
- SLTNode* newnode = BuySLTNode(i);
- if (plist == NULL)
- {
- phead = plist = newnode;
- }
- else
- {
- plist->next = newnode;
- plist = newnode;
- }
- }
- return phead;
- }
- void PrintSList(SLTNode* phead)
- {//打印链表
- SLTNode* cur = phead;
- while (cur!= NULL)
- //如果这里是cur->next != NULL的话,会少一个值,因为cur->next==NULL时,cur->data有值的,它的下一个结构体才没有值
- {
- printf("%d->", cur->data);
- /*printf("[%d|%p]->", cur->data, cur->next);*/
- cur = cur->next;//这里如果想要直接cur->next=...的话有点困难
- }
- printf("NULL\n");
- }
- void SLTPushBack(SLTNode** pphead, SLDataType x)
- {//尾插
- //assert(phead);//这里不需要断言,空链表也可以尾插
- SLTNode* newnode = BuySLTNode(x);
- if (*pphead == NULL)
- {
- *pphead = newnode;
- }
- else {
- SLTNode* tail = *pphead;
- while (tail->next)
- {
- tail = tail->next;
- }
- tail->next = newnode;
- }
- }
- void SLTPopBack(SLTNode** pphead)
- {//尾删
- assert(*pphead);
- if ((*pphead)->next == NULL)
- {
- free(*pphead);
- *pphead = NULL;
- }
- else
- {
- SLTNode* tail = *pphead;
- while (tail->next->next)
- {
- tail = tail->next;
- }
- free(tail->next);
- tail->next = NULL;
- }
- }
- void SLTPushFront(SLTNode** pphead, SLDataType x)
- {//头插
- SLTNode* newnode = BuySLTNode(x);
- newnode->next = *pphead;
- *pphead = newnode;
- }
- void SLTPopFront(SLTNode** pphead)
- {//头删
- assert(*pphead);
- SLTNode* next = (*pphead)->next;
- free(*pphead);
- *pphead = next;
- }
- SLTNode* SLTFide(SLTNode* phead, SLDataType x)
- {//查找
- SLTNode* cur = phead;
- while (cur)
- {
- if (cur->data == x)
- {
- return cur;
- }
- cur = cur->next;
- }
- return NULL;
- }
- void SLTInsertAfter(SLTNode* pos, SLDataType x)
- {//在pos位置之后插入
- assert(pos);
- SLTNode* newnode = BuySLTNode(x);
- newnode->next = pos->next;
- pos->next = newnode;
- }
- void SLTInsertFront(SLTNode** pphead, SLTNode* pos, SLDataType x)
- {//在pos之前插入
- assert(pos);
- SLTNode* newnode = BuySLTNode(x);
- if(*pphead == pos)
- {
- SLTPushFront(pphead, x);
- }
- else
- {
- SLTNode* prev = *pphead;
- while (prev->next != pos)
- {
- prev = prev->next;
- }
- prev->next = newnode;
- newnode->next = pos;
- }
- }
- void SLTEraseAfter(SLTNode* pos)
- {//删除pos之后的位置
- assert(pos);
- if (pos->next == NULL)
- {
- return;
- }
- else
- {
- SLTNode* nextNode = pos->next;
- pos->next = nextNode->next;
- free(nextNode);
- }
- }
- void SLTErase(SLTNode** pphead,SLTNode* pos)
- {//删除pos位置
- assert(pos);
- if (pos = *pphead)
- {
- SLTEraseAfter(pos);
- }
- else
- {
- SLTNode* prev = *pphead;
- while (prev->next != pos)
- {
- prev = prev->next;
- }
- prev->next = pos->next;
- free(pos);
- }
- }
- void SLTDestory(SLTNode** pphead)
- {
- SLTNode* cur = *pphead;
- while (cur)
- {
- SLTNode* next = cur->next;
- free(cur);
- cur = next;
- }
- *pphead = NULL;
- }
- #include"Slist.h"
- //void test1()
- //{
- // //第一种写法:
- // SLTNode* n1 = malloc();
- // SLTNode* n2 = malloc();
- // n1->next = n2;
- // //这里的n1是存放在堆区的,出了这个函数不会被销毁!
- //
- // //第二种写法:
- // //SLTNode n2;
- // //SLTNode n3;
- // //n2.next = &n3;
- // //n2这里是存放函数栈帧里面的,出了函数就会被销毁,因此不能这样写!
- //}
- void test2()
- {
- //SLTNode* n1 = BuySLTNode(1);
- //SLTNode* n2 = BuySLTNode(2);
- //SLTNode* n3 = BuySLTNode(3);
- //SLTNode* n4 = BuySLTNode(4);
- //n1->next = n2;
- //n2->next = n3;
- //n3->next = n4;
- //n4->next = NULL;
-
- SLTNode*plist=CreateSList(10);
- PrintSList(plist);
- printf("\n");
- SLTPushBack(plist, 100);
- PrintSList(plist);
-
- }
- void test3()
- {
- SLTNode* plist = NULL;
- SLTPushBack(&plist, 100);
- SLTPushBack(&plist, 200);
- SLTPushBack(&plist,300);
- PrintSList(plist);
-
- SLTPopBack(&plist);
- PrintSList(plist);
- /*SLTPopBack(&plist);
- PrintSList(plist);
- SLTPopBack(&plist);
- PrintSList(plist);*/
- SLTPushFront(&plist, 1);
- PrintSList(plist);
- SLTPopFront(&plist);
- PrintSList(plist);
- SLTPopFront(&plist);
- PrintSList(plist);
- SLTPopFront(&plist);
- PrintSList(plist);
- }
- void test4()
- {
- SLTNode* plist = NULL;
- SLTNode* pos1 = SLTFide(plist, 200);
- if (pos1 == NULL)
- {
- printf("找不到\n");
- }
- else
- {
- printf("找到了\n");
- }
- SLTPushBack(&plist, 100);
- SLTPushBack(&plist, 200);
- SLTPushBack(&plist, 300);
- PrintSList(plist);
- SLTNode*pos=SLTFide(plist, 200);
- //SLTInsertAfter(pos, 10);
- //PrintSList(plist);
- //SLTInsertFront(&plist, pos, 1);
- //PrintSList(plist);
- SLTErase(&plist,pos);
- PrintSList(plist);
- SLTDestory(&plist);
- //if (pos == NULL)
- //{
- // printf("找不到\n");
- //}
- //else
- //{
- // printf("找到了\n");
- //}
- }
- int main()
- {
- test4();
- return 0;
-
- }