1 双向线性链表
2 单向线性链表
1 双向线性链表
1.1 问题
双向线性链表是采用链式存储的方式存储的线性表。链式存储结构是由一系列结点(链表中每一个元素称为结点)组成,每个结点包括两个部分:一个是存储数据元素的数据域,另一个是存储当前结点的前驱结点和后继结点地址的指针域,结点是在有数据时动态生成的,是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。
1.2 方案
双向线性链表的基本操作包括:
-
初始化操作,在初始化操作中将双向线性链表的头指针指空。
-
求表长,求表长操作是计算出双向线性链表中结点的个数。
-
取双向线性链表中指定位置的结点,即给定双向线性链表中的第几个结点,求其值。
-
查找结点,在双向线性链表中查找值为x的结点,并返回该结点在双向线性链表中的位置。若双向线性链表中有多个结点的值和x相同,则返回首次找到的结点位;若双向线性链表中没有结点的值为x,则返回一个NULL表示查找失败。
-
插入结点,在双向线性链表的第i个结点前插入一个值为x的新结点。
-
删除结点,删除双向线性链表的第i个结点。
1.3 步骤
实现此案例需要按照如下步骤进行。
步骤一:定义双向线性链表
在C语言中:
1)定义一个变量来表示双向线性链表中某个结点的数据。
2)定义两个指针来表示该结点的前驱结点和后继结点的地址。
3)这两方面的信息共同描述一个双向线性链表的结点,可将它们封装在一起。
代码如下所示:
typedef int DataType;
struct Node{
DataType data;
struct Node *pre, *next;
};
上述代码中,以下代码:
typedef int DataType;
是将数据类型int起了一个别名叫做DataType,并在后面的程序中只使用DataType,而不使用int。这样做的好处是当双向线性链表中的数据类型发生变化时,只需要修改此句中的int为要改变的数据类型,即可将程序中所有数据变量的数据类型变成指定的类型。
上述代码中,以下代码:
struct Node *pre, *next;
分别定义了当前结点的前驱指针和后继指针。因为当前结点的前驱结点和后继结点都是双向线性链表的结点数据类型,所以前驱指针和后继指针也是struct Node *类型。
步骤二:初始化操作
在主程序中,定义双向线性链表的头指针。
在初始化函数中将头指针初始化为NULL,表示为空表。
代码如下所示:
void init(struct Node** head)
{
*head = NULL;
}
int main()
{
//头指针
struct Node *headList;
init(&headList);
}
步骤三:求表长
求表长即求双向线性链表中结点的个数,结点的个数是通过遍历链表并统计结点个数得到的。
代码如下所示:
int getSize(struct Node* head)
{
struct Node* p = head;
int count = 0;
while§
{
count ++;
p = p->next;
}
return count;
}
步骤四:取链表中指定位置的数据元素
首先,判断要查找的是否为头结点。
然后,遍历链表,确定要查找的结点位置。
代码如下:
struct Node* getptr(struct Node* head, int pos) {
struct Node *p = head;
if (p == 0 || pos == 0) {
return head;
}
for(int i = 0; p && i < pos; i++) {
p = p->next;
}
return p;
}
步骤五:插入结点
首先,判断要插入的结点位置是否正确。
然后,创建结点。
最后,插入结点,插入时分两种情况处理:一种是插入到第一个结点前面,另一种是插入到链表中间。
//指定位置 插入元素
bool insert(struct Node** head, int position, DataType d) {
if (position < 0 || position > getSize(*head)) {
return false;
}
//创建 节点
struct Node node = (struct Node)malloc(sizeof(struct Node));
node->data = d;
node->pre = NULL;
node->next = NULL;
//插入到第一个节点的前面
if (position == 0) {
node->next = *head;
if (*head != NULL)
(*head)->pre = node;
*head = node;
return true;
}
//插入到链表的中间
struct Node *p = getptr(*head, position - 1);
struct Node* r = p->next;
node->next = r;
r->pre = node;
p->next = node;
node->pre = p;
return true;
}
上述代码中,以下代码:
if (*head != NULL)
(*head)->pre = node;
是判断头指针是否为空,如果不为空,则头指针指向的结点的前驱指针指向新加入的结点。
上述代码中,以下代码:
node->next = r;
r->pre = node;
p->next = node;
node->pre = p;
node为要插入的新结点,插入到p和r指向的结点之间,所以node的后继结点指针指向r,r的前驱结点指针指向node,node的前驱指针指向p,p的后继指针指向node。
步骤六:删除节点
首先,判断要删除的结点位置是否正确。
然后,删除结点,删除时分两种情况处理:一种是删除第一个结点,另一种是删除链表的中间结点。
最后,释放被删除结点所占有的存储空间。
代码如下:
//删除指定位置元素
bool erases(struct Node** head, int pos) {
if (pos < 0 || pos >= getSize(*head))
return false;
//删除第一个结点
struct Node *p = *head;
if (pos == 0) {
*head = (*head)->next;
if (*head != NULL)
(*head)->pre = NULL;
free(p);
p = NULL;
return true;
}
//删除链表的中间结点
p = getptr(*head, pos - 1);
struct Node *q = p->next;
p->next = q->next;
q->next->pre = p;
free(q);
q = NULL;
return true;
}
上述代码中,以下代码:
if (*head != NULL)
(*head)->pre = NULL;
是删除链表的头结点后,如果头指针不为空,即链表中还有结点,则让新的头结点的前驱指针为空。
上述代码中,以下代码:
p->next = q->next;
q->next->pre = p;
p指向要删除结点的前一结点,q指向要删除的结点,此时只需要让p由指向q,改为指向q的后继结点;让q的后继结点的前驱指针由指向q,改为指向p,即可将要删除的结点q从链表中断开,从而达到删除的目的。
步骤七:修改结点中数据
首先,判断要修改的结点是否在链表内。
然后,找到要修改的结点的位置。
最后,修改结点的数据。
//修改指定位置 元素
bool set(struct Node* head, int pos, DataType d) {
if (pos < 0 || pos >= getSize(head)) {
return false;
}
struct Node *p = getptr(head, pos);
p->data = d;
return true;
}
步骤八:删除链表
首先,遍历整个链表。
然后,逐个释放链表中每个结点。
void clears(struct Node* head) {
while (head) {
struct Node *p = head->next;
free(head);
head = p;
}
}
1.4 完整代码
本案例的完整代码如下所示:
#include <stdio.h>
#include <stdbool.h>
#include <stdlib.h>
typedef int DataType;
struct Node{
DataType data;
struct Node pre, next;
};
void init(struct Node head)
{
head = NULL;
}
int getSize(struct Node head)
{
struct Node* p = head;
int count = 0;
while§
{
count ++;
p = p->next;
}
return count;
}
//找到指定位置 元素地址
struct Node* getptr(struct Node* head, int pos) {
struct Node p = head;
if (p == 0 || pos == 0) {
return head;
}
for(int i = 0; p && i < pos; i++) {
p = p->next;
}
return p;
}
//指定位置 插入元素
bool insert(struct Node* head, int position, DataType d) {
if (position < 0 || position > getSize(*head)) {
return false;
}
//创建 节点
struct Node node = (struct Node)malloc(sizeof(struct Node));
node->data = d;
node->pre = NULL;
node->next = NULL;
//插入到第一个节点的前面
if (position == 0) {
node->next = *head;
if (*head != NULL)
(*head)->pre = node;
*head = node;
return true;
}
//插入到链表的中间
struct Node *p = getptr(*head, position - 1);
struct Node* r = p->next;
node->next = r;
r->pre = node;
p->next = node;
node->pre = p;
return true;
}
//删除指定位置元素
bool erases(struct Node** head, int pos) {
if (pos < 0 || pos >= getSize(*head))
return false;
//删除第一个结点
struct Node *p = *head;
if (pos == 0) {
*head = (*head)->next;
if (*head != NULL)
(*head)->pre = NULL;
free(p);
p = NULL;
return true;
}
//删除链表的中间结点
p = getptr(*head, pos - 1);
struct Node *q = p->next;
p->next = q->next;
q->next->pre = p;
free(q);
q = NULL;
return true;
}
//修改指定位置 元素
bool set(struct Node* head, int pos, DataType d) {
if (pos < 0 || pos >= getSize(head)) {
return false;
}
struct Node p = getptr(head, pos);
p->data = d;
return true;
}
//清理 链表
void clears(struct Node head) {
while (head) {
struct Node p = head->next;
free(head);
head = p;
}
}
//打印
void print(struct Node head) {
struct Node *p = head;
while § {
printf("%d “, p->data);
p = p->next;
}
printf(”\n");
}
int main()
{
//头指针
struct Node *headList;
init(&headList);
insert(&headList, 0, 10);
insert(&headList, 0, 20);
insert(&headList, 0, 30);
insert(&headList, 2, 40);
insert(&headList, 2, 50);
insert(&headList, 0, 60);
insert(&headList, 0, 80);
print(headList);
erases(&headList, 1);
print(headList);
set(headList, 0, 100);
set(headList, 0, 110);
print(headList);
return 0;
}
2 单向线性链表
2.1 问题
单向线性链表是采用链式存储的方式存储的线性表。链式存储结构是由一系列结点(链表中每一个元素称为结点)组成,每个结点包括两个部分:一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域,结点是在有数据时动态生成的,是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。
2.2 方案
链表的基本操作包括:
-
初始化操作,在初始化操作中将单向线性链表的头指针指空。
-
求表长,求表长操作是计算出单向线性链表中结点的个数。
-
取单向线性链表中指定位置的结点,即给定链表中的第几个结点,求其值。
-
查找结点,在单向线性链表中查找值为x的结点,并返回该结点在单向线性链表中的位置。若单向线性链表中有多个结点的值和x相同,则返回首次找到的结点位;若单向线性链表中没有结点的值为x,则返回一个NULL表示查找失败。
-
插入结点,在单向线性链表的第i个结点前插入一个值为x的新结点。
-
删除结点,删除单向线性链表的第i个结点。
2.3 步骤
实现此案例需要按照如下步骤进行。
步骤一:定义单向线性链表
在C语言中:
1)定义一个变量来表示单向线性链表中某个结点的数据。
2)定义一个指针来表示该结点的后继结点的地址。
3)这两方面的信息共同描述一个单向线性链表的结点,可将它们封装在一起。
代码如下所示:
typedef int DataType;
struct Node{
DataType data;
struct Node *next;
};
上述代码中,以下代码:
typedef int DataType;
是将数据类型int起了一个别名叫做DataType,并在后面的程序中只使用DataType,而不使用int。这样做的好处是当链表中的数据类型发生变化时,只需要修改此句中的int为要改变的数据类型,即可将程序中所有数据变量的数据类型变成指定的类型。
步骤二:初始化操作
在主程序中,定义链表的头指针。
在初始化函数中将头指针初始化为NULL,表示为空表。
代码如下所示:
void init(struct Node** head)
{
*head = NULL;
}
int main()
{
//头指针
struct Node *headList;
init(&headList);
}
步骤三:求表长
求表长即求链表中结点的个数,结点的个数是通过遍历链表并统计结点个数得到的。
代码如下所示:
int getSize(struct Node* head)
{
struct Node* p = head;
int count = 0;
while§
{
count ++;
p = p->next;
}
return count;
}
步骤四:取链表中指定位置的数据元素
首先,判断要查找的是否为头结点。
然后,遍历链表,确定要查找的结点位置。
代码如下:
struct Node* getptr(struct Node* head, int pos) {
struct Node *p = head;
if (p == 0 || pos == 0) {
return head;
}
for(int i = 0; p && i < pos; i++) {
p = p->next;
}
return p;
}
步骤五:插入结点
首先,判断要插入的结点位置是否正确。
然后,创建结点。
最后,插入结点,插入时分两种情况处理:一种是插入到第一个结点前面,另一种是插入到链表中间。
bool insert(struct Node** head, int position, DataType d) {
if (position < 0 || position > getSize(*head)) {
return false;
}
//创建 节点
struct Node node = (struct Node)malloc(sizeof(struct Node));
node->data = d;
node->next = NULL;
//插入到第一个节点的前面
if (position == 0) {
node->next = *head;
*head = node;
return true;
}
//插入到链表的中间
struct Node *p = getptr(*head, position - 1);
struct Node* r = p->next;
node->next = r;
p->next = node;
return true;
}
步骤六:删除节点
首先,判断要删除的结点位置是否正确。
然后,删除结点,删除时分两种情况处理:一种是删除第一个结点,另一种是删除链表的中间结点。
最后,释放被删除结点所占有的存储空间。
代码如下:
bool erases(struct Node** head, int pos) {
if (pos < 0 || pos >= getSize(*head))
return false;
struct Node *p = *head;
if (pos == 0) {
*head = (*head)->next;
free§;
p = NULL;
return true;
}
p = getptr(*head, pos - 1);
struct Node *q = p->next;
p->next = q->next;
free(q);
q = NULL;
return true;
}
步骤七:修改结点中数据
首先,判断要修改的结点是否在链表内。
然后,找到要修改的结点的位置。
最后,修改结点的数据。
bool set(struct Node* head, int pos, DataType d) {
if (pos < 0 || pos >= getSize(head)) {
return false;
}
struct Node *p = getptr(head, pos);
p->data = d;
return true;
}
步骤八:删除链表
首先,遍历整个链表。
然后,逐个释放链表中每个结点。
void clears(struct Node* head) {
while (head) {
struct Node *p = head->next;
free(head);
head = p;
}
}
2.4 完整代码
本案例的完整代码如下所示:
#include <stdio.h>
#include <stdbool.h>
#include <stdlib.h>
typedef int DataType;
struct Node{
DataType data;
struct Node next;
};
void init(struct Node* head)
{
head = NULL;
}
int getSize(struct Node head)
{
struct Node* p = head;
int count = 0;
while§
{
count ++;
p = p->next;
}
return count;
}
//找到指定位置 元素地址
struct Node* getptr(struct Node* head, int pos) {
struct Node p = head;
if (p == 0 || pos == 0) {
return head;
}
for(int i = 0; p && i < pos; i++) {
p = p->next;
}
return p;
}
//指定位置 插入元素
bool insert(struct Node* head, int position, DataType d) {
if (position < 0 || position > getSize(*head)) {
return false;
}
//创建 节点
struct Node node = (struct Node)malloc(sizeof(struct Node));
node->data = d;
node->next = NULL;
//插入到第一个节点的前面
if (position == 0) {
node->next = *head;
*head = node;
return true;
}
//插入到链表的中间
struct Node *p = getptr(*head, position - 1);
struct Node* r = p->next;
node->next = r;
p->next = node;
return true;
}
//删除指定位置元素
bool erases(struct Node** head, int pos) {
if (pos < 0 || pos >= getSize(*head))
return false;
struct Node *p = *head;
if (pos == 0) {
*head = (*head)->next;
free§;
p = NULL;
return true;
}
p = getptr(*head, pos - 1);
struct Node *q = p->next;
p->next = q->next;
free(q);
q = NULL;
return true;
}
//修改指定位置 元素
bool set(struct Node* head, int pos, DataType d) {
if (pos < 0 || pos >= getSize(head)) {
return false;
}
struct Node p = getptr(head, pos);
p->data = d;
return true;
}
//清理 链表
void clears(struct Node head) {
while (head) {
struct Node p = head->next;
free(head);
head = p;
}
}
//打印
void print(struct Node head) {
struct Node *p = head;
while § {
printf("%d “, p->data);
p = p->next;
}
printf(”\n");
}
int main()
{
//头指针
struct Node *headList;
init(&headList);
insert(&headList, 0, 10);
insert(&headList, 0, 20);
insert(&headList, 0, 30);
insert(&headList, 2, 40);
insert(&headList, 2, 50);
insert(&headList, 0, 60);
insert(&headList, 0, 80);
print(headList);
erases(&headList, 0);
print(headList);
set(headList, 0, 100);
set(headList, 0, 110);
print(headList);
return 0;
}