1 简介、顺序表
程序 = 数据结构 + 算法
1.1 数据结构
数据结构是由数据和结构两方面组成,例如:
姓名 | 年龄 | 性别 |
---|---|---|
张三 | 21 | 男 |
李四 | 22 | 男 |
数据就是姓名、年龄和性别,结构就是姓名、年龄和性别的关系。
数据结构指的是数据与数据之间的逻辑关系。
1.1.1 数据结构有什么用?
解决问题,如何高效(多快好省)的从已知数据求解未知数据。
1.1.2 数据结构的分类
1.2 算法
1.2.1 什么是算法?
算法指的是解决特定问题的步骤和方法。
1.2.2 算法有什么用?
解决问题,如何高效(多快好省)的从已知数据求解未知数据。
1.2.3 如何判断算法的好坏?
对于一个问题的算法来说,之所以称之为算法,首先它必须能够解决这个问题(称为准确性)。其次,通过这个算法编写的程序要求在任何情况下不能崩溃(称为健壮性)。
如果准确性和健壮性都满足,接下来,就要考虑最重要的一点:通过算法编写的程序,运行的效率怎么样。
运行效率体现在两方面:
- 算法的运行时间。(称为“时间复杂度”)
- 运行算法所需的内存空间大小。(称为“空间复杂度”)
1.3 时间复杂度
1.3.1 什么是时间复杂度?
算法的运行时间受硬件、系统等多个因素影响。我们使用一个与环境无关的方式评价算法执行速度:时间复杂度。
时间复杂度主要度量基本操作重复执行的次数,是输入规模和基本操作的数量关联,随着输入规模扩大的增长量。
1.3.2 如何表示时间复杂度
用O记号表示算法的时间性能
- 常用的时间复杂度从小到大的排序
1.3.3 如何计算时间复杂度?
- 找出基本语句(执行次数最多的语句)
最内层循环的循环体 - 计算基本语句的执行次数的数量级
只保留最高次幂,忽略低次幂和最高次幂的系数 - 用大O记号表示算法的时间性能
- O(1)
int n;
scanf("%d",&n);
printf("%d\n",n);
- O(n)
int n;
scanf("%d",&n);
int count = 0;
for(int i=0;i<n;++i){
printf("%d\n",++count);
}
- O(n^2)
int n;
scanf("%d",&n);
int count = 0;
for(int i=0;i<n;++i){
for(int j=0;j<n;++j){
printf("%d\n",++count);
}
}
- O(n^3)
int n;
scanf("%d",&n);
int count = 0;
for(int i=0;i<n;++i){
for(int j=0;j<n;++j){
for(int k=0;k<n;++k){
printf("%d\n",++count);
}
}
}
- O(log(2,n))
int n;
scanf("%d",&n);
int count = 0;
for(int i=0;i<n;i*=2){
printf("%d\n",++count);
}
- O(2^n)
int n;
scanf("%d",&n);
int count = 0;
for(int i=0;i<pow(2,n);++i){
printf("%d\n",++count);
}
- O(n!)
long long factorial(int n){
int res = 1;
for(int i=1;i<n;++i){
res*=i;
}
return res;
}
int n;
scanf("%d",&n);
int count = 0;
for(int i=0;i<factorial(n);++i){
printf("%d\n",++count);
}
1.4 空间复杂度
1.4.1 什么是空间复杂度?
空间复杂度是指运行完一个程序所需内存的大小。
1.4.2 如何表示空间复杂度?
一个程序执行时除了需要存储空间和存储本身所使用的指令、常数、变量和输入数据外,还需要一些对数据进行操作的工作单元和存储一些为现实计算所需信息的辅助空间。程序执行时所需存储空间包括以下两部分:
- 固定部分
这部分空间的大小与输入/输出的数据的个数多少、数值无关。主要包括指令空间(即代码空间)、数据空间(常量、简单变量)等所占的空间。这部分属于静态空间。 - 可变空间
这部分空间的主要包括动态分配的空间,以及递归栈所需的空间等。这部分的空间大小与算法有关。
1.5 线性结构
数据结构中最常用最简单的结构是线性结构。
线性结构,又称线性表。逻辑结构上数据元素之间存在一个对一个的相邻关系。线性结构是n个数据元素的有序(次序)集合,它有下列几个特征:
1.集合中必存在唯一的一个"第一个元素";
2.集合中必存在唯一的一个"最后的元素";
3.除最后元素之外,其它数据元素均有唯一的"后继";
4.除第一元素之外,其它数据元素均有唯一的"前驱"
1.5.1 什么是顺序表?
顺序表是用一组地址连续的存储单元依次存储线性表中的各个元素,使线性表中在逻辑结构上相邻的数据元素存储在相邻的物理存储单元中。
- 顺序表对比数组:
数组的缺点:大小(元素个数)不能改变,不能适用元素个数变化的情况。
数组可以看作无法改变大小的顺序表。
1.5.2 如何使用顺序表?
顺序表通过一个结构体和结构体对应的接口使用。
1.6 如何实现顺序表?
1.6.1 定义结构
typedef int Element;
typedef struct Data{
Element* arr;
int n;
} Data;
定义一个存储单元类型Element
是为了使顺序表适和更多数据类型,使用的时候修改Element
类型即可。
1.6.2 定义操作
- 顺序表:查看、添加、删除等操作
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stdbool.h>
typedef int Element;
typedef struct Data{
Element* arr;
int n;
} Data;
void ShowData(Data* data){
for(int i = 0;i < data->n;++i){
printf("%d ",data->arr[i]);
}
printf("\n");
}
void AddData(Data* data){
printf("请输入数字:");
int num;
scanf("%d",&num);
data->arr = realloc(data->arr,sizeof(int)*(data->n+1));
data->arr[data->n] = num;
data->n++;
}
void DeleteData(Data* data){
printf("请输入要删除的数字:");
int num;
scanf("%d",&num);
int flag = -1;
for(int i = 0;i < data->n;++i){
if(data->arr[i] == num){
flag = i;
break;
}
}
if(flag == -1){
printf("未找到数字%d\n",num);
return;
}
for(int i = flag;i < data->n;++i){
data->arr[i] = data->arr[i+1];
}
data->n--;
data->arr = realloc(data->arr,sizeof(int)*data->n);
}
int main(){
Data data = {NULL,0};
const char* items[] = {
"查看数据",
"添加数据",
"删除数据"
};
typedef void(*func_t)(Data* data);
func_t funcs[] = {ShowData,AddData,DeleteData};
while(true){
printf("选项:\n");
for(int i = 0;i < 3;++i){
printf("%d.%s\n",i+1,items[i]);
}
printf("0.退出\n");
printf("请输入选项:");
int num;
scanf("%d",&num);
if(0 == num) break;
if(num < 0 || num > 3){
printf("输入错误,请重新输入\n");
continue;
}
funcs[num-1](&data);
}
}
- 修改
Element
类型为struct
类
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stdbool.h>
typedef struct Student{
char* name;
int age;
float score;
} Student;
typedef Student Element;
typedef struct Data{
Element* arr;
int n;
} Data;
void ShowData(Data* data){
for(int i = 0;i < data->n;++i){
printf("%s %d %f\n",data->arr[i].name,data->arr[i].age,data->arr[i].score);
}
printf("\n");
}
void AddData(Data* data){
Element e;
printf("请依次输入姓名、年龄、成绩:");
char name[20];
scanf("%s %d %f",name,&e.age,&e.score);
char* s = malloc(strlen(name)+1);
strcpy(s,name);
e.name = s;
data->arr = realloc(data->arr,sizeof(Element)*(data->n+1));
data->arr[data->n] = e;
data->n++;
}
void DeleteData(Data* data){
printf("请输入要删除学生姓名:");
char name[20];
scanf("%s",name);
int flag = -1;
for(int i = 0;i < data->n;++i){
if(strcmp(data->arr[i].name,name) == 0){
flag = i;
break;
}
}
if(flag == -1){
printf("未找到:%s\n",name);
return;
}
for(int i = flag;i < data->n;++i){
data->arr[i] = data->arr[i+1];
}
data->n--;
data->arr = realloc(data->arr,sizeof(Element)*data->n);
printf("删除成功!\n");
}
int main(){
Data data = {NULL,0};
const char* items[] = {
"查看数据",
"添加数据",
"删除数据"
};
typedef void(*func_t)(Data* data);
func_t funcs[] = {ShowData,AddData,DeleteData};
while(true){
printf("选项:\n");
for(int i = 0;i < 3;++i){
printf("%d.%s\n",i+1,items[i]);
}
printf("0.退出\n");
printf("请输入选项:");
int num;
scanf("%d",&num);
if(0 == num) break;
if(num < 0 || num > 3){
printf("输入错误,请重新输入\n");
continue;
}
funcs[num-1](&data);
}
}
1.6.3 优化
- 容量
每次增加一个元素,都要重复释放申请内存。可以预先申请一部分备用。
int capacity; //当前分配的存储容量
- 实例:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stdbool.h>
#define SIZE_ARR(arr) (sizeof(arr)/sizeof(arr[0]))
typedef struct Student {
char* name;
int age;
float score;
} Student;
typedef Student Element;
typedef struct Data {
Element* arr;
int n;
int capacity;
} Data;
void ShowData(Data* data) {
for(int i = 0; i < data->n; ++i) {
printf("%s %d %f\n",data->arr[i].name,data->arr[i].age,data->arr[i].score);
}
printf("\n");
}
void AddData(Data* data) {
Element e;
printf("请依次输入姓名、年龄、成绩:");
char name[20];
scanf("%s %d %f",name,&e.age,&e.score);
char* s = malloc(strlen(name)+1);
strcpy(s,name);
e.name = s;
if(data->capacity == 0){
data->arr = malloc(sizeof(Element));
data->capacity = 1;
}else if(data->capacity <= data->n){
data->capacity *= 2;
data->arr = realloc(data->arr,sizeof(Element)*(data->capacity));
}
data->arr[data->n] = e;
data->n++;
}
void DeleteData(Data* data) {
printf("请输入要删除学生姓名:");
char name[20]; //这里不用申请动态内存,是因为以后不需要访问这块地址
scanf("%s",name);
int flag = -1;
for(int i = 0; i < data->n; ++i) {
if(strcmp(data->arr[i].name,name) == 0) {
flag = i;
break;
}
}
if(flag == -1) {
printf("未找到:%s\n",name);
return;
}
for(int i = flag; i < data->n; ++i) {
data->arr[i] = data->arr[i+1];
}
data->n--;
if(data->n == 0){
free(data->arr);
data->arr = NULL;
}else if(data->n == data->capacity/2){
data->capacity /= 2;
data->arr = realloc(data->arr,sizeof(Element)*data->capacity);
}
printf("删除成功!\n");
}
void InsertData(Data* data){
int index;
printf("请输入插入下标:");
scanf("%d",&index);
if(index < 0 || index > data->n){
printf("输入错误或无此下标\n");
return;
}
Element e;
printf("请依次输入姓名、年龄、成绩:");
char name[20];
scanf("%s %d %f",name,&e.age,&e.score);
char* s = malloc(strlen(name)+1);
strcpy(s,name);
e.name = s;
data->arr = realloc(data->arr,sizeof(Element)*(data->n+1));
data->n++;
for(int i = data->n-1;i > index;i--){
data->arr[i] = data->arr[i-1];
}
data->arr[index] = e;
}
int main() {
Data data = {NULL,0,0};
const char* items[] = {
"查看数据",
"添加数据",
"删除数据",
"插入数据"
};
typedef void(*func_t)(Data* data);
func_t funcs[] = {ShowData,AddData,DeleteData,InsertData};
while(true) {
printf("选项:\n");
for(int i = 0; i < SIZE_ARR(items); ++i) {
printf("%d.%s\n",i+1,items[i]);
}
printf("0.退出\n");
printf("请输入选项:");
int num;
scanf("%d",&num);
if(0 == num) break;
if(num < 0 || num > SIZE_ARR(items)) {
printf("输入错误,请重新输入\n");
continue;
}
funcs[num-1](&data);
}
free(data.arr);
data.arr = NULL;
}