【C语言实现数据结构】顺序表

顺序表

一、线性表

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

什么是线性表


二、顺序表

1、什么是顺序表?

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

顺序表在内存中是连续存储的

2、顺序表的分类
1、静态顺序表
使用定长数组进行存储元素
在这里插入图片描述
2、动态顺序表

动态申请空间,进行存储元素
在这里插入图片描述
静态顺序表相比于动态顺序表,有很大的缺陷
如果是静态顺序表,在数据量很多的情况下,我们申请的空间可能会不够用,当数据量很小的情况下,又会造成很大的空间浪费问题
但如果是动态顺序表,我们能够根据需要自由申请空间,相比于静态顺序表,动态顺序表在空间利用上的优势就很明显

三、顺序表的实现

顺序表结构的定义

静态顺序表

//静态顺序表
#define N 10
typedef int SLTDataType;
typedef struct SeqList
{
	SLTDataType a[N];
}SeqList;

动态顺序表

//动态顺序表
typedef int SLTDataType;
typedef struct SeqList
{
	SLTDataType* a;//指向顺序表的指针
	int size;//顺序表中有效数据的个数
	int capacity;//顺序表的容量大小
}SeqList;

int类型typedef定义为SLTDataType 在后面要存储其他类型的数据的时候,直接修改就可以,比较方便,要指定有效数据的个数和顺序表容量的大小

1、顺序表的初始化

在初始化顺序表的时候,把capacity和size都赋值到相应的初值

//顺序表的初始化
void SeqListInit(SeqList* ps)
{
	assert(ps);
	ps->a = NULL;
	ps->size = ps->capacity = 0;
}

2、顺序表的销毁

销毁顺序表的时候,要把申请的空间给释放掉,并把指针置为空,避免野指针,把size和capacity置为0

//顺序表的销毁
void SeqListDestroy(SeqList* ps)
{
	assert(ps);
	free(ps->a);
	ps->a = NULL;
	ps->size = ps->capacity = 0;
}

3、顺序表的打印

根据顺序表中size,也就是有效数据的个数,来限制打印的数据个数

//顺序表的打印
void SeqListPrint(SeqList* ps)
{
	assert(ps);
	int i = 0;
	for (i = 0; i < ps->size; i++)
	{
		printf("%d ", ps->a[i]);
	}
	printf("\n");
}

4、检查容量

在进行插入的时候,如果size和capacity的值是相等的,那么我们就需要扩容,在扩容的时候,要避免直接用a指针来接收realloc的返回值,因为如果开辟失败,realloc就会返回一个空指针,那样如果我们用a指针直接进行接收的话,就会造成以前申请的空间也找不到了

//检查容量
void SeqListCheckCapacity(SeqList* ps)
{
	if (ps->capacity == ps->size)
	{
		int newcapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
		SLTDataType* ptr = (SLTDataType*)realloc(ps->a, sizeof(SLTDataType) * newcapacity);
		if (ptr == NULL)
		{
			perror("realloc fail");
			exit(-1);
		}
		ps->a = ptr;
		ps->capacity = newcapacity;
	}

}

5、顺序表的尾插

顺序表在尾插过程中,要进行容量检查,如果空间不够,就需要扩容,顺序表的尾插相对比较简单,就是直接在数组的最后面插入就可以了

//顺序表的尾插
void SeqLisPushBack(SeqList* ps, SLTDataType x)
{
	assert(ps);
	SeqListCheckCapacity(ps);
	ps->a[ps->size] = x;
	ps->size++;

}

6、顺序表的头插

顺序表头插过程需要挪动数据,就是把所有的数据往后面挪动一个位置,然后再下标为0的位置进行插入就可以,插入以后,记得把有效数据的个数size+1

//顺序表的头插
void SeqListPushFront(SeqList* ps, SLTDataType x)
{
	assert(ps);
	SeqListCheckCapacity(ps);
	int end = ps->size;
	while (end>=0)
	{
		ps->a[end + 1] = ps->a[end];
		end--;
	}
	ps->a[0] = x;
	ps->size++;
}

7、顺序表的尾删和头删

尾删直接就size–就可以完成了,如果是头删的话,就把所有的数据往前挪动一位,然后再把size–就可以完成了,需要注意的事,如果数组中有效数据的个数小于0,是不能够进行删除的,此时我们会添加一个断言,就是来断言数组中有效数据的个数大于0,否则会直接报错

//顺序表的头删
void SeqListPopFront(SeqList* ps)
{
	assert(ps);
	assert(ps->size > 0);
	int start = 0;
	while (start < ps->size)
	{
		ps->a[start] = ps->a[start + 1];
		start++;
	}
	ps->size
}

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

8、查找数据

在整个顺序表中查找等于x的值,当找到的时候,直接返回x的位置

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

9、在pos位置插入数据

删除pos位置上的数据主要的要求是要控制好边界,并且,pos位置必须要在顺序表中,那么就要用到断言,没有在顺序表中就会报错

