顺序表——王道数据结构复习书算法
1
顺序表的特征
1,顺序表最经典的特征就是随机存取,随机访问。因此当需要在 i 位置插入、删除元素的时候,顺序表可以按序号实现快速存取,时间复杂度是O(1)。这是它的优势。
2,顺序表在表末尾进行插入和删除操作时候不需要移动任何元素。
3,顺序表在计算机内申请的连续的存储地址空间,存储密度大。特点是表中元素的逻辑顺序和物理顺序相同。
静态分配:
源码
#include <stdio.h>
#include <windows.h>
#define MaxSize 10
typedef struct{
int data[MaxSize];
int length;
}SqList;
void InitList(SqList &L) {
/*for (int i = 0; i < MaxSize; i++)
L.data[i] = 0;*/ //如果引用MaxSize,则没有此行将出现脏数据问题
L.length = 0;
}
bool ListInsert(SqList &L, int i, int e) { //i用于判断插入是否合理,i=1为表头,j开始遍历的替换元素,为插入点腾出空间
if (i<1 || i>L.length + 1)
return false;
if (L.length >= MaxSize)
return false;
for (int j = L.length; j >= i; j--)
L.data[j] = L.data[j - 1];
L.data[i - 1] = e; //把腾出的空间插入元素e
L.length++;
return true;
}
bool ListDelete(SqList &L, int i, int &e) { //e必须带取地址符,以和main函数里面e指向同一块数据
if (i<1 || i>L.length){
printf("位序i不合法\n");
return false;
}
if (L.length >= MaxSize){
printf("位序i不合法\n");
return false;
}
e = L.data[i - 1]; //把要删除的元素赋值给e
for (int j = i; j < L.length; j++)
L.data[j - 1] = L.data[j];
L.length--;
printf("调用删除函数,删除了第%d个元素,它的值为%d。\n", i, e);
return true;
}
//按位查找顺序表
int GetElem(SqList L, int i) {
printf("请输入你想查找的第i个位置的数据的值:");
scanf_s("%d", &i);
if (i<1 || i>L.length) {
printf("位序i不合法!\n");
printf("==========================================\n");
Sleep(2 * 1000);
return false;
}
else {
printf("Okay,the value for this order is %d.\n", L.data[i - 1]);
printf("==========================================\n");
Sleep(2 * 1000);
return L.data[i - 1];
}
}
int main() {
SqList L;
InitList(L);
int e;
int c = NULL;
//插入
printf("在代码中插入了如下数据元素:\n");
ListInsert(L, 1, 5);
ListInsert(L, 2, 9);
ListInsert(L, 3, 15);
ListInsert(L, 4, 36);
//打印
for (int i = 0; i < L.length ; i++) //MaxSize是一种不合法的书写.不能读取大于顺序表长度的数据元素。
printf("data[%d]=%d\n", i, L.data[i]);
//按位序查询
c = GetElem(L, c);
//删除
ListDelete(L, 3, e);
//打印新表
printf("==========================================\n");
printf("删除数据元素后的新表如下:\n");
for (int i = 0; i < L.length; i++)
printf("data[%d]=%d\n", i, L.data[i]);
return 0;
}
功能说明
声明定义一个静态表
首先声明一个结构体,里面存储相同数据类型的若干个数据元素(我采用的ElemType是比较普遍的int型),定义MaxSize最大数据长度和当前表长length。void InitList(SqList &L)
用来定义顺序表。要注意此语句下的L.length = 0;
把表的初始长度置0是必要的。
ListInsert
ListInsert(SqList &L, int i, int e)
取地址符“&”用于传参,把数据带回到main函数中。布尔类型用于回传true/false。为了代码的健壮性,需要两条if语句判断一下插入位置是否合理。至于插入的实现,我就不过多阐述,不懂可以自己在纸上画一个表。注意插入是从后遍历,移动元素,而删除是从前面遍历给元素赋值。
按位查找顺序表
按位查找可以写在删除操作之前或之后,只要在main中更改顺序就可以了。为了方便查看代码执行效果可以分别打印删除前删除后的顺序表。
关于按位查找,王道数据结构书上算法给的明明白白,但是你怎么真正实现到程序里面呢?无非就是参数引用,以及代码的补全,健壮性书写。之前我没有在main函数中写入if语句判断GetElem函数回传给我的真假性,因此出现了一个小bug:当我去查询一个完全不存在的位序的元素时候,他总是能给我回传一个0(如图):
这肯定不是我们想要的。因此我写了一个if语句,如果你写的位序太离谱儿,他会给你发出警告。
ListDelete
ListDelete(SqList &L, int i, int &e)
注意e必须带取地址符,以和main函数里面e指向同一块数据。如果你不带取地址符,而main函数中写的是int e;
你会发现非但不能删除元素,程序还报错了,指示出你在main中的e是没有定义的。如果main函数里面写的int e = xxx;
你在终端上看到的始终就是删除了元素xxx,和表中的元素没有任何关系。
另外,在main函数里我定义了i 的值。在main中随意更改i 的值,就可以达到删除不同位序元素的操作。
静态表的总结
MaxSize固定了静态表的最大长度,这也是静态表的一大弊端,当你的数据元素存储超过MaxSize时,你没有办法动态的申请更多的数据存储空间,这样是不切合实际应用的。
当你取得MaxSize而不使用L.length = 0;
语句清空表的内存,你会看到很多的脏数据(如图所示)
可以看到前四个由于我插入了元素,显示正常,但是后面打印的数据出现了所谓的“脏数据”,因为代码中打印表使用到的for语句中,判断i < MaxSize
其实是不合法的,因为你不能打印未插入元素的空表。
关于scanf的小小尝试
这里我在mian函数故意写了一个scanf语句练手(实则大可不必)但发现编译报错。原因是VS2019认为我的这种scanf写法不安全,拒绝编译。解决办法是引用了CSDN上的博客作为参考。2
我采用的是第一种方法,但是有个问题就是同一个源文件中不能重复引用_s写法。想两个cpp文件中都写入scanf语句只能新建项目了。
这个问题一般使用以下几种解决办法:
(1)scanf等类似的函数已经不太安全,要想保证程序的安全性,建议以后采用_s结尾的安全版本,但是很多以前的程序可能还是使用不安全的版本,那么下面给出去掉这种错误提示的几种办法。
(2)在VS中新建项目的时候去掉“安全开发生命周期(SDL)检查”即可将错误转变成警告,使得使用不安全版本也不影响编译和运行,如下图所示。
(3)在头文件包含的最前面,记住是最前面(在include的前面)加上:#define
_CRT_SECURE_NO_WARNINGS这个宏定义即可,如下图所示。
程序运行展示:
输出你想要查找的值,比如查找即将删除的第3个数据元素:
加入了一个延时函数,2秒后显示后续代码实现:
关于延时函数:头文件写在#include <windows.h>
,调用语句Sleep(2 * 1000);
即可实现延时两秒的效果。
动态分配
源码
#include <stdio.h>
#include <malloc.h>
#include <stdlib.h>
#define InitSize 1 //设置默认最大长度
typedef struct{
int *data;
int MaxSize;
int length;
}SeqList;
void InitList(SeqList &L) {
//malloc申请连续的存储空间
L.data = (int *)malloc(InitSize * sizeof(int));
L.length = 0;
L.MaxSize = InitSize;
}
//动态增加数组长度
void IncreaseSize(SeqList &L, int len) {
int *p = L.data;
L.data = (int*)malloc((L.MaxSize + len) * sizeof(int));
for (int i = 0; i < L.length; i++)
L.data[i] = p[i]; //数据复制到新区域
L.MaxSize = L.MaxSize + len; //顺序表最大长度增加len
free(p); //释放原来内存空间
}
bool ListInsert(SeqList &L, int i, int e) {
if (i<1 || i>L.length + 1)
return false;
if (L.length >= L.MaxSize)
return false;
for (int j = L.length; j >= i; j--)
L.data[j] = L.data[j - 1];
L.data[i - 1] = e; //把腾出的空间插入元素e
L.length++;
return true;
}
//按值查找顺序表,找到第一个元素值等于e的位序并返回其位序
int LocateElem(SeqList L, int e) {
int i;
for (i = 0; i < L.length; i++)
if (L.data[i]==e)
return i + 1;
return 0;
}
int main() {
SeqList L;
InitList(L);
IncreaseSize(L, 5); //初始表长为1,现在申请多5个存储空间。第7个因为没有申请,所以不会打印出来
ListInsert(L, 1, 7);
ListInsert(L, 2, 6);
ListInsert(L, 3, 9);
ListInsert(L, 4, 4);
ListInsert(L, 5, 9);
ListInsert(L, 6, 3);
ListInsert(L, 7, 1);
for (int i = 0; i < L.length; i++)
printf("data[%d]=%d\n", i, L.data[i]);
//按值查找
printf("Got you,the value = 9 is in the locate:%d", LocateElem(L, 9) );
return 0;
}
功能说明
声明定义一个动态表
引用指针*data,定义MaxSize,当前表长length,不再多于赘述。指针data的作用在于动态申请内存空间。当你内存不够用时,调用函数就可解决。#define InitSize 1
设置默认最大长度。这里我设置为1,是为了突出malloc申请空间的作用。
InitList
L.data = (int *)malloc(InitSize * sizeof(int));
(int *) 为数据类型强制转换。前后两个星号含义是不一样的。后者则是乘法,得到计算机内部申请地址偏移量+申请空间大小。比如我们使用很多的int型,占用四个字节,若想申请五份数据空间,则申请空间大小为5×4B=20B.
IncreaseSize
关键在于引用指针p把数据复制到新的区域,在新区域中增加最大表长,最后free释放原来的内存空间。这里容易把一些L.MaxSize、L.length的用处搞混,需要知道代码后面的原理。词穷的我不会怎么具体描述,说起来也看的让人晕乎乎的,不如直接上王道的概念图:
LocateElem
按值查找顺序表,找到第一个元素值等于e的位序并返回其位序。其中return得到的 i+1就是int型的数据返回给函数 LocateElem了。因此在main中调用 LocateElem函数即可。
printf("Got you,the value = 9 is in the locate:%d", LocateElem(L, 9) );
看看运行截图:
因为初始设置表长InitSize为1,加上动态申请了5个地址空间,因此我main函数中虽然插入了7个数据,但是表只能打印出来前6个。最后按值查找也是正确的找到第一个值为9的数据,并把他打印出来。因为很多操作在静态表中实现了,因此动态表相对来讲代码简单了点。
动态表的特征
关于动态表,他虽然基础代码比静态表多(多写一个IncreaseSize函数,引用了指针),但是毫无疑问他的功能比静态表强大。用指针指向各个数组下标,可以malloc动态申请,释放存储空间。
附录:算法复杂度速查表
绿色代表好的时间复杂度,O(n)黄色,代表一般复杂度。红色代表最坏时间复杂度。
- 数据结构复杂度
- 排序算法
- 图操作
- 堆操作