链表与指针
- 为什么要学习链表
数组:一次性分配一块连续的存储区域
数组的优点:
随机访问元素效率高
数组的缺点:
需要分配一块连续的存储区域(很大区域,有可能分配失败)
删除和插入某个元素效率低
链表:现实生活中的灯笼
链表优点:
不需要一块连续的存储区域
删除和插入某个元素效率高
链表缺点:
随机访问元素效率低
链表的data域和指针域
动态链表和静态链表
带头链表和不带头链表
单向链表、双向链表
## 结构体套结构体
typedef struct A
{
int a;
int b;
char *p;
}A;
/*1、结构体可以嵌套另一个结构体的任何类型变量
2、结构体嵌套本结构体普通变量(不可以),因为结构体的本质是固定大小的内存块别名,如果结构体嵌套本结构体,那么本结构体的类型大小无法确定。
3、结构体嵌套本结构体指针变量(可以,因为指针变量的类型能确定)
*/
typedef struct B
{
int a;
int b;
char *p;
A temp1; //ok
A *p1; //ok
B temp2; //err
B *next; //ok 32位,4字节,64位,8字节
}B;
静态链表的用法
#define _CRT_SECURE_NO_WARNINGS
#include "stdafx.h"
typedef struct Stu
{
int id;//数据域
char name[100];
Stu *next; //指针域
}Stu;
int main()
{
//初始化三个结构体变量
Stu s1 = { 1,"mike",NULL };
Stu s2 = { 2,"Lily",NULL };
Stu s3 = { 3,"lilei",NULL };
s1.next = &s2;
s2.next = &s3;
s3.next = NULL;
Stu *p = &s1;
while (p != NULL)
{
printf("id = %d,name = %s\n", p->id, p->name);
p = p->next; //结点往后移一位
}
printf("\n");
system("pause");
return 0;
}
头节点的创建和链表的遍历
基本操作
- 建立带有头结点的单向链表
编写函数SList_Creat,建立带有头节点的单向链表。循环创建结点,结点数据域中的数值从键盘输入,以-1作为输入结束标志。链表的头结点地址由函数返回值返回。 - 顺序访问链表中各结点的数据域
编写函数SList_Print,顺序输出单向链表各项结点数据域中的内容。
#define _CRT_SECURE_NO_WARINGS
#include "stdafx.h"
typedef struct Node
{
int id;
struct Node *next; //指针域
}Node;
//下面通过函数*SListCreat()创建头结点
Node *SListCreat()
{
Node *head = NULL;
//头结点作为标志,不存储有效数据
head = (Node *)malloc(sizeof(Node));
if (head == NULL)
{
return NULL;
}
//给head的成员变量赋值
head->id = -1;
head->next = NULL;
//下面定义两个辅助变量
Node *pCur = head;
Node *pNew = NULL;
int data;
while (1)
{
printf("请输入数据:");
scanf_s("%d", &data);
if (data == -1)
{
break;
}
pNew = (Node *)malloc(sizeof(Node)); //新结点动态分配空间
if (pNew == NULL)
{
continue;
}
//给pNew成员变量赋值
pNew->id = data;
pNew->next = NULL;
//下面3条语句是重点,链表建立关系
pCur->next = pNew;//当前结点的next指向pNew
pNew->next = NULL;//pNew的下一个结点指向NULL
pCur = pNew; //把pCur移动到pNew,pCur指向pNew
}
return head; //链表的头结点地址由函数返回值返回
}
//链表的遍历
int SListPrint(Node *head)
{
if (head == NULL)
{
return -1;
}
//取出第一个有效结点(头结点的next)
Node *pCur = head->next;
printf("head->");
while (pCur != NULL)
{
printf("%d->", pCur->id);
//当前结点往后移动一位,pCur指向下一个结点
pCur = pCur->next;
}
printf("NULL\n");
return 0;
}
int main()
{
Node *head = NULL;
head = SListCreat();
SListPrint(head);
printf("\n");
system("pause");
return 0;
}
结果如下:
- 在单向链表中插入结点
编写函数SList_NodeInsert,功能:在值为x的结点前,插入值为y的结点;若值为x的结点不存在,则插在表层。
int SListNodeInsert(Node *head, int x, int y)
{
if (head == NULL)
{
return -1;
}
Node *pPre = head;
Node *pCur = head->next;
while (pCur != NULL)
{
if (pCur->id == x) //找到了匹配结点
{
break;
}
//如果还没找到匹配结点,则往下执行
pPre = pCur;//pPre指向pCur的位置
pCur = pCur->next; //pCur指向下一个结点
} //结果有2种情况
//1.找到匹配的结点,pCur为匹配结点,pPre为pCur上一个结点
//2.没有找到匹配结点,pCur为空结点,pPre为最后一个结点
//给新结点动态分配空间
Node *pNew = (Node *)malloc(sizeof(Node));
if (pNew == NULL)
{
return -2;
}
//给pNew的成员变量赋值
pNew->id = y;
pNew->next = NULL;
//插入到指定位置 关键语句
pPre->next = pNew; //pPre下一个指向pNew
pNew->next = pCur; //pNew下一个指向pCur
return 0;
}
将上面这段程序加入到“建立带有头结点的单向链表和顺序访问链表中各结点的数据域”的程序中,并在main函数中加入以下两条语句:
SListNodeInsert(head, 5, 4);
printf("在5的前面插入4后\n");
SListPrint(head);
就可以实现在链表中插入结点的功能啦~
实现结果如下:
- 删除单向链表中的结点
编写函数SList_NodeDel,删除值为x的结点。
//删除第一个值为x的结点
int SList_NodeDel(SLIST *pHead, int y)
{
SLIST *pCur, *pPre;
//初始化状态
pPre = pHead;
pCur = pHead->next;
while(pCur != NULL)
{
if (pCur->data == y)
{
break;
}
pPre = pCur;
pCur = pCur->next;
}
//删除操作
if (pCur == NULL)
{
printf("没有找到结点值为:%d 的结点\n", y);
return -1;
}
pPre->next = pCur->next;
if (pCur != NULL)
{
free(pCur);
}
return 0;
}