文章目录
1. 数据结构
1.1 数据结构是什么?
数据结构是由数据和结构两方面组成,例如:
- 学生信息
No. | 姓名 | 年龄 | 性别 |
---|---|---|---|
1 | 张三 | 21 | 男 |
2 | 李四 | 22 | 男 |
3 | 王五 | 23 | 女 |
数据就是姓名、年龄和性别,结构就是姓名、年龄和性别的关系。
数据结构指的是数据与数据之间的逻辑关系。
- 计算机存储、组织数据的方式。
- 相互之间存在一种或多种特定关系的数据元素的集合。
1.2 数据结构有什么用?
解决问题,如何高效(多快好省)的从已知数据求解未知数据。
1.3 数据结构分类
分类 | No. | 特点 | e.g. |
---|---|---|---|
1 | 集合结构 | 杂乱无章无序关系 | |
2 | 线形结构 | 一对一前驱后继顺序关系 | 糖葫芦 |
3 | 树形结构 | 一对多祖先后代层次关系 | 族谱 |
4 | 图状结构 | 多对多错综复杂网状关系 | 铁路路线图 |
2. 算法
2.1 算法是什么?
算法指的是解决特定问题的步骤和方法。
2.2 算法有什么用?
解决问题,如何高效(多快好省)的从已知数据求解未知数据。
2.3 如何判断算法的好坏?
对于一个问题的算法来说,之所以称之为算法,首先它必须能够解决这个问题(称为准确性)。其次,通过这个算法编写的程序要求在任何情况下不能崩溃(称为健壮性)。
如果准确性和健壮性都满足,接下来,就要考虑最重要的一点:通过算法编写的程序,运行的效率怎么样。
运行效率体现在两方面:
- 算法的运行时间。(称为“时间复杂度”)
- 运行算法所需的内存空间大小。(称为“空间复杂度”)
2.3.1 时间复杂度
1. 时间复杂度是什么?
算法的运行时间受硬件、系统等多个因素影响。我们使用一个与环境无关的方式评价算法执行速度:时间复杂度。
时间复杂度主要度量基本操作重复执行的次数,是输入规模和基本操作的数量关联,随着输入规模扩大的增长量。
2. 如何表示时间复杂度?
3. 如何计算时间复杂度?
- 循环结构:找出基本语句(执行次数最多的语句)
最内层循环的循环体;
for(int i=0;i<n;++i)
for(int j=0;j<i;++j)
外层 | i 能表达的最大值 | 最后一次循环的index | 执行次数 |
---|---|---|---|
n-1 | n-2 | (n-2)-0+1=n-1 | |
内层 | i 能表达的最大值 | 最后一次循环的index | 最大 执行次数(等差数列递减) |
n-2 | n-3 | n-3-0+1=n-2 | |
情况 | 次数 | ||
– | – | – | – |
(1)i++;i<x | O(x) | ||
(2)i+=y; i<x | O(x) | ||
(3) i*=y; i<x | logy(x) |
-
其他结构: 计算基本语句的执行次数的数量级
只保留最高次幂,忽略低次幂和最高次幂的系数 -
用大O记号表示算法的时间性能
int n;
scanf("%d",&n);
printf("%d\n",n);
int n;
scanf("%d",&n);
int count = 0;
for(int i=0;i<n;++i){
printf("%d\n",++count);
}
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);
}
}
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);
}
}
}
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);
}
- 问题
下面代码的时间复杂度为多少?
0(n^2)
int n;
scanf("%d",&n);
int count = 0;
for(int i=0;i<n;++i){
for(int j=0;j<i;++j){
printf("%d\n",++count);
}
}
4. 练习
算法一:判断有没有的两种情况需要用标志位进行判定(false)
情况一:循环之外定义;情况二;循环之后定义
(1)将所有的数字先打印出来;
(2)将所有的数字与输入的数字比较,如果相等,则为false;
如果不相等,flag = false,打印出i;
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <stdbool.h>
int main()
{
int n, a[n];
scanf("%d",&n);
int i,j;
for(i = 0; i < n; ++i)
{
scanf("%d",&a[i]);
}
for(i = 0; i<= n;++i){
bool has = false;
for(j = 0; j < n;++j){
if(i == a[j]){
has = true;
break;
}
}
if(has == false){
printf("%d\n",i);
break;
}
}
return 0;
}
算法二:序列的和-输入的和= 没有出现的数据
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <stdbool.h>
int main()
{
int n, a[n];
scanf("%d",&n);
int sum_total = (n+1)* n / 2;
int sum = 0;
int i;
for(i = 0; i < n; ++i){
scanf("%d",&a[i]);
sum += a[i];
}
printf("%d\n",sum_total - sum);
return 0;
}
算法三:c++里面的find()函数
find(数组的头地址, 数组的尾地址, 要找的数)
find(nums.begin(), nums.end(), target)
返回的是target第一次出现的地址
如果没有找到返回尾地址nums.end()
#include <iostream>
#include <algorithm>
using namespace std;
int main()
{
int n = 0;
cin >> n;
int nums[n];
fill_n(nums,n,0);
for(int i=0;i<n;++i){
cin >> nums[i];
//scanf("%d",&nums[i]);
}
for(int i=0;i<=n;++i){
if(find(nums,nums+n,i) == nums+n){
cout << i << endl;
break;
}
}
return 0;
}
2.Leecode(136):只出现一次的数字
算法一:暴力解决法()
//1.判断标记; bool flag = false;
// 2.1错误示范 (相等的情况)for(int j = 0;j < numsSize&& j != i ; ++j){
//2.2 if(i == j) continue; //2.2正确示范:相等的话,则跳出本次循环;
//3.一旦为false,break;立马退出循环; if(flag == false){
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
int main()
{
int once, numsSize;
scanf("%d", &numsSize);
int nums[numsSize];
for(int i=0; i<numsSize;++i) {
scanf("%d",&nums[i]);
}
for(int i = 0;i<numsSize; ++i){
bool flag = false; //1.判断标记;
//for(int j = 0;j < numsSize&& j != i ; ++j){ 2.1错误示范
for(int j = 0;j < numsSize ; ++j){
if(i == j) continue; //2.2正确示范:相等的话,则跳出本次循环;
if(nums[i]==nums[j]){
flag = true;
break;
}
}
if(flag == false){ //3.一旦为false,break;立马退出循环;
printf("%d\n",nums[i]);
once = nums[i];
break;
}
}
return once;
}
算法二:排序法()
①排序法的原理;
②注意:数组越界的问题:heap-buffer-overflow on address
需要根据界限判定需不需要限定,并考虑其他情况;
//错误示例:for(int i = 0; i< numsSize; i+=2){
if(nums[i]!=nums[i+1]){
//正确示例:
for(int i = 0; i< numsSize; i+=2){
if(nums[i]!=nums[i+1]){
}else{
once = nums[numsSize-1];
}
③对于最后没有考虑到的案例进行添加;
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <math.h>
int cmp(const void* a, const void* b){
int l = *(int*)a;
int r = *(int*)b;
if(l==r) return 0;
return (l>r) ?1:-1;
}
int main()
{
int once, numsSize;
scanf("%d", &numsSize);
int nums[numsSize];
for(int i=0; i<numsSize;++i) {
scanf("%d",&nums[i]);
}
if(numsSize == 1){
once = nums[0];
}
qsort(nums,numsSize,sizeof(int),cmp);
for(int i = 0; i< numsSize-1; i+=2){
if(nums[i]!=nums[i+1]){
once = nums[i];
break;
}else{
once = nums[numsSize-1];
}
}
return once;
}
2.3.2 空间复杂度
-
空间复杂度是什么?
空间复杂度是指运行完一个程序所需内存的大小。 -
如何表示空间复杂度?
一个程序执行时除了需要存储空间和存储本身所使用的指令、常数、变量和输入数据外,还需要一些对数据进行操作的工作单元和存储一些为现实计算所需信息的辅助空间。程序执行时所需存储空间包括以下两部分:- 固定部分。这部分空间的大小与输入/输出的数据的个数多少、数值无关。主要包括指令空间(即代码空间)、数据空间(常量、简单变量)等所占的空间。这部分属于静态空间。
- 可变空间。这部分空间的主要包括动态分配的空间,以及递归栈所需的空间等。这部分的空间大小与算法有关。
88. 合并两个有序数组 先合并后排序根根据情况而定:①数组1是否足够大,返回的是数组1;②创建新的数组,导致时间超
方案1(排序):先合并成一个数组,然后用qsort()函数进行排序(可能超时)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
int cmp(const void* a, const void* b)
{
int l = *(int*)a;
int h = *(int*)b;
if(l==h) return 0;
return l > h ?1:-1;
}
int main()
{
int m, n, i, j,k;
scanf("%d",&m);
scanf("%d",&n);
int a[m];
int b[n];
int c[m+n];
for(i = 0;i < m; ++i)
{
scanf("%d",&a[i]);
c[i]=a[i];
}
for(j = 0;j < n; ++j)
{
scanf("%d",&b[j]);
c[m+j]=b[j];
}
qsort(c,m+n,sizeof(int),cmp);
for(k = 0;k< n+m; ++k)
{
printf("%d ",c[k]);
}
}
方案1.2(排序):先把小的数组放到大的数组,然后用qsort()函数进行排序
int cmp(const void* a, const void* b){
int l = *(int*)a;
int r = *(int*)b;
if(l==r) return 0;
return (l>r) ?1:-1;
}
void merge(int* nums1, int nums1Size, int m, int* nums2, int nums2Size, int n){
for(int i=m,j=0; i<m+n,j<n; ++i,++j){
nums1[i]=nums2[j];
}
qsort(nums1,m+n,sizeof(int),cmp);
}
方案2.1:①先把小的数组成一个数组,即把一个数组排完②然后把剩余的部分拷贝到数组当中(可以是 A,B)
#include <stdio.h>
int main(){
int n;
scanf("%d",&n);
int a[n];
for(int i=0;i<n;++i){
scanf("%d",a+i);
}
int m;
scanf("%d",&m);
int b[m];
for(int i=0;i<m;++i){
scanf("%d",b+i);
}
int res[n+m];
int i=0,j=0,k=0;
while(i<n && j<m){
res[k++] = a[i]<b[j]?a[i++]:b[j++];
/*
if(a[i] < b[j]){
res[k] = a[i];
++k;
++i;
}else{
res[k] = b[j];
++k;
++j;
}
*/
}
while(i<n){
res[k++] = a[i++];
/*
res[k] = a[i];
++k;
++i;
*/
}
while(j<m){
res[k++] = b[j++];
/*
res[k] = b[j];
++k;
++j;
*/
}
for(int i=0;i<n+m;++i){
printf("%d\n",res[i]);
}
}
方案2.2:①先把小的数组成一个数组,即把一个数组排完②然后把剩余的部分拷贝到数组当中(可以是 A,B),memcpy()函数进行拷贝,2.1的拓展
#include <stdio.h>
#include <string.h>
int main(){
int n;
scanf("%d",&n);
int a[n];
for(int i=0;i<n;++i){
scanf("%d",a+i);
}
int m;
scanf("%d",&m);
int b[m];
for(int i=0;i<m;++i){
scanf("%d",b+i);
}
int res[n+m];
int i=0,j=0,k=0;
while(i<n && j<m){
res[k++] = a[i]<b[j]?a[i++]:b[j++];
/*
if(a[i] < b[j]){
res[k] = a[i];
++k;
++i;
}else{
res[k] = b[j];
++k;
++j;
}
*/
}
if(i<n)
memcpy(res+k,a+i,sizeof(int)*(n-i));
if(j<m)
memcpy(res+k,b+j,sizeof(int)*(m-j));
for(int i=0;i<n+m;++i){
printf("%d ",res[i]);
}
}
3. 线性结构
数据结构中最常用最简单的结构是线性结构。
线性结构,又称线性表。逻辑结构上数据元素之间存在一个对一个的相邻关系。线性结构是n个数据元素的有序(次序)集合,它有下列几个特征:
1.集合中必存在唯一的一个"第一个元素";
2.集合中必存在唯一的一个"最后的元素";
3.除最后元素之外,其它数据元素均有唯一的"后继";
4.除第一元素之外,其它数据元素均有唯一的"前驱"。