【数据结构】——顺序表
创始人
2024-01-20 14:45:27

目录

1.线性表

 2.顺序表

2.1概念及结构

3.静态顺序表

4.动态顺序表

1.定义一个顺序表

2.顺序表的初始化和销毁

3.顺序表尾插

4.顺序表打印

5.顺序表尾删

6.顺序表头插

7.顺序表头删

8.在pos(任意)位置的插入

9.在pos(任意)位置的删除

5.简单的写一个菜单

6.完整的动态顺序表代码

 6.1SeqList.h

 6.2SeqList.c

 6.3Test.c


1.线性表

线性表(linear list)是n个具有相同特性的数据元素的有限序列。

线性表是一种在实际中广泛使用的数据结构,常见的线性表:顺序表、链表、栈、队列、字符串...

线性表在逻辑上是线性结构,也就说是连续的一条直线。但是在物理结构上并不一定是连续的,

线性表在物理上存储时,通常以数组和链式结构的形式存储。

 2.顺序表

2.1概念及结构

顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构,一般情况下采用数组存

储。在数组上完成数据的增删查改。

顺序表一般可以分为:

1. 静态顺序表:使用定长数组存储元素。

2. 动态顺序表:使用动态开辟的数组存储。

顺序表的本质就是数组

顺序表要求的是连续存储

3.静态顺序表

静态顺序表只适用于确定知道需要存多少数据的场景。静态顺序表的定长数组导致N定了,空间开多了浪费,开少了不够用。因此现实生活场景基本不会用到静态顺序表

 

接下来用代码看一看静态顺序表是怎么实现的:

// 静态顺序表 -- 不太实用
// N小了不够用,N大了可能浪费
#define N  100
//typedef struct shunxubiao//不能用拼英取名字
typedef int SLDataType;//重命名结构中的数据类型,以便后期维护
typedef struct SeqList
{SLDataType a[N];int size; // 记录存储多少个有效数据
}SL;// STL命名风格
//void SeqListInit(SL s);void SLInit(SL s);
void SLPushBack(SL s, SLDataType x);//void SLWeicha(SL s, int x);//不能这样取名字

4.动态顺序表

相比于静态顺序表,动态顺序表能够改变内存空间的大小,因此不会出现内存过大或者太小的情况,更适用于平时日常生活的场景。

 接下来用代码看一看动态顺序表是怎么实现的:

1.定义一个顺序表

typedef int SLDataType;
typedef struct SeqList
{SLDataType* a;int size;       // 记录存储多少个有效数据int capacity;   // 空间容量大小 
}SL;

2.顺序表的初始化和销毁

//顺序表初始化
void SLInit(SL* ps)
{assert(ps);ps->a = NULL;ps->size = 0;ps->capacity = 0;
}//顺序表销毁
void SLDestroy(SL* ps)
{assert(ps);//if (ps->a != NULL)if (ps->a){free(ps->a);ps->a = NULL;ps->size = ps->capacity = 0;}
}

3.顺序表尾插

1.先判断数组的容量够不够

2.如果不够的话就需要扩容

3.扩容完成之后,将想要插入的数据x插入到尾部

void SLPushBack(SL* ps, SLDataType x)
{//版本1assert(ps);// 扩容if (ps->size == ps->capacity){int newCapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;SLDataType* tmp = (SLDataType*)realloc(ps->a, newCapacity * sizeof(SLDataType));if (tmp == NULL){perror("realloc fail");exit(-1);//终止程序}ps->a = tmp;ps->capacity = newCapacity;}ps->a[ps->size] = x;ps->size++;//版本2assert(ps);SLCheckCapacity(ps);ps->a[ps->size] = x;ps->size++;//版本3SLInsert(ps, ps->size, x);
}

PS:

探究realloc扩容是原地扩容还是异地扩容

 

4.顺序表打印

void SLPrint(SL* ps)
{assert(ps);for (int i = 0; i < ps->size; ++i){printf("%d ", ps->a[i]);}printf("\n");
}

5.顺序表尾删

PS:并不需要将尾部的那个数据置为0,因为有可能我们存的就是0

