本文已参与「新人创作礼」活动,一起开启掘金创作之路。
一、单链表的实现
1.1链表的节点
节点里面有两个区域,一个数据域,一个指针域。
struct node
{
int data; //数据域,可以再加数据以及数据类型。
struct node *Next; //指向下一个节点的指针
};
struct node只是一个结构体,不占用内存,本身也没有变量生成。只是一节点的模板,在以后实际需要中直接调用这个模板。
1.2 堆内存的使用
为什么要用堆内存,前面关于内存的时候说过,堆的内存需要程序员自己释放,比较灵活。但是如果使用栈,程序自己释放的话,不够灵活,无法与我们的链表相匹配。
堆内存创建链表步骤:
- 申请堆内存,大小作为一个节点的大小。
- 清理堆内存,因为内存是脏的。否者可能会有一些乱七八糟的数据。
- 把申请的堆内存当做一个1新的节点。
- 填充新节点到数据域和指针域。
1.3 链表的初期创建
#include <stdio.h>
#include <stdlib.h>
struct node
{
int data; //数据域,可以再加数据以及数据类型。
struct node *Next; //指向下一个节点的指针
};
int main()
{
/*为节点申请空间,现在malloc前面不加类型了,因为好像在最近的C标准中,malloc自己会转类型1,你加了反而还可能会产生影响,当然加上也没啥大问题1*/
struct node *p = malloc(sizeof(struct node));
if(NULL == p)
{
prrintf("malloc error!!\n");
return -1;
}
//清理申请的内存(一般没用,我自己也很少用)
bzero(p,sizeof(struct node ));
//将数据放入内存中
p->data = 1; //将值放入数据域中。
p->Next = NULL; //指向下一个节点的首地址。
}
1.4 完善链表,并赋值
#include <strings.h>
#include <stdio.h>
#include <stdlib.h>
struct node
{
int data; //数据域,可以再加数据以及数据类型。
struct node *Next; //指向下一个节点的指针
};
int main()
{
struct node *Head = NULL; //头指针
/*为节点申请空间,现在malloc前面不加类型了,因为好像在最近的C标准中,malloc自己会转类型,你加了反而还可能会产生影响,当然加上也没啥大问题,看个人爱好吧*/
struct node *p =malloc(sizeof(struct node));
Head = p; //将第一个节点的值赋值给头节点
if(NULL == p)
{
prrintf("malloc error!!\n");
return -1;
}
//清理申请的内存
bzero(p,sizeof(struct node ));
//将数据放入内存中
p->data = 1; //将值放入数据域中。
p->Next = NULL; //指向下一个节点的首地址。
/**************************************/
struct node *p1 = malloc(struct node);
if(NULL == p1)
{
prrintf("malloc error!!\n");
return -1;
}
//清理申请的内存
bzero(p1,sizeof(struct node ));
//将数据放入内存中
p1->data = 2; //将值放入数据域中。
p->Next = p1; //指向下一个节点的首地址。
/***********************************************/
struct node *p2 = malloc(struct node);
if(NULL == p2)
{
prrintf("malloc error!!\n");
return -1;
}
//清理申请的内存
bzero(p1,sizeof(struct node ));
//将数据放入内存中
p2->data = 3; //将值放入数据域中。
P1->Next = p2; //指向下一个节点的首地址。
}
这样我们就创建了一个有3个节点的链表了。 那么我们来简单访问一下这节链表
#include <strings.h>
#include <stdio.h>
#include <stdlib.h>
struct node
{
int data; //数据域,可以再加数据以及数据类型。
struct node *Next; //指向下一个节点的指针
};
int main()
{
struct node *Head = NULL; //头指针
/*为节点申请空间,现在malloc前面不加类型了,因为好像在最近的C标准中,malloc自己会转类型,你加了反而还可能会产生影响,当然加上也没啥大问题,看个人爱好吧*/
struct node *p = malloc(sizeof(struct node));
Head = p; //将第一个节点的值赋值给头节点
if(NULL == p)
{
printf("malloc error!!\n");
return -1;
}
//清理申请的内存
bzero(p,sizeof(struct node ));
//将数据放入内存中
p->data = 1; //将值放入数据域中。
p->Next = NULL; //指向下一个节点的首地址。
/**************************************/
struct node *p1 = malloc(sizeof(struct node));
if(NULL == p1)
{
printf("malloc error!!\n");
return -1;
}
//清理申请的内存
bzero(p1,sizeof(struct node ));
//将数据放入内存中
p1->data = 2; //将值放入数据域中。
p->Next = p1; //指向下一个节点的首地址。
/***********************************************/
struct node *p2 = malloc(sizeof(struct node));
if(NULL == p2)
{
printf("malloc error!!\n");
return -1;
}
//清理申请的内存
bzero(p2,sizeof(struct node ));
//将数据放入内存中
p2->data = 3; //将值放入数据域中。
p1->Next = p2; //指向下一个节点的首地址。
/*****************************************************/
/****访问******/
printf("node p data = %d\n",p->data);
printf("node p1 data = %d\n",p->Next->data);
printf("node p2 data = %d\n",p->Next->Next->data);
}
1.5 链表优化
刚才的代码看起来繁杂臃肿,因为我们刚才代码重复运用了开辟空间这个函数,我们可以把他封装起来,然后调用就好了。后期我们都需要改善代码,所以我们需要这里先完善一下
#include <strings.h>
#include <stdio.h>
#include <stdlib.h>
struct node
{
int data; //数据域,可以再加数据以及数据类型。
struct node* Next; //指向下一个节点的指针
};
struct node *create(int data)
{
struct node* p = malloc(sizeof(struct node));
if (NULL == p)
{
printf("malloc error!!\n");
return NULL;
}
bzero(p, sizeof(struct node));
p->data = data;
p->Next = NULL;
return p;
}
int main()
{
struct node* Head = NULL;
Head = create(1);
Head->Next = create(2);
Head->Next->Next = create(3);
/****访问******/
printf("node p data = %d\n", Head->data);
printf("node p1 data = %d\n", Head->Next->data);
printf("node p2 data = %d\n", Head->Next->Next->data);
}
这里我们先用头节点去一一访问,如果不用头节点去访问,那么我们的链表就没有任何意义,因为链表的作用就是通过上一个节点去访问下一个节点。
二、链表基本操作
2.1 链表的插入
2.1.1 尾插法
尾部插入较简单,因为不用去更改前面的数据。
我们先看一幅图,可以方便我们理解一下链表的插入:
虽然图丑,但是还是很明白的,我们需要一个头结点,来方便我们找到链表的第一个节点,头结点不算入链表长度内。
尾插法第一步就是找到尾巴,然后把尾节点的指向空改为指向新节点的数据域,把新节点的指针域指向空。
#include <stdio.h>
#include <stdlib.h>
struct node
{
int data;
struct node* Next;
};
/*构建节点,开辟空间*/
struct node *create_node(int data)
{
struct node* p = malloc(sizeof(struct node));
if (NULL == p)
{
printf("malloc error!\n");
return - 1;
}
p->data = data;
p->Next = NULL;
}
/*尾插法函数*/
void insert_tail(struct node *Head,struct node *New)
{
struct node *p = Head; //避免产生误会
/*遍历链表找到尾节点*/
while(NULL != p->Next)
{
p = p->Next;
}
/*找到尾节点后,改变他指针域的指向*/
p->Next = New;
New->Next = NULL;
}
int main()
{
struct node* Head = create_node(0); //头结点,方便我们找到链表的第一个节点
/*尾插法通过头节点插入*/
insert_tail(Head,create_node(1));
insert_tail(Head,create_node(2));
insert_tail(Head,create_node(3));
/*打印头结点的值*/
printf("P1 = %d\n",Head->data);
/*打印第一个点的值*/
printf("P2 = %d\n", Head->Next->data);
/*打印第二个点的值*/
printf("p3 = %d\n", Head->Next->Next->data);
/*打印第三个点的值*/
printf("p4 = %d\n", Head->Next->Next->Next->data);
system("pause");
return 0;
}
2.1.2头插法
头插法就是不停的新节点作头结点插入。
同样的先看图:
这个顺序很重要,先把现在新节点的指针域指向原来的第一个节点,再把原来的头结点指向新的数据域。
#include <stdio.h>
#include <stdlib.h>
struct node
{
int data;
struct node* Next;
};
/*构建节点,开辟空间*/
struct node *create_node(int data)
{
struct node* p = malloc(sizeof(struct node));
if (NULL == p)
{
printf("malloc error!\n");
return - 1;
}
p->data = data;
p->Next = NULL;
}
/*头插法函数*/
void insert_hand(struct node *Head,struct node *New)
{
/*头插法不需要遍历直接找第一个节点就完事
先把新节点的指针指向原来的第一个节点*/
New->Next = Head->Next;
/*原来的头结点指向现在的新节点*/
Head->Next = New;
}
int main()
{
struct node* Head = create_node(0); //头结点,方便我们找到链表的第一个节点
/*尾插法通过头节点插入*/
insert_hand(Head,create_node(1));
insert_hand(Head,create_node(2));
insert_hand(Head,create_node(3));
/*打印头结点的值*/
printf("P1 = %d\n",Head->data);
/*打印第一个点的值*/
printf("P2 = %d\n", Head->Next->data);
/*打印第二个点的值*/
printf("p3 = %d\n", Head->Next->Next->data);
/*打印第三个点的值*/
printf("p4 = %d\n", Head->Next->Next->Next->data);
system("pause");
return 0;
}
这里打印了头结点的值,你打印链表的话可以不用在意这个。
三、链表的遍历
什么是遍历:就相当于你从篮子里面把鸡蛋一个一个的提出来,不能一次拿两个,你也不能把同一个鸡蛋拿两次,按照一定的规律拿,先拿第一层,再拿第二层。 对于链表来说,就是把节点挨个拿出来。 对于数组来说,就是把数组的第一个到最后一个挨个拿出来。 遍历的要点: 1)不能遗漏 2)不能重复 3)追求效率(别人只要10s你来个一分钟,完都完了)
如何遍历: 你想要遍历链表,首先你得找到头和尾吧,你要知道之间的关系,然后从头到尾,链表的尾节点特征就是他的指针域指向的是空。 链表就这个意思,我们直接写代码搞。
#include <stdio.h>
#include <stdlib.h>
struct node
{
int data;
struct node* Next;
};
/*构建节点,开辟空间*/
struct node *create_node(int data)
{
struct node* p = malloc(sizeof(struct node));
if (NULL == p)
{
printf("malloc error!\n");
return - 1;
}
p->data = data;
p->Next = NULL;
}
/*尾插法函数*/
void insert_tail(struct node *Head,struct node *New)
{
struct node *p = Head; //避免产生误会
/*遍历链表找到尾节点*/
while(NULL != p->Next)
{
p = p->Next;
}
/*找到尾节点后,改变他指针域的指向*/
p->Next = New;
New->Next = NULL;
}
/*头插法函数*/
void insert_hand(struct node *Head,struct node *New)
{
/*头插法不需要遍历直接找第一个节点就完事
先把新节点的指针指向原来的第一个节点*/
New->Next = Head->Next;
/*原来的头结点指向现在的新节点*/
Head->Next = New;
}
/*遍历链表*/
void bianli(struct Node *Head)
{
struct node* p = Head;
while (NULL != p->Next)
{
p = p->Next; //一个一个节点往后移动往后移动;
printf("node data = %d \n",p->data);//打印每一个节点
}
}
int main()
{
struct node* Head = create_node(0); //头结点,方便我们找到链表的第一个节点
/*尾插法通过头节点插入*/
insert_tail(Head,create_node(1));
insert_tail(Head,create_node(2));
insert_tail(Head,create_node(3));
bianli(Head);
system("pause");
return 0;
}
其实这里有个关键点:
想一下为什么p = p->Next;要写在打印的前面。如果写在后面会造成什么影响。
这是不是将我们的头结点打印出来了。但是我们的最后一个节点没有打印出来。其实这就是一点,那个链表有无头节点指向第一个指针。
四、链表节点删除
链表删除的步骤: 1)遍历链表,不然你怎么找到对应的链表呢 2)和链表节点进行判断,如果一样,那就找到了,然后删除 3)判断是不是尾节点。 4)使用一个节点,指向我们遍历节点的前一个节点,这样方便我们进行操作 5)释放删除的节点的空间,否则造成内存泄漏 直接上代码
#include <stdio.h>
#include <stdlib.h>
struct node
{
int data;
struct node* Next;
};
/*构建节点,开辟空间*/
struct node *create_node(int data)
{
struct node* p = malloc(sizeof(struct node));
if (NULL == p)
{
printf("malloc error!\n");
return NULL;
}
p->data = data;
p->Next = NULL;
return p;
}
/*删除节点*/
void deletNode(struct Node* Head, int nData)
{
//遍历链表
struct node* p = Head;
//定义一个节点
struct node* pPrev = NULL;
while (NULL != p->Next)
{
pPrev = p;//节点指向遍历节点的前一个
p = p->Next;
//找节点,找到了
if (p->data == nData)
{
//判断是不是尾节点
if (NULL != p->Next)
{
pPrev->Next = NULL;
free(p);
}
else
{
pPrev->Next = p->Next;
free(p);
}
printf("删除完毕\n");
return 0;
}
}
printf("没有找到节点\n");
return -1;
}
五、单链表的逆序输出
连表逆序输出步骤 1)判断连表是不是为空或则只有一个节点 2)遍历之前的链表 3)将之前的链表用头插法不停的插入一个新的链表中 4)遍历新的链表
我们看代码
void reverseLink(struct node* Head)
{
struct node* p = Head->Next; //第一个节点
struct node* pBack;
//如果只有一个节点或没有节点
if ((NULL == p) || (NULL == p->Next))
return;
while (NULL != p->Next)
{
pBack = p->Next; //把原来的尾节点变为现在第一个节点
if (p == Head->Next)
{
p->Next = NULL;
}
else
{
p->Next = Head->Next;
}
Head->Next = p;
p = pBack;
}
insert_hand(Head,p);
}
六、完整的代码
因为这篇文章写了好久,因为中间忙,面试加上自己工作,确实没时间写,所以代码可能有点前后不一,还有就是用的编译器不一样,导致出现了一些问题,这里附上完整的代码
#include <stdio.h>
#include <stdlib.h>
/*定义结构体*/
struct node
{
int data;
struct node* Next;
};
/*构建节点,开辟空间*/
struct node *create_node(int data)
{
struct node* p = malloc(sizeof(struct node));
if (NULL == p)
{
printf("malloc error!\n");
return NULL;
}
p->data = data;
p->Next = NULL;
return p;
}
/*尾插法函数*/
void insert_tail(struct node *Head,struct node *New)
{
struct node *p = Head;
/*遍历链表找到尾节点*/
while(NULL != p->Next)
{
p = p->Next;
}
/*找到尾节点后,改变他指针域的指向*/
p->Next = New;
New->Next = NULL;
}
/*头插法函数*/
void insert_hand(struct node *Head,struct node *New)
{
/*头插法不需要遍历直接找第一个节点就完事
先把新节点的指针指向原来的第一个节点*/
New->Next = Head->Next;
/*原来的头结点指向现在的新节点*/
Head->Next = New;
}
/*遍历链表*/
void traverseLink(struct node *Head)
{
struct node* p = Head;
printf("开始遍历\n");
while (NULL != p->Next)
{
p = p->Next;
printf("node data = %d\n",p->data);
}
printf("遍历完毕\n");
}
/*删除节点*/
int deletNode(struct node *Head, int nData)
{
//遍历链表
struct node* p = Head;
//定义一个节点
struct node* pPrev = NULL;
while (NULL != p->Next)
{
pPrev = p;//节点指向遍历节点的前一个
p = p->Next;
//找节点,找到了
if (p->data == nData)
{
//判断是不是尾节点
if (NULL == p->Next)
{
pPrev->Next = NULL;
free(p);
}
else
{
pPrev->Next = p->Next;
free(p);
}
printf("**********删除完毕**********\n");
return 0;
}
}
printf("没有找到节点\n");
return -1;
}
int reverseLink(struct node* Head)
{
struct node* p = Head->Next;
struct node* pBack;
//如果只有一个节点或没有节点
if ((NULL == p) || (NULL == p->Next))
{
printf("链表不能反转\n");
return -1;
}
while (NULL != p->Next)
{
pBack = p->Next; //把原来的尾节点变为现在第一个节点
if (p == Head->Next)
{
p->Next = NULL;
}
else
{
p->Next = Head->Next;
}
Head->Next = p;
p = pBack;
}
insert_hand(Head,p);
printf("*********逆序后*********\n");
}
int main()
{
struct node* Head = create_node(0); //头结点,方便我们找到链表的第一个节点
/*尾插法通过头节点插入*/
insert_hand(Head,create_node(1));
insert_hand(Head,create_node(2));
insert_hand(Head,create_node(3));
insert_hand(Head,create_node(4));
/*
* traverseLink(Head);
* reverseLink(Head);
* traverseLink(Head);
*/
/*
* traverseLink(Head);
* deletNode(Head,?);
* traverseLink(Head);
*/
return 0;
}