目录
线性表基本分为两种:数组和链表
两者的区别在于:数组是顺序存储(存储空间连续),链表是链式存储(存储空间不连续)
由于数组比较简单,本文只介绍链表的基本操作
1.单链表
-
首先,定义一个结构体
结构体可作为链表的每一个节点,即链表的每一个节点都是一个struct Node类型的变量
在这里,data存储每个节点的值,next为每个节点指向后继节点的指针
typedef struct Node
{
int data;
struct Node* next;
} Node;
在这里,为什么要用typedef呢?这其实是c语言非常经典的写法。
如果我们使用typedef重定义,当我们需要创建一个Node类型变量时,我们需要这么写:
struct Node a;
而当我们重定义之后,创建变量只需要这么写:
Node a;
-
写出链表初始化函数
一般用这个函数初始化链表的头结点
我们使用malloc函数来分配内存空间,这样可以用多少分配多少,节省内存;函数中的if-else语句是为了判断是否正确分配内存空间,有的IDE中不加此语句会报错
由于malloc函数返回指向被分配内存的指针,所以是Node*类型
如果没有下一个节点,那么此节点的next指针指向NULL(即为空),如果有下一个节点,此节点的next指针指向下一个节点
小tip:可以在上面结构体重定义时加上一个*pnode,这样Node* list就可以写为pnode list啦
Node* initlist()
{
Node* list = (Node*)malloc(sizeof(Node));
if (list == NULL)
{
printf("error");
}
else
{
list->data = 0;
list->next = NULL;
}
return list;
}
-
写出头插法函数
头插法指将节点插入头结点的后面,所以如果用头插法依次插入1、2、3,实际在链表中的顺序是:头结点、3、2、1
小tip:注意每次修改指针时的顺序,不要把节点搞丢了
void headinsert(Node* list, int data)
{
Node* node = (Node*)malloc(sizeof(Node));
if (node == NULL)
{
printf("error");
}
else
{
node->data = data;
node->next = list->next;
list->next = node;
list->data++;
//头结点的data一般存放这个链表除头结点外的节点个数,所以需要++
}
}
-
写出尾插法函数
尾插法指将节点插入最后一个节点的后面,所以如果用尾插法依次插入1、2、3,实际在链表中的顺序是:头结点、1、2、3
在此函数中,我们需要利用while循环找到最后一个节点,将新节点链接在最后一个节点后面
void tailinsert(Node* list, int data)
{
Node* head = list;
Node* node = (Node*)malloc(sizeof(Node));
if (node == NULL)
{
printf("error");
}
else
{
node->data = data;
node->next = NULL;
list = list->next;
}
while (list->next != NULL)
{
list = list->next;
}
list->next = node;
head->data++;
}
-
写出删除节点函数
删除一个节点的基本操作是:利用while循环找到这个节点,将这个节点的前驱结点和后继节点链接在一起,然后将这个节点free掉
所以,我们需要新建一个指针保存当前节点的前驱节点,否则循环到这个节点,就找不到它的前驱结点了(因为我们现在写的不是双向链表)
void delete_list(Node* list, int data)
{
Node* pre = list;
Node* current = list->next;
while (current)
{
if (current->data == data)
{
pre->next = current->next;
free(current);
break;
}
pre = current;
current = current->next;
}
list->data--;
}
-
写出输出链表data的函数
循环打印即可~
void printlist(Node* list)
{
list = list->next;
while (list)
{
printf("%d ", list->data);
list = list->next;
}
printf("\n");
}
到此,单链表的基本操作函数就都写完啦,下面我们可以写个main函数验证一下
int main()
{
Node* list = initlist();
headinsert(list, 1);
headinsert(list, 2);
headinsert(list, 3);
tailinsert(list, 6);
tailinsert(list, 7);
tailinsert(list, 8);
printlist(list);
delete_list(list, 2);
printlist(list);
return 0;
}
2.单循环链表
不同于单链表,单循环链表的最后一个节点的next指针指向的是头结点(就像一个环一样),所以操作基本上是一致的,下面只列出不一样的操作
-
链表初始化函数
此处的区别是,初始化头结点时,next指针指向自己
Node* initlist()
{
Node* list = (Node*)malloc(sizeof(Node));
if (list == NULL)
{
printf("error");
}
else
{
list->data = 0;
list->next = list; //当没有尾节点时,头结点指向自己
}
return list;
}
-
尾插法函数
此处的区别是,新节点的next指针指向头结点 ,并且while循环的判断条件是list->next != head
void tailinsert(Node* list, int data)
{
Node* head = list;
Node* node = (Node*)malloc(sizeof(Node));
if (node == NULL)
{
printf("error");
}
else
{
node->data = data;
}
while (list->next != head)
{
list = list->next;
}
node->next = head;
list->next = node;
head->data++;
}
-
删除节点函数
此处的区别是,while循环的判断条件不同
int deletelist(Node* list, int data)
{
Node* pre = list;
Node* node = list->next;
while (node != list)
{
if (node->data == data)
{
pre->next = node->next;
free(node);
}
pre = node;
node = node->next;
}
list->data--;
}
-
链表输出函数
此处的区别是,while循环的判断条件不同
void printlist(Node* list)
{
Node* node = list->next;
while (node != list)
{
printf("%d ", node->data);
node = node->next;
}
printf("\n");
}
3.双链表
-
定义结构体
不同于单链表,双链表是双向的,即可以通过一个节点找到它的前驱结点和后继节点,所以结构体中有pre和next两个指针
typedef struct Node
{
int data;
struct Node* pre;
struct Node* next;
} Node;
-
其他函数
双链表的操作函数与单链表区别不大,只不过从两个指针变成了四个指针(哪四个嘞?要插入节点的next和pre指针,前驱结点的next指针和后继节点的pre指针)
剩余函数放在下面大家自行查看
小tip:注意每次修改指针时的顺序,不要把节点搞丢了(当然双链表没有单链表容易丢)
//初始化链表
Node* initlist()
{
Node* l = (Node*)malloc(sizeof(Node));
if (l == NULL)
{
printf("error");
}
else
{
l->data = 0;
l->next = NULL;
l->pre = NULL;
}
return l;
}
//头插法
void headinsert(Node* l, int data)
{
Node* node = (Node*)malloc(sizeof(Node));
if (node == NULL)
{
printf("error");
}
else
{
node->data = data;
if (l->data == 0)
{
node->next = l->next;
l->next = node;
node->pre = l;
}
else
{
node->pre = l;
node->next = l->next;
l->next->pre = node;
l->next = node;
}
l->data++;
}
}
//尾插法
void tailinsert(Node* l, int data)
{
Node* node = (Node*)malloc(sizeof(Node));
Node* head = l;
if (node == NULL)
{
printf("error");
}
else
{
node->data = data;
while (l->next)
{
l = l->next;
}
node->next = NULL;
l->next = node;
node->pre = l;
head->data++;
}
}
//删除节点
void deletelist(Node* l, int data)
{
Node* node = l->next;
while (node)
{
if (node->data == data)
{
node->pre->next = node->next;
node->next->pre = node->pre;
free(node);
return TRUE;
}
node = node->next;
}
}
4.双循环链表
经过上面三个链表的讲解,想必此时不用说你也知道,双循环链表就是最后一个节点的next指针指向头结点,且头结点的pre指针指向最后一个节点,剩余函数自行查看
//初始化时头结点的两个指针均指向自己
Node* initlist()
{
Node* l = (Node*)malloc(sizeof(Node));
if (l == NULL)
{
printf("error");
}
else
{
l->next = l;
l->pre = l;
l->data = 0;
}
return l;
}
//头插法
void headinsert(Node* l, int data)
{
Node* node = (Node*)malloc(sizeof(Node));
if (node == NULL)
{
printf("error");
}
else
{
node->data = data;
node->next = l->next;
node->pre = l;
l->next->pre = node;
l->next = node;
l->data++;
}
}
//尾插法
void tailinsert(Node* l, int data)
{
Node* node = l;
while (node->next != l)
{
node = node->next;
}
Node* n = (Node*)malloc(sizeof(Node));
if (n == NULL)
{
printf("error");
}
else
{
n->data = data;
n->next = l;
l->pre = n;
n->pre = node;
node->next = n;
l->data++;
}
}
//删除节点,注意while循环判断条件
int deletelist(Node* l, int data)
{
Node* node = l->next;
while (node != l)
{
if (node->data == data)
{
node->pre->next = node->next;
node->next->pre = node->pre;
free(node);
l->data--;
return 1;
}
node = node->next;
}
return 0;
}