文章目录
1.线性表
1.1线性表的概念
线性表(linear list)是n个具有相同特性的数据元素的有限序列。 线性表是一种在实际中广泛使
用的数据结构,常见的线性表:顺序表、链表、栈、队列、字符串…。
线性表在逻辑上是线性结构,也就说是连续的一条直线。但是在物理结构上并不一定是连续的,
线性表在物理上存储时,通常以数组和链式结构的形式存储。
这里就引出来两个名词:逻辑结构、物理结构。
逻辑结构:简单的来说,逻辑结构就是数据之间的关系。逻辑结构常见有四种类型:集合结构,线性结构,树形结构,图形结构。
物理结构:又叫存储结构,分为两种,一种是顺序存储结构一种是链式存储结构,指的是数据在物理存储空间上选择集中存放还是分散存放。
2.顺序表
顺序表本质上就是数组,但是,可以动态增长,并且要求顺序表里面存储的数据必须是从左往右连续的。
当使用数组时,可以在数组的任何位置进行存储数据。
但是,使用顺序表时,就必须保持数据的连续存储,这里的连续是指,数据存储的位置必须是连续的,并不是数据的值是连续的。
2.1概念以及结构
顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构,一般情况下采用数组存
储。在数组上完成数据的增删查改。
2.2接口实现(具体操作)
2.2.1静态顺序表定义
//静态顺序表的定义 使用定长数组存储元素
#define N 10 //好修改数组大小
typedef strcut Seqlist
{
int a[N];
int size;//记录存储多少个有效数据 写死了
}SL;
2.2.2动态顺序表定义
//动态顺序表的定义
//实现数据结构通常是管理内存中数据的结构从而实现增删查改的接口
typedef int SLDataType;
typedef struct SeqList
{
SLDataType* a;//指向动态开辟的数组
int size; //记录有效数据的个数
int capacity; //记录存储数据的最大容量
}SL;
2.2.3 静态顺序表和动态顺序表的区别
>静态顺序表和动态顺序表唯一一点不同的是,静态顺序表再创建顺序表的时候容量已经确定为N了,不可以更改,而动态顺序表的容量是由malloc函数动态开辟,当容量不够用时可以增加容量capacity。’
扩容方法:动态开辟一块新的空间(一般为原空间的两倍),将原空间的数据拷贝到新的空间,然后让指针指向新的空间并且释放旧空间。
优缺点比较
静态顺序表创建空间时为静态开辟,不用malloc函数,代码相对简单(一点点),不存在内存泄露问题
动态顺序表,动态开辟空间,使用相对灵活,相比于静态开辟空间也可以少空间浪费
2.2.3顺序表的初始化
void SeqListInit(SL* s)
{
assert(s);//断言判断S是否空 暴力方法
s->a = NULL;
s->capacity = s->size = 0;
}
2.2.4检查容量是否充足
void SLCheckCpacity(SL* s)
{
assert(s);
if (s->size == s->capacity)//顺序表的容量和有效数据相等的时候就应该需要开辟新空间了
{
//realloc函数动态增容时 通常 是直接增容至原来空间的2倍或者1.5倍,扩容是有代价的,不建议频繁进行扩容、
int newCapacity = s->capacity == 0 ? 4 : s->capacity * 2;//开辟新空间
SLDataType* tmp = NULL;
tmp = (SLDataType*)realloc(s->a, newCapacity * sizeof(SLDataType));
if (tmp == NULL)
{
perror("realloc fail");
exit(-1);//异常终止!!
}
s->a = tmp;
s->capacity = newCapacity;
}
}
2.2.5头插法和头删法
void SLPushFront(SL* s, SLDataType x)//头插法
{
assert(s);
SLCheckCpacity(s); //判断是否要扩容
//从后往前依次后移
for (int i = s->size; i > 0; i--)
{
s->a[i] = s->a[i-1];
}
s->a[0] = x;
s->size++;
//等效!复用
//SLInsert(s, 0, x);
}
//头删法
void SLPopFront(SL* s)
{
assert(s);
assert(s->size > 0);//暴力方法
if (s->size)
{
return;
}
//从前往后依次前移
for (int i = 0; i < s->size; i++)
{
s->a[i] = s->a[i + 1];
}
s->size--;
//等效复用
//SLErase(s, 0);
}
2.2.6尾插法和尾删法
//尾插相对简单
void SLPushBack(SL* s, SLDataType x)//插入数据
{
//assert(s);
//扩容!!
//SLCheckCpacity(s);
//s->a[s->size] = x;
//s->size++;
//都可以!!
SLInsert(s, s->size, x);
}
//尾删法
void SLPopBack(SL* s)
{
//温柔的检查
//if (s->size == 0)//当有效数据的大小为0的时候,再删除就退出
//{
// return;
//}
//暴力的检查
/*assert(s->size > 0);
s->size-=1;*/
SLErase(s, s->size-1);
}
2.2.7顺序表的查询
//查找
//若查找 到 则返回其下标,若找不到,则返回整型int数字 -1 ;
//如果存在多个x,则返回的是第一次出现x的位置的下标
int SLFind(SL* s, SLDataType x,int begin)
{
assert(s);
for (int begin = 0; begin < s->size; begin++)
{
if (s->a[begin] == x)
{
return (begin + 1);
}
}
return -1;
}
2.2.8在pos位插入x和删除pos位的值
//在pos位置插入
void SLInsert(SL* s, size_t pos, SLDataType x)
{
assert(s);
//当心pos的位置小心他越界
assert(pos >= 0 && pos <= s->size);
SLCheckCpacity(s);
if (s->size == 0)
{
s->a[0] = x;
s->size++;
}
else
{
int end = s->size;
while (end >= pos)
{
//将pos位置之前的数据向后边移动
s->a[end + 1] = s->a[end];
end--;
}
s->a[pos] = x;
s->size++;
}
}
//删除pos位置上的数据
void SLErase(SL* s, size_t pos)
{
assert(s);
assert(pos >= 0 && pos < s->size);
//assert(s->size > 0);//前两个已经判断了,因此不用加
int begin = pos+1;
while (begin < s->size)
{
//从pos位置开始到最后一个有效数据依次向前移动
s->a[begin-1] = s->a[begin];
begin++;
}
s->size--;
}
2.2.9打印顺序表
void SLPrint(SL* s)
{
assert(s);
for (int i = 0; i < s->size; i++)
{
printf("%d ", s->a[i]);
}
printf("\n");
}
2.2.10 销毁顺序表
void SLDestroy(SL* s)
{
assert(s);
if (s->a)
{
free(s->a);//数组越界也会报错
s->a = NULL;
s->capacity = s->size = 0;
}
}
3.顺序表的OJ题
3.1移除元素
题目: 给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。 不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并 原地 修改输入数组。 元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素
双指针方法:
int removeElement(int* nums, int numsSize, int val)
{
//双指针优化!
int left=0,right=numsSize;
while(left<right)
{
if(nums[left]==val)
{
nums[left]=nums[right-1];
right--;
}
else
left++;
}
return left;
}
3.2删除有序数组中的重复项
给你一个 升序排列 的数组 nums ,请你 原地 删除重复出现的元素,使每个元素 只出现一次 ,返回删除后数组的新长度。元素的 相对顺序 应该保持 一致 。然后返回 nums 中唯一元素的个数。
考虑 nums 的唯一元素的数量为 k ,你需要做以下事情确保你的题解可以被通过:
更改数组 nums ,使 nums 的前 k 个元素包含唯一元素,并按照它们最初在 nums 中出现的顺序排列。nums 的其余元素与 nums 的大小不重要。
返回 k 。
int removeDuplicates(int* nums, int numsSize)
{
//双指针
int left=0;
for(int right=1;right<numsSize;right++)
{
if(nums[left]!=nums[right])
{
nums[left+1]=nums[right];
left++;
}
}
return left+1;
}
合并两个有序数组
给你两个按 非递减顺序 排列的整数数组 nums1 和 nums2,另有两个整数 m 和 n ,分别表示 nums1 和 nums2
中的元素数目。 请你 合并 nums2 到 nums1 中,使合并后的数组同样按 非递减顺序 排列。
注意:最终,合并后数组不应由函数返回,而是存储在数组 nums1 中。为了应对这种情况,nums1 的初始长度为 m + n,其中前 m
个元素表示应合并的元素,后 n 个元素为 0 ,应忽略。nums2 的长度为 n
void merge(int* nums1, int nums1Size, int m, int* nums2, int nums2Size, int n)
{
int i1=m-1,i2=n-1,j=m+n-1;
while(i1 >= 0&&i2 >= 0)
{
if(nums1[i1]>nums2[i2])
{
nums1[j--]=nums1[i1--];
}
else
nums1[j--]=nums2[i2--];
}
while(i2>=0)
{
nums1[j--]=nums2[i2--];
}
}