一、链表原理
(1)链表的组成
链表是由若干个节点组成的(链表的各个节点结构是完全类似的),节点是由有效数据和指针组成的。有效数据区域用来存储信息完成任务的,指针区域用于指向链表的下一个节点从而构成链表。
(2)链表的作用
时刻谨记:链表就是用来解决数组的大小不能动态扩展的问题,所以链表其实就是当数组用的。直白点:链表能完成的任务用数组也能完成,数组能完成的任务用链表也能完成。但是灵活性不一样。
简单说:链表就是用来存储数据的。链表用来存数据相对于数组来说优点就是灵活性,需要多少个动态分配多少个,不占用额外的内存。数组的优势是使用简单(简单粗暴)。
二、链表构成
1、单链表的实现
(1)单链表是由节点组成的,节点中包含:有效数据和指针。
struct node
{
int data;
struct node *pNext;
}
(2)定义的struct node只是一个结构体,本身并没有变量生成,也不占用内存。结构体定义相当于为链表节点定义了一个模板,但是还没有一个节点,将来在实际创建链表时需要一个节点时用这个模板来复制一个即可。
(3) 头指针pheader(每个链表都会有一个头)
头指针并不是节点,而是一个普通指针,只占4字节。头指针的类型是struct node *类型的,所以它才能指向链表的节点。
2、创建一个链表节点
(1)链表的内存要求比较灵活,不能用栈,也不能用data数据段。只能用堆内存。
(2)用堆内存来创建一个链表节点的步骤:
1、申请堆内存,大小为一个节点的大小(检查申请结果是否正确);
2、清理申请到的堆内存;
3、把申请到的堆内存当作一个新节点;
4、填充你哦个新节点的有效数据和指针区域。
代码如下:
struct node *pHeader = NULL;
/********************************************************************/
// 每创建一个新的节点,把这个新的节点和它前一个节点关联起来
// 创建一个链表节点 malloc返回一个int指针,指向存放数据的地址,这里强制转换
struct node *p = (struct node *)malloc(sizeof(struct node));
if (NULL == p)
{
printf("malloc error.\n");
return -1;
}
// 清理申请到的堆内存
bzero(p, sizeof(struct node));
// 填充节点
p->data = 1;
p->pNext = NULL; // 将来要指向下一个节点的首地址
// 实际操作时将下一个节点malloc返回的指针赋值给这个
pHeader = p; // 将本节点和它前面的头指针关联起来
3、链表头尾插入分析代码
#include <stdio.h>
#include <strings.h>
#include <stdlib.h>
struct node
{
int data ;
struct node *pNext;
};
struct node *pHeader = NULL;
// 作用:创建一个链表节点
// 返回值:指针,指针指向我们本函数新创建的一个节点的首地址
struct node * creat_node(int data)
{
struct node *p = (struct node *)malloc(sizeof(struct node));
if(NULL == p)
{
printf("fail/n");
return NULL;
}
bzero(p,sizeof(struct node));
p->data = data;
p->pNext = NULL;
return p;
}
//作用:尾部插入一个节点
void insert_tail(struct node *pH,int data)
{
struct node *p = pH;
while(NULL != p->pNext) //找到最后一个指向NULL的指针,就是链表的最后一个节点
{
p = p->pNext; //这也是链表遍历的方法
}
p->pNext = creat_node(data); //将新节点插入到最后一个节点尾部
}
void insert_head(struct node *pH,int data)
{
struct node *p = pH;
struct node *new = creat_node(data);
//新的节点的pNext指向空节点之前指向的节点首地址
new->pNext = pH->pNext;
//将空节点的pNext指针指向新节点的首地址;pH->pNext里面的值就是结构体指针
//new里面的值(新节点malloc后在内存中分配的实际地址)
pH->pNext = new;
}
int main()
{
pHeader = creat_node(0);
insert_tail(pHeader,5);
insert_head(pHeader,8);
insert_tail(pHeader,6);
printf("pHeader->data = %d\n",pHeader->data);
printf("pHeader->data = %d\n",pHeader->pNext->data);
printf("pHeader->data = %d\n",pHeader->pNext->pNext->data);
printf("pHeader->data = %d\n",pHeader->pNext->pNext->pNext->data);
}
4、链表删除节点
(1)首先我们传入一个链表的头指针,传入的链表有N个节点
int delate_link(struct node *pH,int data)
{
struct node *p = pH;
struct node *pPrev = NULL;
while(NULL != p -> pNext)
{
//将先前的指针保存,不能保存pPrev = p ->pNext,p ->pNext已经指向了下一个节点的首地址
//所以pPrev保存的是下一个节点的首地址,pPrev = p; 之后再pPrev ->pNext
pPrev = p; //保存被删节点前面的节点首地址
p = p -> pNext;
if(p -> data == data)
{
pPrev -> pNext = p -> pNext; //被删节点前节点pNext指向被删节点的后一个节点的首地址
free(p);
return 0;
}
}
return 0;
}
5 链表的反转
int reserve(struct node *pH)
{
struct node *p = pH->pNext;
struct node *plast;
if(NULL == p || NULL == p -> pNext)
return 0;
while(NULL != p -> pNext)
{
plast = p -> pNext;
if(p == pH->pNext)
{
p -> pNext = NULL;
}
else
{
p -> pNext = pH -> pNext;
pH -> pNext = p;
}
p = plast;
}
insert_head(pHeader,p->data);
}
6,双链表与单链表对比
(1)很多时候我们都用的是双链表,虽然每个节点多占用一个指针(4个字节),但是可以忽略,双链表在做删除节点和插入节点等操作时,会快很多,比如不需要定义plast,pPrev,去保存可能被断开的节点地址,因为他有双向指针,里面有前前后后所以地址的保存。