链表的存储结构

我们首先看看链表是什么样子的

然后我们在看看详细的代码:

1
2
3
4
5
6
typedef int Elemtype;

typedef struct {
Elemtype data;
struct node *next;
}Node;

我们可以看到我们在 node 这个结构体中有两个数据,一个是用来保存我们每个节点中的元素,一个是用于连接后面一个 node 的节点,里面放的是地址,用于指向下一个节点。

我们第一个节点称之为头结点,里面会放入一个特殊的值。由于我们现在学习的是一个单链表,有头节点和没有头结点的情况,那我们写的代码可能会不太一样。

单链表-初始化

现在让我们自己思考一下,我们这个初始化应该怎么做?

首先我们得开辟一块内存用于存放链表的头结点,而这块内存我们可以使用动态内存分配。
然后我们需要让 head 头结点中存储的数据为 0,头结点的 next 指向 NULL,因为目前链表为空,还没有确定下一个节点。

1
2
3
4
5
6
7
8
9
10
11

Node *InitList() {
Node *head = (Node *)malloc(sizeof(Node));
if (head == NULL) {
printf("malloc failed\n");
exit(1);
}
head->data = 0;
head->next = NULL;
return head;
}

单链表-插入节点

头插法

这里我可能会讲的有点绕,首先链表的插入和 next 的指向是很有关系。可以看看我们画的图

我们只需要让我们的 newnode.next 指向的是我们 head.next 所指向的那个 next,然后我们再让 head.next 来指向我们的 newnode 就可以完成头插了

画板

代码实现:

1
2
3
4
5
6
7
int InsertElem(Node *head, Elemtype e) {
Node *newnode = (Node *)malloc(sizeof(Node));
newnode->data=e;
newnode->next=head->next;
head->next=newnode;
return 1;
}

这种插入方式是一种典型的头插法。

尾插法

尾插法会稍微麻烦一点,头插法我们只需要在头节点的后面插入就可以了,但是,尾插法的话,我们需要找到尾在哪里,所以我们这里需要一个便利,让我们的 newnode.next=NULL 的时候,才可以找到尾部。

那我们代码就可以这样来写了

1
2
3
4
5
6
7
8
9
10
11
int InsertTail(Node *head, Elemtype e) {
Node *newnode = (Node *)malloc(sizeof(Node));
newnode->data=e;
newnode->next=NULL;
Node *temp = head;
while (temp->next != NULL) {
temp = temp->next;
}
temp->next=newnode;
return 1;
}

在指定位置插入

我们现在要添加要给新的节点 e,我们需要先给他分配内存,然后我们让节点中的 data=我们要插入的元素

然后我们需要找到我们想要插入的这个位置,也就是我们需要传入一个 POS 进去,但是如果说我们哲哥 pos 是小于 0 的话

那么他是一个非法的插入位置,所以我们这里会直接 return 0;如果说我们 pos 的位置在我们遍历完整个链表都没有找到的话,那么就 return 0;并且我们需要释放这个刚初始化的 newnode, 现在我们传入的 pos 是一个正常的位置,可以被便利找到的位置,那么我们将新节点的 next 指向 temp 的下一个节点,在讲 temp 的 next 指向的新节点

图片可以参考头插法的那个图片

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//任意地方插入
int Insert(Node *head, Elemtype e, int pos) {
if (pos < 0) return 0;

Node *newnode = (Node *)malloc(sizeof(Node));
if (newnode == NULL) return 0;
newnode->data = e;
newnode->next = NULL;

Node *temp = head;
// 找到要插入位置的前一个节点 (pos 从 0 开始,pos=0 时, temp 就是 head)
for (int i = 0; i < pos && temp != NULL; i++) {
temp = temp->next;
}
if (temp == NULL) { // 如果 pos 超出链表长度
free(newnode);
return 0;
}

newnode->next = temp->next;
temp->next = newnode;
return 1;
}

遍历

遍历其实也没有太多需要讲的,尾插法怎么遍历思路,这里也是一样的

1
2
3
4
5
6
7
8
9
void PrintList(Node *head) {
Node *temp = head;
printf("List: ");
while (temp->next != NULL) {
printf("%d ", temp->data);
temp = temp->next;
}
printf("\n");
}

这里我们需要提醒一个东西就是,尾插法和头插法遍历出来的顺序是不一样的。

删除

这个也很好理解,我们让 del 节点的前一个节点指向 del 后一个节点,这样我们就可以直接把 del 这个节点给释放掉了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
int Delete(Node *head, int pos) {
if (pos < 0) return 0;

Node *temp = head;
for (int i = 0; i < pos - 1 && temp != NULL; i++) {
temp = temp->next;
}
if (temp == NULL || temp->next == NULL) return 0;

Node *del = temp->next;
temp->next = del->next;
free(del);
return 1;
}

获取链表长度

我们便利整个链表,每经过一个节点,就让 length+1.

1
2
3
4
5
6
7
8
9
void NodeLength(Node *head) {
Node *temp = head;
int Length = 0;
while (temp != NULL) {
temp = temp->next;
Length++;
}
printf("Length=%d\n",Length);
}

释放链表

指针 p 指向头结点后的第一个节点,然后判断指针 p 是否指向空节点,如果 p 不为空,用指针 q 记录指针 p 的后继节点。释放指针 p 指向的节点,指针 p 和指针 q 指向同一个节点,然后循环上面的操作。

1
2
3
4
5
6
7
8
9
10
void FreeList(Node *head) {
Node *current = head;
Node *next;

while (current != NULL) {
next = current->next; // 保存下一个节点的指针
free(current); // 释放当前节点
current = next; // 移动到下一个节点
}
}