我们只需要缩减数组的长度size就行了,缩减一次,就相当于尾部的数据被删除了

那么下次我们尾插的时候,虽然这个数据还在,但是也会被覆盖掉

要注意的是size为0的时候就不能进行尾删了

void SLPopBack(SL* ps)
{//版本1assert(ps);// 温柔的检查/*if (ps->size == 0){return;}*/// 暴力的检查assert(ps->size > 0);//ps->a[ps->size - 1] = 0;ps->size--;//版本2SLErase(ps, ps->size-1);}

6.顺序表头插

在数组的头部插入一个数据

 

 如果空间满了就需要扩容,想到之前尾插的时候也需要扩容,因此可以将这两次扩容写成一个扩容函数

//扩容函数
void SLCheckCapacity(SL* ps)
{assert(ps);if (ps->size == ps->capacity){int newCapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;SLDataType* tmp = (SLDataType*)realloc(ps->a, newCapacity * sizeof(SLDataType));if (tmp == NULL){perror("realloc fail");exit(-1);}ps->a = tmp;ps->capacity = newCapacity;}
}
void SLPushFront(SL* ps, SLDataType x)
{//版本1assert(ps);//检查扩容SLCheckCapacity(ps);// 挪动数据int end = ps->size - 1;while (end >= 0){ps->a[end + 1] = ps->a[end];end--;}ps->a[0] = x;ps->size++;//版本2SLInsert(ps, 0, x);
}

7.顺序表头删

删除头部的一个元素,将后面的元素整体向前挪动一个元素的空间,就能覆盖掉第一个元素

分析图如下:

(版本1的代码)

 代码如下:

void SLPopFront(SL* ps)
{//版本1assert(ps);assert(ps->size > 0);int begin = 1;while (begin < ps->size){ps->a[begin - 1] = ps->a[begin];begin++;}ps->size--;//版本2SLErase(ps, 0);
}

PS:越界是不一定报错的,尤其是越界读

      

 越界读只是去查看越界位置处的值,因此大部分编译器不会报错

但是越界写是去改变越界位置处的值,这个是不被允许的,属于非法访问了,因此可能会被查出来,但是根据每个编译器查越界位置的监察位置不同,可能会导致查不出的情况,

8.在pos(任意)位置的插入

 参考代码如下:

// 在pos位置插入数据
void SLInsert(SL* ps, int pos, SLDataType x)
{assert(ps);assert(pos >= 0);assert(pos <= ps->size);SLCheckCapacity(ps);int end = ps->size - 1;while (end >= pos){ps->a[end + 1] = ps->a[end];end--;}ps->a[pos] = x;ps->size++;
}

由于上面的代码实现的是任意位置插入,那么头插和尾插也属于任意位置中的两种特殊情况

那么SLInsert()能够被复用,去修改上面提到的尾插和头插的代码。 

9.在pos(任意)位置的删除

 参考代码如下:

// 删除pos位置数据
void SLErase(SL* ps, int pos)
{assert(ps);assert(pos >= 0);assert(pos < ps->size);//assert(ps->size > 0);// 挪动数据覆盖int begin = pos + 1;while (begin < ps->size){ps->a[begin - 1] = ps->a[begin];begin++;}ps->size--;
}

既然pos(任意)位置的值都能被删除了,那么头删和尾删也只是SLErase函数的两者特殊情况,因此头删和尾删的代码能够被SLErase函数替换

 10.找顺序表中某个值

上面的头删和尾删还有pos位置的删除都是通过下标来删除,但是实际生活中,又不是人人都是程序员,别人可能压根不懂下标的概念,那这个时候,我想删除数字10,应该怎么删除呢?

这个时候我们就要去查找数字10对应的下标了,得到下标之后,就能调用上述的函数去删除10了

参考代码如下:

int SLFind(SL* ps, SLDataType x, int begin)
{assert(ps);for (int i = begin; i < ps->size; ++i){if (ps->a[i] == x){return i;}}return -1;
}//找到x了,返回下标
/找不到,返回-1

5.简单的写一个菜单

void menu()
{printf("***********************************************\n");printf("1、尾插数据 2、尾删数据\n");printf("3、头插数据 4、头删数据\n");printf("5、打印数据 -1、退出\n");printf("***********************************************\n");
}int main()
{SL s;SLInit(&s);int option = 0;int val = 0;do{menu();printf("请输入你的操作:>");scanf("%d", &option);switch (option){case 1:printf("请依次输入你要尾插的数据,以-1结束");scanf("%d", &val);while (val != -1){SLPushBack(&s, val);scanf("%d", &val);}break;case 2:SLPopBack(&s);break;case 3:break;case 4:break;case 5:SLPrint(&s);break;default:break;}} while (option != -1);SLDestroy(&s);return 0;
}

PS:对于初学数据结构的朋友来说,不建议去写菜单,菜单的本质就是便于非程序员使用者和计算机啊交互的,但是对于程序员来说,简洁的菜单反而不利于我们去发现代码中的BUG,不利于我们去调试代码,因此菜单尽量不要去写,要写的话,也只是整个代码的逻辑框架已经写完了,最后再去写菜单。

6.完整的动态顺序表代码

 6.1SeqList.h

#define _CRT_SECURE_NO_WARNINGS 1
#pragma once
#include 
#include 
#include // 动态顺序表 -- 按需扩空间typedef int SLDataType;
typedef struct SeqList
{SLDataType* a;	// 指向动态开辟的数组int size;       // 记录存储多少个有效数据int capacity;   // 空间容量大小 
}SL;//顺序表打印
void SLPrint(SL* ps);
//顺序表初始化
void SLInit(SL* ps);
//顺序表销毁
void SLDestroy(SL* ps);
//顺序表尾插
void SLPushBack(SL* ps, SLDataType x);
//顺序表尾删
void SLPopBack(SL* ps);
//顺序表头插
void SLPushFront(SL* ps, SLDataType x);
//顺序表头删
void SLPopFront(SL* ps);//中间插入删除
//在pos(任意)位置插入数据,
void SLInsert(SL* ps, int pos, SLDataType x);
//删除pos(任意)位置数据
void SLErase(SL* ps, int pos);//查找顺序表中x值的位置,返回下标
//int SLFind(SL* ps, SLDataType x);
// begin查找x的起始位置
int SLFind(SL* ps, SLDataType x, int begin);// B树  红黑树  哈希

 6.2SeqList.c

#define _CRT_SECURE_NO_WARNINGS 1
#include"SeqList.h"void SLPrint(SL* ps)
{assert(ps);for (int i = 0; i < ps->size; ++i){printf("%d ", ps->a[i]);}printf("\n");
}
//扩容函数
void SLCheckCapacity(SL* ps)
{assert(ps);if (ps->size == ps->capacity){int newCapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;SLDataType* tmp = (SLDataType*)realloc(ps->a, newCapacity * sizeof(SLDataType));if (tmp == NULL){perror("realloc fail");exit(-1);}ps->a = tmp;ps->capacity = newCapacity;}
}void SLInit(SL* ps)
{assert(ps);ps->a = NULL;ps->size = 0;ps->capacity = 0;
}void SLDestroy(SL* ps)
{assert(ps);//if (ps->a != NULL)if (ps->a){free(ps->a);ps->a = NULL;ps->size = ps->capacity = 0;}
}//时间复杂度O(1)--尽量用尾插,头差时间复杂度高
void SLPushBack(SL* ps, SLDataType x)
{//版本1//assert(ps);// 扩容//if (ps->size == ps->capacity)//{//	int newCapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;//	SLDataType* tmp = (SLDataType*)realloc(ps->a, newCapacity * sizeof(SLDataType));//	if (tmp == NULL)//	{//		perror("realloc fail");//		exit(-1);//终止程序//	}//	ps->a = tmp;//	ps->capacity = newCapacity;//}//ps->a[ps->size] = x;//ps->size++;//版本2//assert(ps);//SLCheckCapacity(ps);//ps->a[ps->size] = x;//ps->size++;//版本3SLInsert(ps, ps->size, x);
}void SLPopBack(SL* ps)
{//版本1//assert(ps);温柔的检查///*if (ps->size == 0)//{//return;//}*/暴力的检查//assert(ps->size > 0);ps->a[ps->size - 1] = 0;//ps->size--;//版本2SLErase(ps, ps->size - 1);}//时间复杂度O(N)
void SLPushFront(SL* ps, SLDataType x)
{版本1//assert(ps);检查扩容//SLCheckCapacity(ps);挪动数据//int end = ps->size - 1;//while (end >= 0)//{//	ps->a[end + 1] = ps->a[end];//	end--;//}//ps->a[0] = x;//ps->size++;//版本2SLInsert(ps, 0, x);
}void SLPopFront(SL* ps)
{版本1//assert(ps);//assert(ps->size > 0);//int begin = 1;//while (begin < ps->size)//{//	ps->a[begin - 1] = ps->a[begin];//	begin++;//}//ps->size--;//版本2SLErase(ps, 0);
}// 在pos位置插入数据
void SLInsert(SL* ps, int pos, SLDataType x)
{assert(ps);assert(pos >= 0);assert(pos <= ps->size);SLCheckCapacity(ps);int end = ps->size - 1;while (end >= pos){ps->a[end + 1] = ps->a[end];end--;}ps->a[pos] = x;ps->size++;
}// 删除pos位置数据
void SLErase(SL* ps, int pos)
{assert(ps);assert(pos >= 0);assert(pos < ps->size);//assert(ps->size > 0);// 挪动数据覆盖int begin = pos + 1;while (begin < ps->size){ps->a[begin - 1] = ps->a[begin];begin++;}ps->size--;
}//int SLFind(SL* ps, SLDataType x)
//{
//	assert(ps);
//
//	for (int i = 0; i < ps->size; ++i)
//	{
//		if (ps->a[i] == x)
//		{
//			return i;
//		}
//	}
//
//	return -1;
//}int SLFind(SL* ps, SLDataType x, int begin)
{assert(ps);for (int i = begin; i < ps->size; ++i){if (ps->a[i] == x){return i;}}return -1;
}

 6.3Test.c

#define _CRT_SECURE_NO_WARNINGS 1
#include "SeqList.h"void TestSeqList1()
{SL sl;SLInit(&sl);SLPushBack(&sl, 1);SLPushBack(&sl, 2);SLPushBack(&sl, 3);SLPushBack(&sl, 4);SLPushBack(&sl, 5);SLPushBack(&sl, 5);SLPushBack(&sl, 5);SLPushBack(&sl, 5);SLPushBack(&sl, 5);SLPrint(&sl);SLDestroy(&sl);
}void TestSeqList2()
{/*SL* s = NULL;SLInit(s);*/SL sl;SLInit(&sl);SLPushBack(&sl, 1);SLPushBack(&sl, 2);SLPushBack(&sl, 3);SLPushBack(&sl, 4);SLPrint(&sl);SLPopBack(&sl);SLPrint(&sl);SLPushBack(&sl, 8);SLPrint(&sl);SLPopBack(&sl);SLPopBack(&sl);SLPopBack(&sl);SLPopBack(&sl);//SLPopBack(&sl);SLPrint(&sl);SLPushBack(&sl, 1);//SLPushBack(&sl, 2);SLPushBack(&sl, 2);SLPushBack(&sl, 2);SLDestroy(&sl);
}void TestSeqList3()
{SL sl;SLInit(&sl);SLPushFront(&sl, 1);SLPushFront(&sl, 2);SLPushFront(&sl, 3);SLPushFront(&sl, 4);SLPrint(&sl);SLPopFront(&sl);SLPopFront(&sl);SLPopFront(&sl);SLPopFront(&sl);//SLPopFront(&sl);SLPrint(&sl);SLPushBack(&sl, 10);SLPrint(&sl);SLDestroy(&sl);
}void TestSeqList4()
{SL sl;SLInit(&sl);SLPushBack(&sl, 1);SLPushBack(&sl, 2);SLPushBack(&sl, 3);SLPushBack(&sl, 4);SLPrint(&sl);SLInsert(&sl, 2, 20);SLPrint(&sl);SLInsert(&sl, 5, 500);SLPrint(&sl);SLInsert(&sl, 0, 500);SLPrint(&sl);SLDestroy(&sl);
}void TestSeqList5()
{SL sl;SLInit(&sl);SLPushBack(&sl, 1);SLPushBack(&sl, 2);SLPushBack(&sl, 3);SLPushBack(&sl, 4);SLPrint(&sl);SLErase(&sl, 2);SLPrint(&sl);SLErase(&sl, 2);SLPrint(&sl);SLErase(&sl, 0);SLPrint(&sl);SLDestroy(&sl);
}void TestSeqList6()
{SL sl;SLInit(&sl);SLPushBack(&sl, 1);SLPushBack(&sl, 2);SLPushBack(&sl, 3);SLPushBack(&sl, 4);SLPushBack(&sl, 5);SLPushBack(&sl, 7);SLPushBack(&sl, 5);SLPushBack(&sl, 8);SLPushBack(&sl, 5);SLPrint(&sl);/*int pos = SLFind(&sl, 5);if (pos != -1){SLErase(&sl, pos);}SLPrint(&sl);*/int pos = SLFind(&sl, 4, 0);if (pos != -1){SLErase(&sl, pos);}SLPrint(&sl);// 删除顺序表所有的5pos = SLFind(&sl, 5, 0);while (pos != -1){SLErase(&sl, pos);pos = SLFind(&sl, 5, pos);}SLPrint(&sl);SLDestroy(&sl);
}void menu()
{printf("***********************************************\n");printf("1、尾插数据 2、尾删数据\n");printf("3、头插数据 4、头删数据\n");printf("5、打印数据 -1、退出\n");printf("***********************************************\n");
}int main()
{//TestSeqList1()//TestSeqList2()//TestSeqList3()//TestSeqList4()//TestSeqList5()//TestSeqList6()SL s;SLInit(&s);int option = 0;int val = 0;do{menu();printf("请输入你的操作:>");scanf("%d", &option);switch (option){case 1:printf("请依次输入你要尾插的数据,以-1结束");scanf("%d", &val);while (val != -1){SLPushBack(&s, val);scanf("%d", &val);}break;case 2:SLPopBack(&s);break;case 3:break;case 4:break;case 5:SLPrint(&s);break;default:break;}} while (option != -1);SLDestroy(&s);return 0;
}

相关内容

热门资讯

埃菲尔铁塔在哪 中国仿建埃菲尔... 2019年4月26日,广西南宁市,街头惊现一座巨型山寨版埃菲尔铁塔,高约20米,白色塔身,造型逼真,...
阳澄湖在哪里哪个省的 阳澄湖是... 点击题目下方苏州生活指南有用 有趣 有态度近期的阳澄湖度很高,3月24日,央视二套《第一时间》直播连...
苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...
北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...
应用未安装解决办法 平板应用未... ---IT小技术,每天Get一个小技能!一、前言描述苹果IPad2居然不能安装怎么办?与此IPad不...
脚上的穴位图 脚面经络图对应的... 人体穴位作用图解大全更清晰直观的标注了各个人体穴位的作用,包括头部穴位图、胸部穴位图、背部穴位图、胳...
长白山自助游攻略 吉林长白山游... 昨天介绍了西坡的景点详细请看链接:一个人的旅行,据说能看到长白山天池全凭运气,您的运气如何?今日介绍...
猫咪吃了塑料袋怎么办 猫咪误食... 你知道吗?塑料袋放久了会长猫哦!要说猫咪对塑料袋的喜爱程度完完全全可以媲美纸箱家里只要一有塑料袋的响...
世界上最漂亮的人 世界上最漂亮... 此前在某网上,选出了全球265万颜值姣好的女性。从这些数量庞大的女性群体中,人们投票选出了心目中最美...
北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...
金属硬度排行 各种金属硬度 世界上最硬的东西排名分别为硫化碳块,石墨烯,金刚石,金属玻璃和金属锇。这五种物体几乎是整个世界上最坚...
埃菲尔铁塔在哪 中国仿建埃菲尔... 2019年4月26日,广西南宁市,街头惊现一座巨型山寨版埃菲尔铁塔,高约20米,白色塔身,造型逼真,...
苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...