单链表的的特点:用一组任意的储存单元存储线性表的数据元素(这组存储单元可以是连续的,也可以是不连续的)。为了表示每个数据元素与其直接后续数据元素的逻辑关系,对于每个数据元素来说,除了存储其本身的信息外,还需存储一个指示其直接后继的信息,这两部分信息便称为结点。
整个链表的存取必须从头指针开始进行,头指针表示链表的第一个结点,同时由于最后一个元素没有直接后继,则单链表中最后一个的节点为空(NULL)。
类型命名
为了方便我们写代码,我们先将类型名重命名。
typedef int lala;
单链表的存储结构
typedef struct SListNode//链表结构
{
lala data;
struct SListNode* next;//指向下一个节点的指针
}sn;
data为存储结点本身的信息,next为指向下一个结点的指针。
链表的打印
void print(sn* ps)//打印链表,接受链表的地址
{
sn* pa = ps;
while (pa)
{
printf("%d->", pa->data);
pa = pa->next;
}
printf("NULL");
}
为了方便我们调试,先写一个链表的打印函数。
插入数据
sn* buy(lala x)//插入数据
{
sn* pa = malloc(sizeof(sn));
if (pa == NULL)
{
printf("NULL");
exit(1);
}
pa->next = NULL;
pa->data = x;
return pa;
}
我们先为要插入的数据创建一个结点,之后再写出尾插和头插函数。
尾插法
void backru(sn** ps, lala x)//尾部插入,创建二级指针是要接受一级结构体指针的地址
{
assert(ps);
sn* pa = buy(x);
if (*ps == NULL)//*ps为指向第一个节点的指针,而这种情况表示链表只有一个节点
{
*ps = pa;
}
else//链表有多个节点的情况
{
sn* pb=*ps;
while (pb->next)
{
pb = pb->next;
}
pb->next = pa;
}
}
这里我们尾插函数是要修改指针里面的内容,所以我们要使用二级指针来接收一级指针的地址,
这里尾插有两种情况,一种是链表内只有一个结点,第二种是链表内有多个结点,所以在尾插函数内要分两种情况讨论。
头插法
void frontru(sn** pa, lala x)//头部插入
{
assert(pa);
sn* pb = buy(x);
pb->next = *pa;
*pa = pb;
}
头插法相对比较简单,首先我们要断言pa指针是否为空,然后生成一个结点,并将这个结点插入头部。
尾删法
void backshan(sn** pa)//尾删
{
assert(pa && *pa);
if ((*pa)->next == NULL)//链表只有一个节点
{
free(*pa);
*pa = NULL;
}
else//链表有多个节点
{
sn* a;
sn* b=*pa;
a = *pa;
while (a->next)
{
b = a;
a = a->next;
}
free(a);
a = NULL;
b->next = NULL;
}
}
尾删法和尾插法一样也分两种情况讨论,链表有一个或多个结点,并且在尾删法的时候我们要进行断言,排除链表为空删除空间这种错误情况。
头删法
void toushan(sn** ps)//头删
{
assert(ps && *ps);
sn* new = (*ps)->next;
free(*ps);
*ps = new;//将第二个节点的地址给头节点
}
在进行头删的时候我们也要进行断言指针,然后创建一个新的结构体指针,释放它所指向的空间并让他指向下一个位置。
查找
sn* find(sn* ps,lala x)//查找
{
assert(ps);
sn* new = ps;
while (new)
{
if (new->data == x)
{
return new;
}
new = new->next;
}
return NULL;
}
可通过查找函数的返回值来判断是否找到数据。
在指定位置之前插入信息
void insert(sn** ps, sn* pos, lala x)//在指定位置之前插入信息
{
assert(ps && *ps);
assert(pos);
sn* new = buy(x);
if (*ps == pos)//链表只有一个节点
{
frontru(ps, x);
}
else//链表有多个节点
{
sn* pa = *ps;
while (pa->next != pos)
{
pa = pa->next;//找到pos前的一个位置
}
//找到位置了
pa->next = new;
new->next = pos;
}
}
我们首先要断言ps和*ps以及pos位置,排除这些位置为空的情况,我们的pos位置可以通过find函数去寻找,如果链表只有一个结点,我们可以直接去使用头插法,如果有多个结点,我们就要去遍历整个链表。
在指定位置之后插入结点
void backint(sn* pos, lala x)//在指定位置之后插入信息
{
assert(pos);
sn* new = buy(x);
new->next = pos->next;
pos->next = new;
}
在指定位置之后插入结点,我们不需要头结点,我们只需要将pos的next指针传给新节点的next,意思是新节点的next指向后继结点,然后再将新节点的地址赋予pos的next。
删除pos结点
void shan(sn** pa, sn* pos)//删除pos节点
{
sn* new = *pa;
if (pos == *pa)
{
toushan(pa);
}
else
{
while (new->next != pos)
{
new = new->next;
}
new->next = pos->next;
free(pos);
pos = NULL;
}
}
删除pos结点也分为两种情况,链表只有一个或多个结点。有多个结点时通过遍历链表来找到pos的位置并将其释放,只有一个结点使用头删便可以删除。
删除pos之后的结点
void after(sn* pos)//删除pos之后节点
{
sn* new = NULL;
new= pos->next;
pos->next = new->next;
free(new);
new = NULL;
}
删除pos之后的结点我们只需通过pos的next来找到pos之后的结点,并将其释放就行了。
单链表的销毁
void delete(sn** ps)
{
sn* del = *ps;
sn* next = NULL;
while (del)
{
next = (*ps)->next;
free(del);
*ps = next;
}
*ps == NULL;
}
我们只需通过遍历链表并不断的释放该链表的结点,最后将头节点置空。
源码
至此单链表的基础结构以全部完成,以下是源码。
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
typedef int lala;
typedef struct SListNode//链表结构
{
lala data;
struct SListNode* next;//指向下一个节点的指针
}sn;
void print(sn* ps)//打印链表,接受链表的地址
{
sn* pa = ps;
while (pa)
{
printf("%d->", pa->data);
pa = pa->next;
}
printf("NULL");
}
sn* buy(lala x)//插入数据
{
sn* pa = malloc(sizeof(sn));
if (pa == NULL)
{
printf("NULL");
exit(1);
}
pa->next = NULL;
pa->data = x;
return pa;
}
void backru(sn** ps, lala x)//尾部插入,创建二级指针是要接受一级结构体指针的地址
{
assert(ps);
sn* pa = buy(x);
if (*ps == NULL)//*ps为指向第一个节点的指针,而这种情况表示链表只有一个节点
{
*ps = pa;
}
else//链表有多个节点的情况
{
sn* pb=*ps;
while (pb->next)
{
pb = pb->next;
}
pb->next = pa;
}
}
void frontru(sn** pa, lala x)//头部插入
{
assert(pa);
sn* pb = buy(x);
pb->next = *pa;
*pa = pb;
}
void backshan(sn** pa)//尾删
{
assert(pa && *pa);
if ((*pa)->next == NULL)//链表只有一个节点
{
free(*pa);
*pa = NULL;
}
else//链表有多个节点
{
sn* a;
sn* b=*pa;
a = *pa;
while (a->next)
{
b = a;
a = a->next;
}
free(a);
a = NULL;
b->next = NULL;
}
}
void toushan(sn** ps)//头删
{
assert(ps && *ps);
sn* new = (*ps)->next;
free(*ps);
*ps = new;//将第二个节点的地址给头节点
}
sn* find(sn* ps,lala x)//查找
{
assert(ps);
sn* new = ps;
while (new)
{
if (new->data == x)
{
return new;
}
new = new->next;
}
return NULL;
}
void insert(sn** ps, sn* pos, lala x)//在指定位置之前插入信息
{
assert(ps && *ps);
assert(pos);
sn* new = buy(x);
if (*ps == pos)//链表只有一个节点
{
frontru(ps, x);
}
else//链表有多个节点
{
sn* pa = *ps;
while (pa->next != pos)
{
pa = pa->next;//找到pos前的一个位置
}
//找到位置了
pa->next = new;
new->next = pos;
}
}
void backint(sn* pos, lala x)//在指定位置之后插入信息
{
assert(pos);
sn* new = buy(x);
new->next = pos->next;
pos->next = new;
}
void shan(sn** pa, sn* pos)//删除pos节点
{
sn* new = *pa;
if (pos == *pa)
{
toushan(pa);
}
else
{
while (new->next != pos)
{
new = new->next;
}
new->next = pos->next;
free(pos);
pos = NULL;
}
}
void after(sn* pos)//删除pos之后节点
{
sn* new = NULL;
new= pos->next;
pos->next = new->next;
free(new);
new = NULL;
}
void delete(sn** ps)
{
sn* del = *ps;
sn* next = NULL;
while (del)
{
next = (*ps)->next;
free(del);
*ps = next;
}
*ps == NULL;
}