一、链表
(1)定义:链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链。
(2)链表的基本知识
链表是一种动态的存储结构,所谓动态就是可以根据表中结点的个数来自动的分配合适的空间。也就是说,当插入结点时,系统自动申请一块空间,进行插入;当删除结点时,系统自动回收该结点所占用的空间,从而避免了空间的浪费。
链表在物理位置上并不相邻,但是在逻辑上是相邻的。
(2)链表的结构
- 带头、不带头
- 单向、双向
- 循环、非循环
将他们组合起来就有8种链表结构:带头单向循环链表、带头单向非循环链表、带头双向循环链表、带头双向非循环链表、不带头单向循环链表、不带头单向非循环链表、不带头双向循环链表、不带头双向非循环链表。
我们通常所说的单链表就是带头单向非循环链表
(3)链表的优缺点
二、单链表
单链表的结构
解析:
-
其中head表示的是头结点,一般来说头结点不用来存放信息,它可以看做单链表的入口地址,关于链表的所有的操作都是从头结点开始的,它指向的是链表中的第一个结点。
-
单链表中的每一个结点都由两部分组成。其中表示数据元素的部分叫做数据域,也就是A、B、C等具体的数据;表示直接后继的部分叫做指针域,它里面存放的是下一个结点的地址。
-
注意:最后一个结点的指针域一定要置为空。
三、单链表主要操作的实现
(1)插入操作
1. 头插:
将新结点插入到链表的第一个位置,换句话说,就是将新结点插入到head结点之后
如下图所示:
过程:
- 将新结点p连接到原先第一个结点之前
p->next=head->next
- 将结点p连接到头结点之后
head->next=p
注意:这个顺序不能弄反。
代码如下
void ListLink_InsertHead(LNode* head,DataType data)
{
LNode* p;
p = (LNode*)malloc(sizeof(LNode));
p->data = data;
p->next = head->next;
head->next = p;
}
2. 尾插: 将新结点插入到最后一个结点的后面
如下图所示:
过程:
- 找到最后一个结点(通过遍历)
- 将新结点连接到最后一个结点后面
- 新结点的
next
置为空
代码如下
void ListLink_InsertTail(LNode* head,DataType data)
{
LNode* p; //代替head的作用
LNode* q; //要插入的新节点
q= (LNode*)malloc(sizeof(LNode));
q->data = data;
q->next = NULL;
p = head;
while (p->next != NULL) //这个循环是为了找到最后一个结点
{
p = p->next;
}
//将新结点从最后一个结点后面插入
p->next = q;
}
3. 随意插入: 在第i个位置处插入(包括第一个和最后一个位置)
如下图所示:
过程:
- 找到第i-1个结点
- 将第i个结点连接到新结点的后面
- 将新结点连接到第i-1个结点的后面
代码如下:
void ListLink_Insert(LNode* head, DataType data,DataType i) //其中这个i是要插入的位置
{
LNode* p;
p = head;
LNode* q; //要插入的新节点
q = (LNode*)malloc(sizeof(LNode));
q->data = data;
q->next = NULL;
int j = 0;
while (p->next != NULL && j < i - 1)
{
j++;
p = p->next; //循环完毕后,p是第i-1个结点
}
q->next = p->next; //将第i个结点连接到新结点的后面
p->next = q; //将新结点连接到第i-1个结点的后面
}
(2)删除操作
1. 头删: 删除链表的第一个结点
如下图所示:
过程:
- 先将第二个结点连接到头结点之后
- 再释放第一个结点
代码如下:
void ListLink_DeleteHead(LNode* head)
{
LNode* p; //p是要删除的结点
if (ListLink_IsEmpty(&L) == -1) //删除之前首先要判空,判空函数会在下面写到
return;
p = head->next;
head->next = p->next;
free(p);
}
2. 尾删: 删除链表中最后一个元素
如下图所示:
过程:
- 找到倒数第二个结点
- 释放最后一个结点
- 将原倒数第二个结点的next置为空
代码如下:
void ListLink_DeleteTail(LNode* head)
{
if (ListLink_IsEmpty(&L) == -1) //删除之前首先要判空
return;
LNode* p,*q;
p = head;
while (p->next->next!= NULL) //循环结束后,p是倒数第二个结点
{
p = p->next;
}
q = p->next; //q是最后一个结点
free(q);
p->next = NULL;
}
3. 随意删除: 删除第i个结点(包括头删和尾删)
如下图所示:
过程:
- 找到第i-1个结点
- 将第i+1个结点连接到第i-1个结点后面
代码如下:
void ListLink_delete(LNode* head,DataType i)
{
LNode* p,*q;
DataType j = 0;
p = head;
while (j < i - 1 && p->next != NULL)
{
j++;
p = p->next; //p是第i-1个元素
}
q = p->next; //q是第i个元素
p->next = q->next; //当q是最后一个结点时,q->next==NULL
free(q);
}
四、完整的单链表的实现
上面所说的主要操作是单链表中比较难的部分,所以我将他们单独拿出来进行主要的讲解。但是一个单链表并不仅仅只是刚才所将的几个操作,只是其他操作较为简单,所以就不进行过多的叙述。只是在代码中进行适当的解释。
#include<stdio.h>
#include<stdlib.h>
#include<malloc.h>
typedef int DataType;
struct Node //这个结构体定义的链表中各个节点的类型
{
DataType data; //数据域
struct Node* next; //指针域
}L; //结构体变量
typedef struct Node LNode;
//链表的初始化其实就是将链表置为空
void ListLink_Init(LNode* head)
{
head->next == NULL;
}
//判断链表是否为空
DataType ListLink_IsEmpty(LNode* head)
{
if (head->next == NULL) //如果链表中是空的就返回-1,否则返回1
return -1;
else
return 1;
}
//头插:只从头部插入,也就是插入到第一个位置
void ListLink_InsertHead(LNode* head,DataType data)
{
LNode* p;
p = (LNode*)malloc(sizeof(LNode));
p->data = data;
p->next = head->next;
head->next = p;
}
//尾插
void ListLink_InsertTail(LNode* head,DataType data)
{
LNode* p; //代替head的作用
LNode* q; //要插入的新节点
q= (LNode*)malloc(sizeof(LNode));
q->data = data;
q->next = NULL;
p = head;
while (p->next != NULL) //这个循环是为了找到最后一个结点
{
p = p->next;
}
//将新结点从最后一个结点后面插入
p->next = q;
}
//插入:从第i个位置上插入结点(任意位置插入)
void ListLink_Insert(LNode* head, DataType data,DataType i) //其中这个i是要插入的位置
{
LNode* p;
p = head;
LNode* q; //要插入的新节点
q = (LNode*)malloc(sizeof(LNode));
q->data = data;
q->next = NULL;
DataType j = 0;
while (p->next != NULL && j < i - 1)
{
j++;
p = p->next; //循环完毕后,p是第i-1个结点
}
q->next = p->next;
p->next = q;
}
//头删:删除第一个结点
void ListLink_DeleteHead(LNode* head)
{
LNode* p;
if (ListLink_IsEmpty(&L) == -1) //删除之前首先要判空
return;
p = head->next;
head->next = p->next;
free(p);
}
//尾删
void ListLink_DeleteTail(LNode* head)
{
if (ListLink_IsEmpty(&L) == -1) //删除之前首先要判空
return;
LNode* p,*q;
p = head;
while (p->next->next!= NULL) //循环结束后,p是倒数第二个结点
{
p = p->next;
}
q = p->next; //q是最后一个结点
free(q);
p->next = NULL;
}
//删除:删除第i个位置的结点(任意位置删除)
void ListLink_delete(LNode* head,DataType i)
{
LNode* p,*q;
DataType j = 0;
p = head;
while (j < i - 1 && p->next != NULL)
{
j++;
p = p->next;
}
q = p->next; //q是第i个元素
p->next = q->next;
free(q); //当q是最后一个结点时,q->next==NULL
}
//根据位置查找:查找第i个结点,并且返回第i个结点
LNode* ListLink_FindLocate(LNode* head, DataType i)
{
LNode* p;
p = head;
DataType j = 0;
while (j < i && p->next != NULL)
{
j++;
p = p->next;
}
return p;
}
//根据数字查找,返回与指定数字相同的结点
LNode* ListLink_FindFigure(LNode* head, DataType data)
{
LNode* p;
p = head->next;
while (p != NULL)
{
if (p->data == data)
{
return p;
}
p = p->next;
}
}
//销毁链表(包括头结点在内的所有结点)
//从头结点开始依次释放
void ListLink_Destory(LNode* head)
{
LNode* p;
while (head->next != NULL)
{
p = head;
head = head->next;
free(p);
}
}
//求链表的长度
DataType ListLink_GetLength(LNode* head)
{
LNode* p;
DataType count = 0;
p = head->next;
while (p != NULL)
{
count++;
p = p->next;
}
return count;
}
//输出链表中的每一个结点
void ListLink_Display(LNode* head)
{
LNode* p;
p = head->next;
while (p != NULL)
{
printf("%d ", p->data);
p = p->next;
}
}
//在main函数中给出一些具体的调用,这样大家知道如何使用这些函数
int main()
{
ListLink_Init(&L);
ListLink_InsertHead(&L, 3);
ListLink_InsertHead(&L,5);
ListLink_InsertHead(&L,7);
ListLink_InsertHead(&L, 1);
ListLink_InsertHead(&L, 39);
ListLink_InsertTail(&L, 32);
ListLink_Display(&L);
system("pause");
return 0;
}
五、小技巧
- 对链表进行遍历时,如果
p
指向的是头结点,那么while
循环结束的标志是
p->next!=NULL
,并且当遍历结束时,此时的p指向的是最后一个结点
p=head;
while(p->next!=NULL)
{
p=p->next;
}
- 对链表进行遍历时,如果
p
指向的是第一个结点,那么while
循环结束的标志是
p!=NULL
,并且当遍历结束时,此时的p指向的是最后一个结点的后面一个不知道的空间
p=head->next;
while(p!=NULL)
{
p=p->next;
}
- 在链表中只要要找第i-1个结点,代码其实是固定的
//第一种:当p=head时
int j=0;
p=head;
while(p->next!=NULL && j<i-1)
{
j++;
p=p->next; //循环完毕后p是第i-1个结点
}
//第二种:p=head->next
int j =0;
p=head->next;
while(p!=NULL && j<i-1)
{
p=p->next;
j++;
}