1.可变数组的缺陷
因为每当有需求的时候,都要去重新申请一个额外的空间然后copy,这样的缺陷:1copy需要时间;2可能会无法再次申请空间。
2.链表(linked list)
每个单元称作做一个结点(node),每个结点包含一个值和一个指针,其中的指针会指向下一个结点。其中head时开始指向链表的东西。
先来个代码展示
单链表单向导航;双链表双向导航;循环列表 最后的结点指向第一个结点。
单链表:
结点不需要按照顺序储存内存中。每个节点包含两个部分,一个用来储存实际数据,另一个储存下一个节点的地址(也就是指针)。这样就可以实现从第一个节点访问第二个节点,第二个节点访问第三个节点,只能是单向的,以此类推…那么如何访问第一个节点呢?需要一个head指针,里面存放第一个节点的地址,可以访问第一个节点。虽然链表在内存中是分散的,但是由于链表的链接部分,他们相互连接在一起。链表是列表的链接表示形式,但不是顺序的。
假设需要储存四个数字,那么可以使用数组或者链表。
使用数组的方式,他们在内存中储存的位置是连续的。可以认为数组是列表的顺序表示。
自引用结构(structure),即里面包含 指向一个相同结构类型的指针 的结构体。
- 一个结点包含两种不同的类型,data部分和link部分,那么使用结构就可以将这两种类型连接在一起:
struct node{
int data;
struct node* link;
};
第一个成员变量是data部分,数据类型不重要,int\float\char…均可;
第二个成员变量是指针类型,struct node表示的是它指向的数据类型。因为每个结点本就属于struct node类型,指针要指向下一个节点,实际上就是:要指向一个struct node类型的变量。所以设置为struct node* link;
link就表示指向下一个结构体结点的指针。结点只不过是一个自引用结构…
需要注意的是,单链表只能有一个指针,数据可以有多个:
----至于什么时候用*head 什么时候用head的思考:
在一开始初始化指针head的时候,使用struct node* head
,表示声明了一个struct node*
类型的变量:即一种指向struct node
结构类型的指针。至此这个指针类型的变量名字就叫head
,你为它分配内存甚至说你去改变它的值都是要用head = ...
。使用*head
表示取指针所指的东西!*head = ...
。当然要表示函数参数表中的形式参数是要求传入指针,需要用*head
表示。
—关于malloc函数:
分配内存所需的内存空间,并且返回一个指针 ,指向已分配内存的变量地址
#include <stdio.h>//c语言标准输入输出
#include <stdlib.h>//引用malloc函数为结构体节点创建内存
struct node {
int data;
struct node* link;
};
int main()
{
struct node* head = NULL;//head是指向 第一个 结构体节点的指针,通过这个指针,既可以访问data部分也可访问link部分
//能不能把head看做一个结构体类型的呢?
head = (struct node*)malloc(sizeof(struct node));//虽然有等号,但这不是赋值?malloc分配内存,帮助我们创建一个节点。这个操作后,节点的地址被返回到头指针head中
//通过头指针head访问第一个节点,并尝试初始化
head->data = 45;
head->link = NULL;//这里也是一样,虽然link是指针类型,但是普通访问指针变量的时候只写名字link即可
//打印第一个节点的data
printf("%d",head->data);//使用头指针来访问第一个节点,因为访问这个结点只有一个路线:通过头指针
return 0 ;
}
在每次为结点创建内存,那就必须得有一个指针来储存该节点的地址。因为如果没有这样的指针,那就无法访问结点。
链表基本上必须由三个节点组成
下面通过程序实现下面的一个链表:
head头指针储存第一个节点的地址,第一个节点的link部分储存第二个节点的地址…最后的节点link部分指向零地址,也就是NULL。
#include <stdio.h>
#include <stdlib.h>
struct node{
int data;
struct node* link;//见到这种反应一定要想到是指向结构类型的指针!!
};
int main()
{
struct node* head;
head = (struct node*)malloc(sizeof(struct node));//malloc函数返回的地址就是1000,节点一的地址
//有了地址之后就可以访问数据
head->data = 45;
head->link = NULL;
}
-----继续建立第二个节点:
struct node* current = malloc(sizeof(struct node));//创建一个新的头指针,并且指向了一个新的节点
current->data = 98;
current->link = NULL;
head->link = current;//更新head->link指向的零地址,地址给到第一个节点,将第一个节点的链接部分的指针指向第二个节点
return 0 ;
----继续建立第三个节点。可以像之前一样初始化一个指针current2、访问、交地址。
struct node* current2 = malloc(sizeof(struct node));//建立第三个字节
current2->data = 3;
current2->link = NULL;//正常建立数据和link
//建立节点之间的连接:
current->link/*第二个结点的link*/ = current2;//更新current->link指向的零地址,
//地址放到第二个节点的link,使第二个节点的link指向第三个节点。
这种方法的图形化:
但是这种方法是浪费内存的,实际上完全没有必要去额外使用指针。
这样,使用头指针就可以实现对链表中所有节点的访问。
#include <stdio.h>
#include <stdlib.h>
struct node {
int data;
struct node* link;
};
int main()
{
//建立head指针
struct node* head = malloc(sizeof(struct node));
head->data = 45;
head->link = NULL;
//建立第二个节点
struct node* current = malloc(sizeof(struct node));
current->data = 98;
current->link = NULL;
//建立第一和第二结点的联系
head->link = current;//此时current已经完成了任务,将第二个节点的地址传给了第一个节点的link,建立了连接
//重复使用指针current
current = (struct node*)malloc(sizeof(struct node));
current->data = 3;
current->link = NULL;
//使用head完成对第三节点的建立连接
head->link->link = current;
}
进行创建struct node*
变量的时候,别忘记分配内存,不然会是储存空间为默认四字节的指针。
核心代码:
struct node* p =malloc(sizeof(struct node));
p = head;
while(p->link != NULL)
{
p = p->link;
}
计数:
#include <stdio.h>
#include <stdlib.h>
int count_of_node(struct node* head);
void num_of_node(struct node* head);
struct node {
int data;
struct node* link;//见到这种反应一定要想到是指向结构类型的指针!!
};
int count1 = 0;
int count2 = 0;
int main()
{
struct node* head;
head = (struct node*)malloc(sizeof(struct node));//malloc函数返回的地址就是1000,节点一的地址
//有了地址之后就可以访问数据
head->data = 45;
head->link = NULL;
struct node* current1 = NULL;
current1 = (struct node*)malloc(sizeof(struct node));
current1->data = 9;
current1->link = NULL;
head->link = current1;
current1 = NULL;
current1 = (struct node*)malloc(sizeof(struct node));
current1->data = 33;
current1->link = NULL;
head->link->link = current1;
num_of_node(head);
printf("\n建立的链表所包含的节点有%d个", count_of_node(head));
free(current1);
free(head);
}
int count_of_node(struct node* head)
{
if (head->link)
{
count1++;
count_of_node(head->link);//注意只能读到最后一个节点,并且并没有计入个数
}
return count1+1;
}
void num_of_node(struct node* head)
{
if (head == NULL)
{
printf("链表中的节点个数为0");
}
struct node* p = head;
while (p != NULL)
{
count2++;
p = p->link;
}
printf("链表中的节点个数为%d", count2);
}
打印:
void Print_data(struct node* head)
{
if (head == NULL)
printf("链表中无数据");
struct node* p = head;
while (p != NULL)
{
printf("%d ", p->data);
p = p->link;
}
}
主要通过改变指针所指向的link实现遍历。
在链表末尾插入新节点:
void Insert(struct node* head,int num)
{
struct node* p = malloc(sizeof(struct node));
p = head;
if (p == NULL)
printf("链表中无数据");
while (p->link != NULL)//为了避免使用NULL进行赋值,首先进行检验,确保p->link不是NULL再更新它的值
{
p = p->link;
}
//创建新节点
struct node* insert = malloc(sizeof(struct node));
insert->data = num;
insert->link = NULL;
p->link = insert;
free(insert);
}
对链表的一些操作,核心都是通过循环 + 改变指针变量所指的link,让循环的指针指向下一个节点。