顺序表
文章目录
一、线性表
线性表(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个元素开始算起的,于是说,很困惑的问题就解决啦。