文章目录
提示:这只是笔记
一、链表的引入
1·1 先说说数组的缺陷:数组元素必须一致;数组元素个数一旦指定就不可更改。
1·2 结构体解决了数组的第一个缺陷,链表就是解决了数组的第二个缺陷。
1·3 时刻记住链表是用来解决数组的大小不能扩展问题。
二、单链表的实现
2·1 单链表的节点构成 : 有效数据 + 指针
2·2 定义的struct node 只是一个结构体,本身并没有变量生成,也不占内存。结构体的定义相当于为链表的节点定义了一个模板,但是还没有一个节点,将来在实际创建链表时需要一个节点时用这个模板来复制一个即可。
2·3 堆内存的申请 与 使用
(1)链表的内存要求比较灵活,不能用栈,也不能用数据段(存储全局变量的)。只能用堆内存。
(2)使用堆内存来创建一个链表节点的步骤:第一,申请堆内存,大小为一个节点(包括检查申请结果是否正确);第二,清理申请到的堆内存;第三,把申请到的堆内存当作一个新节点;第四,填充新节点的有效数据和指针区域。
2·4 链表的头指针
(1)头指针并不是节点,而是一个普通的指针,只占4个字节。头指针的类型是struct node * 类型的,所以他才能指向链表的节点。
(2)一个链表的典型实现是:头指针指向链表的第一个节点,第一个节点中的指针指向第二个节点,第二个节点的指针指向第三个节点,这样一直推。形成了链表。
单链表实现代码:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
// 构建一个链表节点,结构体打包
struct node
{
int data; //有效数据
struct node *pNext; // 指向下一个节点的指针
};
/* 创建一个链表节点的函数
返回值:结构体类型的指针,
指向本函数新创建的节点的首地址。
*/
struct node *cread_node(int Data)
{
// 创建一个结构体指针p,
// p指向的就是malloc申请的新节点首地址
struct node *p = (struct node *)malloc(sizeof(struct node));
if(NULL == p)
{
printf("malloc error!\n");
return NULL;
}
// 清理申请到的堆内存
memset(p,0,sizeof(struct node));
// 填充节点
p->data = Data;
p->pNext = NULL;
//pNext 指向的应该是下一个节点的首地址,
//实际操作时将下一个节点malloc返回的指针赋值给pNext。
return p;
}
int main(void)
{
// 定义头指针
struct node *pHeader = NULL;
// 创建第一个节点,并且和头节点绑定
pHeader = cread_node(1);
// cread_node函数的返回值(指针p)就是(第一个)一个节点的首地址
// 创建第二个节点
pHeader -> pNext = cread_node(2);
// 创建第3个节点
pHeader ->pNext -> pNext = cread_node(3);
// 访问节点,拿出节点数据域的数据
printf("node 1 data = %d\n",pHeader -> data );
printf("node 2 data = %d\n",pHeader ->pNext -> data );
printf("node 3 data = %d\n",pHeader -> pNext ->pNext -> data );
return 0;
}
结果:
node 1 data = 1
node 2 data = 2
node 3 data = 3
注意点:
(1)因为是链表,所以在填充数据以及访问节点的时候 应该从头指针(pHeader)处入手。
(2)封装 创建新节点函数的关键点在于:函数的接口设计(函数传参和返回值的设计)。
三、 单链表的插入
3·1 单链表的尾部插入
// 头指针 -> 第一个节点 -> 第二个节点 -> 第三个节点......的方式。
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
// 构建一个链表节点,结构体打包
struct node
{
int data; //有效数据
struct node *pNext; // 指向下一个节点的指针
};
// 创建一个链表节点的函数
// 返回值:结构体类型的指针,指向本函数新创建的节点的首地址。
struct node *cread_node(int Data)
{
// 创建一个结构体指针p,p指向的就是malloc申请的新节点首地址
struct node *p = (struct node *)malloc(sizeof(struct node));
if(NULL == p)
{
printf("malloc error!\n");
return NULL;
}
// 清理申请到的堆内存
memset(p,0,sizeof(struct node));
// 填充节点
p->data = Data;
p->pNext = NULL;
//pNext 指向的应该是下一个节点的首地址,实际操作时将下一个节点malloc返回的指针赋值给pNext。
return p;
}
// 尾部插入 函数
void insert_tail(struct node *pH,struct node *new)
{
// 两步完成尾部插入
// 第一步,先找到链表最后一个节点
struct node *p = pH;
while (NULL != p->pNext)
{
p = p->pNext; // 往后走一个节点
}
// 第二步,将新节点插入到最后一个节点尾部
p->pNext = new;
}
int main(void)
{
// 定义头指针
struct node *pHeader = cread_node(1);// pHeader 与第一个节点绑定
insert_tail(pHeader,cread_node(23)); // 将第二个节点插入第一个节点尾部
insert_tail(pHeader,cread_node(356));// 将第3个节点插入第2个节点尾部
insert_tail(pHeader,cread_node(467));// 将第4个节点插入第3个节点尾部
// 访问节点,拿出节点数据域的数据
printf("node 1 data = %d\n",pHeader -> data );
printf("node 2 data = %d\n",pHeader ->pNext -> data );
printf("node 3 data = %d\n",pHeader -> pNext ->pNext -> data );
printf("node 4 data = %d\n",pHeader->pNext->pNext->pNext->data );
return 0;
}
结果:
node 1 data = 1
node 2 data = 23
node 3 data = 356
node 4 data = 467
链表还有一种用法,就是把头指针指向第一个节点作为头节点使用。
头节点的特点是:第一,紧跟在头指针后面;第二,头节点的数据是空的(有时是存储的整个链表的节点数),指针部分指向下一节点,也就是第一节点。
如此看来,头节点与其他节点确实不同。所以我们在创建一个链表是添加节点的方法也不同。头节点在创建头指针的时候一并创建并且和头指针关联起来;后面指针存储数据的节点用节点添加函数(intsert_tail)进行添加。
总结来说,没有头结点对第一个结点的操作大多和中间结点不太一样,在第一个元素之前插入或删除第一个元素,每个操作都要考虑特殊情况,有头结点的话就不必考虑那么多了,还不容易出现代码错误。空表和非空表的处理也统一了,有头节点空表head->next==NULL,没有头节点空表,head->NULL,可以很明显的看出来,带头节点的与后面其他节点的格式类似 -----------------------这是借鉴的某一位大神的总结
// 头指针 -> 头节点 -> 第一个节点 -> 第二个节点.....的方式。
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
// 构建一个链表节点,结构体打包
struct node
{
int data; //有效数据
struct node *pNext; // 指向下一个节点的指针
};
// 创建一个链表节点的函数
// 返回值:结构体类型的指针,指向本函数新创建的节点的首地址。
struct node *cread_node(int Data)
{
// 创建一个结构体指针p,p指向的就是malloc申请的新节点首地址
struct node *p = (struct node *)malloc(sizeof(struct node));
if(NULL == p)
{
printf("malloc error!\n");
return NULL;
}
// 清理申请到的堆内存
memset(p,0,sizeof(struct node));
// 填充节点
p->data = Data;
p->pNext = NULL;
//pNext 指向的应该是下一个节点的首地址,实际操作时将下一个节点malloc返回的指针赋值给pNext。
return p;
}
// 尾部插入 函数
// 并且计算添加了新的节点后节点总数,把节点总数写进头节点中。
/* 思路:头指针遍历,直到走到原来的最后一个节点。
原来最后一个节点里面的pNext是NULL,
现在我们只要将它改成new就行了。添加了之后新节点就变成了最后一个。
*/
void insert_tail(struct node *pH,struct node *new)
{
int node_sum_num = 0;
// 两步完成尾部插入
// 第一步,先找到链表最后一个节点
struct node *p = pH;
while (NULL != p->pNext)
{
p = p->pNext; // 往后走一个节点
node_sum_num ++;
}
// 第二步,将新节点插入到最后一个节点尾部
p->pNext = new;
pH -> data = node_sum_num + 1; // 这个1表示头节点
}
int main(void)
{
// 定义头指针 he 绑定头节点
struct node *pHeader = cread_node(-1);
insert_tail(pHeader,cread_node(1)); // 将第1个节点插入头节点尾部
insert_tail(pHeader,cread_node(356));// 将第2个节点插入第1个节点尾部
insert_tail(pHeader,cread_node(467