上一节我们学习了线性表,线性表的查询效率是最高的,时间复杂度O(1)。但他有一些缺点,比如如果我们要存入1000万个int型的数字,那么就需要一块可以存入1000万int型的连续的空间。而链表则不需要连续的空间也可以存储。
我写代码喜欢把不同功能封装到不同函数中,和王道408书里写的链表代码不一样,但是原理肯定都是一样的。而且关于链表的写法,我看几个人就有几种写法,仁者见仁,智者见智吧。我的下面的代码不一定优雅(可以跑)。
1.构建链表
一个链表的结点有数据域,指向下一个结点的指针域构成。
我们用结构体来构造一个链表结点
typedef struct Node {
int data;
struct Node* next;
}Node;
2.初始化链表
我们需要构建一个头结点,头结点是我们找到这个链表的唯一标识。就像数组一样,数组在函数间的传递其实是在传递数组的第一个元素的地址。所以,我们如果要传递一个链表,那就只需要传递链表头结点的指针就可以了。并且,由于单链表的指向单一性,如果头结点丢了,那一定就不能再遍历整个链表了。
//初始化
Node* initInsert() {
Node* list = (Node*)malloc(sizeof(Node));
list->data = 0;
list->next = NULL;
return list;
}
int main(void) {
Node *list=initInsert();
return 0;
}
这段代码就可以构建一个头结点。
3.插入数据
插入数据有三种情况,头插法,尾插法,中间插入。插入一个数据我们就需要构造一个新的结点插入。
头插法
需要注意的是,头插法并不是每插入一个新的结点,就把新结点变成头结点,而是插在头结点和头结点的下一结点中间,就像这样。
因为我刚刚说过头结点是唯一标识,它不能随意变动
//头插法
void headInsert(Node*list,int data) {
Node* node = (Node*)malloc(sizeof(Node));
node->data = data;
node->next = list->next;
list->next = node;
list->data++;
}
尾插法
使用头插法会使得插入的数据顺序和输入的顺序刚好相反,例如,你按1,2,3的顺序插入,1反而是离头结点最远的。头->3->2->1 。
如果想要 头->1->2->3,就要使用尾插法。
//尾插法
void BackInsert(Node* list, int data) {
Node* node = (Node*)malloc(sizeof(Node));
list->data++;
node->data = data;
node->data = NULL;
Node* p = list->next;
int i = 0;
while (1) {
if (p->next == NULL){
p->next = node;
break;
}
p = p->next;
}
}
其实上面这段代码并不是一个好代码,因为它要找到最后一个结点,每次都需要把整个链表遍历一遍,非常憨。这是我第一次写的尾插法代码,并不完美。后来我发现,可以使用尾结点来记录最后一个结点的指针,这样就非常优雅了。我们不能把头结点拿来当尾结点用,因为这样头结点就丢了。所以我们要多传一个结点来当尾结点。
#include<stdio.h>
#include<malloc.h>
typedef struct Node {
int data;
struct Node* next;
}Node;
//尾插法
void BackInsert(Node *list) {
Node* Back = list;
int n;
scanf_s("%d", &n);
for (int i = 0; i <n; i++) {
Node* node = (Node*)malloc(sizeof(Node));
Back->next = node;
node->data = i;
list->data++;
Back = node;
}
}
//初始化
Node* initInsert() {
Node* list = (Node*)malloc(sizeof(Node));
list -> data = 0;
list->next = NULL;
return list;
}
//遍历链表
void input(Node* list) {
Node* p = list->next;
while (p->next != NULL) {
printf("%d\n", p->data);
p = p->next;
}
}
//demo
int main(void) {
Node* list = initInsert();
BackInsert(list);
input(list);
return 0;
}
中间插入
就比如说,题目要求把2这个数插到链表中里某一个数后面。
原理很简单,就是先把链表遍历,到那个数遍历停下,再把2插进去。和上面一样,就不写代码了。
4.遍历整个链表
//遍历整个链表
void input(Node* list) {
Node* p = list->next;
for (int i = 0; i < list->data; i++) {
printf("%d\n", p->data);
p = p->next;
}
}
5.按值删除一个元素
//按值删除一个结点
void delect(Node* list, int data) {
Node* p = list;
Node* q = p->next;
while (1) {
if (q->data==data) {
p->next = q->next;
q = NULL;
list->data--;
break;
}
p = p->next;
q = p->next;
}
}
全部代码
#include<stdio.h>
#include<malloc.h>
typedef struct Node {
int data;
struct Node* next;
}Node;
//初始化
Node* initInsert() {
Node* list = (Node*)malloc(sizeof(Node));
list->data = 0;
list->next = NULL;
return list;
}
Node* initBackInsert() {
Node* list = (Node*)malloc(sizeof(Node));
list->data = 0;
list->next = NULL;
return list;
}
//头插法
void headInsert(Node*list,int data) {
Node* node = (Node*)malloc(sizeof(Node));
node->data = data;
node->next = list->next;
list->next = node;
list->data++;
}
//按值删除一个结点
void delect(Node* list, int data) {
Node* p = list;
Node* q = p->next;
while (1) {
if (q->data==data) {
p->next = q->next;
q = NULL;
list->data--;
break;
}
p = p->next;
q = p->next;
}
}
//遍历整个链表
void input(Node* list) {
Node* p = list->next;
for (int i = 0; i < list->data; i++) {
printf("%d\n", p->data);
p = p->next;
}
}
//尾插法
void BackInsert(Node* list, int data) {
Node* node = (Node*)malloc(sizeof(Node));
list->data++;
node->data = data;
node->data = NULL;
Node* p = list->next;
int i = 0;
while (1) {
if (p->next == NULL){
p->next = node;
break;
}
p = p->next;
}
}
int main(void) {
Node *list=initInsert();
for (int i = 1; i <=4; i++)
{
headInsert(list, i);
}
input(list);
delect(list, 2);
input(list);
return 0;
}