一、单链表
单链表是由若干个节点连接起来的链式结构储存表,单链表在数据存储和数据处理方面拥有较高的效率和简单的操作模式。单链表相较于顺序表,它的添加数据方式更加高效,能够在极大程度上减少对cpu资源的使用,它更加节省存储资源,分散式的存储方式可以避免空间浪费。下面我将通过它的构成和功能上分别介绍我对单链表的理解。
二、构建单链表
单链表是由若干个节点构建起来的,所以要想构建单链表,首先要设计好每个节点的元素。每个节点由数据域和指针域构成,数据域负责存储需要的数据,而指针域负责连接下一个节点,也就是保存下一个节点的地址。
按照上述所说构建结构体节点:
//定义头结点指针
typedef int data_t; //此处是类型重命名
typedef struct node{
data_t data; //数据域
struct node *next;//指针域
}link_list_t;
链表节点已经设计好了,接下来就是在输入数据时自动将每个节点接入链表,创建一个只包含头结点的空链表。此时需要用函数申请空间用来存储节点,因为存储空间一直在改变所以用到了malloc函数用于动态分配内存。
//创建链表
link_list_t *create_linklist(link_list_t *L){
L=(link_list_t *)malloc(sizeof(link_list_t));
if (NULL==L)
{
puts("创建链表失败\n");
return 0;
}
//初始化头链表
L->data=-1;
L->next=NULL;
//返回头结点首地址
return L;
}
三、构建链表对应功能
1.头部插入
头部插入的操作就是在头结点后面插入一个新节点,使新节点从头结点后加入链表。
具体方法:1.将头结点指向的地址传递给新节点的指针域
2.将新节点的地址传递给头结点指针域
这样便完成了节点从头部插入。
//头部插入函数
int head_insert(link_list_t *L,int value){
//健壮性检测
if(NULL==L){
puts("传入表参数非法\n");
return -1;
}
//创建新节点
link_list_t *N=(link_list_t *)malloc(sizeof(link_list_t));
if(NULL==N){
puts("创建新节点失败!\n");
return -1;
}
//初始化新节点
N->data=value;
N->next=NULL;
//头部插入操作
link_list_t *q=L;
N->next=q->next;
q->next=N;
return 0;
}
2.遍历链表
遍历链表就是将链表储存的所有值显示出来。
//遍历链表
int show_link(link_list_t *L){
//健壮性检测
if(NULL==L){
puts("传入表参数非法\n");
return -1;
}
if(NULL==L->next){
printf("表为空表\n");
return 0;
}
link_list_t *q=L;
while (q->next!=NULL)
{
q=q->next;
printf("%d\n",q->data);
}
return 0;
}
3.尾部插入
跟头部插入的效果一样,从尾部插入节点,不过将新节点的地址传递给尾节点后需要将新节点的指针域制空。
//尾部插入
int tail_link(link_list_t *L,int value){
//健壮性检测
if(NULL==L){
puts("传入表参数非法\n");
return -1;
}
if(NULL==(L)->next){
printf("表为空表\n");
}
link_list_t *q=L;
while (q->next!=NULL)
{
q=q->next;
}
link_list_t *n=(link_list_t *)malloc(sizeof(link_list_t));
if(NULL==n)
{
puts("创建新节点失败\n");
return -1;
}
n->next=NULL;
n->data=value;
q->next=n;
return 0;
}
4.删除节点
删除节点就是将节点所占用的空间进行释放,并将后面的节点与前面的节点进行连接。删除节点时一定要注意,要删除的节点需要提前保留好地址以便对节点空间进行释放避免内存泄露的发生。
int del_link(link_list_t *L,int pos){
//健壮性检测
if(NULL==L){
puts("传入表参数非法\n");
return -1;
}
if(NULL==(L)->next){
printf("表为空表\n");
return 0;
}
if(0>pos){
puts("位置参数必须大于0\n");
return -1;
}
int i=0;
link_list_t *p=L;
while (i<pos-1)
{
p=p->next;
if (NULL==p->next &&pos<0)
{
printf("表里只有一个元素");
return -1;
}
if (NULL ==p->next->next&&i<pos-1)
{
printf("位置参数超越表长");
return -1;
}
i++;
}
link_list_t *q=p->next;
p->next=p->next->next;
printf("%d将被删除\n",q->data);
q->next=NULL;
free(q);
q=NULL;
return 0;
}//任意删除节点
还有一种删除方法,就是借用顺序表的删除方式,用后面的数据覆盖前面的数据,最后释放掉尾节点,并建立新的尾节点。不过这种方法对内存资源的使用度过高,不建议在长链表中使用。
5.释放链表
释放链表就是将整个链表的空间全部释放,应注意释放每一个节点时应保留下一个节点的地址。防止内存泄露的发生。
int free_link(link_list_t *L){
//健壮性检测
if(NULL==L){
puts("传入表参数非法\n");
return -1;
}
if(NULL==L->next){
printf("表为空表\n");
return 0;
}
link_list_t *q=L;
while (L!=NULL)
{
q=q->next;
printf("即将释放内存!\n");
free(L);
L=q;
}
L=NULL;
return 0;
}
6.任意位置插入节点
插入的操作跟删除节点的操作大致相同,找到要插入的节点的位置进行数据插入。在这里要注意发生插入点在链表外的情况。
int pos_insert(link_list_t *L,int value,int number){
//健壮性检测
if(NULL==L){
puts("传入表参数非法\n");
return -1;
}
if(NULL==(L)->next){
printf("表为空表\n");
return 0;
}
link_list_t *N=(link_list_t *)malloc(sizeof(link_list_t));
int i=0;
while (i<number-1&&L->next!=NULL)
{
L=L->next;
if(NULL==L->next&&i<number-1)
{
printf("参数超过链表最高长度,访问越界!\n");
return -1;
}
i++;
}
if(NULL==N)
{
puts("创建新节点失败\n");
return -1;
}
N->next=L->next;
N->data=value;
L->next=N;
return 0;
}//任意位置插入
7.反转链表
反转链表将链表内的数据进行反转,其中涉及到了链表中交换数据的操作:将链表从第一个节点出断开,利用头插法将后面的节点依次插入链表中实现反转。
int overturn_list(link_list_t *L){
//健壮性判断
if (NULL==L)
{
puts("传入数据非法\n");
return -1;
}
if(NULL==L->next||NULL==L->next->next){
puts("表为空表或者只有一个数据元素");
return -1;
}
//反转
//分成q表和L表
link_list_t *q=L->next->next;
L->next->next=NULL;
//将p表中节点依次对l表进行头部插入
link_list_t *p=q;
while (q!=NULL)
{
p=q;
q=p->next;
p->next=L->next;
L->next=p;
}
return 0;
}//翻转链表
8.链表冒泡排序
利用传统的冒泡排序方法进行排序,定义两个指针,一个用来交换链表中的节点数据,一个用来保存头结点位置。设置上限时要注意不能越界。
int sort_link(link_list_t *L){
//健壮性检测
if(NULL==L){
puts("传入表参数非法\n");
return -1;
}
if(NULL==(L)->next){
printf("表为空表\n");
return 0;
}
data_t t=0;
link_list_t *q=L;
link_list_t *p=L;
while (L->next!=NULL)
{
while(q->next!=NULL){
if (q->data > q->next->data)
{
t=q->data;
q->data=q->next->data;
q->next->data=t;
}
q=q->next;
}
q=p;
L=L->next;
}
return -1;
}
四、主函数设计及整体感受
在主函数的设计中,我才用了比较喜欢的菜单式。
#include"link_list.h"
int main(int argc, char const *argv[])
{
link_list_t *L=create_linklist(L);
int value,number;
int val;
int pos;
while (1)
{
printf("*********单链表功能选择*********\n");
printf("**********1.初始化**************\n");
printf("**********2.头插入**************\n");
printf("**********3.任意节点删除********\n");
printf("**********4.任意节点插入********\n");
printf("**********5.释放节点************\n");
printf("**********6.反转链表************\n");
printf("**********7.尾部删除************\n");
printf("**********8.头部删除************\n");
printf("**********9.遍历链表************\n");
printf("**********0.排序****************\n");
int flag=0;
scanf("%d",&flag);
while((getchar()!='\n'));
switch (flag)
{
case 1:
L=create_linklist(L);
break;
case 2:
while(1){
printf("请输入您要插入的值");
scanf("%d",&val);
if(val==-1){
break;
}
head_insert(L,val);
}
break;
case 3:
printf("请输入您要删除的位置:");
scanf("%d",&pos);
del_link(L,pos);
break;
case 4:
printf("请输入您要插入的位置");
scanf("%d",&value);
printf("情输入您要插入的值");
scanf("%d",&number);
pos_insert(L,value,number);
break;
case 5:
fre_doulink(&L);
break;
case 6:
overturn_list(L);
break;
case 7:
del_tail_link(L);
break;
case 8:
del_head_link(L);
break;
case 9:
show_link(L);
break;
case 0:
sort_link(L);
break;
default:
return 0;
}
}
}
方便对链表进行操作,也方便观察结果。
感受:链表是我学习c语言以来第一个比较系统项目。虽然很简单功能也很少,但是从创建到释放,每一个功能都是在对c语言语法的联系和更深的理解,以后也会多多分享一些体会,慢慢的感受编程,爱上编程。