目录
前言
在前面数据结构章节的学习中,我们一起学习了通过时间复杂度和空间复杂结构来判断一个算法的好坏,而我们本章节将一起学习线性表中的数据结构–顺序表。
1.线性表
线性表(linear list)是n个具有相同特性的数据元素的有限序列。线性表是一种在实际中广泛使用的数据结构,常见的线性表:顺序表、链表、栈、队列、字符串。线性表在逻辑上是线性结构,也就是说是连续的一条直线,但在物理结构上并不是连续的,线性表在物理上存储时,通常以数组和链式结构的形式存储。
2.顺序表的概念及结构
顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构,一般情况下采用数组存储,在数组上完成数据的增删查改。
顺序表一般可以分为:
1.静态顺序表:使用定长数组存储元素
2.动态顺序表:使用动态开辟的数组存储:
3.接口实现
静态顺序表只适用于确定知道需要存多少数据的场景。静态顺序表的定长数组导致N定大了空间开多浪费,开少了空间不够用。所以现实中基本都是使用动态顺序表,根据需要动态的分配空间的大小,所以下面我们就一起实现动态顺序表。
3.1创建动态结构体:
#define INIT_CAPICITY 3
typedef int SLDataType;
typedef struct SeqList
{
SLDataType* a;//指向动态开辟的数组
size_t size;//有效数据的个数
size_t capicity;//容量的大小
}SeqList;
解释: 在创建动态结构体的同时用
#define
定义一个标识符INIT_CAPICITY
,为后面开辟空间做准备,同时方便我们进行调试;用typedef
对存储值的数据类型进行重命名,如果后面存储的值为其他类型可以直接对齐进行更改即可。
3.2顺序表的初始化:
void SeqListInit(SeqList* ps)
{
assert(ps);//检查地址是否为空
SeqList* tmp = (SeqList*)malloc(sizeof(SeqList) * INIT_CAPICITY);
if (tmp == NULL)
{
perror("SeqListInit::malloc");
return;
}
ps->a = tmp;
ps->capicity = INIT_CAPICITY;
ps->size = 0;
}
首先,需要用
malloc
函数为顺序表开辟一定的空间,给我们程序员输入数据使用,把开辟空间的地址传给ps->a
,并对我们创建的ps->capacity、ps->size
进行初始化。
3.3顺序表的头插
void Checkcapacity(SeqList* ps)
{
assert(ps);
if (ps->size == ps->capicity)
{
SeqList* tmp = (SeqList*)realloc(ps->a, sizeof(SeqList) * INIT_CAPICITY * 2);
if (tmp == NULL)
{
perror("Checkcapacity::realloc");
return;
}
printf("增容成功\n");
ps->a = tmp;
ps->capicity = INIT_CAPICITY * 2;
}
}
void SeqListPushBack(SeqList* ps, SLDataType x)
{
assert(ps);
Checkcapacity(ps);//检查空间是否足够
ps->a[ps->size++] = x;
}
解释: 在构造
SeqListPushBack
函数时,对顺序表开辟的空间,进行检查空间是否满了;如果满了,则需要重新调整顺序空间大小,等会我们构造顺序表头插、任意插函数也需要判断顺序表的空间是否已满,所以我们直接构造一个Checkcapacity
函数进行检查,同时也方便后面函数的重复利用。
顺序表的打印
void SeqListPrint(SeqList* ps)
{
assert(ps);
//暴力检查
assert(ps->size > 0);
///温柔检查
//if (ps->size <= 0)
//{
// return;
//}
int i = 0;
for (i = 0; i < ps->size; i++)
{
printf("%d ", ps->a[i]);
}
printf("\n");
}
调试代码运行的结果:
3.4顺序表的尾删
void SeqListPoshBack(SeqList* ps)
{
//暴力检查
assert(ps->size>0);
///温柔检查
//if (ps->size <= 0)
//{
// return;
//}
ps->size--;
}
解释: 在尾删函数
SeqListPoshBack
中,需要对顺序表有效数据的个数进行判断,如果有效数据个数为零,则不能进行删除,所以需要进行判断:①可以暴力检查用assert
进行断言,直接返回发生错误的行数;②可以用if
语句进行判断,如果有效数据个数为零,return
直接返回,什么都没有发生,后面在其他函数中使用这些语句的原理是一样的。删除操作:①ps->a[ps->size-1]=0;ps->size--;
②ps->size--
,即把有数数据个数减一,不让其打印出来就可以了(也可以最后一个的有效数据的值改为0)。
代码调试的结果为:
3.5顺序表头插
}
void SeqListPushFront(SeqList* ps, SLDataType x)
{
assert(ps);
Checkcapacity(ps);//检查空间是否足够
int end=ps->size;
for (; end>0; end--)
{
ps->a[end] = ps->a[end - 1];
}
ps->a[0] = x;
ps->size++;
}
解释: 需要把原来位置的数据往后挪动,为了保证原来的数据完整,所以应该从最后面的数据开始挪动,直至把顺序表的头位置的原数据移动到下一个位置为止,并把预留出来的位置放入数据
x
。
顺序表的头删
void SeqListPopFront(SeqList* ps)
{
assert(ps);
assert(ps->size > 0);
int i = 0;
for (i = 0; i < ps->size - 1; i++)
{
ps->a[i] = ps->a[i + 1];
}
ps->size--;
}
解释: 头删函数
SeqListPopFront
需要把顺序表头位置的数据删除掉,其他位置的数据的位置也发生相应的改动,从头位置开始把下一个数据覆盖到头位置,直至最后一个位置的数据往前覆盖完成为止(注意for循环的判断条件),然后ps->size--
,有效数据个数减一即可。
代码运行的结果为:
顺序表的任意插
void SeqListInsert(SeqList* ps, size_t pos, SLDataType x)
{
assert(ps);
assert(pos >= 0 && pos <= ps->size);
Checkcapacity(ps);//检查空间是否足够
int end = ps->size;
for (; end > pos; end--)
{
ps->a[end] = ps->a[end - 1];
}
ps->a[pos] = x;
ps->size++;
}
解释: 在顺序表任意插函数
SeqListInsert
中,要判断插入的位置,是否在允许的范围内;为保证原数据完整,数据应从顺序表尾位置开始,把数据往后挪动一位,直到把pos
位置的数据往后一位移动为止,最后把数据x
放到预留的pos位置,ps->size++
。
3.6顺序表的任意删
void SeqListErase(SeqList* ps, size_t pos)
{
assert(ps);
assert(pos >= 0 && pos < ps->size);
int i = pos;
for (; i < ps->size-1; i++)
{
ps->a[i] = ps->a[i+1];
}
ps->size--;
}
解释: 在顺序表的任意删 SeqListErase
中,从pos位置开始(包括pos),把下一个位置的数据移动到当前位置,直至最后一个位置的数据挪动完成为止,然后ps->size--
,和顺序表头删的原理一样。
代码调试的结果为:
3.7顺序表的查找
int SeqListFind(SeqList* ps, SLDataType x)
{
assert(ps);
int i = 0;
for (i = 0; i < ps->size; i++)
{
if (ps->a[i] == x)
{
return i;
}
}
return -1;
}
代码运行的结果为:
3.8顺序表的销毁
void SeqListDestroy(SeqList* ps)
{
assert(ps);
free(ps->a);
ps->capicity = ps->size = 0;
}
3.90 由顺序表任意插函数改造的顺序表尾插
void SeqListPushBack(SeqList* ps, SLDataType x)
{
//版本一
//assert(ps);
//Checkcapacity(ps);//检查空间是否足够
//ps->a[ps->size++] = x;
//版本二
SeqListInsert(ps, ps->size, x);
}
代码运行的结果为:
3.91由顺序表任意插函数改造的顺序表尾删
void SeqListPoshBack(SeqList* ps)
{
//版本一
暴力检查
//assert(ps->size>0);
/温柔检查
if (ps->size <= 0)
{
return;
}
//ps->size--;
//版本二
SeqListErase(ps, ps->size - 1);
}
代码运行的结果为:
3.92由顺序表任意插函数改造的顺序表头插
void SeqListPushFront(SeqList* ps, SLDataType x)
{
//版本二
//assert(ps);
//Checkcapacity(ps);//检查空间是否足够
//int end=ps->size;
//for (; end>0; end--)
//{
// ps->a[end] = ps->a[end - 1];
//}
//ps->a[0] = x;
//ps->size++;
//版本二
SeqListInsert(ps, 0,x);
}
代码运行的结果为:
3.93由顺序表任意插函数改造的顺序表头删
void SeqListPopFront(SeqList* ps)
{
//版本一
//assert(ps);
//assert(ps->size > 0);
//int i = 0;
//for (i = 0; i < ps->size - 1; i++)
//{
// ps->a[i] = ps->a[i + 1];
//}
//ps->size--;
//版本二
SeqListErase(ps, 0);
}
代码运行的结果为:
4.0顺序表源码及测试
4.1Seqlist.h
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#define INIT_CAPICITY 3
typedef int SLDataType;
typedef struct SeqList
{
SLDataType* a;//指向动态开辟的数组
size_t size;//有效数据的个数
size_t capicity;//容量的大小
}SeqList;
//顺序表的初始化
void SeqListInit(SeqList* ps);
//顺序表的销毁
void SeqListDestroy(SeqList* ps);
//检查空间,判断是否需要增容
void Checkcapacity(SeqList* ps);
//顺序表的打印
void SeqListPrint(SeqList* ps);
//顺序表的尾插
void SeqListPushBack(SeqList* ps,SLDataType x);
//顺序表的尾删
void SeqListPoshBack(SeqList* ps);
//顺序表的头插
void SeqListPushFront(SeqList* ps, SLDataType x);
//顺序表的头删
void SeqListPopFront(SeqList* ps);
//在顺序表的pos位置插入x
void SeqListInsert(SeqList* ps, size_t pos, SLDataType x);
//删除顺序表pos位置的值
void SeqListErase(SeqList* ps, size_t pos);
//顺序表的查找
int SeqListFind(SeqList* ps, SLDataType x);
4.2Seqlist.c
#define _CRT_SECURE_NO_WARNINGS 1
#include"Seqlist.h"
void SeqListInit(SeqList* ps)
{
assert(ps);//检查地址是否为空
SeqList* tmp = (SeqList*)malloc(sizeof(SeqList) * INIT_CAPICITY);
if (tmp == NULL)
{
perror("SeqListInit::malloc");
return;
}
ps->a = tmp;
ps->capicity = INIT_CAPICITY;
ps->size = 0;
}
void SeqListDestroy(SeqList* ps)
{
assert(ps);
free(ps->a);
ps->capicity = ps->size = 0;
}
void Checkcapacity(SeqList* ps)
{
assert(ps);
if (ps->size == ps->capicity)
{
SeqList* tmp = (SeqList*)realloc(ps->a, sizeof(SeqList) * INIT_CAPICITY * 2);
if (tmp == NULL)
{
perror("Checkcapacity::realloc");
return;
}
printf("增容成功\n");
ps->a = tmp;
ps->capicity = INIT_CAPICITY * 2;
}
}
void SeqListPushBack(SeqList* ps, SLDataType x)
{
//版本一
//assert(ps);
//Checkcapacity(ps);//检查空间是否足够
//ps->a[ps->size++] = x;
//版本二
SeqListInsert(ps, ps->size, x);
}
void SeqListPrint(SeqList* ps)
{
assert(ps);
//暴力检查
assert(ps->size > 0);
///温柔检查
//if (ps->size <= 0)
//{
// return;
//}
int i = 0;
for (i = 0; i < ps->size; i++)
{
printf("%d ", ps->a[i]);
}
printf("\n");
}
void SeqListPoshBack(SeqList* ps)
{
//版本一
暴力检查
//assert(ps->size>0);
/温柔检查
if (ps->size <= 0)
{
return;
}
//ps->size--;
//版本二
SeqListErase(ps, ps->size - 1);
}
void SeqListPushFront(SeqList* ps, SLDataType x)
{
//版本二
//assert(ps);
//Checkcapacity(ps);//检查空间是否足够
//int end=ps->size;
//for (; end>0; end--)
//{
// ps->a[end] = ps->a[end - 1];
//}
//ps->a[0] = x;
//ps->size++;
//版本二
SeqListInsert(ps, 0,x);
}
void SeqListPopFront(SeqList* ps)
{
//版本一
//assert(ps);
//assert(ps->size > 0);
//int i = 0;
//for (i = 0; i < ps->size - 1; i++)
//{
// ps->a[i] = ps->a[i + 1];
//}
//ps->size--;
//版本二
SeqListErase(ps, 0);
}
void SeqListInsert(SeqList* ps, size_t pos, SLDataType x)
{
assert(ps);
assert(pos >= 0 && pos <= ps->size);
Checkcapacity(ps);//检查空间是否足够
int end = ps->size;
for (; end > pos; end--)
{
ps->a[end] = ps->a[end - 1];
}
ps->a[pos] = x;
ps->size++;
}
void SeqListErase(SeqList* ps, size_t pos)
{
assert(ps);
assert(pos >= 0 && pos < ps->size);
int i = pos;
for (; i < ps->size-1; i++)
{
ps->a[i] = ps->a[i+1];
}
ps->size--;
}
int SeqListFind(SeqList* ps, SLDataType x)
{
assert(ps);
int i = 0;
for (i = 0; i < ps->size; i++)
{
if (ps->a[i] == x)
{
return i;
}
}
return -1;
}
4.3test.c
#include"Seqlist.h"
//#define N 7
//typedef int SLDataType;
//typedef struct SeqList
//{
// SLDataType array[N];
// size_t size;
//}SqeList;
//顺序表的动态存储
void TestSeqlist1()
{
//定义结构体变量
SeqList s;
//初始化顺序表
SeqListInit(&s);
顺序表尾插
SeqListPushBack(&s, 1);
SeqListPushBack(&s, 2);
SeqListPushBack(&s, 3);
SeqListPushBack(&s, 4);
SeqListPushBack(&s, 5);
SeqListPrint(&s);
顺序表尾删
SeqListPoshBack(&s);
SeqListPoshBack(&s);
顺序表打印及销毁
SeqListPrint(&s);
SeqListDestroy(&s);
}
void TestSeqlist2()
{
SeqList s;
SeqListInit(&s);
//顺序表头插
SeqListPushFront(&s, 1);
SeqListPushFront(&s, 2);
SeqListPushFront(&s, 3);
SeqListPushFront(&s, 4);
SeqListPushFront(&s, 5);
SeqListPrint(&s);
//顺序表头删
SeqListPopFront(&s);
SeqListPopFront(&s);
//顺序表打印及销毁
SeqListPrint(&s);
SeqListDestroy(&s);
}
void TestSeqlist3()
{
SeqList s;
SeqListInit(&s);
//头插
SeqListPushFront(&s, 1);
SeqListPushFront(&s, 2);
SeqListPushFront(&s, 3);
SeqListPushFront(&s, 4);
SeqListPushFront(&s, 5);
SeqListPrint(&s);
//任意插
SeqListInsert(&s, 2, 6);
SeqListInsert(&s, 2, 7);
SeqListPrint(&s);
//任意删
SeqListErase(&s, 2);
SeqListErase(&s, 2);
SeqListPrint(&s);
//顺序表查找
int ret=SeqListFind(&s, 1);
if (ret == -1)
{
printf("找不到\n");
}
else
{
printf("该数字在第%d位\n",ret);
}
//顺序表销毁
SeqListDestroy(&s);
}
int main()
{
//TestSeqlist1();
TestSeqlist2();
//TestSeqlist3();
return 0;
}
5.0数组相关面试题
练习一:
int removeElement(int* nums, int numsSize, int val)
{
int src=0;
int dst=0;
while(src<numsSize)
{
if(nums[src]!=val)
{
nums[dst++]=nums[src++];
}
else
{
src++;
}
}
return dst;
}
图形理解:
练习二:
int removeDuplicates(int* nums, int numsSize)
{
int src = 1;
int dst = 0;
while(src < numsSize)
{
if (nums[src] == nums[dst])
{
src++;
}
else
{
nums[++dst] = nums[src++];
}
}
return dst + 1;
}
图形理解:
void merge(int* nums1, int nums1Size, int m, int* nums2, int nums2Size, int n)
{
int end1 = m - 1;
int end2 = n - 1;
int dst = m + n - 1;
while (end1 >= 0 && end2 >= 0)
{
if (nums1[end1] > nums2[end2])
{
nums1[dst--] = nums1[end1--];
}
else
{
nums1[dst--] = nums2[end2--];
}
}
while (end2 >= 0)
{
nums1[dst--] = nums2[end2--];
}
}
图形理解:
1.原地删除数组中所有的元素val,要求时间复杂度是O(N),空间复杂度为O(1)。OJ链接
2.删除数组的重复项。OJ链接
3.合并两个有序的数组。 OJ链接
以上是数组题链接,大家感兴趣可以试试。
总结
本章我们一起学习了链表中顺序表的相关概念、模拟实现了顺序表以及完成了相关的数组题加以巩固,希望对大家认识顺序表结构有些许帮助!最后感谢大家的阅读,如有不对欢迎纠正!🎠🎠🎠