1.线性表
线性表是由n个具有相同特性的数据元素组成的的有限序列。它是一种数据结构,参见线性表有:顺序表,链表,栈和队列等等。二叉树就不是线性表。
线性表在逻辑上是线性结构,即一个数据接着一个数据存放,是连续的一条线,但是在实际上的物理结构不一定是线性的。
看下面例子:
顺序表:
顺序表的数据在物理空间上是连续的
链表:
链表每个数据的物理空间是随机的,不是呈线性结构,但是逻辑上是线性的。
2.顺序表
顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构,一般情况下采用数组存储。
顺序表有两种,一种是静态的顺序表,即数组长度是固定的,只能存储一定量的数据,不够灵活,而另一种就是使用动态申请来改变存储量的动态顺序表;
2.1 实现动态顺序表
首先,下面是顺序表的基本结构:
我们不知道顺序表储存的类型是什么,所以使用宏替换来代替数据类型,在实现接口统一使用宏来实现,之后储存数据类型变化时只需要该宏即可。
#define TYPE int
typedef struct SeqList
{
TYPE* hand; //指向动态开辟的数组
int count; //记录有效数据个数
int capacity;//容量空间大小
}SeqList;
需要实现的接口:
void firstSL(SeqList* SL); //初始化顺序表
void SLDestroy(SeqList* SL); //销毁顺序表
void PrintfSL(SeqList* SL); //打印顺序表
void BackPush(SeqList* SL,TYPE x); //尾插
void BackPop(SeqList* SL); //尾删
void FrontPush(SeqList* SL,TYPE x); //头插
void FrontPop(SeqList* SL); //头删
void PosInsert(SeqList* SL,size_t pos,TYPE x); //在指定位置插入数据
void PosPop(SeqList* SL,size_t pos); //在指定位置删除数据
int SeqListFind(SeqList* SL,TYPE x); //查找数据的位置
初始化顺序表:
将顺序表中的各种数据调成初始值,因为要保证SL的值不是空指针,所以使用断言(assert)来确保SL不是空值
void firstSL(SeqList* SL)//初始化顺序表
{
assert(SL);
SL->hand = NULL;
SL->count = 0;
SL->capacity = 0;
}
销毁顺序表:
销毁顺序表,首先断言SL,然后对SL在堆申请的空间进行释放,最后初始化SL中的值。
void SLDestroy(SeqList* SL)
{
assert(SL);
free(SL->hand);
firstSL(SL);
}
打印顺序表:
首先断言SL,其次可以从SL->count得到数组中的有效个数,依次打印数据即可
void PrintfSL(SeqList* SL)
{
assert(SL);
for (int i = 0; i < SL->count; i++)
{
printf("%d ", *(SL->hand + i));
}
printf("\n");
}
顺序表尾插:
尾插,先断言,之后判断有效数据个数和容量,容量不足则阔扩容,最后存放数据。
void AddCapacity(SeqList* SL)//扩容
{
assert(SL);
static int i = 1;
TYPE* get = (TYPE*)realloc(SL->hand,(i++)*ADD * sizeof(TYPE));
if (get==NULL)
{
printf("扩容失败");
exit(-1);
}
else
{
SL->hand = get;
SL->capacity += ADD;
}
}
void BackPush(SeqList* SL,TYPE x)//尾插
{
assert(SL);
if (SL->capacity <= SL->count)
{
AddCapacity(SL);
}
SL->hand[SL->count++] = x;
}
顺序表尾删:
断言,因为count是记录有效个数的,那么只要count减一,就相当于删除尾部数据了
void BackPop(SeqList* SL)
{
assert(SL);
if(SL->count>0)
SL->count--;
}
顺序表头插:
断言,判断容量,顺序表的头插比较麻烦,需要将所有数据向后移动一位,最后在将数据放入头部。
void FrontPush(SeqList* SL, TYPE x)
{
assert(SL);
if (SL->capacity <= SL->count)
{
AddCapacity(SL);
}
for (int i = SL->count; i > 0; i--)
{
SL->hand[i] = SL->hand[i-1];
}
SL->hand[0] = x;
SL->count++;
}
顺序表头删:
断言,可以把数据往前移动,依次覆盖,就能删除头部数据。
void FrontPop(SeqList* SL)
{
assert(SL);
if (SL->count>0)
{
for (int i = 0; i <SL->count-1; i++)
{
SL->hand[i] = SL->hand[i + 1];
}
SL->count--;
}
}
在指定位置插入数据:
断言,判断pos的位置的有效性,检查容量,之后将pos位置后的数据往后挪动一位,然后再pos位置插入x
void PosInsert(SeqList* SL, size_t pos, TYPE x)
{
assert(SL);
if (!((int)pos > 0 && (int)pos <= SL->count+1))
{
printf("输入指定位置错误\n");
return;
}
if (SL->capacity <= SL->count)
{
AddCapacity(SL);
}
for (int i = SL->count; i >=pos; i--)
{
SL->hand[i] = SL->hand[i-1];
}
SL->hand[pos - 1] = x;
SL->count++;
}
在指定位置删除数据:
断言,判断pos的位置的有效性,之后将pos位置之后的数据往前挪动覆盖就实现删除了
void PosPop(SeqList* SL, size_t pos)
{
assert(SL);
if (!((int)pos > 0 && (int)pos <= SL->count + 1))
{
printf("输入指定位置错误");
return;
}
int left = 0;
while (left+1<SL->count)
{
SL->hand[left] = SL->hand[left+1];
}
SL->count--;
}
查找数据指定位置:
断言,然后依次寻找数据,找到了返回指定位置,否则返回-1
int SeqListFind(SeqList* SL, TYPE x)
{
assert(SL);
for (int i = 0; i < SL->count; i++)
{
if (SL->hand[i] == x)
{
return i+1;
}
}
printf("找不到指定数据的位置\n");
return -1;
}
在最后会给出所有代码实现
3. 顺序表的优缺点
缺点:
1: 顺序表的头插头删或者是中部插入删除的时间复杂度为0(n),如果遇到数据量大的,那么时间消耗大。
2: 顺序表的有效个数大多时候是少于容量的,存在空间浪费情况,在扩容的时候,可以选择一次扩容少量数据,来减少空间浪费,但是,频繁的扩容对时间消耗很大。其次,可以按倍数来扩容,但是空间浪费严重。
3: 使用realloc扩容时,可能会存在整个数组的数据进行迁移(复制和删除),时间消耗大。
优点:
顺序表的储存使用数组,访问速度快,方便使用下标随机访问,缓存命中率高
4.代码
SeqList.h
头文件,包含头文件,定义函数,实现类型等
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#pragma once
#define ADD 20
#define TYPE int
typedef struct SeqList
{
TYPE* hand;
int count;
int capacity;
}SeqList;
void AddCapacity(SeqList* SL);//扩容
void firstSL(SeqList* SL); //初始化顺序表
void PrintfSL(SeqList* SL); //打印顺序表
void BackPush(SeqList* SL,TYPE x); //尾插
void BackPop(SeqList* SL); //尾删
void FrontPush(SeqList* SL,TYPE x); //头插
void FrontPop(SeqList* SL); //头删
void PosInsert(SeqList* SL,size_t pos,TYPE x); //在指定位置插入数据
void PosPop(SeqList* SL,size_t pos);//在指定位置删除数据
void SLDestroy(SeqList* SL); //销毁链表
int SeqListFind(SeqList* SL,TYPE x);
SeqList.c
实现对应函数
#include"SeqList.h"
void firstSL(SeqList* SL)//初始化顺序表
{
assert(SL);
SL->hand = NULL;
SL->count = 0;
SL->capacity = 0;
}
void AddCapacity(SeqList* SL)//扩容
{
assert(SL);
static int i = 1;
TYPE* get = (TYPE*)realloc(SL->hand,(i++)*ADD * sizeof(TYPE));
if (get==NULL)
{
printf("扩容失败");
exit(-1);
}
else
{
SL->hand = get;
SL->capacity += ADD;
}
}
void PrintfSL(SeqList* SL)
{
assert(SL);
for (int i = 0; i < SL->count; i++)
{
printf("%d ", *(SL->hand + i));
}
printf("\n");
}
void BackPush(SeqList* SL,TYPE x)
{
assert(SL);
if (SL->capacity <= SL->count)
{
AddCapacity(SL);
}
SL->hand[SL->count++] = x;
}
void BackPop(SeqList* SL)
{
assert(SL);
if(SL->count>0)
SL->count--;
}
void FrontPush(SeqList* SL, TYPE x)
{
assert(SL);
if (SL->capacity <= SL->count)
{
AddCapacity(SL);
}
for (int i = SL->count; i > 0; i--)
{
SL->hand[i] = SL->hand[i-1];
}
SL->hand[0] = x;
SL->count++;
}
void FrontPop(SeqList* SL)
{
assert(SL);
if (SL->count>0)
{
for (int i = 0; i <SL->count-1; i++)
{
SL->hand[i] = SL->hand[i + 1];
}
SL->count--;
}
}
void SLDestroy(SeqList* SL)
{
assert(SL);
free(SL->hand);
firstSL(SL);
}
void PosInsert(SeqList* SL, size_t pos, TYPE x)
{
assert(SL);
if (!((int)pos > 0 && (int)pos <= SL->count+1))
{
printf("输入指定位置错误\n");
return;
}
if (SL->capacity <= SL->count)
{
AddCapacity(SL);
}
for (int i = SL->count; i >=pos; i--)
{
SL->hand[i] = SL->hand[i-1];
}
SL->hand[pos - 1] = x;
SL->count++;
}
void PosPop(SeqList* SL, size_t pos)
{
assert(SL);
if (!((int)pos > 0 && (int)pos <= SL->count + 1))
{
printf("输入指定位置错误");
return;
}
int left = 0;
while (left+1<SL->count)
{
SL->hand[left] = SL->hand[left+1];
}
SL->count--;
}
int SeqListFind(SeqList* SL, TYPE x)
{
assert(SL);
for (int i = 0; i < SL->count; i++)
{
if (SL->hand[i] == x)
{
return i+1;
}
}
printf("找不到指定数据的位置\n");
return -1;
}