目录
前言:
记得关注我哦,我大部分为算法题题解,有时候涉及算法知识点纯讲解:脑子不好的小菜鸟-CSDN博客
数据结构持续更新哦~
思想陈述:
数据结构大多人的感觉是:明明可以直接用数据类型(int, double……)创建数组,链表,为啥要用一个交elemtype的数据去定义数组元素类型呢?为啥还要用结构体去保存数组的相关的属性(数组元素个数,数组首地址,数组容量)呢?
我的看法是:数据结构更像是一个思想,它所用的写法都是为了通用于各种语言(学习语言的本质就是为了懂它的思想,然后换种语言自己也能通过这些思想写出来),通用于各种数据类型(int, double),方便移植,这些操作不止可以用在int数组,把typedef int elemtype改成typedef double elemtype又可以储存double类型的数据,之间套用该代码就可,学过C++的可能就会想到C++的一个特点:面向对象的泛型编程,这个typedef int elemtype其实和C++的函数模板和类模板一样,同样的代码,能根据需要的不同实现对不同的数据操作
接下来我就讲讲数据结构入门第一章吧:顺序表的模拟和基本操作(增删查改)
概念介绍:
什么是线性表?
线性表是n个具有相同特性的数据元素的有限序列。线性表是一种在实际中广泛应用的数据结构(如学生信息的存储),常见的线性表有顺序表,链表,栈队列,字符串
线性表是在逻辑上具有线性结构,物理(在内存上的地址)不一定具有线性结构(如链表)也就是说是连续的一条直线,但是在物理结构上并不一定是连续的。
线性表在物理上存储时,通常以数组和链式结构存储
什么是顺序表?
逻辑上和物理(存在内存中的地址)上都是顺序存储的线性表
顺序表分类:
静态顺序表
动态顺序表
静态顺序表:就是定长数组
动态顺序表:可动态申请内存,并且可增容的顺序表
代码实现:
首先,所有数据结构的代码都必须包含的东西:
头文件:
stdio.h
stdlib.h:包含 动态申请内存,求sizeof的头文件
#include <stdio.h>
#include <stdlib.h>//动态申请,sizeof
宏定义:
其次:数据结构中常住民:宏定义
#define ok 1//正常返回
#define error 0//错误返回
#define overflow -2//溢出
#define initsize 50//初始数组长度
看不懂没关系,后面会讲解的
重命名:
顺序表中需包含的重命名:
typedef int status;//返回状态
typedef int elemtype;//数据类型:
//若创建int类型的顺序表,typedef后就写int,若为其他类型,就可写其他类型,可以是内置数据类型,也可是自定义类型------>这就体现了数据结构的思想:可移植性,同样的代码可以对不同的数据类型的数组进行增删查改合并的操作
//以上陈述的拓展:若你学过C++,这就是类似于类模板和函数模板
typedef struct list
{
elemtype* elem;//数组首元素地址
int len;//数组元素个数
int listsize;//开辟的数组长度
}qlist;
数组初始化:
status initlist(qlist& L)//status:返回的状态,返回0(define定义的error)表示错误返回,返回1(define定义的ok)就表示正常返回
//若不能理解这里的status,就把status替换成int就好,返回0就是非,错误的意思;返回1就算是,正确的意思
{
elemtype* p = (elemtype*)malloc(sizeof(elemtype) * initsize);
//为自己的数组申请空间
//若不能理解为什么是elemtype:就想想,若我们想自己创建一个可扩容的int数组,那是不是要写成:int* arr = (int*)malloc(sizeof(int));而我们这里给int重命名为elemtype,所以我们就把这一行代码的int该为typedef就好
if (p == NULL)
{//若malloc返回的是NULL,说明动态内存申请失败
printf("空间申请失败!\n");
exit(overflow);
//在宏定义那里,我写了注释:overflow就是代表溢出,而动态申请失败也就算没有完整的空间开辟给这个数组,也就是相当于开辟了及会溢出
//overflow就是非零退出,exit(非零)表示发生错误和异常
}
L.elem = p;//p存的就是从堆区开辟给数组的首地址,而L就是我们的数组的相关信息,elem存的就是数组首地址,所以L.elem = p
L.listsize = initsize;//数组大小初始化为我们一开始宏定义的intitsize的大小,这个大小自己定
L.len = 0;//数组元素个数为0,注意:这个存的是数组元素个数,而不是上面的数组大小
return ok;//正常返回
}
输入数据:
普通int数组输入数据:
status input(int* arr)
{
printf("请输入顺序表的元素个数\n");
int n;
scanf("%d", &n);
int i;
printf("请输入顺序表中%d个元素:\n", n);
//法一:普通写法
for (i = 0; i < n; i++)
scanf("%d", &arr[i]/*或者写为:arr + i*/);/*把平时的数组名更改为L.elem----->数据结构中的写法*/
//法二:指针写法
//int* p = arr;
//for (p; p < arr + n; p++)
// scanf("%d", p);
return ok;
}
把平时的数组名更改为L.elem(数组首地址)
把数组个数n改为L.len (数组元素个数)
status input(qlist& L)
{
printf("请输入顺序表的元素个数\n");
int n;
scanf("%d", &n);
L.len = n;
int i;
printf("请输入顺序表中%d个元素:\n", n);
//法一:普通写法
//for (i = 0; i < L.len/**/; i++)
// scanf("%d", L.elem + i);/*把平时的数组名更改为L.elem*/
//法二:指针写法
int* p = L.elem;//该部分重点:为什么写的是P = L.elem?
//上面初始化就说过,L.elem就是数组名,若不懂这些地方的写法,就先把向int数组输入数据的代码写出来,然后把数组名部分换为L.elem就可以
for (p; p < L.elem + L.len/**/; p++)
scanf("%d", p);/**/
return ok;
}
输出:
输出和输入同理,把数组名arr出现的地方都替换为数组首地址L.elem就好,数组个数n替换成数组大小 L.len就好
注意点:若该函数不会改变数组的值或者不想让该函数可以改变数组内的数据,则参数不传引用
status output(qlist L)//不改变L,不传引用
{
printf("打印顺序表元素:\n");
int i;
//法一:普通写法
//for (i = 0; i < L.len; i++)
// printf("%d ", L.elem[i]);
//printf("\n");
//法二:指针写法
int* p = L.elem;
for (p; p < L.elem + L.len/**/; p++)
printf("%d ", *p);/**/
printf("\n");
return ok;
}
添加元素:
插入:从后向前遍历
status insert(qlist& L)
{
printf("请输入你要插入的位置:\n");
int n;
scanf("%d", &n);
//要判断位置是否正确,该处要注意:位置从1开始,而不是像数组一样从0开始---->n < 1是非法位置
//插入的数据大于元素个数---->非法
if (n < 1 || n > L.len)
{
printf("插入位置错误!\n");
return error;
}
int i;
for (i = L.len - 1; i >= n - 1; i--)
//注意:插入:从后向前
//因为:若从前往后遍历:L.elem[i + 1] = L.elem[i];---->L.elem[i+1]的位置变成L.elem[i]的数据,当i遍历到i+1的时候,L.elem[i]的值是前一个数据的值,那么后面的所有数据都会是相同的值,所以从前往后遍历会导致后面的数据被覆盖,会导致数据损坏,不可从前往后遍历
L.elem[i + 1] = L.elem[i];
printf("请输入你要插入的数据!\n");
int m;
scanf("%d", &m);
L.len++;/*注意记录的元素个数++*/
L.elem[n - 1] = m;/*把要加入的数据放入要加入的位置,注意要+1,因为是位置,而下标 = 位置 - 1*/
printf("插入成功!\n");
return ok;
}
删除数据:
要注意:删除数据是从前往后遍历(与添加数据的遍历顺序相反)
status dele(qlist& L)//会改变顺序表的内容,所以传引用
{
printf("请输入要删除的位置!\n");
int n;
scanf("%d", &n);
//判断位置是否合理
if (n < 1 || n > L.len)
{
printf("位置错误,删除失败!\n");
exit(overflow);
}
int i;
for (i = n - 1; i < L.len - 1; i++)
//从前往后遍历,和插入相反
L.elem[i] = L.elem[i + 1];
L.len--;/*数据个数--*/
printf("删除成功!\n");
return ok;
}
总代码:
/*数据结构练习*/
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <stdlib.h>//动态申请,sizeof
//顺序表的基本操作
#define ok 1
#define error 0
#define overflow -2//溢出
#define initsize 50//初始数组长度
#define addsize 20//每次追加的长度
typedef int status;//返回状态
typedef int elemtype;//数据类型
typedef struct list
{
elemtype* elem;//数组首元素地址
int len;//数组元素个数
int listsize;//开辟的数组长度
}qlist;
status initlist(qlist& L)
{
elemtype* p = (elemtype*)malloc(sizeof(elemtype) * initsize);
if (p == NULL)
{
printf("空间申请失败!\n");
exit(overflow);
}
L.elem = p;
L.listsize = initsize;
L.len = 0;
return ok;
}
status input(qlist& L)
{
printf("请输入顺序表的元素个数\n");
int n;
scanf("%d", &n);
L.len = n;
int i;
printf("请输入顺序表中%d个元素:\n", n);
//法一:普通写法
//for (i = 0; i < L.len/**/; i++)
// scanf("%d", L.elem + i);/*把平时的数组名更改为L.elem*/
//法二:指针写法
int* p = L.elem;
for (p; p < L.elem + L.len/**/; p++)
scanf("%d", p);/**/
return ok;
}
status output(qlist L)//不改变L,不传引用
{
printf("打印顺序表元素:\n");
int i;
//法一:普通写法
//for (i = 0; i < L.len; i++)
// printf("%d ", L.elem[i]);
//printf("\n");
//法二:指针写法
int* p = L.elem;
for (p; p < L.elem + L.len/**/; p++)
printf("%d ", *p);/**/
printf("\n");
return ok;
}
status insert(qlist& L)
{
printf("请输入你要插入的位置:\n");
int n;
scanf("%d", &n);
if (n < 1 || n > L.len)
{
printf("插入位置错误!\n");
return error;
}
int i;
for (i = L.len - 1; i >= n - 1; i--)//插入:从后向前
L.elem[i + 1] = L.elem[i];
printf("请输入你要插入的数据!\n");
int m;
scanf("%d", &m);
L.len++;/**/
L.elem[n - 1] = m;
printf("插入成功!\n");
return ok;
}
status dele(qlist& L)
{
printf("请输入要删除的位置!\n");
int n;
scanf("%d", &n);
if (n < 1 || n > L.len)
{
printf("位置错误,删除失败!\n");
exit(overflow);
}
int i;
for (i = n - 1; i < L.len - 1; i++)
L.elem[i] = L.elem[i + 1];
L.len--;/**/
printf("删除成功!\n");
return ok;
}
int main()
{
qlist L;
initlist(L);
input(L);
output(L);
insert(L);//插入元素
output(L);
dele(L);//删除元素
output(L);
return 0;
}