单链表详解
目录
一、单链表介绍
二、单链表的实现
1.单链表的创建
2.单链表的打印
3.单链表的尾插
4.单链表的头插
5.单链表的尾删
6.单链表的头删
7.单链表的查找
8.在指定位置之前插入数据
9.在指定位置之后插入数据
10.删除指定位置数据
11.删除指定位置之后的数据
12.单链表的销毁
三、代码
一、单链表的介绍
在学习单链表之前,需要首先知道链表是什么,链表是一种物理存储结构非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链表次序实现的。
与顺序表不同,链表里面每一个元素是独立存在的,他们都是独立申请下来的空间,我们称之为节点。
链表是由一个一个节点组成的,每个节点里面有数据,那如何将每个节点链接一起呢,我们规定,每个节点不仅保存自己的数据,也保存这下一个节点的地址。
基本了解以后,我们来尝试实现单链表。
二、单链表的实现
1.单链表的创建
按照前文说的,单链表中每个节点会包含自己的数据和指向下一个节点地址的指针,那我们创建一个结构体那包含它们:
这里data表示数据,next指针就是指向下一个节点地址的指针。同时,我们将结构体重命名为SLTNode ,方便表达和编写。
我们以后处理数据时候,数据不一定是int类型,所以将int类型也改一个名字:
下面开始创建节点,首先这里要用到malloc,用它开辟一块空间,然后给与它数据:
2.单链表的打印
在创建完成单链表以后,我们要尝试将他打印出来,首先创建一个指针,指向第一个节点,然后将这个指针传入我们创建的函数SLTprint()中:
接着我们来实现函数:
看一下运行结果:
3.单链表的尾插
单链表的尾插很简单,思路就是:
1. 创建一个新节点newnode
2. 创建一个指针ptail找到最后一个节点
3.让最后一个节点的nex指针由指向NULL变成指向newnode指针
注意:要判断一开始链表是不是空,如果是空,直接插入
这里我们用了**pphead,是一个二级指针,假设plist是我们链表的原来的头结点,
那么*plist就是指向第一个节点的指针,&plist就是指向第一个节点的指针的地址。
我们在接受的时候,用pphead接受&plist,那就用*pphead接受*plist,用**pphead接受第一个节点plist,所以这里要用**pphead!
先看创建新节点的代码,这里写一个前置函数:
得到了新节点,就可以写尾插的代码了:
4.单链表的头插
单链表的头插相对于顺序表就简单很多,因为单链表每一个节点在物理结构上是相互独立的,所以不好产生"牵一发而动全身”的效果!
首先也是创建一个新节点,然后把节点插入第一个:
看一下3和4的效果:
5.单链表的尾删
尾删要求找到最后一个节点然后把他删除,所以我们需要一个指针来遍历数组,遍历到最后一个节点,就删除最后一个节点。但是这个时候,我们要把倒数第二个节点的next指针置为空,所以我们还需要一个指针,我们命名为prev,用它找到倒数第二个指针,代码如下:
6.单链表的头删
头删也需要两个指针,一个指针指向头结点,另一个指针指向第二个节点,头节点被删除后,将第二个指针指向的第二个节点重新置为头结点:
我们来测试一下头删和尾删:
7.单链表的查找
这个很简单,我们只需要遍历链表就可以了,如果找到了,就返回数据,没有找到,就返回NULL:
我们测试一下:
8.在指定位置之前插入数据
这里假设位置是pos,那要判断链表不为空以及pos不能为空,首先找到pos节点和pos前一个节点,然后我们调用函数创建一个新节点newnode,那让newnode的next指向pos,pos之前的节点的next指向newnode,就插入成功了。
注意,如果要在头节点之前插入数据,那就要判断,如果pos==*pphead,就代表是在头结点之前插入数据,就直接插入就好,代码如下:
9.在指定位置之后插入数据
这个简单很多,因为这种情况就不可能在头结点之前插入数据了,所以我们只需要断言pos就好了,然后定义一个新指针,它指向pos的下一个节点,随后创建一个新节点newnode,让newnode的next指针指向pos的下一个节点,让pos的next指向newnode:
10.删除指定位置数据
要删除pos的节点,我们首先要找到pos之后的节点和pos之前的节点,然后让pos之前的节点的next指针指向pos之后的节点就可以了:
11.删除指定位置之后的数据
这里只需要找到pos的下一个节点和下一个节点的下一个节点,然后跳过pos的下一个节点就好了:
12.单链表的销毁
前面有单链表的初始化,要做到有头有尾,这里就也有单链表的销毁。
初始化链表是一个一个初始化,销毁也要一个一个销毁。
我们在销毁一个节点的时候,同时要保存它的下一个节点,以此类推:
三、代码:
slist.h:
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>typedef int SLData;
typedef struct SListNode
{SLData data;struct SListNode* next;
}SLTNode;void SLTprint(SLTNode* phead);void SLTPushBack(SLTNode** pphead, SLData x);
void SLTPushFront(SLTNode** pphead, SLData x);
void SLTPopBack(SLTNode** pphead);
void SLTPopFront(SLTNode** pphead);SLTNode* SLTFind(SLTNode* phead, SLData x);void SLTInsert(SLTNode** pphead, SLTNode* pos, SLData x);
void SLTInsertAfter(SLTNode* pos, SLData x);
void SLTErase(SLTNode** pphead, SLTNode* pos);
void SLTErase(SLTNode* pos);void SLTDestroy(SLTNode** pphead);
slist.c:
#include"slist.h"void SLTprint(SLTNode* phead)
{SLTNode* pcur = phead;//定义一个指针,指向单链表的头结点while (pcur)//当这个指针不是空的时候,代表单链表有节点{printf("%d->", pcur->data);//打印pcur所指节点的值,并且打印->pcur = pcur->next;//pcur指向下一个节点}//pcur下一个节点是空,跳出循环printf("NULL\n");//打印NULL
}SLTNode* SLTBuyNode(SLData x)
{SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));//给新节点newnode开辟一块空间if (newnode == NULL)//判断节点有没有开辟成功{perror("malloc fail!");exit(1);}//开辟成功newnode->data = x;//节点的数据是xnewnode->next = NULL;//节点现在的next指向的是空return newnode;//返回开辟的新节点
}void SLTPushBack(SLTNode** pphead, SLData x)
{assert(pphead); //断言不为空SLTNode* newnode = SLTBuyNode(x);//创建新节点if (*pphead == NULL)//如果一开始单链表就是空{*pphead = newnode;//新节点就是头结点}else//如果一开始单链表不是空{SLTNode* ptail = *pphead;//创建一个指针ptail找到尾节点while (ptail->next){ptail = ptail->next;}ptail->next = newnode;//尾节点的next指针指向的不再是NULL,而是新节点}
}void SLTPushFront(SLTNode** pphead, SLData x)
{assert(pphead);//断言不为空SLTNode* newnode = SLTBuyNode(x);//创建新节点newnode->next = *pphead;//新节点是头节点,所以它的next指向单链表原来的头结点*pphead = newnode;//新的头结点是newnode
}void SLTPopBack(SLTNode** pphead)
{assert(pphead && *pphead);//断言要求指向第一个节点和指向第一个节点的指针不为空//检查单链表有几个节点if ((*pphead)->next == NULL)//只有一个就直接free掉{free(*pphead);*pphead = NULL;}else//单链表不止一个节点{SLTNode* prev = *pphead;//定义一个prev指针用于找到倒数第二个节点SLTNode* ptail = *pphead;//定义一个ptail指针用于找到最后一个节点while (ptail->next)//用循环找到尾节点和倒数第二个节点{prev = ptail;ptail = ptail->next;}free(ptail);//将尾节点free掉,也就是删除ptail = NULL;//尾节点指向NULL,防止变成野指针prev->next = NULL;//现在倒数第二个节点变成尾节点。让它的next指针指向NULL}}
void SLTPopFront(SLTNode** pphead)
{assert(pphead && *pphead);//断言要求第一个节点和指向第一个节点的指针不为空SLTNode* pcur = (*pphead)->next;//定义一个指针pcur,它指向第二个节点//如果链表只有一个节点,pcur指向的就是NULLfree(*pphead);//将头结点释放掉*pphead = pcur;//现在指针指向第二个节点,如果没有,就是指向空
}SLTNode* SLTFind(SLTNode* phead, SLData x)
{SLTNode* pcur = phead;//定义一个指针从头结点开始while (pcur)//指针还在链表中{if (pcur->data == x){return pcur;//找到就返回}pcur = pcur->next;//没有找到就指向下一个节点}return NULL;//都没有找到就返回NULL
}void SLTInsert(SLTNode** pphead, SLTNode* pos, SLData x)
{assert(*pphead && pphead);//断言要求第一个节点和指向第一个节点的指针不为空assert(pos);//要求pos节点也不能为空SLTNode* newnode = SLTBuyNode(x);//调用函数得到新节点newnodeif (pos == *pphead){SLTPushFront(pphead, x);}else{SLTNode* prev = *pphead;//定义一个指针表示pos之前的节点while (prev->next != pos)//利用循环,使得prev指向pos之前的节点{prev = prev->next;}//prev newnode posnewnode->next = pos;//新节点的next指向posprev->next = newnode;//prev的next指针指向的由pos变成newnode}}void SLTInsertAfter(SLTNode* pos, SLData x)
{assert(pos);//要求pos节点也不能为空SLTNode* newnode = SLTBuyNode(x);//调用函数得到新节点newnodeSLTNode* next = pos->next;//创建一个新指针指向pos的下一个节点newnode->next = pos->next;pos->next = newnode;
}void SLTErase(SLTNode** pphead, SLTNode* pos)
{assert(*pphead && pphead);//断言要求第一个节点和指向第一个节点的指针不为空assert(pos);//要求pos节点也不能为空if (pos == *pphead)//pos如果是头结点{SLTNode* next = (*pphead)->next;free(*pphead);//直接删*pphead = next;}else//pos不是头结点{SLTNode* prev = *pphead;//利用prev找到pos之前的一个节点while (prev->next != pos){prev = prev->next;}prev->next = pos->next;//prev的next指向由pos节点变成pos的下一个节点free(pos);//pos删除了pos = NULL;//置于NULL,防止变成野指针}
}void SLTErase(SLTNode* pos)
{assert(pos);//要求pos节点也不能为空assert(pos->next);//要求pos的下一个节点也不能为空SLTNode* del = pos->next;//定义del是pos的下一个节点pos->next = del->next;//跳过del,pos的next指针直接指向del的下一个节点free(del);//删除deldel = NULL;//置于NULL,防止变成野指针
}void SLTDestroy(SLTNode** pphead)
{assert(*pphead && pphead);//断言要求第一个节点和指向第一个节点的指针不为空SLTNode* pcur = *pphead;//用一个新指针pcur表示头结点while (pcur)//pcur不为空时{SLTNode* next = pcur->next;//定义一个临时指针表示pcur的下一个指针free(pcur);//free掉pcur的节点pcur = next;//pcur现在指向下一个节点}//到这里已经全部销毁完毕*pphead = NULL;//都置为空
}
test.c:
#include"slist.h"void test01()
{//链表是有一个一个节点组成SLTNode* node1 = (SLTNode*)malloc(sizeof(SLTNode));//给节点node1开辟一块空间node1->data = 1;//node1中的数据是1SLTNode* node2 = (SLTNode*)malloc(sizeof(SLTNode));//给节点node2开辟一块空间node2->data = 2;//node2中的数据是1node1->next = node2;//node1中存的是node2的地址,所以node1的next指向node2node2->next = NULL;//node2后面没有下一个节点。所以它的next指针指向NULLSLTNode* plist = node1;SLTprint(plist);
}void test02()
{SLTNode* plist = NULL;//SLTPushBack(&plist, 1);//SLTPushBack(&plist, 2);//SLTPushBack(&plist, 3);//SLTprint(plist);///*SLTprint(plist);/*SLTPushFront(&plist, 5);SLTPushFront(&plist, 5);SLTprint(plist);*///SLTPopBack(&plist);//SLTprint(plist);/*SLTPopBack(&plist);SLTPopFront(&plist);SLTprint(plist);*/SLTPushBack(&plist, 1);SLTPushBack(&plist, 2);SLTPushBack(&plist, 3);SLTprint(plist);SLTNode* find = SLTFind(plist,1);if (find == NULL){printf("找不到!\n");}else{printf("找到了!\n");}
}
void test03()
{SLTNode* plist = NULL;SLTPushBack(&plist, 1);SLTPushBack(&plist, 2);SLTPushBack(&plist, 3);SLTprint(plist);SLTNode* find = SLTFind(plist, 1);SLTInsert(&plist, find, 77);SLTprint(plist);
}
int main()
{//test01();//test02();test03();return 0;
}