顺序表——通过静态分配、动态分配实现增删查(采用王道数据结构复习书算法)


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)黄色,代表一般复杂度。红色代表最坏时间复杂度。

  1. 数据结构复杂度
    在这里插入图片描述
  2. 排序算法
    在这里插入图片描述
  3. 图操作
    在这里插入图片描述
  4. 堆操作
    在这里插入图片描述

  1. 本博客供个人学习使用。 ↩︎

  2. 摘自博客:《解决VS2013中出现类似于error C4996: ‘scanf’: This function or variable may be unsafe的安全检查错误》原网页地址 ↩︎

  • 4
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值