顺序表C语言实现附加力扣题

本文详细介绍了顺序表的概念、结构及其在动态存储中的实现,包括初始化、扩容、头插、尾插、头删、尾删、查找、删除、插入和修改等操作。同时,讨论了顺序表的性能问题,如动态增容的消耗和数据移动的成本,并给出了相关LeetCode题目解决方案,探讨了如何在特定场景下优化顺序表的操作。
摘要由CSDN通过智能技术生成

在这里插入图片描述

1.线性表

线性表(linear list)是n个具有相同特性的数据元素的有限序列。 线性表是一种在实际中广泛使用的数据结
构,常见的线性表:顺序表、链表、栈、队列、字符串…
线性表在逻辑上是线性结构,也就说是连续的一条直线。但是在物理结构上并不一定是连续的,线性表在物
理上存储时,通常以数组和链式结构的形式存储。

2.顺序表

2.1概念及结构
顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构,一般情况下采用数组存储。在数组
上完成数据的增删查改。
顺序表一般可以分为:

  1. 静态顺序表:使用定长数组存储。
    在这里插入图片描述

  2. 动态顺序表:使用动态开辟的数组存储。
    在这里插入图片描述

顺序表缺陷:

1、动态增容有性能消耗(频繁地malloc是会有内存消耗的)
2、需要头部插入数据,需要挪动数据

顺序表的实现

接口声明

//初始化
void SeqListInit(SeqList *ps);
//头插
void SeqListPushFront(SeqList *ps, int x);
//尾插
void SeqListPushBack(SeqList *ps,int x);
//头删
void SeqListPopFront(SeqList *ps);
//尾删
void SeqListPopBack(SeqList *ps);
//销毁
void SeqListDestroy(SeqList *ps);
//扩容
void SeqListCreate(SeqList *ps);
//打印
void SeqListprint(SeqList * ps);
//查找
int SeqListFind(SeqList * ps, int x);
//删除
void SeqListErase(SeqList *ps, int pos);
//插入
void SeqListInsert(SeqList *ps, int pos,int x);
//修改
void SeqListmodif(SeqList *ps, int pos, int x);

实现的接口

//初始化
void SeqListInit(SeqList *ps) 
{
	assert(ps);
	ps->arr = NULL;
	ps->capacity = ps->size = 0;
}
//扩容
void SeqListCreate(SeqList *ps) 
{
	assert(ps);
	
	if (ps->capacity == ps->size) 
	{
		int newcapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
		int *newA = (int *)realloc(ps->arr, sizeof(int) * newcapacity);
		if (newA == NULL) 
		{
			perror("SeqListCreate:");
			exit(1);
		}

		ps->arr = newA;
		ps->capacity = newcapacity;
	}
}
//头插
void SeqListPushFront(SeqList *ps,int x) 
{
	assert(ps);
	SeqListCreate(ps);
	int end = ps->size - 1;
	while (end >= 0) 
	{
		 ps->arr[end + 1] = ps->arr[end];
		 end--;
	}
	ps->arr[0] = x;
	ps->size++;
}
//尾插
void SeqListPushBack(SeqList *ps,int x) 
{
	assert(ps);
	SeqListCreate(ps);
	ps->arr[ps->size++] = x;
}
//头删
void SeqListPopFront(SeqList *ps) 
{
	assert(ps);
	assert(ps->size > 0);
	int begin = 0;
	while (begin  < ps->size) 
	{
		ps->arr[begin] = ps->arr[begin + 1];
		begin++;
	}
	ps->size--;
}
//尾删
void SeqListPopBack(SeqList *ps) 
{
	assert(ps);
	assert(ps->size > 0);
	ps->size--;
}
//销毁
void SeqListDestroy(SeqList *ps)
{
	assert(ps);
	free(ps->arr);
	ps->arr = NULL;
	ps->capacity = ps->size = 0;
}

//打印
void SeqListprint(SeqList * ps) 
{
	assert(ps);
	for (int i = 0; i < ps->size; i++) 
	{
		printf("%d ",ps->arr[i]);
	}
	printf("\n");
}
//查找
int SeqListFind(SeqList * ps, int x)
{
	assert(ps);
	for (int i = 0; i < ps->size; i++) 
	{
		if (ps->arr[i] == x) 
		{
			return i;
		}
	}
	return -1;
}

//删除
void SeqListErase(SeqList *ps, int pos) 
{
	assert(ps);
	assert(pos >= 0 && pos < ps->size);
	int begin = pos;
	while (begin < ps->size) 
	{
		 ps->arr[begin] = ps->arr[begin + 1];
		 ++begin;
	}
	ps->size--;

}
//插入
void SeqListInsert(SeqList *ps, int pos, int x) 
{
	assert(ps);
	assert(pos >= 0 && pos < ps->size);
	SeqListCreate(ps);
	int end = ps->size - 1;
	while (end >= pos) 
	{
		ps->arr[end + 1] = ps->arr[end];
		end--;
	}
	
	ps->arr[pos] = x;
	ps->size++;
}