//插入
void SeqListinsert(SeqList* ps, size_t pos, SLTDataType x)
{
	assert(ps);
	assert(pos <= (size_t)ps->size);
	SeqListCheckCapacity(ps);
	size_t end = (size_t)ps->size;
	while (end > pos)
	{
		ps->a[end] = ps->a[end - 1];
		end--;
	}
	ps->a[pos] = x;
	ps->size++;
}

10、删除pos位置的值

删除的时候,pos位置的值必须要小于size,因为在顺序表中,是跟数组的存储逻辑是一样的,顺序表的最后一个元素的存储的地方其实是size-1

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

11、修改pos位置的值

//修改
void SeqListModify(SeqList* ps, size_t pos, SLTDataType x)
{
	assert(ps);
	ps->a[pos] = x;
}

四、完整代码实现

SeqList.h

#pragma once
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
typedef int SLTDataType;
typedef struct SeqList
{
	SLTDataType* a;
	int size;//有效数据的个数
	int capacity;//容量大小
}SeqList;
//初始化
void SeqListInit(SeqList* ps);
//销毁
void SeqListDestroy(SeqList* ps);
//打印
void SeqListPrint(SeqList* ps);
//容量检查
void SeqListCheckCapacity(SeqList* ps);
//尾插
void SeqListPushBack(SeqList* ps, SLTDataType x);
//头插
void SeqListPushFront(SeqList* ps, SLTDataType x);
//尾删
void SeqListPopBack(SeqList* ps);
//头删
void SeqListPopFront(SeqList* ps);
//查找
int SeqListFind(SeqList* ps, SLTDataType x);
//插入
void SeqListinsert(SeqList* ps, size_t pos, SLTDataType x);
//删除
void SeqListErase(SeqList* ps, size_t pos);
//修改
void SeqListModify(SeqList* ps, size_t pos, SLTDataType x);

SeqList.c

#include"SeqList.h"
//初始化
void SeqListInit(SeqList* ps)
{
	assert(ps);
	//a的地址置为空 容量和有效数据的大小置为0
	ps->a = NULL;
	ps->capacity = ps->size = 0;
}
//销毁
void SeqListDestroy(SeqList* ps)
{
	assert(ps);
	free(ps->a);
	ps->a = NULL;
	ps->size = ps->capacity = 0;

}
//打印
void SeqListPrint(SeqList* ps)
{
	assert(ps);
	int i = 0;
	for (i = 0; i < ps->size; i++)
	{
		printf("%d ", ps->a[i]);
	}
	printf("\n");
}
//容量检查
void SeqListCheckCapacity(SeqList* ps)
{
	assert(ps);
	int newCapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
	SLTDataType* tmp = (SLTDataType*)realloc(ps->a, newCapacity * sizeof(SLTDataType));
	if (tmp == NULL)
	{
		perror("realloc fail");
		exit(-1);
	}
	ps->a = tmp;
	ps->capacity = newCapacity;
}
//尾插
void SeqListPushBack(SeqList* ps, SLTDataType x)
{
	assert(ps);
	SeqListCheckCapacity(ps);
	int end = ps->size;
	ps->a[ps->size] = x;
	ps->size++;
}
//头插
void SeqListPushFront(SeqList* ps, SLTDataType x)
{
	assert(ps);
	SeqListCheckCapacity(ps);
	int end = ps->size;
	while (end > 0)
	{
		ps->a[end] = ps->a[end - 1];
		end--;
	}
	ps->a[0] = x;
	ps->size++;
}
//尾删
void SeqListPopBack(SeqList* ps)
{
	assert(ps);
	ps->size--;
}
//头删
void SeqListPopFront(SeqList* ps)
{
	assert(ps);
	int begin = 0;
	while (begin < ps->size - 1)
	{
		ps->a[begin] = ps->a[begin + 1];
		begin++;
	}
	ps->size--;
}
//查找
int SeqListFind(SeqList* ps, SLTDataType x)
{
	assert(ps);
	int i = 0;
	while (i < ps->size)
	{
		if (ps->a[i] == x)
		{
			return i;
		}
		i++;
	}
	return -1;
}
//插入
void SeqListinsert(SeqList* ps, size_t pos, SLTDataType x)
{
	assert(ps);
	assert(pos <= (size_t)ps->size);
	SeqListCheckCapacity(ps);
	size_t end = (size_t)ps->size;
	while (end > pos)
	{
		ps->a[end] = ps->a[end - 1];
		end--;
	}
	ps->a[pos] = x;
	ps->size++;
}
//删除
void SeqListErase(SeqList* ps, size_t pos)
{
	assert(ps);
	assert(pos < (size_t)ps->size);
	size_t begin = pos;
	while (begin < (size_t)ps->size)
	{
		ps->a[begin] = ps->a[begin + 1];
		begin++;
	}
	ps->size--;
}
//修改
void SeqListModify(SeqList* ps, size_t pos, SLTDataType x)
{
	assert(ps);
	ps->a[pos] = x;
}

test.c

