携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第11天,点击查看活动详情 >>
线性表
- 线性表有两种分别为
顺序表、链表,在内存中,顺序表所占内存地址连续,链表所占内存地址不连续 - 前驱元素:某一元素左侧的元素
- 后继元素:某元素的右侧的元素
顺序表:
初始化一个新的顺序表
#include <stdio.h>
#include <stdlib.h>
#define Size 5
typedef struct Table{
int * head;//声明了一个名为head的长度不确定的数组,也叫“动态数组”
int length;
int size;
}table;
table initTable(){
table t;
t.head=(int*)malloc(Size*sizeof(int));
if (!t.head)
{
printf("初始化失败");
exit(0);
}
t.length=0;
t.size=Size;
return t;
}
//输出顺序表中元素的函数
void displayTable(table t){
for (int i=0;i<t.length;i++) {
printf("%d ",t.head[i]);
}
printf("\n");
}
int main(){
table t=initTable();
//向顺序表中添加元素
for (int i=1; i<=Size; i++) {
t.head[i-1]=i;
t.length++;
}
printf("顺序表中存储的元素分别是:\n");
displayTable(t);
return 0;
}
注意在初始化过程中,需要判断申请的内存地址是否申请成功,当不成功时创建会失败
typedef关键字代表此结构体可以重命名
插入
- 若空间不足,需要重新申请空间
- 使用realloc方法申请空间
table add(table t,int state,int num) {
//当内存不够的时候 需要申请新的内存
if (t.size <= t.length) {
t.head = (int*)realloc(t.head,(t.size + 1) * sizeof(int));
if (!t.head)
{
printf("新增初始化失败");
return t;
}
t.size = t.size + 1;
}
displayTable(t);
//后移元素
//下标从0开始 长度从1开始
for (int i = t.length-1; i >= state-1; i--)
{
t.head[i + 1] = t.head[i];
}
t.head[state - 1] = num;
t.length++;
return t;
}
C的内存分配 realloc和malloc区别:
在C中有三个操作内存的函数,分别是
void* realloc(void* ptr, unsigned newsize);
void* malloc(unsigned size);
void* calloc(size_t numElements, size_t sizeOfElement);
它们的返回值都是请求系统分配的地址,如果请求失败就返回NULL
- malloc用于申请一段新的地址,参数size为需要内存空间的长度,如: char* p;p=(char*)malloc(20);
- calloc与malloc相似,参数sizeOfElement为申请地址的单位元素长度,numElements为元素个数,如:
char* p;p=(char*)calloc(20,sizeof(char));
这个例子与上一个效果相同 - realloc是给一个已经分配了地址的指针重新分配空间,参数p为原有的空间地址,newsize是重新申请的地址长度如:
char* p;
p=(char*)malloc(sizeof(char)20);
p=(char)realloc(p,sizeof(char)*40);
注意,这里的空间长度都是以字节为单位。
malloc与calloc的区别为1块与n块的区别:\
- malloc调用形式为(类型*)malloc(size):在内存的动态存储区中分配一块长度为“size”字节的连续区域,返回该区域的首地址。\
- calloc调用形式为(类型*)calloc(n,size):在内存的动态存储区中分配n块长度为“size”字节的连续区域,返回首地址。\
- realloc调用形式为(类型*)realloc(*ptr,size):将ptr内存大小增大到size。\
- free的调用形式为free(void*ptr):释放ptr所指向的一块内存空间
删除
从顺序表中删除元素比较简单,当找到这个元素后 将后面元素向前移动,则会自动覆盖这个元素
查找
遍历数据表元素即可
链表
- 链表中每个数据由两部分组成
-
- 数据元素本身:数据域
- 指向后继元素指针:指针域
节点结构体
链表中每个节点的具体实现,需要使用 C 语言中的结构体,具体实现代码为:
typedef struct Link{
char elem; //代表数据域
struct Link * next; //代表指针域,指向直接后继元素
}link; //link为节点名,每个节点都是一个 link 结构体
-
头指针:一个普通的指针,它的特点是永远指向链表第一个节点的位置。很明显,头指针用于指明链表的位置,便于后期找到链表并使用表中的数据;
-
节点:链表中的节点又细分为头节点、首元节点和其他节点:
- 头节点:其实就是一个不存任何数据的空节点,通常作为链表的第一个节点。对于链表来说,头节点不是必须的,它的作用只是为了方便解决某些实际问题;
- 首元节点:由于头节点(也就是空节点)的缘故,链表中称第一个存有数据的节点为首元节点。首元节点只是对链表中第一个存有数据节点的一个称谓,没有实际意义;
- 其他节点:链表中其他的节点;
初始化链表
- 头指针未分配内存
- 头指针指向第一个节点
link * initLink(){
link * p=NULL;//创建头指针
link * temp = (link*)malloc(sizeof(link));//创建首元节点
//首元节点先初始化
temp->elem = 1;
temp->next = NULL;
p = temp;//头指针指向首元节点
//从第二个节点开始创建
for (int i=2; i<5; i++) {
//创建一个新节点并初始化
link *a=(link*)malloc(sizeof(link));
a->elem=i;
a->next=NULL;
//将temp节点与新建立的a节点建立逻辑关系
temp->next=a;
//指针temp每次都指向新链表的最后一个节点,其实就是 a节点,这里写temp=a也对
temp=temp->next;
}
//返回建立的节点,只返回头指针 p即可,通过头指针即可找到整个链表
return p;
}
link * initLink(){
link * p=(link*)malloc(sizeof(link));//创建一个头结点
link * temp=p;//声明一个指针指向头结点,
//生成链表
for (int i=1; i<5; i++) {
link *a=(link*)malloc(sizeof(link));
a->elem=i;
a->next=NULL;
temp->next=a;
temp=temp->next;
}
return p;
}
插入
- 插入分为头部插入,尾部插入,中间插入
- 三种插入方式步骤一样
-
- 将新节点指向下一个节点地址
- 将原节点地址指向新节点
注意:这两步不可以反过来!
link* add(link* p, int elem, int add) {
link* temp = p;
for (int i = 1; i < add-1; i++)
{
temp = temp->next;
if(temp == NULL) {
printf("违规");
return 0;
}
}
link* new = (link*)malloc(sizeof(link));
new->elem = elem;
new->next = temp->next;
temp->next = new;
return temp;
}
- 包含头链表的新增在循环条件处不需要-1
删除
link* del(link* p,int del) {
link* temp = p;
link* n = NULL;
for (int i = 1; i < del - 1; i++)
{
temp = temp->next;
if (temp == NULL) {
printf("违规");
return 0;
}
}
n = temp->next;
temp->next =n->next ;
free(n);
return p;
}
顺序表与链表比较
| 顺序表 | 链表 |
|---|---|
| 存储数据,需预先申请一整块足够大的存储空间,然后将数据按照次序逐一存储,数据之间紧密贴合,不留一丝空隙 | 存储方式与顺序表截然相反,什么时候存储数据,什么时候才申请存储空间,数据之间的逻辑关系依靠每个数据元素携带的指针 维持 |
| 存储数据实行的是 "一次开辟,永久使用",即存储数据之前先开辟好足够的存储空间,空间一旦开辟后期无法改变大小(使用动 态数组的情况除外)。 | 链表则不同,链表存储数据时一次只开辟存储一个节点的物理空间,如果后期需要还可以再申请。 |
| 空间利用率高 | 空间利用率低 |
| 多访问,少插入删除 | 少访问。多删除移动 |
单链表的反转
反转即单链表反转或逆置
方法1:迭代反转链表
此方法需要借助三个指针,遍历将三个指针逐个后移,直至mid指向最后一个,end指向空时
- 首先改变1的指向,将其指向空
- 将三个辅助指针向后移动一位
- 再将2指向beg指针指向的地方
- 循环往复
代码实现:
link* revers(link* head) {
if (head == NULL || head->next == NULL) {
return head;
}
else
{
link* beg = NULL;
link* mid = head;
link* end = head->next;
while (1)
{
mid->next = beg;
if (end == NULL) {
break;
}
beg = mid;
mid = end;
end = end->next;
}
head = mid;
return head;
}
}
存储结构与存取结构
线性表的顺序存储结构是随机存取结构,而不是顺序存取结构;线性表的链式存储结构,又可以称为顺序存取结构,而不是 随机存取结构。
因为顺序表可以根据下标进行读取,但是链表不可以。