//修改
void SeqListmodif(SeqList *ps, int pos, int x) 
{
	assert(ps);
	assert(pos >= 0 && pos < ps->size);
	ps->arr[pos] = x;
}

顺序表的初始化

初始化表结构的成员

//初始化
void SeqListInit(SeqList *ps) 
{
	assert(ps);
	ps->arr = NULL;
	ps->capacity = ps->size = 0;
}

顺序表的销毁

释放在堆上开辟的空间

//销毁
void SeqListDestroy(SeqList *ps)
{
	assert(ps);
	free(ps->arr);
	ps->arr = NULL;
	ps->capacity = ps->size = 0;
}

顺序表扩容

面对空间已满,没法再插入数据的情况,需要增容

//扩容
void SeqListCreate(SeqList *ps) 
{
	assert(ps);
	//空间满了开辟一块新的空间
	if (ps->capacity == ps->size) 
	{
		int newcapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
		int *newA = (int *)realloc(ps->arr, sizeof(int) * newcapacity);
		if (newA == NULL) 
		{
			perror("SeqListCreate:");
			exit(1);
		}

		ps->arr = newA;
		ps->capacity = newcapacity;
	}
}

顺序表的头插

头插数据需要考虑是从前往后挪动数据还是从后往前挪动数据,很明显如果是从前往后挪动数据的话,会将原有的数据给覆盖掉,所以只能从后往前,头插的时间复杂度是o(n)

//头插
void SeqListPushFront(SeqList *ps,int x) 
{
	assert(ps);
	SeqListCreate(ps);
	int end = ps->size - 1;
	//从后往前挪动数据
	while (end >= 0) 
	{
		 ps->arr[end + 1] = ps->arr[end];
		 end--;
	}
	//头插数据
	ps->arr[0] = x;
	ps->size++;
}

顺序表的尾插

其实顺序表的尾插,时间复杂度o(1)

//尾插
void SeqListPushBack(SeqList *ps,int x) 
{
	assert(ps);
	SeqListCreate(ps);
	ps->arr[ps->size++] = x;
}

顺序表的遍历打印

遍历顺序表,o(n)的时间复杂度

//打印
void SeqListprint(SeqList * ps) 
{
	assert(ps);
	for (int i = 0; i < ps->size; i++) 
	{
		printf("%d ",ps->arr[i]);
	}
}

顺序表的尾删

这里要考虑的是需要判断表中有没有数据,如果没有数据就不需要删除

//尾删
void SeqListPopBack(SeqList *ps) 
{
	assert(ps);
	assert(ps->size > 0);
	ps->size--;
}

顺序表的头删

将后一个数据覆盖掉前一个数据,最后- -size,因为需要挪动数据所以时间复杂度是o(n)

//头删
void SeqListPopFront(SeqList *ps) 
{
	assert(ps);
	assert(ps->size > 0);
	int begin = 0;
	while (begin  < ps->size) 
	{
		ps->arr[begin] = ps->arr[begin + 1];
		begin++;
	}
	ps->size--;
}

顺序表的查找

找到返回该位置的下标,O(N)时间复杂度

//查找
int SeqListFind(SeqList * ps, int x)
{
	assert(ps);
	for (int i = 0; i < ps->size; i++) 
	{
		if (ps->arr[i] == x) 
		{
			return i;
		}
	}
	return -1;
}

删除

删除指定pos位置的元素,可以挪动数据将其覆盖,从前往后的覆盖法,挪动数据时间复杂度O(N)

//删除
void SeqListErase(SeqList *ps, int pos) 
{
	assert(ps);
	assert(pos >= 0 && pos < ps->size);
	int begin = pos;
	while (begin < ps->size) 
	{
		 ps->arr[begin] = ps->arr[begin + 1];
		 ++begin;
	}
	ps->size--;
}

插入

将pos位置和pos后的数据整体往后一挪动,再将x插入到指定pos位置,注意选择的位置不能超出顺序表的大小
时间复杂度O(N)

void SeqListInsert(SeqList *ps, int pos, int x) 
{
	assert(ps);
	assert(pos >= 0 && pos < ps->size);
	SeqListCreate(ps);
	int end = ps->size - 1;
	while (end >= pos) 
	{
		ps->arr[end + 1] = ps->arr[end];
		end--;
	}
	
	ps->arr[pos] = x;
	ps->size++;
}

修改

修改该位置的值

void SeqListmodif(SeqList *ps, int pos, int x) 
{
	assert(ps);
	assert(pos >= 0 && pos < ps->size);
	ps->arr[pos] = x;
}

顺序表的问题及思考:

1、顺序表的指定插入和删除都需要挪动数据,时间复杂度为O(N)
2、增容需要申请空间,拷贝数据,释放就空间,会有不小的消耗,一定程度上也会影响效率,增容会存在空间浪费,开大了浪费,开小了又不够

力扣题

27. 移除元素:

