目录
一、什么是顺序表?
1. 线性表
线性表是具有相同特性的数据元素的有限序列,线性表是一种在实际中广泛使用的数据结构。常见的线性表:顺序表,链表,栈,队列...。线性表在逻辑结构上是线性结构,可以想象成一条直线;但在物理结构上不一定是连续的。
2. 顺序表的概念及结构
顺序表的底层结构是数组,所以顺序表的逻辑结构和物理结构和数组是一致的。只是顺序表是对数组的一个封装,顺序表在数组的基础上又增加了一些增删查改的接口。

对于数组arr,其逻辑结构是线性的,可以理解为一条直线;由于其在内存中占用连续的存储空间,且逻辑上相邻的元素在物理位置上也相邻,所以其物理结构(存储结构)是连续的。

对于顺序表,其底层结构为数组,所以逻辑结构是线性的,物理结构(存储结构 )是连续的。
二、顺序表的分类
1. 静态顺序表
静态顺序表即数组长度是给定的。
//静态顺序表
#define N 10
//宏定义常量,便于修改操作
struct SeqList
{
int arr[N];//定长数组
int size;//有效数据个数
};
静态顺序表由于数组长度是固定的,因此,在工作场景中不够灵活。空间给少了不够用,空间给大了造成浪费 。所以静态顺序表存在很大的缺陷
2. 动态顺序表
动态顺序表相比于静态顺序表,其空间是按需申请的,使用过程中更加灵活。因此,我们下面主要介绍动态顺序表各种功能的实现。
//动态顺序表
typedef int DateType;
typedef struct SeqList
{
DateType* arr;
int size;//有效数据个数
int capacity;//空间容量
}SL;

动态顺序表的优点就是,所需要的空间根据实际情况用realloc函数来扩容,不会造成空间浪费。
三、动态顺序表的实现
1. 顺序表的初始化
由于arr为指针,则初始化为NULL;size和capacity为整型变量,则初始化为0。
//顺序表初始化
void InitSL(SL* ps)
{
ps->arr = NULL;//指针设置为空
ps->size = ps->capacity = 0;
}
2. 顺序表扩容
在下面的顺序表尾插,头插,指定位置插入操作中都需要判断当前数组的空间是否足够,当空间不足时,就需要对顺序表进行扩容操作。因此,我们单独将扩容的函数封装起来,在使用时直接调用来提高效率。
扩容其实就是要判断空间中的有效数据个数(ps->size)和空间容量大小(ps->capacity)是否相等。若相等,就需要扩容操作。
(1)先使用if语句判断size和capacity的大小是否相等;
(2)若相等,就扩容,但是又有两种不同的情况,我们用三目操作符来判断:
情况1:ps->size和ps->capacity均为0,此时给capacity增加4个空间。
情况2:ps->size和ps->capacity不为0,此时,capacity增加为原来的两倍。
(3)为了防止realloc函数申请空间失败而丢失数据,先用tmp记下扩容后空间的起始地址,判断tmp指针不为空之后再让arr指针指向tmp。
(4)将扩容后的newcapacity赋值给ps->capacity。
需要注意:当ps->size和ps->capacity相等且不为0时,我们在这里进行2倍扩容,这是一种对于空间处理较优的解决方式。如果大家感兴趣,可以搜索相关的证明。
代码展示
//扩容
void ChechCapacity(SL* ps)
{
if (ps->size == ps->capacity)
{
//用三目操作符判断
int newcapacity = ps->capacity == 0 ? 4 : 2 * ps->capacity;
//给数组arr调整空间大小
DateType* tmp = (SL*)realloc(ps->arr, sizeof(DateType) * newcapacity);
if (tmp == NULL)
{
perror("realloc");
exit(1);
}
ps->arr = tmp;
ps->capacity = newcapacity;
}
}
3. 顺序表的尾插
(1)断言,防止传入空指针,无法对空指针进行解引用操作;
(2)想要尾插,数组就要有足够的空间,因此,在尾插之前我们需要先检查。而上面的顺序表扩容已经实现好了检查,同时,还调整了空间的大小,所以我们只需要调用上面的函数;
(3)将需要尾插的值插入,但是别忘了让有效数据个数size++。
代码展示如下
//顺序表的尾插
void SLPushBack(SL* ps, DateType x)
{
//检查,防止传入空指针
assert(ps);
//调用扩容函数
ChechCapacity(ps);
ps->arr[ps->size++] = x;//赋值同时让size++
}
4. 顺序表的头插
头插的前面与尾插类似,但是,为了防止头插之后覆盖掉第一个数据,我们需要将数组中的元素整体往后移动一个位置。然后,赋值并让size++
代码如下
//顺序表的头插
void SLPushFront(SL* ps, DateType x)
{
//断言
assert(ps);
ChechCapacity(ps);
//将数组整体向后移动一个位置
memmove(ps->arr + 1, ps->arr, ps->size * (sizeof(DateType)));
ps->arr[0] = x;//赋值
ps->size++;//有效数据加1
}
5. 顺序表的打印
实现顺序表的打印,方便测试。
//顺序表打印
void SLPrint(SL* ps,int sz)
{
assert(ps);
for (int i = 0; i < sz; i++)
{
printf("%d->", ps->arr[i]);
}
}
运行结果

