1.线性表
线性表(linear list)是n个具有相同特性的数据元素的有限序列。 线性表是一种在实际中广泛使用的数据结构,常见的线性表:顺序表、链表、栈、队列、字符串… 线性表在逻辑上是线性结构,也就说是连续的一条直线。但是在物理结构上并不一定是连续的,线性表在物理上存储时,通常以数组和链式结构的形式存储。
2.顺序表
2.1概念及结构
顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构,一般情况下采用数组存储。在数组 上完成数据的增删查改。 顺序表一般可以分为: 1. 静态顺序表:使用定长数组存储元素。 2. 动态顺序表:使用动态开辟的数组存储。
2.2接口实现
静态顺序表只适用于确定知道需要存多少数据的场景。静态顺序表的定长数组导致N定大了,空间开多了浪费,开少了不够用。所以现实中基本都是使用动态顺序表,根据需要动态的分配空间大小,所以下面我们实现动态顺序表。 我们采用分文件的方式来实现:
2.2.1 SeqList.h
#pragma once
#include <stdio.h>
#include <string.h>
#include <assert.h>
typedef int SLDataType;
typedef struct SeqList
{
SLDataType *a;
int size;
int capacity;
}SL;
void SeqListInit(SL *ps);
void SeqListPrint(SL *ps);
void SeqListDestroy(SL *ps);
void SeqListCheckCapacity(SL *ps);
void SeqListPushBack(SL* ps, SLDataType x);
void SeqListPopBack(SL* ps);
void SeqListPushFront(SL* ps, SLDataType x);
void SeqListPopFront(SL* ps);
void SeqListInsert(SL* ps, size_t pos, SLDataType x);
void SeqListErase(SL* ps, size_t pos);
int SeqListFind(SL* ps, SLDataType x);
void SeqListModify(SL *ps, size_t pos, SLDataType x);
2.2.2 SeqList.c
2.2.2.1 初始化顺序表
void SeqListInit(SL *ps)
{
ps->a = NULL;
ps->size = 0;
ps->capacity = 0;
}
2.2.2.2 打印顺序表
void SeqListPrint(SL *ps)
{
assert(ps);
for(int i=0;i<ps->size;i++)
{
printf("%d ",ps->a[i]);
}
printf("\n");
}
2.2.2.3 释放顺序表
void SeqListDestroy(SL *ps)
{
free(ps->a);
ps->a=NULL;
ps->size=ps->capacity=0;
}
2.2.2.4 检查容量并扩容
void SeqListCheckCapacity(SL *ps)
{
if(ps->size==ps->capacity)
{
int newcapacity = ps->capacity == 0 ? 4:ps->capacity*2;
SLDataType *tmp =(SLDataType*)realloc(ps->a,newcapacity*sizeof(SLDataType));
if(tmp==NULL)
{
printf("realloc fail\n");
exit(-1);
}
else
{
ps->a=tmp;
ps->capacity =newcapacity;
}
}
}
注:我们知道realloc存在原地扩和异地扩的情况,而且扩容失败会返回空指针,所以用realloc扩容一定要记得检查是否扩容成功。
2.2.2.5 尾插
void SeqListPushBack(SL *ps, SLDataType x)
{
SeqListCheckCapacity(ps);
ps->a[ps->size]=x;
ps->size++;
}
2.2.2.6 尾删
void SeqListPopBack(SL *ps)
{
assert(ps->size > 0);
ps->size--;
}
2.2.2.7 头插
void SeqListPushFront(SL *ps,SLDataType x)
{
SeqListCheckCapacity(ps);
int end = ps->size-1;
while(end>=0)
{
ps->a[end+1]=ps->a[end];
end--;
}
ps->a[0]=x;
ps->size++;
}
2.2.2.8 头删
void SeqListPopFront(SL *ps)
{
assert(ps);
if(ps->size>0)
{
int start = 1;
while(start < ps->size)
{
ps->a[start-1]=ps->a[start];
start++;
}
ps->size--;
}
}
2.2.2.9 在pos位置插入x
void SeqListInsert(SL* ps, size_t pos, SLDataType x)
{
assert(ps);
if(pos>ps->size)
{
printf("pos 越界:%d\n",pos);
return;
}
SeqListCheckCapacity(ps);
int end = ps->size-1;
while(end >= pos)
{
ps->a[end+1]=ps->a[end];
end--;
}
ps->a[pos]=x;
ps->size++;
}
大家看这种写法有没有问题。 请看下面一种情况: 当end走到-1时,与end进行比较时会整型提升为size_t 类型,会变成一个很大的数,此时程序会进入死循环。
正确的写法:
void SeqListInsert(SL* ps, size_t pos, SLDataType x)
{
assert(ps);
if(pos>ps->size)
{
printf("pos 越界:%d\n",pos);
return;
}
SeqListCheckCapacity(ps);
size_t end = ps->size;
while(end > pos)
{
ps->a[end]=ps->a[end-1];
end--;
}
ps->a[pos]=x;
ps->size++;
}
注:
2.2.2.10 删除pos位置的数据
void SeqListErase(SL* ps, size_t pos)
{
assert(pos<ps->size);
int start = pos+1;
while(start < ps->size)
{
ps->a[start-1]=ps->a[start];
start++;
}
ps->size--;
}
下一次尾插数据直接覆盖4.
2.2.2.11 顺序表查找
int SeqListFind(SL* ps, SLDataType x)
{
for(int i=0;i<ps->size;i++)
{
if(ps->a[i]==x)
{
return i;
}
}
return -1;
}
2.2.2.12 顺序表修改
void SeqListModify(SL *ps, size_t pos, SLDataType x)
{
aseert(pos<ps->size);
ps->a[pos]=x;
}
2.2.2.13 利用Erase,Insert接口简化尾插、尾删、头插、头删
void SeqListPushBack(SL* ps, SLDataType x)
{
assert(ps);
SeqListInsert(ps, ps->size, x);
}
void SeqListPopBack(SL* ps)
{
assert(ps);
SeqListErase(ps, ps->size-1);
}
void SeqListPushFront(SL* ps, SLDataType x)
{
assert(ps);
SeqListInsert(ps, 0, x);
}
void SeqListPopFront(SL* ps)
{
assert(ps);
SeqListErase(ps, 0);
}
2.2.2.14 SeqList.c完整代码
#include "SeqList.h"
void SeqListInit(SL* ps)
{
ps->a = NULL;
ps->size = 0;
ps->capacity = 0;
}
void SeqListPrint(SL* ps)
{
assert(ps);
for (int i = 0; i < ps->size; i++)
{
printf("%d ", ps->a[i]);
}
printf("\n");
}
void SeqListDestroy(SL* ps)
{
free(ps->a);
ps ->a = NULL;
ps->size = ps -> capacity = 0;
}
void SeqListCheckCapacity(SL* ps)
{
if (ps->size == ps->capacity)
{
int newcapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
SLDataType* tmp = (SLDataType*)realloc(ps->a, newcapacity * sizeof(SLDataType));
if (tmp == NULL)
{
printf("realloc fail\n");
exit(-1);
}
else
{
ps->a = tmp;
ps->capacity = newcapacity;
}
}
}
void SeqListPushBack(SL* ps, SLDataType x)
{
SeqListCheckCapacity(ps);
ps->a[ps->size] = x;
ps->size++;
}
void SeqListPopBack(SL* ps)
{
assert(ps->size > 0);
ps->size--;
}
void SeqListPushFront(SL* ps, SLDataType x)
{
SeqListCheckCapacity(ps);
int end = ps->size - 1;
while (end >= 0)
{
ps->a[end + 1] = ps->a[end];
end--;
}
ps->a[0] = x;
ps->size++;
}
void SeqListPopFront(SL* ps)
{
assert(ps);
if (ps->size > 0)
{
int start = 1;
while (start < ps->size)
{
ps->a[start - 1] = ps->a[start];
start++;
}
ps->size--;
}
}
void SeqListInsert(SL* ps, size_t pos, SLDataType x)
{
assert(ps);
if ((int)pos > ps->size)
{
printf("pos 越界:%d\n", pos);
return;
}
SeqListCheckCapacity(ps);
size_t end = ps->size;
while (end > pos)
{
ps->a[end] = ps->a[end - 1];
end--;
}
ps->a[pos] = x;
ps->size++;
}
void SeqListErase(SL* ps, size_t pos)
{
assert((int)pos < ps->size);
int start = pos + 1;
while (start < ps->size)
{
ps->a[start - 1] = ps->a[start];
start++;
}
ps->size--;
}
int SeqListFind(SL* ps, SLDataType x)
{
for (int i = 0; i < ps->size; i++)
{
if (ps->a[i] == x)
{
return i;
}
}
return -1;
}
void SeqListModify(SL* ps, size_t pos, SLDataType x)
{
assert((int)pos < ps->size);
ps->a[pos] = x;
}
2.2.3 Test.c
#define _CRT_SECURE_NO_WARNINGS 1
#include "SeqList.h"
void TestSeqList1()
{
SL s;
SeqListInit(&s);
SeqListPushBack(&s, 1);
SeqListPushBack(&s, 2);
SeqListPushBack(&s, 3);
SeqListPushBack(&s, 4);
SeqListPushBack(&s, 5);
SeqListPushBack(&s, 0);
SeqListPrint(&s);
SeqListPopBack(&s);
SeqListPopBack(&s);
SeqListPrint(&s);
SeqListPrint(&s);
SeqListPushBack(&s, 10);
SeqListPushBack(&s, 20);
SeqListPrint(&s);
}
void TestSeqList2()
{
SL s;
SeqListInit(&s);
SeqListPushBack(&s, 1);
SeqListPushBack(&s, 2);
SeqListPushBack(&s, 3);
SeqListPushBack(&s, 4);
SeqListPushFront(&s, 0);
SeqListPushFront(&s, -1);
SeqListPrint(&s);
SeqListPopFront(&s);
SeqListPopFront(&s);
SeqListPopFront(&s);
SeqListPopFront(&s);
SeqListPopFront(&s);
SeqListPrint(&s);
SeqListPopFront(&s);
SeqListPrint(&s);
SeqListPopFront(&s);
SeqListPopFront(&s);
SeqListPushFront(&s, 0);
SeqListPushFront(&s, -1);
SeqListPrint(&s);
}
void TestSeqList3()
{
SL s;
SeqListInit(&s);
SeqListPushBack(&s, 1);
SeqListPushBack(&s, 2);
SeqListPushBack(&s, 3);
SeqListPushBack(&s, 4);
SeqListPrint(&s);
SeqListInsert(&s, 10, 100);
SeqListInsert(&s, 1, 20);
SeqListInsert(&s, 5, 50);
SeqListInsert(&s, 0, 50);
SeqListPrint(&s);
SeqListDestroy(&s);
}
void TestSeqList4()
{
SL s;
SeqListInit(&s);
SeqListPushBack(&s, 1);
SeqListPushBack(&s, 2);
SeqListPushBack(&s, 3);
SeqListPushBack(&s, 4);
SeqListPushBack(&s, 5);
SeqListPrint(&s);
SeqListErase(&s, 4);
SeqListPrint(&s);
SeqListErase(&s, 0);
SeqListPrint(&s);
SeqListErase(&s, 1);
SeqListPrint(&s);
}
void TestSeqList5()
{
SL s;
SeqListInit(&s);
SeqListPushBack(&s, 1);
SeqListPushBack(&s, 2);
SeqListPushBack(&s, 3);
SeqListPushBack(&s, 4);
SeqListPushBack(&s, 5);
SeqListPushFront(&s, -1);
SeqListPushFront(&s, -2);
SeqListPrint(&s);
SeqListPopFront(&s);
SeqListPopFront(&s);
SeqListPopBack(&s);
SeqListPopBack(&s);
SeqListPrint(&s);
}
void menu()
{
printf("***********************************************\n");
printf("1.尾插数据 2.头插数据\n");
printf("5.打印数据 6.查找数据\n");
printf("-1.退出 \n");
printf("***********************************************\n");
}
int main()
{
TestSeqList5();
return 0;
}
注: 测试接口时最好写一个接口测一个接口,不要一口气写完再测试,不然你会崩溃的哦。
2.2.4 运行结果
3.数组相关面试题
思路1:
查找到每一个val,依次挪动数据覆盖,时间复杂度O(N^2).
思路2:
把不是val的值拷贝到新数组,时间复杂度O(N),空间复杂度O(N).
思路3:
双指针,时间复杂度O(N),空间复杂度O(1) 令dst,src为0,如果src下标处的值不等于val,则nums[dst]=nums[src],src++,dst++。否则只让src++。
代码:
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];
dst++;
src++;
}
else
{
src++;
}
}
return dst;
}
代码:
int removeDuplicates(int* nums, int numsSize)
{
int src = 1;
int dst = 0;
while(src < numsSize)
{
if(nums[src] == nums[src-1])
{
src++;
}
else
{
nums[dst] = nums[src-1];
src++;
dst++;
}
}
nums[dst] = nums[numsSize-1];
dst++;
return dst;
}
思路1:
思路2:
思路3:要求不能额外开数组。
一种情况: 另一种情况:
代码:
void merge(int* nums1, int nums1Size, int m, int* nums2, int nums2Size, int n)
{
int i = m-1, j = n-1;
int dst = m+n-1;
while(i >= 0 && j >= 0)
{
if(nums1[i]>nums2[j])
{
nums1[dst--] = nums1[i--];
}
else
{
nums1[dst--] = nums2[j--];
}
}
while(j >= 0)
{
nums1[dst--] = nums2[j--];
}
}
4. 顺序表的问题及思考
问题:
- 中间/头部的插入删除,时间复杂度为O(N)
- 增容需要申请新空间,拷贝数据,释放旧空间。会有不小的消耗。
- 增容一般是呈2倍的增长,势必会有一定的空间浪费。例如当前容量为100,满了以后增容到200,我们再继续插入了5个数据,后面没有数据插入了,那么就浪费了95个数据空间。
思考:如何解决以上问题呢?这就需要链表的结构来解决。
|