点我.
在这里插入图片描述
思路一:
不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并 原地 修改输入,找出值为val的位置挪动后面的数据覆盖掉val的值,
时间复杂度是0(N*N),考虑最坏的情况如果数组中的值全是val的话,每次找出一个值为val的时间复杂度是o(N),挪动数据又是o(N),整体就是O(N * N)
在这里插入图片描述
思路二:
开辟一个空间足够大的数组,遍历一遍找出原数组将值不为val的,拷贝到新数组中去,时间复杂度是O(N),空间复杂度是O(N)。空间换时间

思路三:
双指针解法,在原数组的基础上创建一个虚拟数组,src和dest都从0开始,src去找值不为val的,找到后将src位置的值覆盖掉dest位置的值,他们都自增,如果src的值等于val,src就继续找,最后返回dest,时间复杂度是O(N),空间复杂度是O(1),
在这里插入图片描述

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;
}

26. 删除有序数组中的重复项:

点我.
在这里插入图片描述
解题思路:
src和next一前一后,如果src的值不等于dst,就将src的值覆盖在dst的位置上,dst++,next的位置赋值给src,next++找下一个数,在回来判断next位置的值是否与src相等,如果相等继续执行,直到条件为假循环终止
时间复杂度是O(N),空间复杂度是O(1)
在这里插入图片描述
考虑这种情况,如果next已经 > numsize了,循环终止,但是末尾的那个元素还没有拷贝过来
在这里插入图片描述

int removeDuplicates(int* nums, int numsSize){
    if(numsSize == 0)
    {
        return 0;
    }
    int cur = 0;
    int next = 1;
    int count = 0;
    int dest = 0;
    while(next < numsSize)
    {
  	   //cur和next将重复的值给过滤掉,只留下一个值给dst
        if(nums[cur] != nums[next])
        {
        nums[dest++] = nums[cur];
        cur = next;
        ++next;
        }
        else
        {
          ++next;
        }
    }
    //防止循环终止后,最后一个数据丢失
    if(cur < numsSize)
    nums[dest++] = nums[cur];
    return dest;
}

27. 数组形式的整数加法:

  1. 点我.
    . 在这里插入图片描述
    测试用例
    在这里插入图片描述

解题思路:
计算出val的十进制位数,取val或数组长度大的那一个作为新数组的大小(存在进位问题),继续将val的每一位取下来对应的个位、十位、百位…看成与数组元素的一种关联关系,通过不断拆分相加它们之间的和,和值大于10就考虑进位,小于10不进位,最后将这两个值相加得到的和存放到对应的位置上,由于是从低位开始计算的,存在数组的顺序也是倒过来的,所以再最后需要整体一逆转

int* addToArrayForm(int* A, int numSize, int k, int* returnSize){
    int ksize = 0;
    int num = k;
    //记录num的十进制位数
    while(num)
    {
        num /= 10;
        ++ksize;
    }

    int reti = 0;
    //扩容一位提供进位
    int len = numSize > ksize ? numSize + 1 : ksize + 1;
    int Ai = numSize - 1;
    int ki = 0;
    int next = 0;//进位
    int *retArr = (int *)malloc(sizeof(int) * len);
    while(Ai >= 0 || ki < ksize)
    {
    
        int aval = 0;
        if(Ai >= 0)
        aval = A[Ai--];

        int kval = k % 10;
        k /= 10;
       
        ki++;
        int ret = kval + aval + next;
        if(ret >= 10)
        {   
            next = 1;
            ret -= 10;
        }
        else
        {
            next = 0;
        }
        retArr[reti++] = ret;
    }
    //存放最高位
    if(next == 1)
    {
        retArr[reti++] = 1;
    }
	//逆置数组
    int begin = 0;
    int end = reti - 1;
    while(begin < end)
    {
        int tmp = retArr[begin];
        retArr[begin] = retArr[end];
        retArr[end] = tmp;

        begin++;
        end--;
    }
    *returnSize = reti;
    return retArr;
}

旋转数组:

点我.
在这里插入图片描述

解题思路:
将数组整体旋转一次,以k为划分左旋转0 ~ k - 1这个区间,
右旋转k ~ numsize - 1区间,整体就是旋转k次的数组,有点类似于左旋转字符串,时间复杂度是O(N + N),还是O(N)
在这里插入图片描述

//逆置数组
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);
   
}

合并数组数组:

点我.
在这里插入图片描述

解题思路:
从后往前处理,因为两个数组都是有序的,从nums1和nums2中先将最大的数挑出来插入到目标数组,这样目标数组的后index就已经可以确定是有序的了,已经是最大了,最后判断哪个数组还剩元素就继续插入在目标数组中
时间复杂度是O(N)

void merge(int* nums1, int nums1Size, int m, int* nums2, int nums2Size, int n){
        int i = m - 1, j = n - 1, index = m + n - 1;
        while(i >= 0 && j >= 0)
         nums1[index--] = nums1[i] > nums2[j] ? nums1[i--] : nums2[j--];
        
       
         while(j >= 0)
         nums1[index--] = nums2[j--];
         
}
评论 23
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

爱生活,爱代码

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值