这篇文章的主要内容:线性表的增删改查,线性表的优缺点,以及为什么之后要学习链表。
一、顺序表分为线性表和链表
通俗易懂的讲,线性表就是物理地址和逻辑地址一样的一种存储方式,而链表的物理地址和逻辑地址不一样。
注:举一个具体的例子,把数据都存在火车上。
访问线性表,就相当于访问第n节车厢(数组),只要知道第一个乘客(元素)的地址,并知道每个座位(元素)的大小,就可以清晰的知道每个乘客(元素)所在的位置。并且可以通过座位号(元素下标)来快速找到乘客(元素)的位置。
访问链表,就相当于访问 第n节车厢 和 第n+1节车厢 。每一节车厢都有对应的编号(数据域)、连接的钩子(指针域)。并且我们知道,单链表的头节点没有前驱节点、尾结点没有后继节点(与火车的样子一模一样)。
我们先有这么一个认识就好了。
二、那么我们接下来来具体了解,线性表的基本操作。
1.在具体实现操作之前,我们需要知道线性表的结构是什么。
结构体的三个成员:SLDataType* arr、int capacity、int size。(注意SLDataType* arr和SLDataType arr 的区别和联系,元素内容有什么不同?)
在我们之前学习当中,结构体的容量大小,一开始就是确定的,后期不好修改,也存在空间浪费等问题。那么,如何进行动态管理?核心:动态内存管理。
2.具体的操作
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include "contact.h"
#include <string.h>
//静态顺序表
//#define N 100
//struct Seqlist {
//
// SLDateType a[N];//数据类型
// int size; //数据的数量有几个
//};
//动态顺序表
typedef InFo SLDataType;
//这里的InFo,需要用户自己定义,要把什么内容,当做数组的元素
typedef struct SeqList
{
SLDataType* arr; //存储数据的底层结构
int capacity; //记录顺序表的空间大小
int size; //记录顺序表当前有效的数据个数
}SL;
//typedef struct SeqList SL; //另一种命名方式
//区别‘传值’调用和‘传址’调用
// 传值:只是对该数值进行利用,函数调用结束后不会对该值进行修改,离开作用域后就会释放内存。
// 传值:会将该数值进行修改,并进行保存,不会释放内存。
这里我们需要知道,传址调用和传值调用。(具体看上图最后三行)
//初始化、销毁、查看(打印)
void SLInit(SL* ps);
void SLDestroy(SL* ps);
void SLPrint(SL* ps);
//顺序表的尾部、头部插入
void SLPushBack(SL* ps, SLDataType x);
void SLPushFront(SL* ps, SLDataType x);
//顺序表的尾部、头部删除
void SLPopBack(SL* ps);
void SLPopFront(SL* ps);
//指定位置之前插入数据
void SLInsert(SL* ps, int pos, SLDataType x);
//删除指定位置数据
void SLErase(SL* ps, int pos);
以上两张图的代码,应该保存在一个头文件当中,都是一些大纲性的内容,没有具体实现。
//打印顺序表
void SLPrint(SL* ps)
{
assert(ps);
for (int i = 0; i < ps->size; i++)//就当做访问数组一样
{
printf("%d ", ps->arr[i]);
}
printf("\n");
}
//初始化和销毁
void SLInit(SL* ps)
{
assert(ps);
ps->arr = NULL;
ps->capacity = ps->size = 0;
}
void SLDestroy(SL* ps)
{
assert(ps);
//想清楚什么情况下,需要销毁
if (ps->arr)
{
free(ps->arr);
}
ps->arr = NULL;
ps->capacity = ps->size = 0;
}
扩容问题!!!
以下图片中的代码是整段代码最为重要的的部分
void SLCheckCapacity(SL* ps)
{
if (ps->capacity == ps->size)
{
int newCapacity = ps->capacity == 0 ? 4 : 2 * ps->capacity;
SLDataType* tmp = (SLDataType*)realloc(ps->arr, newCapacity * sizeof(SLDataType));
if (tmp == NULL)
{
perror("realloc fail!");
exit(1);
}
//扩容成功
ps->arr = tmp;
ps->capacity = newCapacity;
}
}
注:三目操作符:表达式a ? 表达式b :表达式c。如果表达式a为真,则执行表达式b,如果表达式a为假,执行表达式c。
//顺序表的尾部插入
void SLPushBack(SL* ps, SLDataType x)
{
assert(ps);
//空间不够,进行扩容
SLCheckCapacity(ps);
//空间足够,直接插入
ps->arr[ps->size++] = x;
}
//顺序表的头部插入
void SLPushFront(SL* ps, SLDataType x)
{
assert(ps);
//在插入时,每次都需要判断是否需要扩容
SLCheckCapacity(ps);
for (int i = ps->size; i > 0; i--)//注意for循环,从哪来开始挪动数据,从哪里结束挪动数据
{
ps->arr[i] = ps->arr[i - 1];//以最后一次循环为例。arr[1]=arr[0],挪动完数据
}
ps->arr[0] = x;//插入成功
//插入成功后,size要加1
ps->size++;
}
//尾删
void SLPopBack(SL* ps)
{
assert(ps);
assert(ps->size);
ps->size--;
}
//头删
void SLPopFront(SL* ps)
{
//判断该顺序表是否正常,
assert(ps);
//以及是否可执行该删除操作
assert(ps->size);
for (int i = 0; i <ps->size-1; i++)
{
ps->arr[i] = ps->arr[i+1];
//结束条件:arr[0]=arr[1];
}
ps->size--;//有效数据直接-1。相当于把数组最后两个相同的元素,直接舍弃后一个
}
//在指定位置插入数据
void SLInsert(SL* ps, int pos, SLDataType x)
{
assert(ps);
assert(pos >= 0 && pos <= ps->size);
SLCheckCapacity(ps);
for (int i = ps->size; i > pos; i--)//要注意挪动几次数据
{
ps->arr[i] = ps->arr[i - 1];
}
ps->arr[pos] = x;
ps->size++;
}
//删除指定位置数据
void SLErase(SL* ps, int pos)
{
assert(ps);
assert(pos >= 0 && pos < ps->size);
for (int i = pos; i < ps->size-1; i++)
{
ps->arr[i] = ps->arr[i + 1];
//结束条件:arr[size-1]=arr[size-2]
}
ps->size--;
}