6. 顺序表尾删
尾删即在保证有效数据个数不为0的情况下,让有效数据个数减1。
代码如下
//顺序表的尾删
void SLPopback(SL* ps)
{
assert(ps);
assert(ps->size != 0);//针对ps->size为0
ps->size--;//有效数据减1
}
7. 顺序表头删
头删可以让数组首元素后面的元素整体向前移动一个位置,用覆盖首元素的方式实现头删。
代码如下
//顺序表的头删
void SLPopFront(SL* ps)
{
assert(ps);
assert(ps->size != 0);
memmove(ps->arr, ps->arr + 1, (--ps->size) * sizeof(DateType));//头删并--size(前置--,先减后用)
}
8. 顺序表指定位置插入
(1)断言:防止传空指针,同时,指定的位置要在数组下标范围之内;
(2)要插入数据,就要判断空间大小是否足够;
(3)将pos及之后的元素整体向后移动一个位置,防止插入的数据覆盖原来的数据;
(4)插入数据,size++
//在指定位置插入数据
void SLInsert(SL* ps, int pos,DateType x)
{
assert(ps);
//pos只能在下标范围之内
assert(pos >= 0 && pos < ps -> size);
//检查空间大小
ChechCapacity(ps);
//移动pos及之后的元素
memmove(ps->arr + pos + 1, ps->arr + pos, (ps->size - pos) * sizeof(DateType));
ps->arr[pos] = x;//插入
ps->size++;//有效数据加1
}
9. 顺序表指定位置删除
//删除指定位置的数据
void SLEasert(SL* ps, int pos)
{
assert(ps);
assert(pos >= 0 && pos < ps->size);
memmove(ps->arr + pos, ps->arr + pos + 1, (ps->size - pos - 1) * sizeof(DateType));
ps->size--;
}
10. 查找
遍历数组
//查找
int Find(SL* ps, DateType x)
{
assert(ps);
for (int i = 0; i < ps->size; i++)
{
if (ps->arr[i] == x)
return i;
}
return -1;
}
四、完整代码
(1)SeqList.h文件(头文件和函数声明)
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<string.h>
//静态顺序表
//#define N 10
//struct SeqList
//{
// int arr[N];//定长数组
// int size;//有效数据个数
//};
//动态顺序表
typedef int DateType;
typedef struct SeqList
{
DateType* arr;
int size;//有效数据个数
int capacity;//空间容量
}SL;
//顺序表初始化
void InitSL(SL* ps);
//顺序表的销毁
void DestrySL(SL* ps);
//顺序表的尾插
void SLPushBack(SL* ps, DateType x);
//顺序表的头插
void SLPushFront(SL* ps, DateType x);
//顺序表的打印
void SLPrint(SL* ps,int sz);
//顺序表的尾删
void SLPopback(SL* ps);
//顺序表的头删
void SLPopFront(SL* ps);
//查找
int Find(SL* ps, DateType x);
//在指定位置插入数据
void SLInsert(SL* ps, int pos,DateType x);
//删除指定位置的数据
void SLEarse(SL* ps, int pos);
(2)SeqList.c文件(增删查改等实现代码)
#define _CRT_SECURE_NO_WARNINGS 1
#include"SeqList.h"
//顺序表初始化
void InitSL(SL* ps)
{
ps->arr = NULL;//指针设置为空
ps->size = ps->capacity = 0;
}
//顺序表的销毁
void DestrySL(SL* ps)
{
if (ps->arr)
{
free(ps->arr);
}
ps->arr = NULL;
ps->size = ps->capacity = 0;
}
//扩容
void ChechCapacity(SL* ps)
{
if (ps->size == ps->capacity)
{
//用三目操作符判断
int newcapacity = ps->capacity == 0 ? 4 : 2 * ps->capacity;
//给数组arr调整空间大小
DateType* tmp = realloc(ps->arr, sizeof(DateType) * newcapacity);
if (tmp == NULL)
{
perror("realloc");
exit(1);
}
ps->arr = tmp;
ps->capacity = newcapacity;
}
}
//顺序表的尾插
void SLPushBack(SL* ps, DateType x)
{
//检查,防止传入空指针
assert(ps);
//调用扩容函数
ChechCapacity(ps);
ps->arr[ps->size++] = x;//赋值同时让size++
}
//顺序表的头插
void SLPushFront(SL* ps, DateType x)
{
//断言
assert(ps);
ChechCapacity(ps);
//将数组整体向后移动一个位置
memmove(ps->arr + 1, ps->arr, ps->size * (sizeof(DateType)));
ps->arr[0] = x;//赋值
ps->size++;//有效数据加1
}
//顺序表打印
void SLPrint(SL* ps,int sz)
{
assert(ps);
for (int i = 0; i < sz; i++)
{
printf("%d->", ps->arr[i]);
}
}
//顺序表的尾删
void SLPopback(SL* ps)
{
assert(ps);
assert(ps->size != 0);//针对ps->size为0
ps->size--;
}
//顺序表的头删
void SLPopFront(SL* ps)
{
assert(ps);
assert(ps->size != 0);
memmove(ps->arr, ps->arr + 1, (--ps->size) * sizeof(DateType));//头删并--size(前置--,先减后用)
}
//查找
int Find(SL* ps, DateType x)
{
assert(ps);
for (int i = 0; i < ps->size; i++)
{
if (ps->arr[i] == x)
return i;
}
return -1;
}
//在指定位置插入数据
void SLInsert(SL* ps, int pos,DateType x)
{
assert(ps);
//pos只能在下标范围之内
assert(pos >= 0 && pos < ps -> size);
//检查空间大小
ChechCapacity(ps);
//移动pos及之后的元素
memmove(ps->arr + pos + 1, ps->arr + pos, (ps->size - pos) * sizeof(DateType));
ps->arr[pos] = x;//插入
ps->size++;//有效数据加1
}
//删除指定位置的数据
void SLEasert(SL* ps, int pos)
{
assert(ps);
assert(pos >= 0 && pos < ps->size);
memmove(ps->arr + pos, ps->arr + pos + 1, (ps->size - pos - 1) * sizeof(DateType));
ps->size--;
}
(3)test.c文件(测试代码)
#define _CRT_SECURE_NO_WARNINGS 1
#include"SeqList.h"
//测试尾插,头插,打印
void test01()
{
SL s;
InitSL(&s);//初始化
//尾插
SLPushBack(&s, 1);
SLPushBack(&s, 2);
////头插
//SLPushFront(&s, 4);
//SLPushFront(&s, 5);
//尾删
SLPopback(&s);
//SLPopback(&s);
//SLPopback(&s);
//头删
SLPopFront(&s);
//SLPopFront(&s);
//SLPopFront(&s);
//打印
SLPrint(&s, s.size);
}
void test02()
{
SL s;
InitSL(&s);//初始化
//尾插
SLPushBack(&s, 1);
SLPushBack(&s, 2);
SLPushBack(&s, 3);
//指定位置插入
SLInsert(&s, 1, 4);
////指定位置删除
//SLEasert(&s, 2);
//打印
SLPrint(&s, s.size);
}
int main()
{
//test01();
test02();
return 0;
}
五、顺序表经典算法
1. 移除元素

