数据结构 顺序表

线性表

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

顺序表概念

顺序表是在计算机内存中以数组的形式保存的线性表,线性表的顺序存储是指用一组地址连续的存储单元一次存储线性表中的各个元素、使得线性表中的逻辑结构上相邻的数据元素存储在相邻的物理存储单元中,即通过数据元素物理存储的相邻关系来反映数据元素之间逻辑上相邻的关系,采用顺序表是将表中的结点依次存放在计算机内存中一组地址连续的存储单元
在这里插入图片描述

顺序表和链表优点和缺点

顺序表
优点:可以根据便宜实现快速的随机读。
缺点:扩容,增删元素的时间复杂度高。
链表
优点:扩容方便,增删效率高。
缺点:不支持随机访问。

顺序表的操作

顺序表分为静态表和动态表

#define N 100

typedef int SLDataType;

//静态顺序表
struct seqList1
{
	SLDataType data[N];
	int size;
};

//动态顺序表   (常用)
typedef struct seqList
{
	SLDataType* data;
	int size;
	int capacity;
}seqList;

因为动态顺序表是我们比较常用的,所以这里我主要讲的是对动态顺序表的一些操作(增删查改)。
操作接口

//初始化顺序表
void initseqList(seqList* sl);

//尾插:给顺序表最后一个有效数据的末尾插入新的数据
void seqListpushBack(seqList* sl, SLDataType val);

//尾删:删除最后一个数据
void seqListpopBack(seqList* sl);

//查找当前下标的元素
SLDataType seqListAt(seqList* sl, int pop);

//判断表是否为空
int seqListEmpty(seqList* sl);

//打印表
void seqListPrint(seqList* sl);

//查看表的有效个数
int seqListSize(seqList* sl);

//检查容量
void seqListCheckCapacity(seqList* sl);

//头插
void seqListPushFront(seqList* sl, SLDataType val);

//头删
void seqListPopFront(seqList* sl);

//任意插入
void seqListInsert(seqList* sl, int pos, SLDataType val);

//任意删除
void seqListErase(seqList* sl, int pos);

//查找返回索引
int seqListFind(seqList* sl, SLDataType val);

//删除表中的一个节点
void listErase(struct list* lst, struct listNode* node)//销毁
void seqListDestroy(seqList* sl); 

实现接口操作

#include"seqList.h"

//初始化顺序表
void initseqList(seqList* sl)
{
	if (sl == NULL)
		return;
	sl->data = NULL;
	sl->size = 0;
	sl->capacity = 0;
}

void seqListpopBack(seqList* sl)
{
	/*if (sl == NULL)
		return;
	sl->size--;*/
	seqListErase(sl, sl->size-1);
}

int seqListEmpty(seqList* sl)
{
	if (sl == NULL || sl->size == 0)
		return 0;
	return 1;
}

void seqListPrint(seqList* sl)
{
	if (sl == NULL)
		return ;
	for (int i = 0; i < sl->size; i++)
	{
		printf("%d ", sl->data[i]);
	}
	printf("\n");
}

int seqListSize(seqList* sl)
{
	if (sl == NULL || sl->size == 0)
		return 0;
	else
		return sl->size;
}

void seqListpushBack(seqList* sl, SLDataType val)
{
	//if (sl == NULL)
	//	return;
	检查容量
	//seqListCheckCapacity(sl);

	//sl ->data[sl->size] = val;
	//sl->size++;
	seqListInsert(sl, sl->size, val);
}

SLDataType seqListAt(seqList* sl, int pop)
{
	return sl->data[pop];
}

void seqListCheckCapacity(seqList* sl)
{
	if (sl->size == sl->capacity)
	{
		//空间已满
		//开新的空间
		int newCapacity = sl->capacity == 0 ? 1 : 2 * sl->capacity;
		
		sl->data = (SLDataType*)realloc(sl->data, newCapacity * sizeof(SLDataType));
		if (sl->data)
			return;

		//更新容量
		sl->capacity = newCapacity;

	}
}

void seqListPushFront(seqList* sl, SLDataType val)
{
	/*if (sl == NULL)
		return;
	seqListCheckCapacity(sl);

	int end = sl->size;
	while (end>0)
	{
		sl->data[end] = sl->data[end - 1];
		--end;
	}
	sl->data[0] = val;
	sl->size++;*/
	seqListInsert(sl, 0, val);
}

void seqListPopFront(seqList* sl)
{
	/*if (sl == NULL || sl->size == 0)
		return;
	int start = 0;
	while (start<(sl->size))
	{
		sl->data[start] = sl->data[start+1];
		++start;
	}
	sl->size--;*/
	seqListErase(sl, 0);
}

