一、线性表
线性表(linear list)是n个具有相同特征的数据元素的有限序列。线性表是一种在实际中广泛使用的数据结构,常见的线性表:顺序表、链表、栈、队列、字符串…
线性表在逻辑上是线性结构,是连续的一条直线。但是在物理结构上并不一定是连续的,线性表在物理上存储时,通常以数组和链式结构的形式存储。
二、顺序表
1.顺序表的概念
顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构,一般常采用数组存储。在数组上完成增删查改。
2.顺序表的分类
(1)静态顺序表
使用定长数组存储元素。
//顺序表静态存储
#define N 10
typedef int SLDataType; //typedef对类型进行重定义,便于顺序表中数组元素类型的多样化
struct SeqList
{
SLDataType arr[N]; //定长数组
int size; //有效数据个数
};
(2)动态顺序表
使用动态开辟的数组存储
typedef int SLDataType;
struct SeqList
{
SLDataType* a;
int size;
int capacity;
};
静态顺序表只适用于确定需要存储多少数据的情景。静态顺序表的定长数组容易导致N定大了,空间开多了浪费,开少了不够用。所以实际中常使用动态顺序表,根据动态的分配空间大小。
设计函数实现对顺序表的初始化、增 (头插、尾插、指定下标)删(头删、尾删、指定下标)查改。(以动态顺序表为例)
3.接口实现
头文件SeqList.h:主要内容包括头文件的包含,结构体定义和接口函数的声明。
//SeqList.h
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#define INIT_CAPACITY 4
typedef int SLDataType;
typedef struct SeqList
{
SLDataType *a;
int size; //有效数据个数
int capacity; //空间容量
}SL;
void SLInit(SL* ps); //实现顺序表的初始化。将结构体成员变量置为0(指针置为NULL)。
void SLPrint(SL* ps); //实现顺序表内容的打印。用for循环打印,一共打印ps->size个;打印的数据是结构体成员变量数组的元素。
void SLDestroy(SL* ps); //顺序表销毁
void SLCheckCapacity(SL* ps); //// 检查空间,如果满了,进行扩容
void SLPushBack(SL* ps,SLDataType x); //顺序表尾插
void SLPushFront(SL* ps,SLDataType x); //顺序表头插
void SLPopBack(SL* ps); //顺序表尾删
void SLPopFront(SL* ps); //顺序表头删
void SLInsert(SL* ps, int pos, SLDataType x); // 顺序表在pos位置插入x
void SLErase(SL* ps, int pos); // 顺序表删除pos位置的值
源文件SeqList.c:主要内容为函数接口的实现。
//SeqList.c
#include "SeqList.h"
//实现顺序表的初始化。将结构体成员变量置为0(指针置为NULL)
void SLInit(SL* ps)
{
ps->a=(SLDataType*)malloc(sizeof(SLDataType)*INIT_CAPACITY);
if(ps->a==NULL) //初始化判断
{
perror("malloc fail");
return;
}
ps->size=0;
ps->capacity=INIT_CAPACITY; //初始化值赋值
}
//实现顺序表内容的打印。用for循环打印,一共打印ps->size个;打印的数据是结构体成员变量数组的元素。
void SLPrint(SL* ps)
{
for(int i=0;i<ps->size;i++)
{
printf("%d ",ps->a[i]);
}
printf("\n");
}
//头插、尾插函数都需要先调用该函数,再进行插入操作
void SLCheckCapacity(SL* ps)
{
assert(ps);
if (ps->size == ps->capacity) //判断实际数据个数和空间容量个数来检查数组是否已经存满数据
{
//如果两个数大小相等,将容量扩大到原来的2倍
SLDataType* tmp = (SLDataType*)realloc(ps->a, sizeof(SLDataType) * ps->capacity * 2);
if (tmp == NULL) //判断realloc的返回值,为空则扩容失败,函数直接结束。
{
perror("realloc fail");
return;
}
ps->capacity *= 2; //空间容量*2
}
}
//用free函数释放动态申请的内存空间,将指针置空,将psl->size和psl->capacity置为0
void SLDestroy(SL* ps)
{
free(ps->a); //用free函数释放动态申请的内存空间
ps->a=NULL; //将指针置空
ps->size=ps->capacity=0; //将ps->size和ps->capacity置为0。
}
//尾插一个数到结构体成员数组中
void SLPushBack(SL* ps,SLDataType x)
{
SLCheckCapacity(ps);
ps->a=[ps->size++]=x;
}
//顺序表尾删
void SLPopBack(SL* ps)
{
assert(ps->size > 0); //断言
ps->size--;
}
//头插一个数到结构体成员数组中
void SLPushFront(SL* ps, SLDataType x)
{
assert(ps);
SLCheckCapacity(ps); //检查容量是否足够(不足够会扩容)
int end = ps->size - 1; //从最后一个元素ps->size - 1开始
while (end >= 0)
{
ps->a[end + 1] = ps->a[end]; //每个元素依次向后挪一位
--end;
}
ps->a[0]=x; //第一个元素赋值为要插入的值
ps->size++;
}
//删除结构体成员数组的第一个元素
void SLPopFront(SL* ps)
{
assert(ps);
assert(ps->size > 0);
int begin = 1;
while (begin < ps->size) //从第二个元素开始,依次向前挪一位
{
ps->a[begin-1] = ps->a[begin];
++begin;
}
ps->size--;
}
//将数x插入结构体成员数组的指定下标pos处
void SLInsert(SL* ps, int pos, SLDataType x)
{
assert(ps);
assert(pos >= 0 && pos <= ps->size);
SLCheckCapacity(ps);
int end = ps->size-1;
while(end >= pos)
{
ps->a[end+1] = ps->a[end]; //从最后一个元素开始,到下标为pos的元素为止,依次向后挪一位
--end;
}
ps->a[pos] = x;
ps->size++;
}
//将结构体成员数组下标为pos处的元素删除
void SLErase(SL* ps, int pos)
{
assert(ps);
assert(pos >= 0 && pos <= ps->size);
int begin = pos+1;
while(begin <= ps->size)
{
ps->a[begin-1] = ps->a[begin]; //从下标为pos+1的元素开始,依次向前挪一位
++begin;
}
ps->size--;
}
#include "SeqList.h"
void SLtest1()
{
SL s;
SLInit(&s);
SLPushBack(&s, 1);
SLPushBack(&s, 2);
SLPushBack(&s, 3);
SLPushBack(&s, 4);
SLPrint(&s); //打印:1 2 3 4
SLPopBack(&s); //尾删一个
SLPrint(&s); //打印:1 2 3
SLDestroy(&s);
}
void TeatSeqlist2()
{
SL s;
SLInit(&s);
SLPushFront(&s, 1);
SLPushFront(&s, 2);
SLPushFront(&s, 3);
SLPushFront(&s, 4);
SLPrint(&s); //打印:4 3 2 1
SLPopFront(&s); //头删一个
SLPopFront(&s); //头删一个
SLPrint(&s); //打印:2 1
SLInsert(&s,2,30);
SLPrint(&s); //打印:2 1 30
SLErase(&s,0);
SLPrint(&s); //打印:1 30
SLDestroy(&s);
}
int main()
{
SLtest1(); //调用函数
SLtest2();
}
SLInsert()函数也可以头插和尾插;SLErase()函数也可以头删和尾删。
三、总结
1.书写函数较多的代码时,最好写一个测一个,可以有效避免写完之后代码报错但由于代码量过大无从下手的问题。
2.动态申请的空间一定要记得正确释放,避免造成内存泄漏。