一、什么是链表
将内存中若干个地址空间,用指针连起来。
链表:
是一种物理
存储单元
上非连续、非顺序的存储结构
,数据元素的逻辑顺序
是通过链表中的指针链接
次序实现的。
二、单向链表
链表中最简单的一种是单向链表
,它包含两个域,一个信息域
和一个指针域
。
这个链接指向列表中的下一个节点
,而最后
一个节点则指向一个空值
。
信息域:存储值
。指针域:存储下个节点的首地址
。
- 单向链表实现
- 链表节点结构体定义
typedef struct student
{
char name[10];
char sex;
int age;
//以上为信息域
//以下为指针域
struct student * pNext;//指向下一个节点的指针
}Student;
- 定义节点
//定义节点 第一个节点
Student *s1 = (Student*)malloc(sizeof(Student));
//节点赋值
strcpy_s(s1->name, sizeof(s1->name), "Binson");
s1->age = 10;
s1->sex = 'm';
//定义节点 第二个节点
Student *s2 = (Student*)malloc(sizeof(Student));
//节点赋值
strcpy_s(s2->name, sizeof(s2->name), "name2");
s2->age = 12;
s2->sex = 'f';
//定义节点 第三个节点
Student *s3 = (Student*)malloc(sizeof(Student));
//节点赋值
strcpy_s(s3->name, sizeof(s3->name), "name3");
s3->age = 20;
s3->sex = 'f';
- 链接节点
//将各节点连起来,形成链表
//定义一个头节点,头节点永远指向头。
Student* pHead = s1;
Student* pLast = NULL;//尾
Student* p = pHead;
//后面用各节点内部的指针,指向下一个节点。
s1->pNext = s2;// 第一个节点的指针域,指向第二个节点
s2->pNext = s3;// 第二个节点的指针域,指向第三个节点
s3->pNext = NULL;//最后一个节点的指针,指向NULL。
当然,后面还可以继续链接更多的节点。
注意: 永远不要丢失头指针
(pHead),这样,我们就通过pHead
,找到后面所有的节点。
- 例子: 实现一个10个学生节点的链表,并输出其值。(基于上述代码基础上实现)
Student* pHead = NULL;//首
Student* pLast = NULL;//尾
int i = 0;
int size = 10;
for ( i = 0; i < size; i++)
{
Student* p = (Student*)malloc(sizeof(Student));
p->pNext = NULL;
p->age = i + 18;//赋个值,打印的时候确保其唯一性
if (i == 0)//第一个节点,用pHead指向
{
pHead = p;
pLast = p;//只有一个节点,首尾为同一个
}
else
{
//不是第一个节点,就把其增加到后面
pLast->pNext = p;
pLast = p;//再把尾指针移动最后一个
}
}
//通过 pHead 输出
Student* p = pHead;//不要改变pHead的指向,使用临时指针进行遍历
while (p != NULL)
{
printf("%d\n", p->age);
p = p->pNext;//依次往后移动指针,循环变量的改变
}
//释放内存空间,这一步很重要,不使用了就要释放,不然就造成了内存泄漏
p = pHead;
while (p != NULL)
{
//需要先用个临时指针指向后一个,不然释放了之后,通过pNext找不到后一个了
Student* tmp = p->pNext;
free(p);
p = tmp;//依次往后移动指针
}
- 例子: 插入一个新节点:输入其年龄,实现在一个10个学生节点的链表中(按年级有序链表),将这个新节点插入进链表中,使其仍然有序。
//单链表前增加
Student* newStu = (Student*)malloc(sizeof(Student));
printf("请输入年龄:");
scanf_s("%d", &newStu->age);
Student* p = pHead;
Student* perP = pHead;
while (p != NULL)
{
if (p->age >= newStu->age )//表示要将newStu插入到p的前面
{
//判断p是否是pHead
if (p == pHead)
{
newStu->pNext = p;
pHead = newStu;
break;
}
else
{
newStu->pNext = p;
perP->pNext = newStu;
break;
}
}
else if(p == pLast)//没找到比age大的,则增加到最后一个
{
pLast->pNext = newStu;
newStu->pNext = NULL;
pLast = newStu;
break;
}
perP = p;//单向链表,在前面增加元素,需要一个前面的节点指针
p = p->pNext;//依次往后移动指针
}
- 例子: 实现在一个10个学生节点的链表中,删除与指定年龄一致的节点。
printf("输入年龄:");
scanf_s("%d", &age);
Student* p = pHead;
Student* perP = p;
while (p != NULL)
{
if (age == p->age)
{
if (p == pHead)//删除头节点
{
pHead = p->pNext;
free(p);//释放
p = pHead;//重新指向头指针,再遍历删除
continue;
}
else if (p == pLast)//删除尾节点
{
perP->pNext = NULL;
pLast = perP;
free(p);
break;
}
else//删除中间节点
{
perP->pNext = p->pNext;
free(p);
p = perP;
}
}
perP = p;
p = p->pNext;//依次往后移动指针
}
以上基本实现,单链表的遍历、增、删
。
三、双向链表
双向链表: 在节点的结构体定义中,有两个指针,一个指向前一个节点
、一个指向后一个节点
。
双向链表对比单向链表,双向链表可以通过当前节点找到上一个节点和下一个节点,而单向链表只能找到下一个节点。
- 双向链表结构体定义,如:
typedef struct student
{
char name[10];
char sex;
int age;
struct student * pPre;//指向上一个节点的指针
struct student * pNext;//指向下一个节点的指针
}Student;
- 例子: 实现一个10个学生节点的双向链表,并输出其值。
Student* pHead = NULL;//首
Student* pLast = NULL;//尾
int i = 0;
int size = 10;
for (i = 0; i < size; i++)
{
Student* p = (Student*)malloc(sizeof(Student));
p->pNext = NULL;
p->pPre = NULL;
p->age = i + 18;//随机赋个值,打印的时候确保其唯一性
if (i == 0)//第一个节点,用pHead指向
{
pHead = p;
pLast = p;//只有一个节点,首尾为同一个
}
else
{
//不是第一个节点,就把其增加到后面
pLast->pNext = p;
p->pPre = pLast;//把新增节点的前指针,指向前一个。
pLast = p;//再把尾指针移动最后一个
}
}
//通过 pHead 输出
Student* p = pHead;//不要改变pHead的指向
while (p != NULL)
{
printf("%d\n", p->age);
p = p->pNext;//依次往后移动指针
}
//释放内存空间
p = pHead;
while (p != NULL)
{
//需要先用个临时指针指向后一个,不然释放了之后,通过pNext找不到后一个了
Student* tmp = p->pNext;
free(p);
p = tmp;//依次往后移动指针
}
例子: 双向链表插入节点:输入其年龄,实现在一个10个学生节点的链表中(按年级有序链表),将这个新节点插入进链表中,使其仍然有序。
Student* newStu = (Student*)malloc(sizeof(Student));
printf("请输入年龄:");
scanf_s("%d", &newStu->age);
Student* p = pHead;
//这两句很关键,默认per和next指向空
newStu->pNext = NULL;
newStu->pPre = NULL;
while (p != NULL)
{
if (p->age >= newStu->age)//表示要将newStu插入到p的前面
{
//判断p是否是pHead。插入到最前面
if (p == pHead)
{
p->pPre = newStu;//头指针的per指向 新节点
newStu->pNext = p;//新节点的next指向 原来的头
pHead = newStu;//头指针指向new节点,重新把头指针指向第一个。
}
else
{
newStu->pPre = p->pPre;//新节点的pre指向前一个节点
newStu->pNext = p;//新节点的next指向当前节点
p->pPre->pNext = newStu;//前一个节点的next指向新节点
p->pPre = newStu;//单前节点的pre指向新节点
}
Break;
}
else if (p == pLast)//没找到比age大的,则增加到最后一个
{
pLast->pNext = newStu;
newStu->pPre = pLast;
pLast = newStu;
break;
}
p = p->pNext;//依次往后移动指针
}
- 例子: 双向链表节点删除:实现在一个10个学生节点的链表中,删除与指定年龄一致的节点。
int age;
printf("输入年龄:");
scanf_s("%d", &age);
Student* p = pHead;
Student* tmp = NULL;
while (p != NULL)
{
if (age == p->age)
{
if (p == pHead)//删除头节点
{
pHead = p->pNext;
pHead != NULL ? pHead->pPre = NULL : NULL;
free(p);//释放
p = pHead;//重新指向头指针,再遍历删除
continue;
}
else if (p == pLast)
{
p->pPre->pNext = NULL;
free(p);
break;
}
else
{
p->pNext->pPre = p->pPre;//当前节点的下一个节点的pre指向当前节点的上一个节点
p->pPre->pNext = p->pNext;//当前节点的上一个节点的next指向当前节点的下一个节点
tmp = p;//临时指针指向p,用于释放
p = p->pPre;//p指向上一个节点,继续下次循环
free(p);
}
}
p = p->pNext;//依次往后移动指针
}