文章目录
前言
- 关于
单链表
:单链表在插入结点上的效率优于顺序表,单链表使用malloc函数为链表的结点分配空间,通过头结点来访问链表。 - 为什么要建立
头结点
?
建立头结点统一了空表与非空表的区别,简化了链表的操作;如果直接删除链表的第一个结点,可能会导致表的丢失。 - 想要学好单链表,就得动手画一画,链表中频繁使用指针来访问结点,写写画画能让我们的思路保持清晰
单链表
单链表的存储结构
单链表的结点由数据域
和指针域
两部分构成。
结构体表示:
typedef struct Node
{
ElemType data; //ElemType可以根据实际需求更改为需要的数据类型
struct Node * next; //本类型指针
}Node, * LinkList;
单链表ADT实现
单链表的逻辑结构:头结点的next指针指向链表的首个结点
- CreateList(LinkList L, int *a, int i): 创建单链表,L为指向链表第一个元素的指针,即头指针;a为数组的地址,数组a用来接收从键盘获得数据;
//尾插法建立单链表,不断向链表的末尾添加结点,
//每一个插入的结点的next指针都为NULL
void CreateList(LinkList L, int * a, int i)
{
Node * node;
Node * head = L;
while(i < length)
{
node = (Node *)malloc(sizeof(Node));
node->data = *(a+i);
node->next = NULL;
L->next = node;
L = L->next;
printf("%d",node->data);
i++;
}
L = head;
}
//头插法建立单链表,每个结点都插入为链表的第一个结点
//头结点的next指针始终指向插入结点
void CreateList(LinkList L, int *a, int i)
{
Node * head;
Node * node;
while(i < length)
{
node = (Node *)malloc(sizeof(Node));
node->data = *(a + length - ++i);
node->next = L->next;
L->next = node;
}
}
- PrintList(LinkList L): 打印链表
void PrintList(LinkList L)
{
printf("打印结果:\n");
for(int j = 0; j < length; j++)
{
L = L->next;
printf("%d\t",L->data);
}
printf("\n");
}
- DeleteList(LinkList L): 删除链表
void DeleteList(LinkList L)
{
while(L->next != NULL)
{
Node *t = L->next;
L->next = L->next->next;
free(t);
}
printf("删除成功\n");
}
- GetNode(LinkList L, int i): 查找结点,i 为的结点位置
Node* GetNode(LinkList L, int i)
{
for(int j = 0; j < i; j++)
{
L = L->next;
}
return L;
}
- Locate(LinkList L, int x): 查找结点,x为结点的数据值
int Locate(LinkList L, int x)
{
for(int j = 0; j < length; j++)
{
L = L->next;
if(L->data == x)
break;
}
return ++j;
}
- DeleteNode(LinkList L, int i): 删除结点,i 为结点位置
void DeleteNode(LinkList L, int i)
{
for(int j = 0; j < i - 1; j++)
{
L = L->next;
}
Node *t;
t = L->next;
L->next = L->next->next;
free(t);
length--;
}
- DeleteData(LinkList L, int x): 删除结点,x为结点的数据值
void DeleteData(LinkList L, int x)
{
int i = Locate(L, x);
if(i > length)
printf("删除元素不存在");
else
DeleteNode(L, i);
}
链表初始化问题
在链表的结构体中,*LinkList 为结构体指针类型,LinkList L,L表示指向结构体的指针;LinkList *L,L是「指针的指针」
,表示指向结构体指针的指针。
问题: 仅仅声明头指针,而未使用malloc函数为头指针分配空间,然后初始化链表,会出现程序异常结束问题。
原因:
声明一个指向结构体的指针并不创建该结构,而是给出足够的空间容纳结构可能会使用的地址。创建未被声明过的记录的唯一方法是使用malloc库函数。
例如
//定义头指针后变将,头指针的next指针域指向NULL
LinkList L;
L->next = NULL;
L = (LinkList)malloc(sizeof(Node));
链表删除问题
链表通过访问头指针来,遍历结点,如果直接删除结点会导致,部分结点无法访问到,从而无法完成整个链表的删除的操作;故我们需要一个中间结点来暂存即将删除的结点。
例如
Node *t = L->next;
L->next = L->next->next;
free(t);
双向链表
双向链表的结构体
双向链表在单链表的基础上添加一个指针指向结点的前驱结点。当链表为空时,头结点的两个指针都指向NULL。
typedef struct Node
{
int data;
struct Node * prior, *next; //prior指向前驱结点,next指向后继结点
}Node,*DoubleList;
双向链表的逻辑结构
双向链表的ADT实现
双向链表的操作和单链表的操作相似,如果从尾部遍历链表,双向链表比单链表更加方便。
双向链表的结点删除问题
与单链表结点删除不同,当删除单链表的最后一个结点时,只需要将前一个结点的next指针指向最后一个结点的next指针指向,即指向NULL。
而双向链表删除结点不仅需要修改next指针的指向,还需要修改prior指针的指向,故需要判断删除的结点是否为最后一个结点。
//删除指定位置结点
void DeleteNode(DoubleList L, int i)
{
for(int j = 0; j < i - 1; j++)
{
L = L->next;
}
Node *t;
t = L->next;
if(t->next != NULL)
{
L->next = L->next->next;
L->next->prior = L->next;
}
free(t);
length--;
}
循环链表
循环链表逻辑结构
循环链表有循环单链表和循环双链表,循环链表的最后一个结点指向头结点(单链表和双链表中指向NULL),循环双向链表的头指针的前驱结点为最后一个结点(循环链表可以设置头指针,也可以设置尾指针,也可以不设置)。