void seqListInsert(seqList* sl, int pos, SLDataType val)
{
	if (sl == NULL)
		return;
	if (pos <0|| pos>(sl->size))
		return;
	seqListCheckCapacity(sl);
	int end = sl->size;
	while (end > pos)
	{
		sl->data[end] = sl->data[end - 1];
		--end;
	}
	sl->data[pos] = val;
	sl->size++;
}

void seqListErase(seqList* sl, int pos)
{
	if (sl == NULL || sl->size == 0)
		return;
	if (pos <0 || pos>=(sl->size))
		return;
	seqListCheckCapacity(sl);
	int start = pos;
	while (start < (sl->size)-1)
	{
		sl->data[start] = sl->data[start + 1];
		++start;
	}
	sl->size--;
}

int seqListFind(seqList* sl, SLDataType val)
{
	if (sl == NULL || sl->size == 0)
		return -1;
	for (int i = 0; i < sl->size; i++)
	{
		if (sl->data[i] == val)
			return i;
	}
	return -1;
}

void seqListDestroy(seqList* sl)
{
	if (sl != NULL && sl->data != NULL)
	{
		free(sl->data);
		sl->data=NULL;
	}
}

由上面的操作来看,我们可以看出顺序表的种种缺点
1、中间/头部的插入删除时间复杂度都为O(n)
2、增容需要申请新的空间,拷贝数据,释放旧的空间,都会有不小的消耗
3、增容一般是以2倍的增长,会造成空间的浪费

顺序表的应用举例

我们已经学会顺序表的一些基本操作,但是这对我们来说是太简单了,在笔试或者面试中一般都不会这么直接得考察你,而是在这些操作的基础上去完成一些更有价值的操作。例如:1、删除数组中所有值为val的节点,并且有时间O(n)空间O(1)的要求。2、删除排序数组的重复项。3、合并两个有序数组。4、旋转数组。5、数组形式做加法等等


1、删除数组中所有值为val的节点,并且有时间O(n)空间O(1)的要求
question1:给你一个数组 nums 和一个值 val,你需要原 移除所有数值等于 val 的元素,并返回移除后数组的新长度。不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并原地修改输入数组。元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。

这道题相对来说还是比较简单的。在顺序表中存在有效个数,所以我们不管有效个数后面的元素是怎么排序的

int removeElement(int* nums, int numsSize, int val)
{
    int idx = 0;
    for(int i=0;i<numsSize;i++)
    {
        if(nums[i]!=val)
        {
            nums[idx++]=nums[i]; 
        }
        
    }
    return idx; //有效个数
}

2、删除排序数组的重复项
question2:给定一个排序数组,你需要在原地删除重复出现的元素,使得每个元素只出现一次,返回移除后数组的新长度。不要使用额外的数组空间,你必须在原地修改输入数组并在使用 O(1) 额外空间的条件下完成。
在这里插入图片描述

int removeDuplicates(int* nums, int numsSize){
   int i = 0;  //i数组的下标
   if(nums == NULL || numsSize == 0) //如果是个空数组,则就没必要继续往下执行
    return 0;
   for(int j = 1; j < numsSize; j++)
   {
       if(nums[j] != nums[i]) //只要循环的数据不等于最后一个有效数字,都将它们存进数组里面
       {
           i++; //下标加一
           nums[i] = nums[j]; //最后一个有效数组等于新来的数字
       }
   }
   return i + 1;
}

3、合并两个有序数组
question3:给你两个有序整数数组 nums1 和 nums2,请你将 nums2 合并到 nums1 中,使 nums1 成为一个有序数组。说明:初始化 nums1 和 nums2 的元素数量分别为 m 和 n 。你可以假设 nums1 有足够的空间(空间大小大于或等于 m + n)来保存 nums2 中的元素。输入:nums1 = [1,2,3,0,0,0], m = 3 nums2 = [2,5,6], n = 3 输出:[1,2,2,3,5,6]
在这里插入图片描述

void merge(int* nums1, int nums1Size, int m, int* nums2, int nums2Size, int n){
    
    int i=m-1; //i为第一个数组最后一个元素的下标
    int j=n-1; //j为第二个数组最后一个元素的下标
    int idx=m+n-1;  //两数组合并后最后一个元素的下标
    while(i>=0 && j>=0)
    {
        if(nums1[i]>=nums2[j]) //如果第一个数组的元素比第二个数组的元素大
        {
            nums1[idx--]=nums1[i--]; //那就将该元素排在合并后的数组的最后一个位置
        }
        else{ 
            nums1[idx--]=nums2[j--];
        }  
    }
    //如果nums2中有剩余元素,拷贝
        if(j>=0)
        {
        	//将第二个数组中的剩余元素放到合并数组元素的头端,因为比较后剩下的元素肯定都比比过的元素小
            memcpy(nums1,nums2,sizeof(int)*(j+1));
        }
}

