顺序表+练习题
1.线性表
线性表(linear list)是n个具有相同特性的数据元素的有限序列。 线性表是一种在实际中广泛使用的数据结
构,常见的线性表:顺序表、链表、栈、队列、字符串…
线性表在逻辑上是线性结构,也就说是连续的一条直线。但是在物理结构上并不一定是连续的,线性表在物理上存储时,通常以数组和链式结构的形式存储。
2.顺序表
顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构,一般情况下采用数组存储。顺序表就是数组,但是再数组的基础上,它还要求数据是连续存储的,不能跳跃间隔。在数组
上完成数据的增删查改。
顺序表的缺陷:
1、动态增容有性能消耗(频繁调用malloc会有内存消耗)
2、插入数据要挪动数据。
顺序表一般可以分为:
- 静态顺序表:使用定长数组存储元素。
静态数组特点:如果满了就不让插入
静态数组缺点:空间开多大不确定,给小了不够用,给多了浪费
#pragma once
#include<stdio.h>
typedef int SLDataType;//方便以后更改类型
#define N 1000 // 方便以后更改数组
typedef struct SeqList
{
SLDataType a[N];
int size;//表示数组中存储了多少个数据
}SL;//简写结构体名
静态顺序表只适用于确定知道需要存多少数据的场景。静态顺序表的定长数组导致N定大了,空间开多了浪
费,开少了不够用。所以现实中基本都是使用动态顺序表,根据需要动态的分配空间大小,所以下面我们实
现动态顺序表。
2. 动态顺序表:使用动态开辟的数组存储
如下图:数组中存放的有5个元素,所以 size的大小为5,数组容量为7,所以capicity为7
指针array是指向动态内存开辟的一块空间,capicity表示容量,当size等于capicity时,动态内存会进行扩容,所以就要有专门的一个功能来实现扩容这操作
typedef int SLDataType;
typedef struct SepList
{
SLDataType * a;//指向动态开辟的数组
size_t size;//有效数据个数
size_t capicity;//空间的大小
} SepList;
接口声明
/接口的创建
// 接口函数--命名风格是跟着STL走的
//初始化
void SepListInit(SepList* ps);
//头插
void SepListPushFront(SepList* ps, int x);
//尾插
void SepListPushBack(SepList* ps, int x);
//头删
void SepListPopFront(SepList* ps);
//尾删
void SepListPopBack(SepList* ps);
//销毁
void SepListDestroy(SepList* ps);
//扩容
void SepListCreate(SepList* ps);
//打印
void SepListprintf(SepList* ps);
//查找
int SepListFind(SepList* ps, int x);
//删除
void SepListErase(SepList* ps, int pos);
//插入
void SepListInsert(SepList* ps, int pos,int x );
//修改
void SepListmodif(SepList* ps, int pos, int x);
实现接口
#include"SepList.h"
//初始化
void SepListInit(SepList* ps)
{
assert(ps);
ps->a = NULL;
ps->capicity = ps->size = 0;
}
//扩容
void SepListCreate(SepList* ps)
{
assert(ps);
if (ps->capicity == ps->size)
{
int newcapicity = ps->capicity == 0 ? 4 : ps->capicity*2 ;
int* newSepList = (int*)realloc(ps->a, sizeof(int) * newcapicity);
if (NULL == newSepList)
{
perror("SepListCreate:");
exit(-1);
}
ps->a = newSepList;
ps->capicity = newcapicity;
}
}
//头插
void SepListPushFront(SepList* ps, int x)
{
assert(ps);
//检查一下要么增容
SepListCreate(ps);
//头插
int end = ps->size-1;
while (end>=0)
{
ps->a[end] = ps->a[end - 1];
end--;
}
ps->a[0] = x;
ps->size++;
}
//尾插
void SepListPushBack(SepList* ps, int x)
{
assert(ps);
SepListCreate(ps);
ps->a[ps->size++] = x;
}
//头删
void SepListPopFront(SepList* ps)
{
int begin = 0;
while (begin < ps->size-1 )
{
ps->a[begin] = ps->a[begin + 1];
begin++;
}
ps->size--;
}
//尾删
void SepListPopBack(SepList* ps)
{
assert(ps);
assert(ps->size > 0);
ps->size--;
}
//销毁
void SepListDestroy(SepList* ps)
{
assert(ps);
free(ps->a);
ps->a = NULL;
ps->capicity = 0;
ps->size = 0;
}
//打印
void SepListprintf(SepList* ps)
{
assert(ps);
for (int i = 0; i < ps->size; i++)
{
printf("%d ", ps->a[i]);
}printf("\n");
}
//查找
int SepListFind(SepList* ps, int x)
{
assert(ps);
for (int i = 0; i < ps->size; i++)
{
if (ps->a[i] == x)
{
return i;
}
}
return -1;
}
//删除
void SepListErase(SepList* ps, int pos)
{
assert(ps);
assert(pos >= 0 && pos < ps->size);
int begin = pos;
while (begin < ps->size)
{
ps->a[begin] = ps->a[begin + 1];
begin++;
}
ps->size--;
}
//插入
void SepListInsert(SepList* ps, int pos, int x)
{
assert(ps);
SepListCreate(ps);
assert(pos >= 0 && pos < ps->size);
int end = ps->size - 1;
while (end >= pos)
{
ps->a[end+1] = ps->a[end];
end--;
}
ps->a[pos] = x;
ps->size++;
}
//修改
//修改
void SepListmodif(SepList* ps, int pos, int x)
{
assert(ps);
assert(pos >= 0 && pos < ps->size);
ps->a[pos] = x;
}
问题:
- 中间/头部的插入删除,时间复杂度为O(N)
- 增容需要申请新空间,拷贝数据,释放旧空间。会有不小的消耗。
- 增容一般是呈2倍的增长,势必会有一定的空间浪费。例如当前容量为100,满了以后增容到200,我们
再继续插入了5个数据,后面没有数据插入了,那么就浪费了95个数据空间。
思考:如何解决以上问题呢?
针对顺序表的缺陷,就设计出了链表,链表在下章介绍
力扣OJ题
1. 删除有序数组中的重复项
解题思路:
因为数组存放的是有序序列的,所以如果src不断的往后遍历,例如01122,当src指向第一个2时,前面的1,与2不相等,
为了防止越界访问,所以把src 与dest初始化为1;
如下详细代买:
时间复杂度是O(N),空间复杂度为O(1);
int removeDuplicates(int* nums, int numsSize){
if(numsSize==0)
{
return 0;
}
int dest =1;
int src=1;
//dest 控制要存放的值
//src 遍历 ,如果 nums[src] != nums[src-1] 就把nums[src]的值给nums[dest]
//
while(src<numsSize)
{
if(nums[src]!=nums[src-1])
{
nums[dest++]=nums[src++];
}
else
{
src++;//如果不是那么就继续遍历
}
}
return dest;
}
2.移除元素
思路一:
用指针控制,进行遍历,如果遇到val值就把后面的数网前挪,把val值覆盖掉,然后继续遍历直到遍历完整个数组
空间复杂度为O(1),时间复杂度最坏是O(N^2)
思路二:
空间复杂度O(N),时间复杂度为O(N),以空间换取时间的概念。
先开辟一块空间,然后遍历数组,如果不等于val值就放进新数组里;
思路三:
空间复杂度O(1),时间复杂度为O(N),通过双指针的方式,先定两个下标,分别为目标下标和遍历下标
遍历下标如果不等于val,就把值放到目标下标对应的数组里并且目标下标自增1,如果等于val值则,遍历目标越过val 值,
如果图:
int removeElement(int* nums, int numsSize, int val){
if(numsSize==0)// 判断数组大小
{
return 0 ;
}
int dest = 0;// 目标下标初始化
int src = 0 ;// 遍历下标初始化
while(src<numsSize)//遍历结束条件
{
if(nums[src]!=val)//
{
nums[dest++]=nums[src++];
}
else
{
src++;
}
}
return dest ;
}
3.选择数组
思路一、
先把最后一个元素拿出来,然后把7前面的数往后挪,挪动
空间复杂度o(n),时间复杂度O(N^N)
思路二:
将数组整体旋转一次,以k为划分左旋和右旋,统一设置一个选择函数,只要把左下标和右下标传参,就能把左右下标范围的元素进行选择
如图
不管是先左旋还右旋,还是整体旋,都可以达到目标
void reverse(int *nums1,int *nums2)
{
while(nums1<nums2)
{
int tmp=*nums1;
*nums1=*nums2;
*nums2=tmp;
nums1++;
nums2--;
}
}
void rotate(int* nums, int numsSize, int k){
if(k>=numsSize)
{
k%=numsSize;//必须有,预防越界
}
//整体选择
reverse(nums,nums+numsSize-1);
//左旋
reverse(nums,nums+k-1);
//右旋
reverse(nums+k,nums+numsSize -1);
}
4.合并两数组
题目分析:
两个数组都是递增的有序。
解题思路
从后往前放,分别用两个指针指向num1和num2最大的元素,两个指针指向的元素进行比较,
然后,在设置一个指针,把最大元素放到指针指向的位置
void merge(int* nums1, int nums1Size, int m, int* nums2, int nums2Size, int n){
int i=m-1;int j =n-1;int index=m+n-1;
while(i>=0&&j>=0)
{
nums1[index--]=nums1[i]>nums2[j]?nums1[i--]:nums2[j--];
}
while(j>=0)
{ //防止nums2数组元素没放完
nums1[index--]=nums2[j--];//把nums2数组元素放进nums1数组里
}
}
5.数组形式的整数加法
1.因为题目要求,返回一个动态内存数组,首先我们就要确定动态内存数组的大小,我们知道两个数相加,最大的那个数的长度为len,两个数相加后的结果不可能大于len+1的长度,例如:999+999=1988;
2.动态内存数组是用来存放相加后的结果的,首先我们要在k里取最低位的数,然后取数组最后一个元素的,两个数进行相加,报错结果,判断结果是否大于或等于10,如果是则要用一个变量保存进位的数1
,这个变量在下一次两数相加时会用上,以此内推,k从个位开始取,二数组就从最后一个元素开始取。
简单的画个图方便理解:
int* addToArrayForm(int* num, int numSize, int k, int* returnSize) {
//判断开辟多大空间
int ksize = 0;//计算整数k的长度
int knum = k;//为了不改变k值,创建了一个临时变量
while (knum)// 计算k的长度
{
knum /= 10;
ksize++;
}
//因为 两数相加后的结果,不肯能大于最大数的长度+1
//计算要开辟多大空间
int newcapacity = ksize > numSize ? ksize+1 : numSize+1;
int* numsArr = (int*)malloc(newcapacity * sizeof(int));
//统计结构数组的元素个数
int reti = 0;
//两数的加法运算
int next = 0;//10进制进位,如果要进位则next是1,否则为0;
int ki = 0;//控制k的取值长度
int Ai = numSize - 1;//控制数组取值的长度
while (ki < ksize || Ai >= 0)
{
//每次取k一位;
int kval = 0;
kval = k % 10;
k /= 10;
ki++;
//
int Aval = 0;
if(Ai>=0)//防止越界访问
Aval = num[Ai--];
int retval = Aval + kval + next;
if (retval >= 10)
{
retval -= 10;
next = 1;
}
else
{
next = 0;
}
numsArr[reti++] = retval;
}
if(next==1)
{
numsArr[reti++]=1;
}
// 数组逆序
int begin = 0;
int end = reti - 1;
while (begin < end)
{
int tmp = numsArr[begin];
numsArr[begin] = numsArr[end];
numsArr[end] = tmp;
begin++;
end--;
}
*returnSize = reti;
return numsArr;
}