先语:前面记录了怎么评判算法好坏的时间复杂度,空间复杂度。下面正式记录一个数据结构,顺序表。在说顺序表的时候,先来说说线性表。因为顺序表属于线性表。线性表通俗的说,就是一根线一样,一连串的。这是逻辑上的。物理存储上的并不是像数组一样一连串的内存空间啊。当然也有物理上是连续的 就是下面要说的顺序表,
线性表(linear list)
-
概念
n个具有相同特性的数据元素的有限序列。(和数组有点像)
-
分支
- 顺序表
- 链表
- 栈
- 队列
(当然这里只是列举了常见的。)
正式有请——
顺序表
-
概念
一段物理地址连续的存储单元依次存储数据元素的线性结构。一般情况写采用数组存储。
(一看到数组是不是就觉得没那么高大上了呢?其实不只只是数组嘿)
-
分类
1、 静态顺序表
//C语言
#define N 100 //静态的,必须先规定容量
typedef struct SeqListnode
{
int arr[n];
int size; //有效的数据个数
}SeqL;
2 、动态顺序表
typedef strcut SeqListnode
{
int* arr;
int size; //有效个数
int maxcap; //容量
}SeqL;
-
实现
“纸上得来终觉浅,绝知此事要躬行。”所以现在来写写。因为静态顺序表我们用的并不多,而且数组的个数都是规定好了的。要么存在长度太小存不下,要么长度太大,浪费内存。所以,下面记录的是动态顺序表。一方面,它有点难,另一方面,用的相对较多。看我博客的前辈或道友们,如果如果下面的代码有什么问题,还请不吝赐教。直接上代码。
//C语言
typedef struct SeqList {
int* arr;
size_t size;
size_t maxNum;
}Sql;
//先创建一个表。这个表有一个 int 指针,还有一个有效个数的 size,还有一个最大个数容量的maxNum
//下面初始化。结构体前面学过哈。
void sql_init(Sql* sl, size_t maxnum)
{
assert(sl != NULL); //先检查用户传来的是不是空表。
sl->arr = (int*)malloc(sizeof(int)*maxnum); //给数组动态分配一个内存
sl->maxNum = maxnum;
sl->size = 0; //开是没有数据,所以size = 0;
}
void sql_destroy(Sql* sl)
{
assert(sl != NULL);
sl->arr = NULL; //数组置空
sl->maxNum = sl->size = 0; //有效数字和最大容量置空
}
void print_arr(Sql* sl)
{
assert(sl != NULL);
for (size_t i = 0; i < sl->size; i++) //遍历数组
{
printf("%d ",sl->arr[i]);
}
printf("\n");
}
//扩容接口
void check_maxNum(Sql* sl)
{
assert(sl != NULL);
if (sl->size==sl->maxNum)
{
printf("正在扩容……\n");
sl->maxNum *= 2; //最大容量扩大一倍
int* newarr = (int*)malloc(sizeof(int)*sl->maxNum); //开辟一个新的数组
for (size_t i = 0; i < sl->size; i++) //存储老数组的数据
{
newarr[i] = sl->arr[i];
}
free(sl->arr); //释放老数组的内存
sl->arr = newarr; //搬运数据
free(newarr); //释放掉不用的新的数组
printf("扩容成功");
}
}
这里创建了一个初始的表。里面的参数是这个样子的。得注意,这是size个数据,数组的下标是从 0 开始的。所以数组下标是 0 ~ size -1;
我们把服务用的函数都写好了。其实也可以叫接口。这里呢没有写头文件。服务就是准备开始。或者救灾救难的。比如扩容的啊,初始化啊。在代码里面有详细的注释交代。因为边看到代码 便看注释,比较好。如果代码的可读性不好,一看注释就好了。不罗嗦了。下面是顺序表的操作,增删查改。
//C语言
//查寻接口
//返回的是下标
int sql_find(Sql* sl, int num)
{
assert(sl != NULL); //还是检查是不是传进来一个空表
for (size_t i = 0; i < sl->size; ++i) //size: 有效的个数
{
if (num==sl->arr[i]) //比较是否数据相同
{
return i; //相同返回下标
}
}
return -1; //没找到返回 -1;别写 return 0,因为这就返回第 0 个下标的数据
}
//修改某一位置的数据
void sql_updata(Sql* sl, size_t pos, int num)
{
assert(sl != NULL);
sl->arr[pos] = num; //直接覆盖该下标的数据,完成修改
}
//增接口
//头插
void sql_add_front(Sql* sl, int num)
{
assert(sl != NULL);
for (size_t add = sl->size; add > 0; add--)
{
sl->arr[add] = sl->arr[add - 1]; //用第 size 的空间存储 第size - 1的数据,进行循环。实现数据的后移,这样第一个位置就腾出来了。
}
sl->arr[0] = num; //给第一个下标赋值
++sl->size; //插入数据了,就要把有效个数加 1 了。
}
//尾插
void sql_add_end(Sql* sl, int num)
{
assert(sl != NULL);
sl->arr[sl->size] = num; 把第size下标直接赋值
++sl->size;
}
//随机加入
void sql_add_rand(Sql* sl, size_t pos, int num)
{
assert(sl != NULL);
for (size_t i = sl->size; i > pos; i--) //这里和头插差不多。只是把头插的位置换成我们想要的位置就行。
{
sl->arr[i] = sl->arr[i-1];
}
sl->arr[pos] = num;
++sl->size;
}
//C语言
//删接口
//头删
void sql_del_front(Sql* sl)
{
assert(sl != NULL);
for (size_t del = 0; del < sl->size-1; del++) //删除就覆盖
{
sl->arr[del] = sl->arr[del + 1];
//头删要从从第二个数覆盖第一个数开始,
//第三个覆盖第二个。依次覆盖.直到最后一个属覆盖倒数第二个
}
--sl->size; //删除完成,有效个数就要减少1
}
//尾删
void sql_del_end(Sql* sl)
{
assert(sl != NULL);
sl->arr[sl->size - 1] = sl->arr[sl->size]; //把最后下标的数直接覆盖就行了。
--sl->size; //删除完成,有效个数就要减少1
}
//随机删除
void sql_del_randpos(Sql* sl, size_t pos) //这是随机位置删除
{
assert(sl != NULL);
for (size_t i = pos; i < sl->size; i++) //把头删改一下,就行了。仔细对比
{
sl->arr[pos] = sl->arr[pos + 1];
}
--sl->size; //删除完了,一定要记得--size啊。
}
void sql_del_randnum(Sql* sl, int num) //这是删除第一个遇见的数。如,数组中
//有 不知一个 num ,就删除下标最小的 num
{
assert(sl != NULL);
int pos = sql_find(sl, num);
sql_del_randpos(sl, pos);
}
//C语言
//拓展
//冒泡排序
void sql_bubblesort(Sql* sl) //冒泡排序就不多说了。
{ //主要是参数的替换。换成结构体里的参数
assert(sl != NULL);
for (size_t i = 0; i < sl->size; i++)
{
int con = 1;
for (size_t j = 0; j < sl->size - i - 1; j++)
{
if (sl->arr[j] > sl->arr[j+1]) //产生交换
{
int temp = sl->arr[j];
sl->arr[j] = sl->arr[j + 1];
sl->arr[j + 1] = temp;
con = 0;
}
}
if (con == 1) //这是优化程序的语句
{
return; //如果 没有产生交换,说明已经排序好了,就可以退出循环了
}
}
}
//折半查找
int sql_half_find(Sql* sl, int num)
{
assert(sl != NULL);
int left = 0;
int right = sl->size;
while (left < right) //这里只有取等和不取等要细说一下,就是
//left < right / left =< right
//上个博客介绍了折半查找,以前也写过栗子
//简单的说就是,取等的话,right == size - 1
//不取等就是 right == size
{
int mid = (left + right) / 2;
if (sl->arr[mid]==num)
{
return mid;
}
else if (sl->arr[mid] < num)
{
left = mid + 1;
}
else
{
right = mid;
}
}
return -1;
}
//删除相同的数
//意思就是删除数组中与 num 相同的所有的数
void sql_del_allsame(Sql* sl, int num)
{
assert(sl != NULL);
int notsame = 0;
for (size_t i = 0; i < sl->size; i++)
{
if (sl->arr[i]!=num) //这里就是如果没有遇见就把值继续放在原来的下标下面。
{
sl->arr[notsame++] = sl->arr[i];
} //遇见的话,就不管,直到遇见不等于 num 的数,把这个数覆盖到 num 上
}
sl->size = notsame; //画图来解释
现在把这个删除相同的数用图来表示。方便理解
终于把这个画完了,有点啰嗦,但是好理解啊。
结尾
到这儿顺序表介绍完毕。欢迎评价和指出错误。