4、旋转数组
question4:给定一个数组,将数组中的元素向右移动 k 个位置,其中 k 是非负数。输入: [1,2,3,4,5,6,7] 和 k = 3 输出: [5,6,7,1,2,3,4]
在这里插入图片描述

void reverse(int *nums,int start,int end) //交换函数
{
    while(start<end)
    {
        nums[start]=nums[start]^nums[end];
        nums[end]=nums[start]^nums[end];
         nums[start]=nums[start]^nums[end];
         ++start;
         --end;
    }
}

void rotate(int* nums, int numsSize, int k){
    k%=numsSize; //当k>数组长度时,相当于反转k对数组长度取余的个数
    reverse(nums,0,numsSize-k-1);
    reverse(nums,numsSize-k,numsSize-1);
    reverse(nums,0,numsSize-1);
}

5、数组形式做加法
question5:对于非负整数 X 而言,X 的数组形式是每位数字按从左到右的顺序形成的数组。例如,如果 X = 1231,那么其数组形式为 [1,2,3,1]。给定非负整数 X 的数组形式 A,返回整数 X+K 的数组形式。输入:A = [1,2,0,0], K = 34输出:[1,2,3,4]解释:1200 + 34 = 1234
在这里插入图片描述

int* addToArrayForm(int* A, int ASize, int k, int* returnSize){

    int len =0; //k的长度
    int temp = k;
    while(temp)
    {
        len++;
        temp/=10;
    }
    int arrlen = ASize>len ? ASize+1:len+1; //长度为最大数的长度+1
    int *arr = (int*)malloc(sizeof(int)*(arrlen+1));
  
    int end = ASize-1; //数组从最后一个元素开始相加
    int step = 0; //进位值
    int sum = 0;  //相加结果
    int idx = 0; //相加后存放的数组下标
    while(end>=0 || k>0) //只要有一个数还没算完,都要进行相加 
    {
        sum = step;  //先让相加的结果为进位值
        if(end>=0)
            sum += A[end];
        if(k>0)
            sum += k%10;
        if(sum>9) //相加结果是两位数,就进位 10~18
        {
            step=1;
            sum-=10;
        }
        else
        {
            step =0;
        }
        arr[idx++] = sum; //该位置为个位或者十位或者百位或者....相加的结果
        end--; //数组向前一位
        k/=10; //数字向更高的一位走
    }

    if(step == 1) //如果最后相加完后有进位,就让最后一个元素为1
        arr[idx++]=1;

    int start = 0;
    int end1 = idx-1;
    //反转数组
    while(end1>start)
    {
        arr[start] = arr[start]^arr[end1];
        arr[end1] = arr[start]^arr[end1];
        arr[start] = arr[start]^arr[end1];
        end1--;
        start++;
    }
    *returnSize = idx;
    return arr;
}

以上顺序表的基本操作还有关于数组的题都会做了,在笔试面试中遇到的问题都可以迎刃而解了

  • 7
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论
使用c++实现的顺序表:多文件编程,层清晰,函数有注释 SeqList();//构造函数,存储的元素个数设为0 bool setLength(size_t length);//设置已经存储的元素个数 bool addElement(ElemType element);//把某个元素添加到顺序表末尾 bool addElement(ElemType element , size_t n);//插入一个元素,使其成为第n个元素,其余元素后移 bool delElement();//删除所有的元素 bool delElement(size_t n);//删除第n个元素 bool delElement(string elementDetailType,string elementDetail);//通过某个元素细节找到元素,把这个元素删除 bool replaceElement(ElemType element , size_t n);//使用一个元素,替换掉第n个元素 bool swapElement(size_t n1 , size_t n2);//把第n1个元素和第n2个元素交换 ElemType* getElement();//得到数组头的指针 ElemType* getElement(size_t n);//得到第n个元素的指针 size_t getLength();//得到存储的元素个数 size_t getMaxSize();//得到顺序表容量 bool showElementDetail();//输出所有的元素细节 bool showElementDetail(size_t n);//输出第n个元素的细节 bool showElementDetail(string elementDetailType,string elementDetail);//通过某个元素细节找到元素,输出元素所有细节 size_t findElement(string elementDetailType,string elementDetail);//通过某个元素细节找到元素位置 static int inputAInt(int min = 0,int max = 9,int defaultValue = -1);//从键盘读取,限制为一个min到max间的整数,非法情况返回defaultValue void startControlLoop();//打开控制界面 ~SeqList();//析构函数

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

WhiteShirtI

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

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

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

打赏作者

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

抵扣说明:

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

余额充值