#include"SeqList.h"
void SeqListTest()
{
	SeqList s;
	SeqListInit(&s);
	SeqListPushFront(&s, 100);
	SeqListPushFront(&s, 200);
	SeqListPushFront(&s, 300);
	SeqListPushFront(&s, 400);
	SeqListPushFront(&s, 500);
	SeqListPrint(&s);
	int ret = SeqListFind(&s, 100);
	printf("%d ", ret);
	SeqListModify(&s, ret, ret * 100);
	SeqListPrint(&s);

	SeqListDestroy(&s);
}
int main()
{


	SeqListTest();

	return 0;
}

五、顺序表相关题目

一、移除元素

移除元素
在这里插入图片描述
在这里插入图片描述

思路一:遍历删除,实现方法是,一直往后遍历,遇到等于val的值,就把它覆盖,直到把所有的val删除


思路二:开辟一个数组空间,遇到不是val的值,就把它拷贝到新开辟的数组中,然后再把这个数组空间中的内容拷贝回去,输出数组中内的内容

思路三:定义两个指针src和dst,ruguo val遇到等于val的值,只是src往前面走,dst就不动,最后返回的是dst的长度

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

二、删除数组中的重复项

删除数组中的重复项
在这里插入图片描述

思路:双指针src和dst,遇到src和dst相等的时候,就移动src,dst不动,直到遍历到最后一个数,这道题注意画图
在这里插入图片描述

int removeDuplicates(int* nums, int numsSize){

    int src = 0;
    int dst  =0;
    nums[dst] = nums[src];
    while(src<numsSize)
    {
        if(nums[src]==nums[dst])
        {
            src++;
        }
        else
        {
            nums[++dst] = nums[src++];
        }

    }
    return dst+1;

}

三、合并两个有序数组

合并两个有序数组

在这里插入图片描述

思路一:开辟一个新数组,把两个数组中的元素放入新数组中,并对数组中的数据进行重新排序

思路一:代码实现
在实现开辟数组空间的时候,我们可以用到calloc函数,calloc=malloc+memset

函数的功能是为num个大小为size的空间开辟一个空间,并且把空间的每个字节初始化为0
与函数malloc的区别只在于calloc会在返回地址之前把申请的空间的每个字节初始化为全0

void merge(int* nums1, int nums1Size, int m, int* nums2, int nums2Size, int n)
{

    int arr1 = 0;
    int arr2 = 0;
    int index = 0; //定义的index是标识的位置
    // 这里用到calloc函数,是既申请空间,又能把空间给初始化
    int* tmp = (int*)calloc(m+n,sizeof(int));

    while(arr1<m && arr2<n)//只有当满足这个条件的时候,才会继续
    {
        if(nums1[arr1] > nums2[arr2])//nums1的值大于nums2的值的时候
                                    //就把nums2数组的内容放到新数组中
        {
            tmp[index++] = nums2[arr2++];
        }
        else
        {
            tmp[index++] = nums1[arr1++];
        }
    }

        if(arr1 < m)  //如果nums1中的元素有剩余
    {
        while(arr1 < m)
        {
            tmp[index++] = nums1[arr1++];
        }
    }

    if(arr2 < n)  //如果nums2中的元素有剩余
    {
        while(arr2 < n)
        {
            tmp[index++] = nums2[arr2++];
        }
    }
memcpy(nums1,tmp, (m+n)*sizeof(int));//将nums1数组中的数据覆盖
    free(tmp);
    tmp = NULL;

    }


思路二:nums1和nums2同时存在,因为要把nums2中的元素拷贝到nums1中,所以nums1中的空间是足够大的,逐一比较两个数组中的元素,把两个数组中稍大的元素放到nums1中的靠后位置
因为nums1中的数组是有序的,所以在nums1中有还有元素剩余的时候,是不需要管的

思路二:代码实现

void merge(int* nums1, int nums1Size, int m, int* nums2, int nums2Size, int n){
    int end1 = m-1;//这里的end1指向的是nums1的最后一个有效元素
    int end2 = n-1;//这里的end2指向的是nums2的最后一个有效元素
    int end = m+n-1;//这里的end指向的是nums数组的最后一个位置
    while(end1>=0 && end2>=0)
    //只有在end1和end2都有效的时候循环才会继续,否则就会跳出循环
    {
        if(nums1[end1]<nums2[end2])
        {
            nums1[end--] = nums2[end2--];
        }
        else
        {
            nums1[end--] = nums1[end1--];
        }
       
    }
     //因为nums1中的数据是一直有序的,所以不需要判断nums1中的熟悉怒
        //如果nums2中的数组元素还有剩余,我们就可以直接把数组中的内容拷贝放入到nums1中去
        if(end2>=0)
        {
            while(end2>=0)
            {
                nums1[end--] = nums2[end2--];
            }
        }

}

让我比较困惑的是,在进行赋值的时候,为什么数组中的元素没有变化,原来是oj题目中并没有把[1,2,3,0,0,0]中的0给算到数组中的元素,所以end1是从第3个元素开始算起的,于是说,很困惑的问题就解决啦。
在这里插入图片描述

  • 4
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值