线性表
线性表(linear list)是n个具有相同特性的数据元素的有限序列。
线性表是一种在实际中广泛使 用的数据结构,常见的线性表:顺序表、链表、栈、队列、字符串... 线性表在逻辑上是线性结构,也就说是连续的一条直线。但是在物理结构上并不一定是连续的, 线性表在物理上存储时,通常以数组和链式结构的形式存储。
顺序表
概念及结构
顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构,一般情况下采用数组存储。在数组上完成数据的增删查改。 (顺序表就是数组,但是只能连续储存数据,不能跳跃存储)数组是一片连续的物理空间。
顺序表一般可以分为:
1. 静态顺序表:使用定长数组存储元素。
//静态顺序表
#define N 100
typedef int SLDatatype;
struct SeqList
{
SLDatatype a[N];
int size;
};//缺点:给小了不够用,给多了浪费
2.动态顺序表
//动态顺序表
typedef int SLDatatype;
typedef struct SeqList
{
SLDatatype* a;
int size;//存储的有效数据的个数
int capacity;//容量
}SL;
顺序表函数代码
SeqList.h
#pragram once
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
//给别人用的函数声明也可以叫接口
//动态顺序表
//可以通过realloc去扩容
typedef int SLDateType;//typedef重命名类型,可以使得程序类型更易改,更方便
typedef struct Seqlist
{
SLDateType* a;//指向动态开辟的数组
int size;//存储数据的个数
int capacity;//存储空间的大小
}SL;
//初始化顺序表
void SeqListInit(SL* ps);
//对于顺序表扩容
void SeqListAdd(SL* ps);
//销毁
void SeqListDestroy(SL* ps);
// 对数据的管理:增删查改
void SeqListPrint(SL* ps);
void SeqListPushBack(SL* ps, SLDateType x);//pushback推入尾部
void SeqListPushFront(SL* ps, SLDateType x);
void SeqListPopFront(SL* ps);//头删尾删 pop推出
void SeqListPopBack(SL* ps);
// 顺序表查找
int SeqListFind(SL* ps, SLDateType x);
// 顺序表在pos位置插入x
void SeqListInsert(SL* ps, size_t pos, SLDateType x);
// 顺序表删除pos位置的值
void SeqListErase(SL* ps, size_t pos);
SeqList.c
#define _CRT_SECURE_NO_WARNINGS
#include"SeqList.h"
void SLInit(SL* psl)
{
assert(psl);
psl->a = (SLDatatype*)malloc(sizeof(SLDatatype)*4);
if (psl->a == NULL)
{
perror("malloc fail");
return;
}
psl->size = 0;
psl->capacity = 4;
}
void SLPrint(SL* psl)
{
assert(psl);
for (int i = 0; i < psl->size; i++)
{
printf("%d ", psl->a[i]);
}
printf("\n");
}
void SLDestroy(SL* psl)
{
assert(psl);
free(psl->a);
psl->a = NULL;
psl->size = 0;
psl->capacity = 0;
}
void SLCheckCapacity(SL* psl)
{
assert(psl);
if (psl->size == psl->capacity)
{
SLDatatype* tmp = (SLDatatype*)realloc(psl->a, sizeof(SLDatatype) * psl->capacity * 2);
if (tmp == NULL)
{
perror("realloc fail");
return;
}
psl->a = tmp;
psl->capacity *= 2;
}
}
void SLPushBack(SL* psl, SLDatatype x)
{
assert(psl);
SLCheckCapacity(psl);
psl->a[psl->size++] = x;
//SLInsert( psl,size,x) //复用Insert
}
void SLPushFront(SL* psl, SLDatatype x)
{
assert(psl);
SLCheckCapacity(psl);
int end = psl->size - 1;
while (end >= 0)
{
psl->a[end + 1] = psl->a[end];
end--;
}
psl->a[0] = x;
psl->size++;
//SLInsert( psl,0,x) //复用Insert
}
void SLPopBack(SL* psl)
{
//暴力检查
assert(psl->size > 0);
//温柔检查
//if(psl->size == 0)
// return;
psl->size--;
}
void SLPopFront(SL* psl)
{
assert(psl->size > 0);//暴力检查
int start = 0;
while (start < psl->size - 1)
{
psl->a[start] = psl->a[start + 1];
start++;
}
psl->size--;
}
void SLInsert(SL* psl, int pos, SLDatatype x)
{
assert(pos >= 0 && pos <= psl->size);
SLCheckCapacity(psl);
int end = psl->size - 1;
while (end >= pos)
{
psl->a[end + 1] = psl->a[end];
end--;
}
psl->a[pos] = x;
psl->size++;
}
void SLErase(SL* psl, int pos, SLDatatype x)
{
assert(pos >= 0 && pos <= psl->size);
int start = pos + 1;
while (pos < psl->size)
{
psl->a[start - 1] = psl->a[start];
}
psl->size--;
}
int SLFind(SL* psl, SLDatatype x)
{
assert(psl);
for (int i = 0; i < psl->size; i++)
{
if (psl->a[i] == x)
{
return i;
}
}
return -1;
}
void SLModify(SL* psl, int pos, SLDatatype x)
{
assert(0 <= pos && pos < psl->size);
psl->a[pos] = x;
}
顺序表注意事项
malloc、realloc扩容
realloc扩容是有两种方式:
1.原地扩容
2.异地扩容 后面空间不够,找到新空间,移植数据,释放旧空间3.扩容失败,返回空指针。可以设置一个中间变量,接受,防止之前数据丢失。
void SeqListAdd(SL* ps)
{
if (ps->a== NULL)
{
ps->a = (SLDateType*)malloc(5*sizeof(SLDateType));
ps->capacity = 5;
return;
}
else
{
SLDateType*tmp = (SLDateType*)realloc(ps->a, ps->capacity * 2*sizeof(SLDateType));//创建一个中间变量
if(tmp!=NULL)//如果空间申请成功
{
ps->a=tmp;
}
ps->capacity = ps->capacity *2;
return;
}
}
传址调用
注意传参的时候,进行传址调用。传指针。
因为如果传值调用的话,形参是实参的临时拷贝,形参的改变不会影响实参
assert检查
assert();
在一定不能为空的情况下就要断言,断言的好处,可以快速的把错误报出来。
例题
https://leetcode-cn.com/problems/remove-element/
1.暴力求解 时间复杂度为O(N^2)
int removeElement(int* nums, int numsSize, int val){ for(int i = 0; i<numsSize; i++) { if(nums[i] == val) { int j = i; while(j < numsSize-1) { nums[j] = nums[j+1]; j++; } i--; numsSize--; } } return numsSize; }
2.双指针
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; }
https://leetcode-cn.com/problems/merge-sorted-array/
void merge(int* nums1, int nums1Size, int m, int* nums2, int nums2Size, int n){ // end1、end2:分别标记nums1 和 nums2最后一个有效元素位置 // end标记nums1的末尾,因为nums1和nums2中的元素从后往前往nums1中存放 // ,否则会存在数据覆盖 int end1 = m-1; int end2 = n-1; int index = m+n-1; // 从后往前遍历,将num1或者nums2中较大的元素往num1中end位置搬移 // 直到将num1或者num2中有效元素全部搬移完 while(end1 >= 0 && end2 >= 0) { if(nums1[end1] > nums2[end2]) { nums1[index--] = nums1[end1--]; } else { nums1[index--] = nums2[end2--]; } } // num2中的元素可能没有搬移完,将剩余的元素继续往nums1中搬移 while(end2 >= 0) { nums1[index--] = nums2[end2--]; } // num1中剩余元素没有搬移完 ---不用管了,因为num1中剩余的元素本来就在num1中 }
https://leetcode-cn.com/problems/remove-duplicates-from-sorted-array/
int removeDuplicates(int* nums, int numsSize){ int dst = 0; int src = 1; while(src < numsSize) { //dst和src对应数据不相同的时候,src先++再赋值,因为src始终存放不重复的数据 if (nums[dst] != nums[src]) { nums[++dst]=nums[src++]; } //dst和src相同的时候,dst++跳过重复数据即可 else { src++; } } return dst+1; }
顺序表的问题及思考
1. 中间/头部的插入删除,时间复杂度为O(N)
2. 增容需要申请新空间,拷贝数据,释放旧空间。会有不小的消耗。
3. 增容一般是呈2倍的增长,势必会有一定的空间浪费。例如当前容量为100,满了以后增容到200,我们再继续插入了5个数据,后面没有数据插入了,那么就浪费了95个数据空间。