P4 数组vs链表
which one is better?
the answer is not certain
1.访问某个元素所需成本: 数组更好
2.内存需求: 数组需要更大的内存空间来储存可能添加的元素, 链表则需要为指针分配内存空间
数组所需空间连续, 可能需要重新盛名一个新数组并复制一个数组过去
3.插入或者删除元素的成本
4.谁更好用: 链表更容易出现错误
P5 链表: C/C++实现
每个节点包含的两个数据类型: 一个整型int(举例), 一个指向节点的指针
typedef struct Node {
int data;
struct Node* link;
} Node;
遍历链表: 先声明一个指向头节点的指针(一个存储头节点地址的变量)
好的, 那我们开始
声明一个指向头节点的指针A
Node* A;
当链表为空时, 指针不应该指向任何位置
A = NULL;
假如我们要插入一个内存块, 在c中我们用malloc函数
malloc(sizeof(Node))
假设分配的内存块地址为200,malloc返回一个void指针, 提供分配的内存块的地址, 因此创建一个指向节点的指针来保存
Node* temp = (Node*)malloc(sizeof(Node)) (注意要进行类型转换)
我们将temp作为指向节点的指针
现在我们要做的是填写该节点的数据并且调整链接
(*temp).data = 2;
(*temp).link = NULL; (现在它是第一个也是最后一个节点)
也可以这么写:
temp->data = 2;
temp->link = NULL;
A = temp; (temp是暂时存储节点地址的)
配合截图理解
现在我们想在末尾再插入两个节点, 储存数据4和6
Node* temp = (Node*)malloc(sizeof(Node))
temp->data = 4;
temp->link = NULL;
为了建立链接, 我们需要将新创建节点的地址最后一个节点的地址字段, 因此我们必须遍历链表到达末尾
创建一个新的变量temp1, 让它刚开始指向头节点
Node* temp1 = A;
while (temp1->link != NULL){
temp1 = temp1->link;
}
为什么我们要创建一个指针temp1 = A而不是直接用A来遍历整个链表呢?
如果那样做, 我们就是在修改A, 我们将丢失头节点的地址, 存储头节点的地址不应该被修改
我们仅修改临时变量以遍历链表
到达末尾节点后, 我们写
temp1->link = temp;
链接成功建立
同样的方法插入一个编号为6的节点
P6 链表: 头部插入一个节点
#include <stdio.h>
#include <stdlib.h>
struct Node {
int data;
struct Node* next;
};
struct Node* head; //全局变量(指向头节点的指针)
void Insert(int x){
struct Node* temp = (struct Node*)malloc(sizeof(Node));
temp->data = x; //对指针变量进行解引用(结合截图)
temp->next = NULL;
if (head != NULL) temp->next = head; (结合截图)
上面两句可以简写为 temp->next = head; (涵盖了链表为空的情况)
head = temp;
}
//上面函数的功能是实现一个不断的从用户处读取数据并且创建一个节点然后插入到链表的头部
//链表插入已经完成, 接下来Print函数的作用是遍历链表并且打印出每个节点储存data的值
void Print(){
struct Node* temp = head; //head为全局变量(指向头节点的指针)
while (temp != NULL){
printf("%d",temp->data);
temp = temp->next;
}
printf("\n");
}
//创建一个临时变量的原因是我们不能修改链表头, 否则会失去对第一个节点的引用
//用一个临时变量temp指向头节点, 然后用 temp = temp->next修改临时变量的值以此来遍历链表
int main(){
head = NULL; //链表为空
printf("How many numbers?\n");
int n,i,x;
scanf("%d",&n);
for (i = 0;i<n;i++){
printf("Enter the number\n");
scanf("%d",&x);
Insert(x);
Print();
}
return 0;
}
如果head不是全局变量?
那么删除全局变量声明, 将head的声明放在main函数内
即 struct Node *head = NULL; //链表为空
现在这个head在其他函数中将无法访问, 因此我们需要把头节点的地址作为打印和插入函数的参数
打印函数中, 我们将传入一个参数, 假设我们将此参数命名为head
void Print(struct Node* head){
......
}
调用Print函数时就要把head传进去, 即Print(head);
注意这两个头节点分别是两个函数的局部变量, 它们不相同
因此在打印函数中我们不需要临时变量了, 我们可以用这个头节点本身来遍历链表
类似地, 在插入函数中
void Insert(struct Node* head, int x){
......
}
因为这个head是一个副本, 所以说Insert函数不会修改main函数中的链表
有两种方法可以实现:
1)我们可以将struct Node* 指针返回, 并加上一句return:
struct Node* Insert(struct Node* head, int x){
......
return head;
}
在main函数中要写为head = Insert(head,x);
2)通过引用传递这个特定的头节点:
Insert(&head,x); //还是挺好理解的, 通过对指针的解引用来插入元素, 将原先的head替换为*pointerToHead
相应地, 函数需要改为
void Insert(struct Node** pointerToHead, int x){
struct Node* temp = (struct Node*)malloc(sizeof(Node));
temp->data = x; //对指针变量进行解引用(结合截图)
temp->next = NULL;
if (*pointerToHead != NULL) temp->next = *pointerToHead;
*pointerToHead = temp;
}