题目描述:
给你一个数组 nums 和一个值 val,你需要移除所有数值等于 val 的元素。元素的顺序可能发生改变。然后返回 nums 中与 val 不同的元素的数量。
思路一:创建新数组
创建一个新的数组,同时,创建一个变量 k 来记录与val 值不同的元素个数。然后遍历源数组,当源数组中的值不等于 val值时,将该值拿过来放到新数组中,并且k++。

思路二:双指针法
创建两个指针src和det,并且同时指向数组的第一个元素,即同时记录数组首元素的下标。然后,开始遍历数组,当数组元素等于val 值时,仅src++;反之,将src指向的元素赋值到det指向的位置,同时,src++,det++,k++。
该方法不开辟额外的空间。

代码实现
int removeElement(int* nums, int numsSize, int val)
{
int src=0;
int det=0;
int k=0;
for(int i=0;i<numsSize;i++)
{
if(nums[i]==val)
{
src++;
}
else
{
nums[det++]=nums[src++];
k++;
}
}
return k;
}
2. 合并两个有序数组

题目描述 :
给你两个按 非递减顺序 排列的整数数组 nums1 和 nums2,另有两个整数 m 和 n ,分别表示 nums1 和 nums2 中的元素数目。请你 合并 nums2 到 nums1 中,使合并后的数组同样按 非递减顺序 排列。题目提到不能开辟额外的空间。
思路一:
先将nums2数组中的所有元素先放到nums1数组的后面,然后,对nums1数组进行排序。
思路二:创建三个指针
创建三个指针s1,s2,s3,分别指向nums1的最后一个有效数据,nums2的最后一个有效数据以及nums1数组的最后,这样当我们遍历数组比较并赋值时,就不会出现将还没使用的数据覆盖掉的情况。如下图

然后,开始遍历两个数组。当s1指向元素大于或等于s2指向的元素时,将s1指向的元素拿过来放到s3指向的位置,同时,s1++,且s3++。一次操作如下图所示

但是,又有两种不同的情况。
情况一:s2 先出循环。如图,

此时,nums1中已经是合并好的数据,所以不用再做处理。
情况二:s1先出循环。如图,

此时,nums2中的数据1并没有放到nums1中,因此,我们还需要做额外的处理,将剩下的数据放到nums1中。
代码实现
void merge(int* nums1, int nums1Size, int m, int* nums2, int nums2Size, int n)
{
int s1=m-1;
int s2=n-1;
int s3=m+n-1;
while(s1>=0&&s2>=0)
{
if(nums1[s1] >= nums2[s2])
{
nums1[s3--]=nums1[s1--];
}
else
{
nums1[s3--]=nums2[s2--];
}
}
while(s2>=0)
{
nums1[s3--]=nums2[s2--];
}
}
&spm=1001.2101.3001.5002&articleId=149234470&d=1&t=3&u=0ce8e5d31aaf44e3a5aabecd12ef9291)
1万+

被折叠的 条评论
为什么被折叠?



