1.单链表是什么?
2.实现单链表
1.单链表是什么?
单链表是什么?
和顺序表类似,单链表是用来存储数据的。
单链表有什么特点?
逻辑上是连续的,物理结构上却不是
为什么要用单链表?是有什么好处吗?
还记得之前的顺序表吧?
1.realloc扩大空间时,当前位置空间不足时,会将该位置数据拷贝,在其位置开辟空间并将数据放进去,最后还要将原位置的空间释放,消耗时间巨大。而链表不同,链表时用malloc申请空间的,在屋里结构上不连续,逻辑上会通过某种方式连接,使其连续。开辟空间时,只需在任意位置开辟,再将其连接就可以了。
2.在顺序表的前面或中间插入数据时,要将所有数据往后移一位,消耗时间巨大。单链表不同,单链表只需让需插入的节点与插入位置的两个节点相连接就可以了,消耗时间不大。
可见,单链表优点多,值得我们去学习!
2.实现单链表
链表图解:
先看代码(代码后面有详解)
Linked lists.h
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
typedef int linkdatatype;//初始化首节点指针
typedef struct node//节点
{
linkdatatype data;//节点数据
struct node* next;//下一个节点地址
}node;
void initlist(node** pphead);//初始化首元素指针
void printlist(node* phead);//打印链表
node* mall(node* phead);//创建节点
void pushback(node** pphead);//后增
void pushfront(node** pphead);//前增
void popback(node** pphead);//后删
void popfront(node** pphead);//前删
void pushpos(node** pphead, int pos);//指定位置插入
void poppos(node** pphead, int pos);//指定位置删除
void cheak(node** pphead, int data);//找出对应数据的位置
void breaklist(node** pphead);//销毁链表
Linked lists.c
#define _CRT_SECURE_NO_WARNINGS
#include "Linked lists.h"
void initlist(node** pphead) //初始化首节点指针
{
*pphead = NULL;
}
void printlist(node* phead)//打印链表
{
if (phead == NULL)
{
return;
}
while (phead)
{
printf("%d ", phead->data);
phead = phead->next;
}
putchar('\n');
}
node* mall(node* phead)//创建节点
{
node* newnode = (node*)malloc(sizeof(*phead));
if (newnode == NULL)
{
perror("malloc");
exit(-1);
}
newnode->next = NULL;
scanf("%d", &newnode->data);//在该节点装入数据
return newnode;//返回节点地址
}
void pushback(node** pphead)//后增
{
assert(pphead);
node* newnode = mall(*pphead);
if (*pphead == NULL)//如果没有节点
{
*pphead = newnode;
return;
}
node* ptail = *pphead;
while (ptail->next)//至少有一个节点
{
ptail = ptail->next;
}
ptail->next = newnode;
}
void pushfront(node** pphead)//前增
{
assert(pphead);
node* newnode = mall(*pphead);
newnode->next = *pphead;//将新节点与旧的首节点连接
*pphead = newnode;//让新节点成为首节点
}
void popback(node** pphead)//后删
{
assert(pphead);
if (*pphead = NULL)//没有节点,不用删
{
return;
}
node* ptail = *pphead;
if (ptail->next == NULL)
{
free(ptail);
*pphead = NULL;
return;
}
node* prev = NULL;
while (ptail->next)//多个节点
{
prev = ptail;
ptail = ptail->next;
}
free(prev->next);
prev->next = NULL;
ptail = NULL;
}
void popfront(node** pphead)
{
assert(pphead);
if (*pphead = NULL)//没有节点,不用删
{
return;
}
node* ptail = *pphead;
*pphead = ptail->next;
free(ptail);
ptail = NULL;
}
void pushpos(node** pphead, int pos)//指定位置插入
{
assert(pphead);
int count = 0;
node* ptail = *pphead;
while (ptail)//计算元素个数
{
ptail = ptail->next;
count++;
}
if (pos <= 0 || pos > count + 1)//输入错误,直接return
return;
if (*pphead == NULL || pos == 1)//没有节点或需要首插时
{
pushfront(pphead);
return;
}
ptail = *pphead;
node* newnode = mall(*pphead);
node* pafter = NULL;
int i;
for (i = 0; i < pos - 2; i++)//有节点时
{
ptail = ptail->next;//被插入位置左边的元素
pafter = ptail->next;//被插入位置的元素
}
ptail->next = newnode;//插入新节点
newnode->next = pafter;
}
void poppos(node** pphead, int pos)
{
assert(pphead);
int count = 0;
node* ptail = *pphead;
while (ptail)//计算元素个数
{
ptail = ptail->next;
count++;
}
if (pos <= 0 || pos > count)//不符合要求,return
{
return;
}
if (pos == 1)//需要删除首节点时
{
*pphead = (*pphead)->next;
return;
}
ptail = *pphead;
node* pafter = NULL;
int i;
for (i = 0; i < pos - 2; i++)
{
ptail = ptail->next;//ptail是被删除节点的左节点
}
pafter = ptail->next;
pafter = pafter->next;//pafter是被删除节点的右节点
free(ptail->next);
ptail->next = pafter;//让被删除节点的左节点连接被删除节点的右节点
}
void cheak(node** pphead, int data)//找出对应数据的位置
{
node* ptail = *pphead;
int count = 0;
while (ptail)//遍历链表,找元素
{
if (ptail->data == data)
printf("%d ", count + 1);
ptail = ptail->next;
count++;
}
putchar('\n');
}
void breaklist(node** pphead)//销毁链表
{
assert(pphead);
if (*pphead == NULL)
{
return;
}
int count = 0;
node* ptail = *pphead;
while (ptail)//计算元素个数
{
ptail = ptail->next;
count++;
}
int i;
ptail = *pphead;
while (count)//元素个数就是销毁次数
{
for (i = 0; i < count - 1; i++)//销毁非首节点
{
ptail = ptail->next;
}
free(ptail);
ptail = NULL;
count--;
}
*pphead = NULL;
}
test.c
#define _CRT_SECURE_NO_WARNINGS
#include "Linked lists.h"
int main()
{
node* phead;
initlist(&phead);
breaklist(&phead);
breaklist(&phead);
pushback(&phead);
pushback(&phead);
printlist(phead);
return 0;
}
下面由我来详细解说:
每个节点里都存储一个数据,并存储下一个节点的指针。
这样,在首节点就能通过直接找到下一个节点,在第二个节点就能通过直接找到第三个节点。以此类推,就可以顺藤摸瓜,通过首节点地址访问所有的节点。
创建首节点地址,并将其初始化为NULL。如果不初始化,那么phead就是野指针
void printlist(node* phead)//打印链表
{
if (phead == NULL)
{
return;
}
while (phead)
{
printf("%d ", phead->data);
phead = phead->next;
}
putchar('\n');
}
1.首节点指针为空,说明没有节点,直接返回。
2.首节点指针不为空,打印首节点数据,并让首节点指针指向下一个节点,直到phead为空,停止打印。
node* mall(node* phead)//创建节点
{
node* newnode = (node*)malloc(sizeof(*phead));
if (newnode == NULL)
{
perror("malloc");
exit(-1);
}
newnode->next = NULL;
scanf("%d", &newnode->data);//在该节点装入数据
return newnode;//返回节点地址
}
1.开辟空间,使newnode指向该空间,并判断是否开辟成功。
2.为防止野指针,让节点内的指针变量为NULL
3.输入数据
4.返回节点地址
void pushback(node** pphead)//后增
{
assert(pphead);
node* newnode = mall(*pphead);
if (*pphead == NULL)//如果没有节点
{
*pphead = newnode;
return;
}
node* ptail = *pphead;
while (ptail->next)//至少有一个节点
{
ptail = ptail->next;
}
ptail->next = newnode;
}
1.pphead为NULL,说明链表不存在,需assert断言
2.创建节点,用newnode接受该节点地址
3.如果链表没有节点,直接让该节点成为首节点即可
4.如果至少有一个节点,找到最后一个节点,并让该节点的指针变量指向下一个节点
5.由于创建节点时,将其指针变量初始化为NULL,不需要后续处理。
void pushfront(node** pphead)//前增
{
assert(pphead);
node* newnode = mall(*pphead);
newnode->next = *pphead;//将新节点与旧的首节点连接
*pphead = newnode;//让新节点成为首节点
}
1.断言
2.创建节点,用newnode接受该节点地址
3.让newnode的指针变量指向首节点
4.让首节点指针指向刚创建的节点
void popback(node** pphead)//后删
{
assert(pphead);
if (*pphead = NULL)//没有节点,不用删
{
return;
}
node* ptail = *pphead;
if (ptail->next == NULL)
{
free(ptail);
*pphead = NULL;
return;
}
node* prev = NULL;
while (ptail->next)//多个节点
{
prev = ptail;
ptail = ptail->next;
}
free(prev->next);
prev->next = NULL;
ptail = NULL;
}
1.断言
2.如果首节点指针为NULL,说明没有节点,直接返回
3.如果只有一个节点,直接释放
4.如果有多个节点,找到倒数第二个节点,释放最后一个节点的空间,并让倒数第二个节点的指针变量为空
5.ptail此时为野指针,要置为空指针
void popfront(node** pphead)//前删
{
assert(pphead);
if (*pphead = NULL)//没有节点,不用删
{
return;
}
node* ptail = *pphead;
*pphead = ptail->next;
free(ptail);
ptail = NULL;
}
1.断言
2.如果首节点指针为NULL,说明没有节点,直接返回
3.让首节点指针指向第二个节点
4.将原来的首节点空间释放
5.ptail此时为野指针,要置为空指针
void pushpos(node** pphead, int pos)//指定位置插入
{
assert(pphead);
int count = 0;
node* ptail = *pphead;
while (ptail)//计算元素个数
{
ptail = ptail->next;
count++;
}
if (pos <= 0 || pos > count + 1)//输入错误,直接return
return;
if (*pphead == NULL || pos == 1)//没有节点或需要首插时
{
pushfront(pphead);
return;
}
ptail = *pphead;
node* newnode = mall(*pphead);
node* pafter = NULL;
int i;
for (i = 0; i < pos - 2; i++)//有节点时
{
ptail = ptail->next;//被插入位置左边的元素
pafter = ptail->next;//被插入位置的元素
}
ptail->next = newnode;//插入新节点
newnode->next = pafter;
}
1.断言
2.计算元素个数
3.如果要插入的位置不存在,直接返回
4.没有节点或需要首插时,直接用之前写好的函数就行了
5.至少有一个节点时,找到插入位置旁的两个节点,按顺序连接就好
6.该函数支持在最后面插入元素,比如链表只有5个节点,在第六个位置插入。
为什么可以呢?
在该情况下,ptail指向最后一个节点,pafter指向NULL,将元素嵌入,恰好组成了一条链表。
void poppos(node** pphead, int pos)//指定位置删除
{
assert(pphead);
int count = 0;
node* ptail = *pphead;
while (ptail)//计算元素个数
{
ptail = ptail->next;
count++;
}
if (pos <= 0 || pos > count)//不符合要求,return
{
return;
}
if (pos == 1)//需要删除首节点时
{
*pphead = (*pphead)->next;
return;
}
ptail = *pphead;
node* pafter = NULL;
int i;
for (i = 0; i < pos - 2; i++)
{
ptail = ptail->next;//ptail是被删除节点的左节点
}
pafter = ptail->next;
pafter = pafter->next;//pafter是被删除节点的右节点
free(ptail->next);
ptail->next = pafter;//让被删除节点的左节点连接被删除节点的右节点
}
1.断言
2.统计元素个数
3.判断需删除的节点是否存在
4.若需删除首节点时,可以采用代码中的方法,也可以直接使用写好的函数
5.若删除的不是首节点,找到需删除节点的左节点指针和右节点指针,连接左右节点
6.为什么代码能删除最后一个节点?
答:找到的右节点指针为NULL,将左节点的指针变量指向NULL,刚好删除了最后一个节点
7.将需删除的节点的空间释放
void cheak(node** pphead, int data)//找出对应数据的位置
{
node* ptail = *pphead;
int count = 0;
while (ptail)//遍历链表,找元素
{
if (ptail->data == data)
printf("%d ", count + 1);
ptail = ptail->next;
count++;
}
putchar('\n');
}
1.计算节点数目
2.遍历链表,一一比较,指出该节点是第几个节点
void breaklist(node** pphead)//销毁链表
{
assert(pphead);
if (*pphead == NULL)
{
return;
}
int count = 0;
node* ptail = *pphead;
while (ptail)//计算元素个数
{
ptail = ptail->next;
count++;
}
int i;
ptail = *pphead;
while (count)//元素个数就是销毁次数
{
for (i = 0; i < count - 1; i++)//销毁非首节点
{
ptail = ptail->next;
}
free(ptail);
ptail = NULL;
count--;
}
*pphead = NULL;
}
1.断言
2.如果没有节点,直接返回
3.计算元素个数,计算销毁次数
4.外循环每循环一次,就销毁当前的尾节点
5.内循环循环完,找到最后一个节点,销毁