线性表包括顺序表,链表,栈,队列
本章我们只看顺序表。
顺序表分为动态表和静态表。
动态表就是空间大小可以被更改的表,静态表则为空间大小不可以变化的表。静态顺序表空间开辟在栈上,儿动态开辟在堆上。
那么顺序表怎么定义?
#pragma once
typedef struct seqList
{
//大小确定,一般不要太大,因为静态空间是开在栈空间上的,栈空间相比较而言比较小
int _array[100];
int _size;
}seqList;
静态空间空间一般不会很大,因为静态空间是开辟在栈空间上的,栈空间相对而言比较小,而且固定空间的话可能会造成一个非常大的空间浪费,所以我们一般使用动态顺序表,用多少开辟多少就行了,不会有很大的资源浪费。
那么动态的顺序表是怎么开辟呢?
一般的话我们会给一个指针,还有一个容量的概念。
#pragma once
typedef int DataType;//类型别名,增加代码的灵活性
typedef struct seqList
{
DataType *_array;
unsigned int _size;//或者用size_t _size;也是无符号整型,不过要包含头文件#include<stdio.h>
unsigned int _capacity;
}seqlist;
-size表示目前表中有多少元素。
capacity表示当前有多大的容量,能用的空间一共有多大。
一般要实现的接口有,插入,删除,
void seqListInit(seqList* sl);//初始化
void seqListPushBack(seqList* sl,DataType value);//尾插
void seqListPopBack(seqList* sl);//尾删
void seqListPushFront(seqList* sl,DataType value);//头插
void seqListPopFront(seqList* sl);//头删
void seqListPushInsert(seqList* sl,size_t pos,DataType value);//在某个地方插入一个元素
void seqListErase(seqList* sl,size_t pos);//删除pos位置的元素
int seqListFind(seqList* sl,DataType value);//查找
void seqListPrint(seqList* sl);//打印
接口的实现如下:
#include "seqlist.h"
#include <stdlib.h>
void seqListInit(seqList* sl)//初始化
{
//初始化数组
sl->_array = (int *)malloc(sizeof(DataType)* 4);
sl->_capacity = 4;
sl->_size = 0;
}
void seqListPushBack(seqList* sl, DataType value)//尾插
{
sl->_array[sl->_size++] = value;
//把value赋值给数组有效数据_size的下一个地方
}
void seqListPrint(seqList* sl)//打印
{
for (size_t i = 0; i < sl->_size; i++)
{
printf("%d", sl->_array[i]);
}
printf("\n");
}
尝试
void test()
{
seqList sl;
seqListInit(&sl);
seqListPushBack(&sl, 1);
seqListPrint(&sl);
seqListPushBack(&sl, 2);
seqListPrint(&sl);
seqListPushBack(&sl, 3);
seqListPrint(&sl);
}
int main()
{
test();
system("pause");
return 0;
}
结果1 12 123
头插原理及代码:重点:size是元素个数
void seqListPushFront(seqList* sl, DataType value)//头插
{
//把元素放在零的位置,如果直接放的话会覆盖其他元素,所以要先移开。
// 从后向前移,若是从前向后的话那就是将原来的第零个位置复制了一整个数组
size_t end = sl->_size;//元素从后向前移动,防止元素覆盖
while (end > 0)
{
sl->_array[end] = sl->_array[end-1];
--end;
}
sl->_array[0] = value;//插入头元素
++sl->_size;//更新元素个数
}
如果写成下面这种会不会有问题呢?
void seqListPushFront(seqList* sl, DataType value)//头插
{
size_t end = sl->_size-1;
while (end >= 0)
{
sl->_array[end+1] = sl->_array[end ];
--end;
}
sl->_array[0] = value;
++sl->_size;
}
答案:会有问题;end是无符号整形,到零之后再减不会是负数,而是另一个更大的数。
结论:如果变量是无符号整型,则用while循环的时候条件不能是(>=0),不然循环停不下来,如果是的话,程序会出问题。
将size_t改成int可以,但是size_t更符合情景,所以用第一种方法。
申请的空间有大小,插入的元素可能会超过申请的空间,所以在插入之前要进行检测。
下面是检测容量的程序,如果小于,直接插入,若是大于,则增容。
void checkCapacity(seqList* sl)//增容
{
if (sl->_size == sl->_capacity)
{
sl->_capacity *= 2;
//开空间
DataType* newArray = (DataType*)malloc(sizeof(DataType)*sl->_capacity);
//拷贝
memcpy(newArray, sl->_array, sl->_size*sizeof(DataType));
//释放空间
free(sl->_array);
sl->_array = newArray;
//sl->_array = (DataType*)realloc(sl->_array, sizeof(DataType)*sl->_capacity);//相当于上面好多步
}
}
下面插播二条:1、malloc,realloc的区别
realloc如果后面还有空间,那就做一个标记,然后继续在原来空间里面插入。如果空间不足够,那么就新开。并且会自动释放原来地址,若是再次人工释放,会造成二次释放。
2、memcpy等用法
头删代码:
从第一个位置开始,让他等于start,把第一个未知的挪到前一个位置上,strat++。strat<_size.
每个数组里面都是size-1个元素。
void seqListPopFront(seqList* sl)//头删
{
//把后面的位置全部向前挪动一位,从头开始挪
if (sl->_size)
{
size_t start = 1;//第一个位置开始
while (start < sl->_size)
{
sl->_array[start - 1] = sl->_array[start];
++start;
}
sl->_size--;
}
}
在任意位置POS插入元素
void seqListPushInsert(seqList* sl, size_t pos, DataType value)//在某个地方插入一个元素
{
//判断位置是否合法
if (pos <= sl->_size)//如果POS等于size,就是尾插
{
//检查容量
checkCapacity(sl);
//移动元素 从pos--size-1
size_t end = sl->_size;
while (end > pos)
{
sl->_array[end] = sl->_array[end - 1];
--end;
}
sl->_array[pos] = value;
++sl->_size;
}
}
头插的代码可以改为在0的位置插入,尾插改为在size位置插入数据。
void seqListPushFront1(seqList* sl, DataType value)//头插,尾插
{
seqListPushInsert(sl, 0, value);//头插 seqListPushInsert(sl, sl->_size, value);//尾插
}
查找元素
int seqListFind(seqList* sl, DataType value)//查找
{
//其实就是一个简单地遍历过程
for (int i = 0; i < sl->_size; i++)
{
if (sl->_array[i] == value)
return i;
}
return -1;
}
实际应用:
void test()
{
seqList sl;
seqListInit(&sl);
seqListPushBack(&sl, 1);//插入
seqListPrint(&sl);
seqListPushBack(&sl, 2);
seqListPrint(&sl);
seqListPushBack(&sl, 3);
seqListPrint(&sl);
seqListPushFront(&sl, 6);
seqListPrint(&sl);
seqListPopBack(&sl);//删除
seqListPrint(&sl);
seqListPopBack(&sl);
seqListPrint(&sl);
seqListPopFront(&sl);//头删
seqListPrint(&sl);
seqListPushInsert(&sl, 1, 9);//在1位置插入9
seqListPrint(&sl);
seqListPushFront1(&sl, 8);//尾删,接口的复用
seqListPrint(&sl);
seqListErase(&sl, 0);//删除0位置的元素
seqListPrint(&sl);
printf("%d",seqListFind(&sl, 8));//查找
}
总结一下:
顺序表
插入:增容
尾插:O(1)
头插,移动元素,O(n)
中间位置插入,移动元素,O(n)
删除
尾删:O(1)
头删:移动元素,O(n)
中间位置删除:移动元素,O(n)
查找:遍历,O(n)
顺序表不适合频繁地插入删除,最大的特点,支持随机访问。可以访问任意位置得元素。