顺序表
一、简介
顺序表是在计算机内存中以数组的形式保存的线性表,是指用一组地址连续的存储单元依次存储数据元素的线性结构,逻辑结构与存储位置吻合。
顺序表分为静态顺序表和动态顺序表两种。二者差异如下:
相同点:
内存空间连续, 数据顺序存储不同点:
(1)容量大小
静态顺序表再创建顺序表的时候容量已经确定为MAX_SIZE,不可以更改,而动态顺序表的容量是由malloc函数动态开辟,当容量不够用时可以增加容量capacity(2)二者所占内存空间的位置不同。
静态定义一个顺序表, 顺序表所占的内存空间开辟在内存的静态区, 即所谓的函数栈上, 随着函数调用的结束, 这块内存区域会被系统自动回收;而动态生成一个顺序表, 顺序表所占的内存空间开辟在内存的动态区, 即所谓的堆内存上, 这块区域不会随着函数调用的结束被系统自动回收, 而是需要程序员主动去释放它.
静态、动态顺序表优缺点:
静态顺序表:
操作简单, 不用malloc函数,不用手动释放其内存空间, 不存在内存泄漏
动态顺序表:
可以动态开辟内存空间, 操作灵活, 相比于静态开辟空间也可以少空间浪费
二、静态顺序表
1、结构定义
静态顺序表不可扩展空间,使用前需要定义最大空间大小MAX_SIZE。表结构定义需要记录两项数据:
- 顺序表申请的存储容量;
- 顺序表的长度,也就是表中存储数据元素的个数;
#define MAX_SIZE 100
#define ERROR 0
typedef int ElemType;
typedef struct SeqList {
ElemType data[MAX_SIZE];
int length;
}SeqList, *pSeqList;
2、初始化
void InitSeqList(SeqList *seq)
{
assert(nullptr != seq);
memset(seq->data, 0, sizeof(ElemType)*MAX_SIZE);
seq->length = 0;
}
3、插入元素
3.1 头插
头插,总是将新元素插入到顺序表的首地址位置,需要将顺序表的length个元素往后移动一个位置,时间复杂度O(n)。
void InsertFront(SeqList *seq, ElemType e)
{
assert(nullptr != seq);
if (seq->length == MAX_SIZE)
printf("SeqList is FULL!\n");
for (int i = seq->length; i > 0; --j)
seq->data[i] = seq->data[i-1];
seq->data[0] = e;
seq->length++;
}
3.2 尾插
尾插,总是从顺序表的末尾插入,没有元素的移动,时间复杂度O(1)
void InsertBack(SeqList *seq, ElemType e)
{
assert(nullptr != seq);
if (seq->length == MAX_SIZE)
{
printf("SeqList is FULL!\n");
return;
}
seq->data[seq->length++] = e;
}
3.3 任意指定位置插入
实现思路:
1.通过遍历,找到数据元素要插入的位置
2.将要插入位置元素以及后续的元素整体向后移动一个位置
3.将元素放到腾出来的位置(即指定位置)
void Insert(SeqList seq, ElemType e, int pos)
{
assert(nullptr != seq);
if (seq->length == MAX_SIZE)
{
printf("SeqList is FULL!\n");
return;
}
if (seq->length == 0)
{
printf("SeqList is Empty!\n");
p = 1;
}
for (int i = seq->length; i >= pos; --i)
seq->data[i] = seq->data[i-1];
seq->data[p-1] = e;
seq->length++;
}
4、删除元素
4.1 头删
总是从顺序表的首地址开始删除元素,将其后的length-1个元素都需要向前移动一位置, 时间复杂度为O(n)
void DeleteFront(SeqList *seq)
{
assert(nullptr != seq);
if (seq->length == 0)
{
printf("SeqList is Empty!\n");
return;
}
for (int i = 0; i < seq->length; ++i)
{
seq->data[i] = seq->data[i+1];
}
seq->length--;
}
4.2 尾删
尾删,总是从顺序表的尾部开始删除元素,没有元素的移动,时间复杂度为O(1)
void DeleteBack(SeqList *seq)
{
assert(nullptr != seq);
if (seq->length == 0)
{
printf("SeqList is Empty!\n");
return;
}
seq->length--;
}
4.3 任意指定位置删除
只需找到目标元素,并将其后续所有元素整体前移 1 个位置即可。(后续元素整体前移一个位置,会直接将目标元素删除,可间接实现删除元素的目的)
void Delete(SeqList *seq, int pos)
{
assert(nullptr != seq);
if (seq->length == 0)
{
printf("SeqList is Empty!\n");
return;
}
if (pos > seq->length || p < 1)
{
printf("ERROR!\n")
}
for (int i = pos-1; i < seq->length; ++i)
seq->data[i] = seq->data[i+1];
seq->length--;
}
5、查找
简单粗暴的方法就是遍历表数据,逐一进行比较判断。实现如下:
int Find(SeqList *seq, ElemType value)
{
assert(nullptr != seq);
for (int i = 0; i < seq->length; ++i)
{
if (seq->data[i] == value)
return i;
}
return -1;
}
除此之外,可以对表数据先进行排序,然后再进行二分查找等方式。
6、排序(升序)
// 排序
void BubbleSort(SeqList *seq)
{
assert(nullptr != seq);
for (int i = 1; i < seq->length; ++i)
{
for (j = 0; j < seq->length - i; ++j)
{
if (seq->data[j] > seq->data[j+1])
{
int tmp = seq->data[j];
seq->data[j] = seq->data[j+1];
seq->data[j+1] = tmp;
}
}
}
}
上述代码是简单等冒泡排序方式,当中还有可优化方案以及其它一些排序方法。
7、逆序反转
void Reverse(SeqList *seq)
{
assert(nullptr != seq);
int start = 0;
int end = seq->length-1;
while (start != end)
{
int temp = seq->data[start];
seq->data[start] = seq->data[end];
seq->data[end] = temp;
start++;
end--;
}
}
三、动态顺序表
相比与静态顺序表,动态顺序表支持扩容机制。即动态开辟一块新的空间(一般为原空间的两倍),将原空间的数据拷贝到新的空间,然后让array指针指向新的空间并且释放旧空间。
1、结构定义
#define LIST_INT_SIZE 100 //线性表存储空间的初始分配量
#define LIST_INCREMENT 100 //线性表存储空间的分配增量
typedef int ElemType;
typedef struct
{
ElemType *data; //存储空间基址
int length; //当前长度
int capacity; //当前分配的存储容量(等于静态顺序表MAX_SIZE)
}SqList;
2、初始化和销毁
//初始化
void Init(SeqList *seq)
{
seq->capacity = LIST_INT_SIZE;
seq->data = (ElemType*)malloc(sizeof(ElemType) * seq->capacity);
assert(pSeq->parray);
pSeq->length = 0;
}
//销毁
void Destroy(SeqList *seq)
{
free(seq->data);
pSeq->capacity = 0;
pSeq->data = nullptr;
pSeq->length = 0;
}
3、扩容判断
void IsNeedExpand(SeqList *seq)
{
//扩容条件
if (seq->length < seq->capacity)
return;
//扩容
seq->capacity *= 2;
//1.申请新空间
ElemType *newData = (ElemType*)malloc(sizeof(ElemType) * seq->capacity);
assert(newData);
//2.数据搬移
for (int i = 0; i < seq->size; i++)
{
newData[i] = seq->data[i];
}
// 3. 释放旧空间,关联新空间
free(seq->data);
seq->data = newData;
}
4、其它操作
插入、删除、查找等操作基本和静态顺序表差不多,额外需要注意等是所有的插入都需要扩容判断处理。